Easyadminbundle: QueryBuilder in TypeOptions fails

Created on 24 Nov 2016  路  17Comments  路  Source: EasyCorp/EasyAdminBundle

Hey all,

Currently I'm using version 1.16.2 and the functionality described in #1145 fails with Expected argument of type "Doctrine\ORM\QueryBuilder or \Closure", "string" given.

The config looks like the following:

CampaignType:
        class: Bundle\Entity\CampaignType
        controller: Bundle\Controller\EasyAdmin\SomeController
        edit:
          fields:
            - { property: 'purposes', type_options: { query_builder: "Bundle\Repository\EasyAdmin\SomeRepo::getAccessible" } }

with the method getAccessible specified in the repository

public static function getAccessible(EntityRepository $er) {
        return ...
    }

Anyone got an idea, why this fails?

Most helpful comment

i can confirm that this works under EasyAdmin 1.17, Sf 3.3.13:

- { property: 'types', type_options: { 'expanded':false, 'multiple': true, 'query_builder': 'YourBundle\Repository\YourRepository::pubStaticFunction', 'group_by': 'category'} }

group_by is obviously optional but i needed it in my case.

however, it was hard to find for me the right syntax for the repository function, as it's not the same as described in Symfony doc, so i paste an example here, in case it can help :

let's say this is about Product and Categories :

use Doctrine\ORM\EntityRepository;

public static function getListOrderByCategory(EntityRepository $er){
        return $er->getEntityManager()->createQueryBuilder('p')
                ->select('p')
                ->from('BackendBundle\Entity\Product', 'p')
                ->leftJoin('p.category', 'c')
                ->orderBy('p.name, c.name', 'ASC')
                ;
    }

or a simple orderBy:

public static function getOrderedList(EntityRepository $er){
        return $er->createQueryBuilder('p')
            ->orderBy('p.name', 'ASC')
            ;
    }

All 17 comments

Try with array notation ['Bundle\Repository\EasyAdmin\SomeRepo', 'getAccessible'] ? (use single quote)

In my case, I use what is proposed by Javier in #1145. I use a custom admin controller and I introduce the query_builder in options at form builder level.

Example with my Event entity:

use JavierEguiluz\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
use FBN\GuideBundle\Entity\EventRepository;

class AdminController extends BaseAdminController
{

    public function createEventEntityFormBuilder($entity, $view)
    {
        $formBuilder = parent::createEntityFormBuilder($entity, $view);

        // stuff...

        $id = (null !== $entity->getId()) ? $entity->getId() : 0;
        $formBuilder->add('eventPast', EntityType::class, array(
            'class' => 'FBNGuideBundle:Event',
            'query_builder' => function (EventRepository $repo) use ($id) {
                return $repo->getEventsWithCoordinatesAndExcludedId($id);
                },
            // ...
            )
        );

        // stuff...
    }
}

@CruzyCruz Unfortunately using the $formBuilder->add() method resets the whole field and you lose all easyadmin attributes (the field gets displayed at the bottom as a simple dropdown menu instead of the select2 field). I tried to edit the field options instead of overwriting them, but it doesn't work either...

    public function createEntityFormBuilder($entity, $view)
    {
        $formBuilder = parent::createEntityFormBuilder($entity, $view);

        $projectID = (null !== $entity->getId()) ? $entity->getId() : 0;

        $options = $formBuilder->get("impacts")->getOptions();
        $options['query_builder'] = function (EntityRepository $repo) use ($projectID) {
            return $repo->createQueryBuilder('entity')
                ->where('entity.project = :projectID')
                ->setParameter('projectID', $projectID);
        };
        $formBuilder->add('impacts', EntityType::class, $options);

        return $formBuilder;
    }

If anyone managed to have this working correctly, let us know!

@yceruto Thanks for your reply. Now I tried as you proposed using

- { property: 'purposes', type_options: { query_builder: ['Bundle\Repository\EasyAdmin\SomeRepo', 'getAccessible'] } }

Unfortunately, the error stays the same:

Expected argument of type "Doctrine\ORM\QueryBuilder or \Closure", "array" given

In the mean time, I got the approach working by using a Form Type Extension on the EntityType:

use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class EntityTypeExtension extends AbstractTypeExtension {

    public function setDefaultOptions(OptionsResolverInterface $resolver) {

        // Get some information you want to filter

        $resolver->setNormalizers(array(
            'query_builder' => function (Options $options, $configs) {
                // class which is currently affected by the form
                $class = $options->get('class');

                return function (EntityRepository $er) use ($class) {
                    // return modified query builder
                    return $er
                        ->createQueryBuilder('e')
                        // ->... 
                    ;
                };
            }
        ));
    }

    /**
     * Returns the name of the type being extended.
     *
     * @return string The name of the type being extended
     */
    public function getExtendedType() {
        return 'entity';
    }
}

And then registering this class as a form type extension in services.xml:

<service id="entity_type_extension" class="%entity_type_extension.class%" public="true">
            <tag name="form.type_extension" alias="entity" />
</service>

Still, I would be happy for any further indicators what I'm doing wrong with the initially stated approach

@srosset81

as a simple dropdown menu instead of the select2 field

Try to add this:

    public function createEntityFormBuilder($entity, $view)
    {
        //...
        $options['attr'] = ['data-widget' => 'select2'];
        $formBuilder->add('impacts', EntityType::class, $options);

        //...
    }

the field gets displayed at the bottom

I don't know why. It works for me. In my case, as I re-add this field at form builder level, I only declare it without any options at config level. Full code (css_class is used for javascript needs):

admin.yml

easy_admin:                   
        Event: 
            class : FBN\GuideBundle\Entity\Event
            form:
                fields:
                    # ...                
                    - { property: 'eventPast', css_class: 'col-xs-12 eventPast' }
                    # ...           

Custom AdminController

namespace FBN\GuideBundle\Controller;

use JavierEguiluz\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use FBN\GuideBundle\Entity\EventRepository;

class AdminController extends BaseAdminController
{
    public function createEventEntityFormBuilder($entity, $view)
    {
        $formBuilder = parent::createEntityFormBuilder($entity, $view);
        // stuff...

        $id = (null !== $entity->getId()) ? $entity->getId() : 0;
        $formBuilder->add('eventPast', EntityType::class, array(
            'class' => 'FBNGuideBundle:Event',
            'query_builder' => function (EventRepository $repo) use ($id) {
                return $repo->getEventsWithCoordinatesAndExcludedId($id);
                },
            'attr' => ['data-widget' => 'select2'],
            'placeholder' => 'label.form.empty_value',
            'required' => false,
            )
        );

        return $this->getFormBuilderForNonDefaultLocale($formBuilder, $entity, $view);
    }
}

Thanks @CruzyCruz

Actually the "select2" data-widget is being passed down by my code and it is working, I don't know why I had issues with that.

As for the field being at the bottom (below the save button), I suspect this is because I use EasyAdmin's "group" option. By removing and adding back the field, it messes up with the groups organization. Unfortunately, there is no way in Symfony to directly edit the options of the FormBuilder (there is no setOptions() function). So I'm kind of stuck...

@srosset81

Actually the "select2" data-widget is being passed down by my code and it is working, I don't know why I had issues with that.

I am surprised because it was not the case for me. But I will try to remove this from my code to see if it work.

For the field displaying at the bottom of the page, let us know if you find a solution. Strange because it works correctly for me. Can you show your entity configuration ?

@CruzyCruz

I am surprised because it was not the case for me. But I will try to remove this from my code to see if it work.

It works for me because I first copy all the options, and this includes the attr field ($options = $formBuilder->get("impacts")->getOptions();)

For the field displaying at the bottom of the page, let us know if you find a solution. Strange because it works correctly for me. Can you show your entity configuration ?

Here's the form configuration for this entity:

easy_admin:
    entities:
        Project:
            class: AppBundle\Entity\Project\Project
            controller: AppBundle\Entity\Project\ProjectAdmin
            label: 'Projets'
            list:
                fields:
                    ...
            show:
                fields:
                    ...
            form: 
                fields:
                    - { type: 'group', css_class: 'col-sm-8', label: 'Projet', icon: 'bullhorn' }
                    - name
                    - { property: 'backgroundFile', type: 'vich_image' }
                    - { property: 'currentFunding', type: 'money', type_options: {scale: 0} }
                    - { property: 'targetFunding', type: 'money', type_options: {scale: 0} }
                    - impacts   # <-- this is the field displayed at the bottom
                    - { property: 'story', type: 'ckeditor' }
                    - { type: 'group', css_class: 'col-sm-4', label: 'Relations', icon: 'link' }
                    - organization
                    - { property: 'causes', type_options: { by_reference: false } }
                    - { type: 'group', css_class: 'col-sm-4', label: 'G茅n茅ral', icon: 'folder-o' }
                    - createdAt
                    - visible

It works for me because I first copy all the options, and this includes the attr field

Yes of course !

I understand now what you call the group option (I don't have this in my configuration). So did you try without this element ? If it works it will confirm your doubts. May be there is a way to replace the field at the right place in the group (by accessing the configuration or something else).

Sadly we must close this as "can't fix". This option expects a QueryBuilder object or a PHP closure (https://symfony.com/doc/current/reference/forms/types/entity.html#query-builder) but in YAML there's not a simple way to represent those (in Symfony you can do that serializing objects, but it's really ugly: https://symfony.com/doc/current/components/yaml.html#object-parsing-and-dumping).

This is one of those edge-cases where YAML config format is not enough. Luckily, we can use PHP to solve this issue, although is not as simple and fast as using YAML.

@javiereguiluz probably the docs needs to be updated as passing a callable string syntax is feasible for query_builder option (see source):

This is working for me now (EA=1.17, SF=3.3.10):

... type: 'entity', type_options: { query_builder: 'Full\Qualified\ClassName::pubStaticFunc' } ...

i can confirm that this works under EasyAdmin 1.17, Sf 3.3.13:

- { property: 'types', type_options: { 'expanded':false, 'multiple': true, 'query_builder': 'YourBundle\Repository\YourRepository::pubStaticFunction', 'group_by': 'category'} }

group_by is obviously optional but i needed it in my case.

however, it was hard to find for me the right syntax for the repository function, as it's not the same as described in Symfony doc, so i paste an example here, in case it can help :

let's say this is about Product and Categories :

use Doctrine\ORM\EntityRepository;

public static function getListOrderByCategory(EntityRepository $er){
        return $er->getEntityManager()->createQueryBuilder('p')
                ->select('p')
                ->from('BackendBundle\Entity\Product', 'p')
                ->leftJoin('p.category', 'c')
                ->orderBy('p.name, c.name', 'ASC')
                ;
    }

or a simple orderBy:

public static function getOrderedList(EntityRepository $er){
        return $er->createQueryBuilder('p')
            ->orderBy('p.name', 'ASC')
            ;
    }

@anybug how can I pass the logged user to the static method?

Hi Frazelli, good question! I haven't done it through EasyAdmin yet, but as far as I know, there is no simple way to pass a variable to this method (sometimes i miss SF 1.4 and its sfContext variable...).
Unless someone had a better solution, I would recommend using formbuilder override within the AdminController as per CruzyCruz solution

Thanks @yceruto and @anybug, you save my day 馃榾

@anybug Thanks man!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

BigMichi1 picture BigMichi1  路  3Comments

lukasluecke picture lukasluecke  路  3Comments

shakaran picture shakaran  路  4Comments

Ealenn picture Ealenn  路  3Comments