Save object with original data after form bind - php

I want to make a single revision option for saving certain objects in Sonata Admin.
I though to do this in the following way:
user edits entry
form is validated
the new information is saved as a separate entry (i'll call it revision)
the original object is not modified, except for a relation to the revision
So the code looks something like this (source Sonata\AdminBundle\Controller\CRUDController::editAction()):
$object = $this->admin->getObject($id);
$this->admin->setSubject($object);
$form = $this->admin->getForm();
$form->setData($object);
$form->bind($this->get('request')); // does this persist the object ?
// and here is what I basically want to do:
$object->setId(null);
$orig = $em->getRepository("MedtravelClinicBundle:Clinic")->find($id);
$orig->setRevision($object);
$this->admin->update($orig);
The problem is that $orig loads the already modified, so var_dump($orig === $object) is true.
I also tried $em->getUnitOfWork()->getOriginalEntityData($object); - which grabs the correct data, but as an array, not as an object (this will probably be the last resort).
So, how can I get (and save) the original object after the form bind took place ?

I think you should use the clone keyword to get a independent instance of the object you want to store. It should works by following these steps:
Load the original entity ($object)
Clone the original entity to get a new temporary entity ($newObject)
Alter the $newObject to make it a new entry: $newObject->setId(null);
Bind $newObject to the form
Save (persist) $newObject as a revision
Add the revision to ($object) and persist it too
I hope that if the form is invalid you won't lose all the data sent by the user.
Just in case, I used this answer to find the differences between the original entity and the one modified by the form.

Related

Symfony4 stop handleRequest() from removing values of my object

I am submitting a form and in my controller I use handleRequest to validate the form.
Lets say I try to update my object name 'object' - It has an id, name and color field.
On the same page I also show a lost with the names of all my objects that I fetch from the database
Here is my code:
$object = self::getObjectById($objectId);
$objects = self::getAllObjects();
$objectForm = self::CreateObjectForm($object);
$objectFormForm->handleRequest($request);
dd($objects);
When I submit the form and I leave the name field open,
It throws an error that the field is required when it reloads the page, the name field of the form is still empty which is normal.
But here is the problem, in the list of objects that is also showing on this page the name of the object I tried to update has no name showing anymore in this list.
I don't know why this is happening since I fetched this list of objects completely separately from the form object. When I dd() the objects after the handleRequest() I cans see in the dumped vars that indeed the name field is empty. When I check the database, the name field is not empty and still holds the old name. Which makes sense because the object is not persisted and flushed to de db.
When I dd() the same list before the handleRequest() everything is normal.
How can I prevent this behavior? And why is this happening?
I don't know why this is happening since i fetched this list of objects completely seperatly from the form object.
It's referencing the same entity which get's dumped in it's current, live, dirty state as submitted by the form.
If you need to render the clean, persisted values then you just need to store them in a variable as a string instead of as the object, and before handling the request.
Try something like this:
$object = self::getObjectById($objectId);
$objects = self::getAllObjects();
$persistedObjects = [];
foreach($objects as $obj){
$persistedObjects[$obj->getId()] = $obj->getName();
}
$objectForm = self::CreateObjectForm($object);
$objectFormForm->handleRequest($request);
dd($persistedObjects);

Clone entity and all related entities in CakePHP 3

In my CakePHP 3 app, I have a somewhat elaborate tree of entities that I need to clone and save.
The root of the structure is a Questionnaire, a Questionnaire hasMany Questions, each Question hasMany Fields, etc. (it goes deeper). Now I want the user to be able to define a new questionnaire by copying an old one. Then they can change whatever they need.
I can get a dump of what I need to copy by using $questionnaire->$this->Questionnaires->get($id) with the appropriate contain fields. Is there a clever way to save this as a bunch of new entities while preserving the data and the structure between them?
I think the best possible way would be following work flow:
Get object you want to clone
Go through the collection and remove all ID's
Convert to array and use that in $this->Questionnaires->newEntity($arrayData, ['associated' => ['Questions', '...']]);
Now save the new entity with all the related data you want to keep
AFAIK there's no "smarter" way of cloning an entity with associations in Cake 3 :-)
You could also use this plugin. You only have to configure the behavior and it gives you other functionalities, like setting default values or appending text to some field.
use EntityInteface toArray() to get all its fields:
$newEtity = $someTable->newEntity($oldEtity->toArray());
unset($newDevice->created);
unset($newDevice->id);
$someTable->save($newEntity);
$original = $this->Table->get($id)->toArray();
$copy = $this->Table->newEntity();
$copy = $this->Table->patchEntity($copy, $original);
unset($copy['id']);
// Unset or modify all others what you need
$this->Table->save($copy);
Work perfectly like this :)
Here's the way I did it when I needed something similar (adapted to the Questionnaire example):
// load the models
$this->loadModel('Questionnaire');
$this->loadModel('Questions');
// get the questions to questionnaire association - will need the PK name
$qAssoc = $this->{'Questions'}->associations()->get('Questionnaire');
// get the existing entity to copy
$existingEntity = $this->{'Questionnaire'}->get($id, [
'contain' => ['Questions'],
]);
// clean up the existing entity
$existingEntity->unsetProperty($this->{'Questionnaire'}->getPrimaryKey());
$existingEntity->unsetProperty('created');
$existingEntity->unsetProperty('modified');
// clean up the associated records of the existing entity
foreach ($existingEntity->questions as &$q) {
$q->unsetProperty($this->{'Questions'}->getPrimaryKey());
$q->unsetProperty($qAssoc->getForeignKey());
}
// create a new entity and patch it with source data
$newEntity = $this->{'Questionnaire'}->patchEntity(
$this->{'Questionnaire'}->newEntity(),
$existingEntity->toArray()
);
// save the new entity
$result = $this->{'Questionnaire'}->save($existingEntity);
References:
https://api.cakephp.org/3.0/class-Cake.ORM.Entity.html
https://api.cakephp.org/3.0/class-Cake.ORM.Table.html

How can I pass the current referenced object in form collection in Symfony 2?

I use this code
$builder->add('userTasks','collection',array('type' => new UserTaskType()));
This is working fine
userTasks will be a collection different userTask objects which will in turn create the form.
Now is there any way that i can pass that individual UserTask object in the constructor like this
$builder->add('userTasks','collection',array('type' => new UserTaskType($userTask)));
so that i can use that to generate Label for the form.
Is it possible
If i use $user->UseTasks then that will be a collection of all tasks but i only want that object whose form is being created
If I understand the question correctly, you want to adjust the form based on the actual data object which the form will be bound to? It is one of the those simple sounding requirements that is actually a bit involved.
You need to use the form event system:
http://symfony.com/doc/master/cookbook/form/dynamic_form_generation.html
It's not so bad once you have worked through it.
===
To answer the first comment:
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$data will be your object, in this case a $userTask.

Doctrine (Mongo) Persisting Partial Document

I have a custom class that populates a controller's action parameters based on the typehint of the parameter. This works well for documents (using public properties and setters).
My aim is to make the controller simple:
function updateAction(Article $article)
{
$dm = new DocumentManager(); // code elsewhere
$dm->merge($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
The problem is that the input supplying the fields to programatically populate the Article class doesn't contain all of the properties of an Article class (perhaps the edit form only contains Title and Content, but disregards Author, etc).
I was hoping that the presence of an ID would allow the document to be merged gracefully with what is currently in the database. However, any fields that are missing at the time of a merge will be removed from the document in the database.
Is there a way to update a document in such a way that only the fields that are present (non-null, I guess) are updated?
Rather than hitting the db twice - once for the find, and once for the update, you can use a FIND_AND_UPDATE query.and do it all in one step.
See this docs page for details: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/find-and-update.html
It seems that a clean way would be to bind the model AFTER retrieving it from the database. Something along the lines of ASP.NET MVC's UpdateModel.
function updateAction($id)
{
$dm = new DocumentManager(); // code elsewhere
$article = $dm->getRepository('Article')->find($id);
$this->updateModel($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
If there are any better suggestions, feel free to answer...

Symfony/propel form saving an object with additional properties

First, let me say, that I find the sfFormPropel form's interface inconsistent.
There is bind(), which returns nothing, but triggers validation, save() which returns the saved object, and bindAndSave(), which returns boolean, actually the return value of isValid(). Now, I have a working application, but I don't feel the code is right, and I'm quite new to symfony, so perhaps I'm missing something.
The object I need to create needs some external properties, that are not presented in the form, are external to the model, and are handled by the application (for example, the userId of the user, that created the entity, an external-generated guid, etc.).
Right now the flow is as follows:
get values from request and bind them to form
check if form is valid
if it's valid, add additional values and bind them to form one more time
save the form and return the object
The obvious answer would to add application-specific values to the values, retrieved from request, but It does not make sense to bind the application-specific values if the form is not valid, since they can be potentially expensive operations, may create database records, etc. Additionally, it should not be possible to pass those values with the post request, they should come from application only.
Now, I though that I have to let the model do these things, but since the data is external to the model, action still need to pass it to the model. The problem is, if I call $form->getObject() after bind(), it still has the old data, and not the data submitted.
What is the correct way to implement this kind of post-processing?
Second bounty is started to award the other valuable answer
The correct way would be setting your default values on the object you are passing to the form constructor. For example if you want to set the logged in user id on an object you are creating:
$article = new Article();
$article->setUserId($this->getUser()->getId());
$form = new ArticleForm($article);
if ($request->isMethod('post')) {
$form->bind($request->getParameter('article'));
if ($form->isValid()) {
$form->save();
}
}
Likewise for existing object, you can load the record and change any properties before passing it to the form constructor.
EDIT:
If you want to modify the object after validating, you can use $form->updateObject() like Grad suggests in his response. If the generated values depend on the submitted values, you can override sfFormObject::processValues():
class UserForm {
public function processValues($values) {
$values['hash'] = sha1($values['id'] . $values['username']);
return parent::processValues($values);
}
}
In case you need something from the action, you can always pass it as an option to the form:
$form = new UserForm($user, array('foo' => $bar));
That way, you can use $this->getOption('foo') anywhere in your form code, eg. in processValues().
It kind of depends of who has "knowledge" about the extra attributes. If they're really request specific, thus need to be processed in the controller, I go for binding, testing if valid and then update the bound object. To get the updated object with the bound (and validated) fields use the updateObject function.
$form->bind(..)
if ($form->isValid()) {
$obj = $form->updateObject(); // Updates the values of the object with the cleaned up values. (returns object)
$obj->foo = 'bar';
$obj->save();
}
But since this normally is also behaviour that is form specific, I usually go for overriding the Form class. By overriding the doUpdateValues() function you can easily access submitted data, and append your own data. Of course you can also go higher in the chain, and override the save() function.
To set custom data for this form, you can also 'publish' public methods, which can then be used by the controller.

Categories