Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QueryBuilder in TypeOptions fails #1405

Closed
rmatil opened this issue Nov 24, 2016 · 17 comments

Comments

Projects
None yet
9 participants
@rmatil
Copy link

commented Nov 24, 2016

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?

@yceruto

This comment has been minimized.

Copy link
Collaborator

commented Nov 24, 2016

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

@CruzyCruz

This comment has been minimized.

Copy link

commented Nov 24, 2016

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...
    }
}
@srosset81

This comment has been minimized.

Copy link

commented Nov 30, 2016

@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!

@rmatil

This comment has been minimized.

Copy link
Author

commented Nov 30, 2016

@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

@CruzyCruz

This comment has been minimized.

Copy link

commented Nov 30, 2016

@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);
    }
}
@srosset81

This comment has been minimized.

Copy link

commented Nov 30, 2016

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...

@CruzyCruz

This comment has been minimized.

Copy link

commented Nov 30, 2016

@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 ?

@srosset81

This comment has been minimized.

Copy link

commented Nov 30, 2016

@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
@CruzyCruz

This comment has been minimized.

Copy link

commented Nov 30, 2016

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).

@javiereguiluz

This comment has been minimized.

Copy link
Collaborator

commented Oct 18, 2017

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.

@yceruto

This comment has been minimized.

Copy link
Collaborator

commented Oct 18, 2017

@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' } ...
@yceruto

This comment has been minimized.

Copy link
Collaborator

commented Oct 18, 2017

@anybug

This comment has been minimized.

Copy link

commented Nov 30, 2017

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')
            ;
    }
@frazelli

This comment has been minimized.

Copy link

commented Feb 5, 2018

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

@anybug

This comment has been minimized.

Copy link

commented Feb 5, 2018

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

@thlbaut

This comment has been minimized.

Copy link
Contributor

commented Apr 18, 2018

Thanks @yceruto and @anybug, you save my day 😀

@Glancuu

This comment has been minimized.

Copy link

commented Jan 24, 2019

@anybug Thanks man!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.