Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 and ParamConverter(s)

Accessing my route /message/new i'm going to show a form for sending a new message to one or more customers. Form model has (among others) a collection of Customer entities:

class MyFormModel
{
   /**
    * @var ArrayCollection
    */
    public $customers;
}

I'd like to implement automatic customers selection using customers GET parameters, like this:

message/new?customers=2,55,543

This is working now by simply splitting on , and do a query for getting customers:

public function newAction(Request $request)
{
    $formModel = new MyFormModel();

    // GET "customers" parameter
    $customersIds = explode($request->get('customers'), ',');

    // If something was found in "customers" parameter then get entities
    if(!empty($customersIds)) :

        $repo  = $this->getDoctrine()->getRepository('AcmeHelloBundle:Customer');
        $found = $repo->findAllByIdsArray($customersIds);

        // Assign found Customer entities
        $formModel->customers = $found;
    endif;

    // Go on showing the form
}

How can i do the same using Symfony 2 converters? Like:

public function newAction(Request $request, $selectedCustomers)
{
}
like image 380
gremo Avatar asked Jun 05 '12 20:06

gremo


2 Answers

Answer to my self: there is not such thing to make you life easy. I've coded a quick and dirty (and possibly buggy) solution i'd like to share, waiting for a best one.

EDIT WARNING: this is not going to work with two parameter converters with the same class.

Url example

/mesages/new?customers=2543,3321,445

Annotations:

/**
 * @Route("/new")
 * @Method("GET|POST")
 * @ParamConverter("customers",
 *     class="Doctrine\Common\Collections\ArrayCollection", options={
 *         "finder"    = "getFindAllWithMobileByUserQueryBuilder",
 *         "entity"    = "Acme\HelloBundle\Entity\Customer",
 *         "field"     = "id",
 *         "delimiter" = ",",
 *     }
 * )
 */
public function newAction(Request $request, ArrayCollection $customers = null)
{
}

Option delimiter is used to split GET parameter while id is used for adding a WHERE id IN... clause. There are both optional.

Option class is only used as a "signature" to tell that converter should support it. entity has to be a FQCN of a Doctrine entity while finder is a repository method to be invoked and should return a query builder (default one provided).

Converter

class ArrayCollectionConverter implements ParamConverterInterface
{
    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
     */
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    function apply(Request $request, ConfigurationInterface $configuration)
    {
        $name    = $configuration->getName();
        $options = $this->getOptions($configuration);

        // Se request attribute to an empty collection (as default)
        $request->attributes->set($name, new ArrayCollection());

        // If request parameter is missing or empty then return
        if(is_null($val = $request->get($name)) || strlen(trim($val)) === 0)
            return;

        // If splitted values is an empty array then return
        if(!($items = preg_split('/\s*'.$options['delimiter'].'\s*/', $val,
            0, PREG_SPLIT_NO_EMPTY))) return;

        // Get the repository and logged user
        $repo = $this->getEntityManager()->getRepository($options['entity']);
        $user = $this->getSecurityContext->getToken()->getUser();

        if(!$finder = $options['finder']) :
            // Create a new default query builder with WHERE user_id clause
            $builder = $repo->createQueryBuilder('e');
            $builder->andWhere($builder->expr()->eq("e.user", $user->getId()));

            else :
                // Call finder method on repository
                $builder = $repo->$finder($user);
        endif;

        // Edit the builder and add WHERE IN $items clause
        $alias   = $builder->getRootAlias() . "." . $options['field'];
        $wherein = $builder->expr()->in($alias, $items);
        $result  = $builder->andwhere($wherein)->getQuery()->getResult();

        // Set request attribute and we're done
        $request->attributes->set($name, new ArrayCollection($result));
    }

    public function supports(ConfigurationInterface $configuration)
    {
        $class = $configuration->getClass();

        // Check if class is ArrayCollection from Doctrine
        if('Doctrine\Common\Collections\ArrayCollection' !== $class)
            return false;

        $options = $this->getOptions($configuration);
        $manager = $this->getEntityManager();

        // Check if $options['entity'] is actually a Dcontrine one
        try
        {
            $manager->getClassMetadata($options['entity']);
            return true;
        }
        catch(\Doctrine\ORM\Mapping\MappingException $e)
        {
            return false;
        }
    }

    protected function getOptions(ConfigurationInterface $configuration)
    {
        return array_replace(
            array(
                'entity'         => null,
                'finder'         => null,
                'field'          => 'id',
                'delimiter'      => ','

            ),
            $configuration->getOptions()
        );
    }

    /**
     * @return \Doctrine\ORM\EntityManager
     */
    protected function getEntityManager()
    {
        return $this->container->get('doctrine.orm.default_entity_manager');
    }

    /**
     * @return \Symfony\Component\Security\Core\SecurityContext
     */
    protected function getSecurityContext()
    {
        return $this->container->get('security.context');
    }
}

Service definition

arraycollection_converter:
  class: Acme\HelloBundle\Request\ArrayCollectionConverter
  arguments: ['@service_container']
  tags:
    - { name: request.param_converter}
like image 189
gremo Avatar answered Sep 16 '22 20:09

gremo


It's late, but according to latest documentation about @ParamConverter, you can achieve it follow way:

 * @ParamConverter("users", class="AcmeBlogBundle:User", options={
 *    "repository_method" = "findUsersByIds"
 * })

you just need make sure that repository method can handle comma (,) separated values

like image 29
Oleg Andreyev Avatar answered Sep 16 '22 20:09

Oleg Andreyev