Symfony - form event - get new and old data - php

I have a Post entity and PostType.
Post entity have field $requireModeration it means someone must check this post and approve it before it can be published.
When I'm editing post I want to compare old post value and new post value. And if value is changed I also want to change requireModeration flag to true.
Can I do it through form events?
Something like this:
public function postSubmit(FormEvent $event)
{
$newPost = $event->getData();
$newContent = $post->getContent(); // return new contant of post
$oldPost = ... // here I want to get old post
$oldContent = $oldPost->getContent();
if($newContent != $oldContent) {
// ...
}
}
But unfortunately I can get only new, just sent data through FormEvent object.

You should use Doctrine's lifecycle events and UnitOfWork for this instead of Form events (I assume that you use Doctrine at your project).
Add preUpdate listener to your Post entity and do something like:
$uow = $em->getUnitOfWork();
$changeset = $uow->getEntityChangeSet($entity);
In $changeset variable you will have fields list that was changed in the $entity during current request with their old and new values.

public function postSubmit(FormEvent $event)
{
$newPost = $event->getData();
$newContent = $newPost->getContent();
$uow = $this->em->getUnitOfWork();
$oldPost = $uow->getOriginalEntityData($post);
$oldContent = $OriginalEntityData["content"];
if($newContent != $oldContent) {
// ...
}
}

Related

How to manipulate value before node is saved in Drupal 8?

I have an editing node form. When user enters new value and clicks on submit to edit the node, I first want to get the old node back, manipulate the value and then just save/update the node.
Below is my solution, but it does not work.
function custom_module_form_node_form_alter(&$form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
if (!$editing_entity->isNew()) {
$form['actions']['submit']['#submit'][] = 'custom_module_node_form_submit';
}
}
function custom_module_node_form_submit($form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
$entity = Drupal::entityTypeManager()->getStorage('node')->load($editing_entity->id());
}
In the form_submit hook, I tried to get the old node back but it is already too late and the node is already updated/saved. How can I get the old node back and manipulate the value before updating/saving the node in Drupal 8?
Try using hook_entity_presave():
/**
* Implements hook_entity_presave().
*/
function YOUR_MODULE_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
switch ($entity->bundle()) {
// Here you modify only your day content type
case 'day':
// Setting the title with the value of field_date.
$entity->setTitle($entity->get('field_date')->value);
break;
}
}
Solution taken from here: https://drupal.stackexchange.com/questions/194456/how-to-use-presave-hook-to-save-a-field-value-as-node-title
Also you can get old value like: $entity->original. Check it out here:
https://drupal.stackexchange.com/questions/219559/how-to-get-the-original-entity-on-hook-entity-presave
I decide to manipulate the values in the form validate hook as following.
function custom_module_form_node_form_alter(&$form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
if (!$editing_entity->isNew()) {
$form['#validate'][] = 'custom_module_node_form_validate';
}
}
function custom_module_node_form_validate(array &$form, FormStateInterface $form_state) {
$old_entity = $form_state->getFormObject()->getEntity();
$old_values = $old_entity->get('field_name')->getValue()
$new_values = $form_state->getValue('field_name');
// Manipulate and store desired values to be save here.
$to_save_value = ['a', 'b', 'c'];
$form_state->setValue('field_name', $to_save_value);
}
Use hook_ENTITY_TYPE_presave, like so:
function yourmodulename_node_presave(Drupal\node\NodeInterface $entity) {
if ($entity->getType() == 'your_content_type') {
$entity->setTitle('Hello');
$entity->set('body', 'this is body');
}
}
This is the best solution, because with hook_form_alter like MilanG you will be changing the value only when the node is saved from the particular form you are altering! If the node is saved programmatically from within the code or by some other method your hook_form_alter will not kick in.

Create an Event Listener to update another database table

this is my first question so please bear with me.
How can I implement a postPersist Event Listener to update a log table when creating or updating an order in the order table using Sonata.
I understand how to use a prePersist to add information to the same database table as soon as I create a new order. (See the following code snippet)
public function prePersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
if ($user) {
$order->setCreatedBy($user);
$order->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
}
}
}
But I don't fully understand how I would do this when updating another table, because it is not the same entity.
The moment an order is created, (I think) a postPersist should update another table with that order's ID and some extra information.
I think something between the lines like this;
public function postPersist(LifecycleEventArgs $args)
{
$log = $args->getEntity();
if ($log instanceof PmodLog) {
$order = ....;
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
$department = $this->serviceContainer->get('security.token_storage')->getToken()->getUser()->getDepartment();
if ($order) {
$log->setOrder($order);
$log->setCreatedBy($user);
$log->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
$log->setDepartment($department);
$log->setAction("created");
}
}
}
I don't get how to get the current order I'm busy with. And how the setAction will be something else when the user modified the order. For example 'edited' or 'approved'. I've been trough the documentation of Sonata with no luck unless I miss read something.
Remember I use Sonata, otherwise this would've been easy to implement in my own Controller Actions.
You can directly add to your entity a listener that create/update your order's logs.
First you create the listener class :
use Doctrine\ORM\Event\LifecycleEventArgs;
class OrderListener
{
public function postPersist(Order $order, LifecycleEventArgs $event)
{
// for example
// if you want to store the date creation :
if($order->getId() == null)
{
$order->setDateCreate(new \DateTime('now'));
}
// if you want to store the last update date :
$order->setDateUpdate(new \DateTime('now'));
//... or whatever you want to store...
}
}
Then register it in a service.yml :
order_listener:
class: YOUR\NAMESPACE\OrderListener
tags:
- { name: doctrine.orm.entity_listener }
Finally, link your entity to the listener (here with annotations) :
/**
* #ORM\EntityListener("YOUR\NAMESPACE\OrderListener")
*/
class Order
{
...
}

Symfony 2 - Adding error to form element on preSubmit event subscriber

I have a preSubmit event in an event subscriber for a form, and for a specific case I want to add an error to a form field. My method within the subscriber is as follows:
public function onPreSubmit(FormEvent $event)
{
$sourceData = $event->getData();
$form = $event->getForm();
$identifier = &$sourceData['identifier'];
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (is_null($identifier)) {
$form->get('identifier')->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($sourceData);
}
}
However when I print the form error in the view, it is empty. Is it possible to do this in a preSubmit event? Am I looking at this the wrong way?
The issue is related to the Symfony\Component\Form\Form::submit method that removes all of the form field specific errors assigned after the PRE_SUBMIT event.
During Form::submit it iterates over all the forms child objects (which are also themselves Form objects as noted by the other answers) and calls their submit methods individually. Resulting in the form element errors that were added during the parent's PRE_SUBMIT event to be reset to an empty array.
This is why you can use $form->addError() in the parent PRE_SUBMIT event or set the form element to error_bubbling => true and it will display as the parent form errors, but not to a specific form element.
Here's the example of what occurs without looking through the entire codebase for Symfony Forms.
class Form
{
public function addError($error)
{
if($this->parent && $this->config->getErrorBubbling()) {
$this->parent->addError($error); //element had error_bubbling => true, attach the error to the parent.
} else {
$this->errors[] = $error; //add it to the current object's errors array
}
}
public function submit()
{
$this->errors = array(); //resets the errors of the current object
$this->preSubmitEvent();
foreach($this->children as $child) {
$child->submit(); //errors in child object are reset
}
}
}
So it results in
Form:
submitMethod:
preSubmitEvent
children:
submitMethod:
preSubmitEvent
To get around the issue you can add a PRE_SUBMIT event directly to your form element to validate that element and add errors to it.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('identifier', Form\TextType::class);
//...
$builder->get('identifier')->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'validateIdentifier']);
}
Then alter your onPreSubmit method accordingly.
public function validateIdentifier(FormEvent $event)
{
$identifier = $event->getData();
$element = $event->getForm();
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (null === $identifier) {
$element->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($identifier);
}
}
It should be possible if you look at the Form::submit(), you'll see that the errors are reset before the PRE_SUBMIT data is dispatched. Also Validation Listener doesn't reset the errors on the form, and the Violation Mapper only adds Errors to the form. And AFIK there aren't any listeners that reset the form's errors. So maybe you're doing something else wrong.
It is possible that errors are just not displayed in your layout.
To debug, make sure your form is actually made invalid by your custom check (if you show your controller I may be able to help with actual code).
If that is in fact your issue, you may want to add error_bubbling => true to your field definition.

Getting textbox values Symfony2 form

I have a form in my project which is very simple.
It is a textbox with a label, that's it.
But when I try to get the data from those textboxes, it returns NULL.
get($id) and get($id)->getData(), both return NULL.
It's a form without any class attached to it, just to keep it simple.
The purpose of this form is to adjust a number.
What am I doing wrong? Or is there a better way to solve this?
public function makenAction()
{
$em = $this->getDoctrine()->getManager();
$orderregels = $this->getRequest()->getSession()->get('orderregelz')->getOrderregels();
$overzicht = $this->createFormBuilder();
foreach($orderregels as $value)
{
/*
* getting some values from database
*/
$overzicht->add($temp->getOrderregelD(),"text",array('label'=>$tmpcompleet));
}
$overzicht->add('Verzenden','submit');
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$data = array();
foreach ($orderregels as $value)
{
$data[] = $overzicht->get($value);
}
}
this is the way you get data from the request
GET
$value = $request->query->get("input_name");
POST
$value = $request->request->get("input_name");
Otherwise you can simply do:
$formData = $overzicht->getData();
and then access the values like:
$foo = $formData['foo'];
To directly access all the values of a specified form, do:
$formData = $this->getRequest()->request->get('form');
After all, you should to bind the request to form.
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
var_dump($editForm->getData()); die;
}
doc

In symfony, how to set the value of a form field?

I'm overriding my doSave() method to basically do the following: I have a sfWidgetFormPropelChoice field that the user can either choose from, or type a new option. How can I change the widget's value? Or maybe I am approaching this the wrong way. So here is how I overrode the doSave() method:
public function doSave($con = null)
{
// Save the manufacturer as either new or existing.
$manufacturer_obj = ManufacturerPeer::retrieveByName($this['manufacturer_id']->getValue());
if (!empty($manufacturer_obj))
{
$this->getObject()->setManufacturerId($manufacturer_obj->getId()); // NEED TO CHANGE THIS TO UPDATE WIDGET'S VALUE INSTEAD?
}
else
{
$new = new Manufacturer();
$new->setName($this['manufacturer_id']->getValue());
$new->save();
$this->getObject()->setManufacturerId($new->getId()); // NEED TO CHANGE THIS TO UPDATE WIDGET'S VALUE INSTEAD?
}
parent::doSave($con);
}
You should use setDefault or setDefaults and then it will autopopulate with the bound values.
(sfForm) setDefault ($name, $default)
(sfForm) setDefaults ($defaults)
usage
$form->setDefault('WidgetName', 'Value');
$form->setDefaults(array(
'WidgetName' => 'Value',
));
You could do it in the action :
$this->form->getObject()->setFooId($this->foo->getId()) /*Or get the manufacturer id or name from request here */
$this->form->save();
But I prefer to do the kind of work you are doing with your manufacturer directly in my Peer so my business logic is always at the same place.
What I put in my forms is mainly validation logic.
Example of what to put in the save method of the Peer :
public function save(PropelPDO $con= null)
{
if ($this->isNew() && !$this->getFooId())
{
$foo= new Foo();
$foo->setBar('bar');
$this->setFoo($foo);
}
}
Two assumption here: a) your form gets the name of the manufacturer and b) your model wants the ID of a manufacturer
public function doSave($con = null)
{
// retrieve the object from the DB or create it
$manufacturerName = $this->values['manufacturer_id'];
$manufacturer = ManufacturerPeer::retrieveByName($manufacturerName);
if(!$manufacturer instanceof Manufacturer)
{
$manufacturer = new Manufacturer();
$manufacturer->setName($manufacturerName);
$manufacturer->save();
}
// overwrite the field value and let the form do the real work
$this->values['manufacturer_id'] = $manufacturer->getId();
parent::doSave($con);
}

Categories