6

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)
{
}

2 Answers 2

13

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}
1
  • 1
    You should probably use preg_quote() around the insertion of $options['delimiter'] Jun 6, 2012 at 15:29
4

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.