Search code examples
phpsymfonysymfony-2.6

How to make sure Synfony Entity is loaded to trigger postLoad event?


I'm new to Symfony and following the Jobeet tutorial. I'm trying to inject Container into Entity using service listener and postLoad. The purpose is to use LiipImagineBundle to write a thumbnail image in @ORM\PostPersist. The problem is that the Job entity is not loaded on some routes and then postLoad is not triggered. So, the container which is used in @ORM\PostPersist is not available.

According to the tutorial, there are two routes where PostPersist run:

  • job/create
  • job/update

I found that the Job entity is not loaded and postLoad is not triggered on job/create. The route job/update is fine and the container is injected. I posted some code snippets of

  • services.yml
  • JobListener.php
  • Entity\Job.php
  • JobController.php

src/Ibw/JobeetBundle/Resources/config/services.yml

services:
    ibw.jobeet.entity.job.container_aware:
        class: Ibw\JobeetBundle\Doctrine\Event\Listener\JobListener
        calls:
            - [setContainer, ["@service_container"]]
        tags:
            - { name: doctrine.event_listener, event: postLoad }

src/Ibw/JobeetBundle/Doctrine/Event/Listener/JobListener.php

<?php
namespace Ibw\JobeetBundle\Doctrine\Event\Listener;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;

class JobListener
{
    /** @var ContainerInterface */
    protected $container;

    /**
    * @param ContainerInterface @container
    */
    public function setContainer(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * @ORM\PostLoad
     */
    public function postLoad(LifecycleEventArgs $eventArgs)
    {
        $entity = $eventArgs->getEntity();
        if (method_exists($entity, 'setContainer')) {
            $entity->setContainer($this->container);
        }
    }
}

src/Ibw/JobeetBundle/Entity/Job.php

// ....
/**
 * @ORM\PostPersist
 */
public function upload()
{
    if (null === $this->getFile()) {
        return;
    }

    if ($this->getFile()->move($this->getUploadTmpRootDir(), $this->logo)) {
        $this->moveAndResizeImage();
    }

    $this->file = null;
}

private function moveAndResizeImage($filter = 'primary')
{
    $path = $this->getWebTmpPath();
    $target = $this->getAbsolutePath();

    if (file_exists($path)) {
        $container = $this->container; 
        // got problem here. $container is null for /job/create
        // but it is okay for /job/update
        $dataManager = $container->get('liip_imagine.data.manager');
        $filterManager = $container->get('liip_imagine.filter.manager');

        $image = $dataManager->find($filter, $path); 
        $thumb = $filterManager->applyFilter($image, $filter);

        file_put_contents($target, $thumb->getContent());

        unlink($path); // remove the temp client original image
    }
}
// ...

src/Ibw/JobeetBundle/Controller/JobController.php

// ...
public function createAction(Request $request)
{
    $entity = new Job();
    $form = $this->createCreateForm($entity);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($entity);
        $em->flush();

        return $this->redirect($this->generateUrl('ibw_job_preview', array(
            'company' => $entity->getCompanySlug(),
            'location' => $entity->getLocationSlug(),
            'token' => $entity->getToken(),
            'position' => $entity->getPositionSlug()
        )));
    }

    return $this->render('IbwJobeetBundle:Job:new.html.twig', array(
        'entity' => $entity,
        'form'   => $form->createView(),
    ));
}
// ....

Is there any way to solve this problem?


Solution

  • I solved this by adding prePersist in the event listener class to make sure the container is available before persist. The LiipImagineBundle services are called on postPersist. I renamed JobListener to MyListener. Here is my working code:

    src/Ibw/JobeetBundle/Resources/config/services.yml

    ibw.jobeet.event.mylistener:
        class: Ibw\JobeetBundle\Listener\Event\MyListener
        calls:
            - [setContainer, ["@service_container"]]
        tags:
            - { name: doctrine.event_listener, event: postLoad }
            - { name: doctrine.event_listener, event: prePersist }
    

    src/Ibw/JobeetBundle/Listener/Event/MyListener.php

    <?php
    namespace Ibw\JobeetBundle\Listener\Event;
    
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Doctrine\ORM\Event\LifecycleEventArgs;
    
    use Ibw\JobeetBundle\Entity\Job;
    
    class MyListener
    {
        /** @var ContainerInterface */
        protected $container;
    
        /**
        * @param ContainerInterface @container
        */
        public function setContainer(ContainerInterface $container)
        {
            $this->container = $container;
        }
    
        /**
         * @param LifecycleEventArgs $eventArgs
         */
        public function postLoad(LifecycleEventArgs $eventArgs)
        {
            $this->injectContainer($eventArgs);
        }
    
        /**
         * @param LifecycleEventArgs $eventArgs
         */
        public function prePersist(LifecycleEventArgs $eventArgs)
        {
            $this->injectContainer($eventArgs);
        }
    
        protected function injectContainer(LifecycleEventArgs $eventArgs)
        {
            $entity = $eventArgs->getEntity();
            if ($entity instanceof Job) {
                $entity->setContainer($this->container);
            }
    
            $em = $eventArgs->getEntityManager();
            $repository = $em->getRepository('IbwJobeetBundle:Job');
            if ($entity instanceof Job) {
                $repository->setContainer($this->container);
            }
        }
    }
    

    There may be a better way to handle this. If you have, please post an answer. I know that injecting the whole container is a bad practice. I will update to inject the required services only later.