Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

DQL Filtering & Sorting

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $8.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

What else could we possibly configure with the list view? How about sorting, or filtering list via DQL. OoooOOooo.

Configuring Sort

First, sorting... which we get for free. Already, the genuses are sorted by id, but we can click any column to sort by that. But this isn't sticky: when you come back to the genus list page, it's back to filtering by id.

Sorting by name would be a bit more awesome. And you can probably guess what the config looks like to do this. Under Genus and list, add sort: name:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
Genus:
... lines 92 - 94
list:
... lines 96 - 106
sort: 'name'
... lines 108 - 136

This is the new default field for sorting.

Sorting via Relations

Oh, but we can get fancier. Under GenusNote, what if I told you I wanted to sort by the name of the Genus it's related to? Yea, that would mean sorting across a relation. But that's totally possible: sort: ['genus.name', 'ASC']:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusNote:
... lines 112 - 113
list:
... lines 115 - 121
sort: ['genus.name', 'ASC']
... lines 123 - 137

This also controls the direction. It sorts descending by default.

Try it! Nice! This works... just don't get too confident and try to do this across multiple relationships... that's not going to work.

Disabling Sort Fields

The ability to sort via any field with no setup is great! Though... sometimes it doesn't make sense - like with the "User avatar" field. To tighten things up, you can disable sorting. Find that field's list config and add a new option at the end: sortable: false:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusNote:
... lines 112 - 113
list:
... line 115
fields:
... lines 117 - 118
- { property: 'userAvatarFilename', label: 'User avatar', type: 'image', base_path: '/images/', sortable: false }
... lines 120 - 137

And... gone!

DQL Filtering

Ok, let's turn to something fun: DQL filtering. Like, what if we want to hide some genuses entirely from the list and search page?

But first, so far, it seems like we're limited to one entity section per entity. That's a lie! Let me show you: add a new section under entities called GenusHorde - I just made that up. Below, set its class to AppBundle\Entity\Genus:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
class: AppBundle\Entity\Genus

You see, some scientists are worried that certain genuses are becoming too large... and threaten the survival of mankind. They want a new GenusHorde section where they can keep track of all of the genuses that have a lot of species. It's scary stuff, so we'll add a label: HORDE of Genuses with a scary icon:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
class: AppBundle\Entity\Genus
label: HORDE of Genuses ? !!!
... lines 114 - 140

Tip

Fun fact! You can press Control+Command+Space to open up the icon menu on a Mac.

And all of a sudden... ah! We have a new "Horde of Genuses" section! Run!!!

Of course, this still shows all genuses. I want to filter this to only list genuses that have a lot of species. Start by adding a list key and a new, awe-inspiring option: dql_filter. For the value, pretend that you're building a query in Doctrine. So, entity.speciedCount >= 50000:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
... lines 112 - 113
list:
dql_filter: 'entity.speciesCount >= 50000'
... lines 116 - 142

The alias will always be entity.

Try it! Ten down to... only 7 menacing genuses!

And just like any query, you can get more complex. How about: AND entity.isPublished = true:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
... lines 112 - 113
list:
dql_filter: 'entity.speciesCount >= 50000 AND entity.isPublished = true'
... lines 116 - 144

And to really focus on the genuses that are certain to overtake humanity, sort it by speciesCount and give the section a helpful message: Run for your life!!! Add scary icons for emphasis:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
... lines 112 - 113
list:
dql_filter: 'entity.speciesCount >= 50000 AND entity.isPublished = true'
sort: 'speciesCount'
help: Run for your life!!! ???
... lines 118 - 144

Ok... refresh! Ah... now only three genuses are threatening mankind.

Oh, and search automatically re-uses the dql_filter from list: these are 2 results from the possible 3. And like always, you can override this. Under search, set the dql_filter to the same value, but without the isPublished check:

... lines 1 - 80
easy_admin:
... lines 82 - 89
entities:
... lines 91 - 110
GenusHorde:
... lines 112 - 117
search:
dql_filter: 'entity.speciesCount >= 50000'
... lines 120 - 146

Try that. Boom! 3 more genuses that - when published - will spell certain doom for all.

Next! We'll save humanity by learning how to override the many templates that EasyAdminBundle uses.

Leave a comment!

6
Login or Register to join the conversation
Brent Avatar

Hi,
I have an entity with two date fields (when entered, due date). Does dql_filter support filtering a date field (eg. whenentered >= now)? I'm having trouble finding an example of this. Thank you in advance.

Reply

Hey Brent !

Excellent question. Hmm, that is a bit tricky. In a normal query, I would do something like andWhere('whenEntered > :now')->setParameter('now', new \DateTime()), but we can't do that inside of YAML. So, we'll need to apply the dql_filter manually (not in YAML). There are a few ways to do this, but the one I'd use is an event:

1) Add an event listener (https://symfonycasts.com/screencast/easyadminbundle/event-hooks) on an event called EasyAdminEvents::POST_LIST_QUERY_BUILDER (which is the string easy_admin.post_list_query_builder).

2) Your listener function will be passed a GenericEvent object, but with a query_builder argument on it that you can mess with - something like this:


public function onPostListQueryBuilder(GenericEvent $event)
{
    // make sure to only modify the query for the one entity
    if (!$event->getSubject() instanceof YourTargetEntity::class) {
        return;
    }

    $queryBuilder = $event->getArgument('query_builder');
    $queryBuilder->andWhere('entity.whenEntered > :now')
        ->setParameter('now', new \DateTime());
}

I haven't tested this, but that should do the trick :). Let me know if you have any issues. You might also want to add another event listener on the EasyAdminEvents::POST_LIST_QUERY_BUILDER event.

Cheers!

Reply
Kaizoku Avatar
Kaizoku Avatar Kaizoku | posted 5 years ago

Hi team !

I need to add an advance search feature. Ex filter the list view by sub-family.
I don't think I've seen such feature in this course (maybe I'm missing something?).
So I checked the doc, but I didn't found anything except this : https://github.com/javiereg...
but this is only a proposal.

Do you have any idea how to implement this kind of filter ?

Best regards.

Reply

Yo Kaizoku!

Hmm. How would your search field work exactly? I mean, would I type in the name of the sub-family and it would find all Genus that are related to that Sub-Family? Or would you have some sort of drop-down to select the sub-family? Regardless of what you need exactly, keep in mind that the searchAction is just an action that creates a custom paginator and then renders the list template: https://github.com/javiereguiluz/EasyAdminBundle/blob/aac1732adc3ac1401dc08dca756ea19708a248af/src/Controller/AdminController.php#L347. So, in theory, I think you could override this, create your own paginator by using whatever logic you want, and then render the same template :).

Cheers!

Reply
Kaizoku Avatar
Kaizoku Avatar Kaizoku | weaverryan | posted 5 years ago | edited

[edit : remove code part as this isn't easy to read. If someone want code example send me an email.]

Thank you Ryan for the suggestion.
Here is how I choose to implement a dynamic DQL filter.
As suggested I overrided the search template


{% extends '@EasyAdmin/default/list.html.twig' %}
{% block search_action %}  Copie paste original code and add your custom form code.{% endblock %}

This will give you 2 drop down select box along the search input.

Now about the controller.
The filters I need are specific to only one entity and it should apply to the list and search view.
So I went for the "Customization Based on Entity Controllers" option.
https://symfony.com/doc/current/bundles/EasyAdminBundle/book/complex-dynamic-backends.html#customization-based-on-entity-controllers
I created a specific controller and override the list and search action.
In both method I added a form with the filters I needed.
And the trick is to use the dql_filter parameter provided in the searchAction.
It's the same parameter that is hardcoded in the config file in this video, but this time it's changed by the user.
It works very well.
The only problem is that it's not possible to make complex filter action as according to the doc you can put only WHERE condition in the $dql_filter parameter.
For instance I needed a filter based on a JOIN and I couldn't find how to do this.

I think this is a very usefull case - Backend dynamic DQL Filter - and maybe it can be added in a futur video ;)
Here is how the controler looks like.


namespace App\Controller;

...
use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;

class AdminCandidatureController extends BaseAdminController
{

    public function listAction()
    {
       // copy/paste code from the parent listAction
      // Add a Symfony form
        return $this->render( ...);
    }

    public function searchAction()
    {
        $this->dispatch(EasyAdminEvents::PRE_SEARCH);
        $query = trim($this->request->query->get('query'));
        $filter_diplome = trim($this->request->query->get('frm_diplome'));
        $filter_offre = trim($this->request->query->get('frm_offre'));

        $dql_filter = '';
        if ($filter_diplome) {
            $dql_filter = 'entity.diplome = '.$filter_diplome;
        }

        if ($filter_offre) {
            if ($dql_filter) {
                $dql_filter .= ' AND ';
            }
            $dql_filter .= 'entity.offer = '.$filter_offre;
        }
        
        // if the search query is empty, redirect to the 'list' action
        if ('' === $query &&
            '' === $filter_diplome &&
            '' === $filter_offre
        ) {
           // copie paste original code
        }
      
         // copie paste original code, change  $this->entity['search']['dql_filter'] by  $dql_filter in the paginator object
        //add the same form we created in the list view
       
        return $this->render(  ...      );
    }
}
Reply

Nice work! And thanks for sharing (and even using the code blocks from Disqus - woo!). I agree on the DQL filtering limitation - it's a bummer. Currently , there are a bunch of little things like this in EasyAdminBundle - Javier (the maintainer) is balancing staying simple... which means not solving *every* problem. I get it - it's but yea, when you find a feature you want that's not there, it's no fun :).

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built on an older version of Symfony & EasyAdminBundle. Many of the concepts are the same, but you can expect major differences in newer versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.17.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.40
        "nelmio/alice": "^2.1", // v2.3.5
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}
userVoice