Symfony2: File Upload via Doctrine does not fire the PrePersist/PreUpdate lifecycle-event - doctrine-orm

i tried to implement the file upload via doctrine/lifecycle callbacks as described here:
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html#using-lifecycle-callbacks
So far it works, but the PrePersist/PreUpdate Event is not fired, the function "preUpload" is not called.
Functions like "upload" and "removeUpload" triggered by other lifecycle events are called correctly.
Does anyone have an idea why the event is not fired or a solution for this problem?
Thanks

I have another solution to this problem:
My entity has a field "updatedAt" which is a timestamp of the last update. Since this field gets set anyway (by the timestampable extension of Gedmo) I just use this field to trick doctrine into believing that the entitiy was updated.
Before I persist the entity I set this field manually doing
if( $editForm['file']->getData() )
$entity->setUpdateAt(new \DateTime());
This way the entity gets persisted (because it has changed) and the preUpdate and postUpdate functions are called properly.
Of course this only works if your entity has a field that you can exploit like that.

You need to change tracking policies.
Full explanation.

there's a much simpler solution compared with changing tracking policies and other solutions:
in controller:
if ($form->isValid()) {
...
if ($form->get('file')->getData() != NULL) {//user have uploaded a new file
$file = $form->get('file')->getData();//get 'UploadedFile' object
$news->setPath($file->getClientOriginalName());//change field that holds file's path in db to a temporary value,i.e original file name uploaded by user
}
...
}
this way you have changed a persisted field (here it is path field), so PreUpdate() & PostUpdate() are triggered then you should change path field value to any thing you like (i.e timestamp) in PreUpdate() function so in the end correct value is persisted to DB.

A trick could be to modify the entity no matter what..on postLoad.
1 Create an updatedAt field.
/**
* Date/Time of the update
*
* #var \Datetime
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
2 Create a postLoad() function that will modify your entity anyway:
/**
* #ORM\PostLoad()
*/
public function postLoad()
{
$this->updatedAt = new \DateTime();
}
3 Just update that field correctly on prePersist:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
$this->updatedAt = new \DateTime();
//...update your picture
}

This is basically a slight variation of #philipphoffmann's answer:
What i do is that i modify an attribute before persisting to trigger the preUpdate event, then i undo this modification in the listener:
$entity->setToken($entity->getToken()."_tmp");
$em->flush();
In my listener:
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof MyEntity) {
$entity->setToken(str_replace('_tmp', '', $entity->getToken()));
//...
}
}

Another option is to display the database field where the filename is stored as a hidden input field and when the file upload input changes set that to empty so it ends up triggering doctrine's update events. So in the form builder you could have something like this:
->add('path', 'text', array('required' => false,'label' => 'Photo file name', 'attr' => array('class' => 'invisible')))
->add('file', 'file', array('label' => 'Photo', 'attr' => array('class' => 'uploader','data-target' => 'iddp_rorschachbundle_institutiontype_path')))
Path is a property managed by doctrine (equal to the field name in the db table) and file is the virtual property to handle uploads (not managed by doctrine). The css class simply sets the display to none. And then a simple js to change the value of the hidden input field
$('.uploader').change(function(){
var t = $(this).attr('data-target');
//clear input value
$("#"+t).val('');
});

For me, it worked good when I just manually called these methods in the controller.

Do you have checked your metadata cache driver option in your config.yml file?If it exists, just try to comment this line:
metadata_cache_driver: whateverTheStorage
Like this:
#metadata_cache_driver: whateverTheStorage

Related

Symfony4 returning serialized json repsonse

I'm looking at a new Symfony5 project , where I'm trying to return a JSON response of some data.
I have a Project and a ProjectItem
I have the following:
// Project.php
/**
* #ORM\OneToMany(targetEntity="App\Entity\ProjectItem", mappedBy="project")
*/
private $projectItems;
// ProjectItem.php
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="projectItems")
*/
private $project;
I have one Project, that can have many ProjectItems
I then have a controller that I'm trying to return a json response:
public function index()
{
$itemsList = $this->getDoctrine()
->getRepository(Project::class)
->findAll();
$items = $this->get('serializer')->serialize($itemsList, 'json');
return new Response($items, 200);
}
This is currently returning an error:
A circular reference has been detected when serializing the object of class "App\Entity\Project" (configured limit: 1)
Am I using the serializer correctly or are my models incorrectly configured?
Simply use json_encode:
public function index()
{
$itemsList = $this->getDoctrine()
->getRepository(Project::class)
->findAll();
return new Response(
json_encode($itemsList),
200
);
}
or use JsonResponse class:
return new JsonResponse($itemsList);
You have a circular reference with your relations. Im guessing ProjectItem has a field project that is referencing Project which causes a loop for the serializer. You can ignore said attribute to prevent this from happening. Checkout the ignored attributes section of the serializer documentation.
Another option would be to use Serialization Groups. Every property would get a Group annotation like for example #Groups("group1") excluding that reference property back to Project.
You would then tell the serializer to serialize that group:
$json = $serializer->serialize(
$itemList,
'json', ['groups' => 'group1']
);
You may also checkout JMS Serializer which adds #Exclude and #Include annotations to make this step a bit easier.

Apollo Link State Default Resolver Not Working (#client query parameter variables)

Example here: https://codesandbox.io/s/j4mo8qpmrw
Docs here: https://www.apollographql.com/docs/link/links/state.html#default
TLDR: This is a todo list, the #client query parameters don't filter out the list.
This is the query, taking in $id as a parameter
const GET_TODOS = gql`
query todos($id: Int!) {
todos(id: $id) #client {
id
text
}
}
`;
The query passes the variable in there
<Query query={GET_TODOS} variables={{ id: 1 }}>
/* Code */
</Query>
But the default resolver doesn't use the parameter, you can see it in the codesandbox.io example above.
The docs say it should work, but I can't seem to figure what I'm missing. Thanks in advance!
For simple use cases, you can often rely on the default resolver to fetch the data you need. However, to implement something like filtering the data in the cache or manipulating it (like you do with mutations), you'll need to write your own resolver. To accomplish what you're trying to do, you could do something like this:
export const resolvers = {
Query: {
todos: (obj, args, ctx) => {
const query = gql`
query GetTodos {
todos #client {
id
text
}
}
`
const { todos } = ctx.cache.readQuery({ query })
return todos.filter(todo => todo.id === args.id)
},
},
Mutation: {},
}
EDIT: Every Type we define has a set of fields. When we return a particular Type (or List of Types), each field on that type will utilize the default resolver to try to resolve its own value (assuming that field was requested). The way the default resolver works is simple -- it looks at the parent (or "root") object's value and if it finds a property matching the field name, it returns the value of that property. If the property isn't found (or can't be coerced into whatever Scalar or Type the field is expecting) it returns null.
That means we can, for example, return an object representing a single Todo and we don't have to define a resolver for its id or text fields, as long as the object has id and text properties on it. Looking at it another way, if we wanted to create an arbitrary field on Todo called textWithFoo, we could leave the cache defaults as is, and create a resolver like
(obj, args, ctx) => obj.text + ' and FOO!'
In this case, a default resolver would do us no good because the objects stored in the cache don't have a textWithFoo property, so we write our own resolver.
What's important to keep in mind is that a query like todos is just a field too (in this case, it's a field on the Query Type). It behaves pretty much the same way any other field does (including the default resolver behavior). With apollo-link-state, though, the data structure you define under defaults becomes the parent or "root" value for your queries.
In your sample code, your defaults include a single property (todos). Because that's a property on the root object, we can fetch it with a query called todos and still get back the data even without a resolver. The default resolver for the todos field will look in the root object (in this case your cache), see a property called todos and return that.
On the flip side, a query like todo (singular) doesn't have a matching property in the root (cache). You need to write a resolver for it to have it return data. Similarly, if you want to manipulate the data before returning it in the query (with or without arguments), you need to include a resolver.

Symfony - Validate entity differently in INSERT, UPDATE or DELETE

I want validate an entity doctrine differently when the entity is created, updated or deleted.
There is an entity constraint validator in my entity class.
// src/AppBundle/Entity/AcmeEntity.php
use AppBundle\Validator\Constraints as AcmeAssert;
/**
* #AcmeAssert\CustomConstraint
*/
class AcmeEntity
{
// ...
protected $name;
// ...
}
In my CustomConstraint I want determine if the Entity will be updated, created or delete for execute a specific validator.
Using unit of work is a solution ?
What is the best way to make this?
I think this problematic is common in lot of application ?
Thank's all ;)
You could either use validation groups based on the submitted data or handle itwhen you create the form by passing the validation group.
For example, in your controller when you create the form;
$form = $this->createForm(new AcmeType(), $acme, ['validation_groups' => ['create']]);
Then you entity would be something like;
/**
* Get name
*
* #Assert\Length(min=2, max=11, groups={"create", "update"})
* #AcmeAssert\ContainsAlphanumeric(groups={"create"}) // only applied when create group is passed
* #return string
*/
public function getName()
{
return $this->name;
}
This is what validation groups are made for.
Since Symfony Forms read validations from entity annotations and use internally the Validator component you'd have a look at these articles in the documentation:
http://symfony.com/doc/current/form/validation_groups.html
http://symfony.com/doc/current/validation/groups.html
http://symfony.com/doc/current/validation/sequence_provider.html

Zend Framework 2 Saving layout and template content in database

I want to save both layout and view template CONTENT in the database. Each view template will be associated to a layout_id. When a controller action loads, it will fetch the appropriate layout and view from database.
I've done fair amount of researching, looks like this hasn't been discussed before, at least not with ZF2. Not only I want different themes for the site, I also want version control on the design, user can work on a version of template, save it and when done publish the site. I've been looking into custom view strategy and renderer and could not find out how to piece everything together.
Please advice how to proceed with this problem. If there are any tutorials out there please let me know.
First, you have to decide your database structure. I would prefer a table where for each controller/action, depending on the version of the design, you have a file for the layout view, and another for the action view. I think it is a flexible way, since you can have for instance the same layout for all the controllers, but a a different view for the content of every action, or you can have a different layout for an specific controller.
It could be something like this.
CREATE TABLE `templates` (
`version` INT(5) NOT NULL,
`controller` VARCHAR(30) NOT NULL,
`action` VARCHAR(30) NOT NULL,
`layout_view` VARCHAR(30) DEFAULT NULL,
`action_view` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`version`,`module`,`controller`,`action`)
)
remember that the template names has to be 'MODULE/CONTROLLER/ACTION' that is the format that the function ViewModel::setTemplate is expecting. So if in your Module Foo, you have the folder view, with a hierachy like this Foo/view/foo/controllerName/actionName.phtml in your database you should store: foo/controllerName/actionName
Then you have to set, somewhere in the config files, the current design version that has to be used. This version could be a different one in your local machine, and in the production server. For that, you leverage the /config/autoload/local.php that allows you to have different configuration in the different enviroments, just telling your git, subversion, or ftp, to ignore this file, so you can have a different one in every enviroment. Check this docs. It will be enough if you put something like this:
/config/autoload/local.php
return array(
'designVersion' => 1,
);
Then when the application loads, you have to check the current version of the design, and set the correct templates to be used. I would do it after the dispatch event, so the way would be to add a listener in the app bootstrarp. For that, in your main module (i.e. "App" or "Application" module), in the Module.php you overwrite the onBootstrap function, load the event manager, and add a callback to the MvcEvent::EVENT_DISPATCH event.
There, you can have access to the serviceManager, so you can retrieve your database adapter, also, you can know the current requested controller and action, the current viewmodel object, and from it, the action viewmodel. So you already have everything you need. So you can go like this:
public function onBootstrap(MvcEvent $e) {
//get the service manager
$sm = $e->getApplication ()->getServiceManager ();
//get your db adapter
$db=$sm->get("whatever is the service name of your database adapter, entity manager, or whatever you are using");
//get the config
$config = $sm->get ( 'config' );
$version = $config ['designVersion'];
//get the event manager, and attach a callback to the MvcEvent::EVENT_DISPATCH
$em = $e->getApplication ()->getEventManager ();
$em->attach ( MvcEvent::EVENT_DISPATCH, function ($e) use($sm) {
//you get the routeMath, so you can know the controller and action
$routeMatch = $e->getRouteMatch ();
$action=$routeMatch->getParam ( 'action' );
$controller=$routeMatch->getParam ( 'controller' );
//now, with $action, $controller, and $version
//you query your db to get the layout and view templates:
$views=$db->whetever..
$layout_template= $views->layout_view;
$action_template= $views->action_view;
//you get the current viewModel, it hasnt been rendered yet, so we can set the templates
$viewModel = $e->getViewModel ();
if (is_null ( $viewModel ))
return;
$viewModel->setTemplate ($layout_template);
//and now, we get the action view model, that we know that is set as a children of the layout viewmodel. So we can retrieve it like this:
$children = $viewModel->getCurrent()->getChildren();
$child = $children[0];
//if we are afraid the view could have more children, and you want to make sure that you rerieve the correct one, then you could iterate over $children and look for the child that has the correct captureTo name set. For the action’s view model, this defaults to content:
/*
$children = $viewModel->getCurrent()->getChildren();
$child=null;
foreach($children as $c) {
if ($c->captureTo() == 'content') {
$child=$c;
break;
}
}
*/
//and now, you set the template to the view:
$child->setTemplate ($action_template);
}, - 100 );
}

Recomputing entity changeset in onFlush listener

Consider the following schema:
[Work]
id
tags ManyToMany(targetEntity="Tag", inversedBy="works", cascade={"persist"})
[Tag]
id
works_count
works ManyToMany(targetEntity="Work", mappedBy="tags")
works_count is a counter cache for Tag::works.
I have a onFlush listener on Work that checks if Work::tags has changed, and updates each of the tags' works_count.
public function onFlush(OnFlushEventArgs $args)
{
foreach ($uow->getScheduledEntityUpdates() as $work) {
$changedTags = /* update relevant tags and return all the changed ones */
$metadata = $em->getClassMetadata('Acme\Entity\Tag');
foreach ($changedTags as $tag) {
$uow->recomputeSingleEntityChangeSet($metadata, $tag);
}
}
}
Now if I read the changesets of the updated tags, the changes of works_count appears correctly, but they don't get updated in the database..
If I replace recomputeSingleEntityChangeSet() with computeChangeSet() then everything works as expected and the DB is updated, but computeChangeSet() has an #internal Don't call from the outside. annotation on it, so I'm not sure what the consequences are..
Every source on the internet says to use recomputeSingleEntityChangeSet so why doesn't it work in this case?
P.S
The tags are managed by the EntityManager ($em->contains($tag) returns true)
This problem was related with a bug in UnitOfWork and finally it's fixed with the release of Doctrine ORM 2.4.3 on September 11, 2014. See DDC-2996 for details.
It seems that Doctrine 2.2 can merge change sets or generate new change sets, but it needs to know which. If you get it wrong, it will either replace your existing change sets or do nothing at all. I'd be very interested to know if there is a better option than this, or if this is even right.
if($uow->getEntityChangeSet($entity)) {
/** If the entity has pending changes, we need to recompute/merge. */
$uow->recomputeSingleEntityChangeSet($meta, $contact);
} else {
/** If there are no changes, we compute from scratch? */
$uow->computeChangeSet($meta, $entity);
}
In doctrine 2.4.1, use recomputeSingleEntityChangeSet only if you are changing tag in the event listener AND UOW contain tag ChangeSet (Change that happen outside of the event listener). Basically recomputeSingleEntityChangeSet is a function to merge ChangeSet for an entity.
Doc from the function
The passed entity must be a managed entity. If the entity already has a change set because this method is invoked during a commit cycle then the change sets are added whereby changes detected in this method prevail.
NOTE: You need to make sure UOW already have ChangeSet for the entity, otherwise it will not merge.
For future readers, at all cost try to avoid the listeners. Those are hardly testable, your domain should not rely on magic. Consider OP's test case how to achieve the same without Doctrine events:
Work class:
public function addTag(Tag $tag): void
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
$tag->addWork($this);
}
}
Tag class:
public function addWork(Work $work): void
{
if (!$this->works->contains($work)) {
$work->addTag($this);
$this->works->add($work);
$this->worksCount = count($this->works);
}
}
TagTest class:
public function testItUpdatesWorksCountWhenWorkIsAdded()
{
$tag = new Tag();
$tag->addWork(new Work());
$tag->addWork(new Work());
$this->assertSame(2, $tag->getWorkCount());
}
public function testItDoesNotUpdateWorksCountIfWorkIsAlreadyInCollection()
{
$tag = new Tag();
$work = new Work();
$tag->addWork($work);
$tag->addWork($work);
$this->assertSame(1, $tag->getWorkCount());
}