Evolution is the key

How QueryTypes ease developer's job

le vendredi 27 septembre 2019

A feature that rocks

While creating this blog, I've used the latest version of eZ Platform, introducing "QueryTypes" (available from version 1.0, but starting to be awesome as of 1.6), and this feature really rocks!

I remember when I was studying computer science in college (ten years ago, aouch!), my professor told us that in a few years, developers may disappear in favor of software architects.

With this QueryType feature, it starts to become a reality! You don't need to write PHP code anymore (well, still a little) in order to inject required variables into a template. That's a good start for simplifying most of your dev tasks while creating a webapp.

Life without QueryTypes

When you need to display a "folder" content with its children, you need to enrich the business logic by adding your own logic, in order to fetch children using a PHP Query and inject the SearchResult into your template.

Something like that:

   

ezpublish:
    system:
        eng:
            content_view:
                full:
                    folder:
                        controller: AcmeDemoBundle:Folder:getChildren
                        template: "@ezdesign/full/folder.html.twig"
                        match:
                            Identifier\ContentType: ["folder"] 
   

You also need to create the dedicated PHP action to get all existing children.

   
<pre><?php

    namespace Acme\DemoBundle\Controller;

    use eZ\Bundle\EzPublishCoreBundle\Controller;
    use eZ\Publish\API\Repository\Values\Content\LocationQuery;
    use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
    use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
    use eZ\Publish\API\Repository\Values\Content\Query;
use Symfony\Component\HttpFoundation\Response
        
    class FolderController extends Controller
    {
    public function getChildrenAction(int $locationId, string $viewType, string $layout, array $params):Response
        {   
        $repository = $this->getRepository();  
        $locationService    = $repository->getLocationService();
        $contentService    = $repository->getContentService();
        $folderLocation   = $locationService->loadLocation($locationId);
        $query              = new LocationQuery();
        $query->criterion   = new Criterion\LogicalAnd([
             new Criterion\Subtree([$folderLocation->pathString]),
             new Criterion\ContentTypeIdentifier( 'article' )
        ]);
        $query->sortClauses = [
            new SortClause\Location\Priority(Query::SORT_DESC)
        ];
        $children = $repository->getSearchService()->findLocations($query);
        $params += ['children' => $children];

        return $this->get( 'ez_content' )->viewLocation($locationId, $viewType, $layout, $params);
    }
}</pre>
   

And the dedicated template.

   
<pre>{% extends "@ezdesign/pagelayout.html.twig" %}

{% block content %}
    {% for child in children.searchHits %}
        {{ ez_content_name(child.valueObject.contentInfo) }}
    {% endfor %}
{% endblock %}</pre>
   

As you can see, there is a lot of (non-reusable) PHP code, only to inject article children into your template. And it's a hard work to design this action generic enough for all kind of children (e.g. blog with blog_post?). 

Tips: A good workaround would be to use the parameter provider feature from the EzCoreExtraBundle (paramProvider are reusable across multiple override rules).

But, let's focus QueryTypes.

The QueryTypes era

Thanks to Bertrand Dunogier, QueryTypes ease designing websites using only Yaml configuration files and templates.

Let's do the same as above but with QueryType this time.

You will need an override rule, a template and a generic PHP fetcher (that will be common and reusable in your override rules).

   

ezpublish:
    system:
        eng:
            content_view:
                full:
                    folder:
                        controller: "ez_query:contentQueryAction"
                        template: "@ezdesign/full/folder.html.twig"
                        match:
                            Identifier\ContentType: ["folder"]
                        params:
                           query:
                               query_type: "CRParameterProvider:GetChildren"
                               parameters:
                                   parentLocationId: "@=location.id"
                                   type: ["article"]
                               assign_results_to: "children" 
   

Then just create a generic children "fetcher" query type:

   
<pre><?php
namespace CR\Bundles\ParameterProviderBundle\QueryType;

use eZ\Publish\Core\QueryType\QueryType;
use eZ\Publish\API\Repository\Values\Content\Query;

class GetChildrenQueryType implements QueryType
{
    public function getQuery(array $parameters = [])
    {
         $criteria = [
              new Query\Criterion\Visibility(Query\Criterion\Visibility::VISIBLE)
         ];

         if (isset($parameters['parentLocationId'])) {
            $criteria[] = new Query\Criterion\ParentLocationId($parameters['parentLocationId']);
         }  

         if (isset($parameters['type'])) {
             $criteria[] = new Query\Criterion\ContentTypeIdentifier($parameters['type']);
         } 
        
         if (isset($parameters['relatedContentIds'])) {
            $criteria[] = new Query\Criterion\ContentId($parameters['relatedContentIds']);
         } 

         // 10 is the default limit we set, but you can have one defined in the parameters
         return new Query([
             'filter' => new Query\Criterion\LogicalAnd($criteria),
             'sortClauses' => [new Query\SortClause\DatePublished(Query::SORT_ASC)],
             'limit' => $parameters['limit'] ?? 10,
         ]);
    }

    public static function getName()
    {
         return 'CRParameterProvider:GetChildren';
    }

    /**
    * Returns an array listing the parameters supported by the QueryType.
    * @return array
    */
    public function getSupportedParameters()
    {
        return ['parentLocationId', 'type', 'relatedContentIds', 'limit'];
    }
}</pre>
   

And then, you just need to reuse this configuration for all your next override rules by changing the parameters sent to the query.

If you need to inject related contents from an Object Relations field, use the expression language like this :

   

# ...
query:
    queryType: "CRParameterProvider:GetChildren"
    parameters:
        relatedContentIds: "@=content.getFieldValue('related_content').destinationContentIds" 
   

QueryTypes Limitations

QueryTypes open doors and create opportunities for future developments, but as you can see, you can only have one query injecting a variable into a template.

One possible improvement would be to allow multiple queries. That way we'd be able to add as many result sets as needed, like for override rules. For example:

   

ezpublish:
    system:
        eng:
            location_view:
                full:
                    folder:
                        controller: "ez_query:contentQueryAction"
                        template: "@ezdesign/full/folder.html.twig"
                        match:
                            Identifier\ContentType: "folder"
                       params:
                           query:
                               <strong>article_children</strong>:
                                   query_type: "CRParameterProvider:GetChildren"
                                   parameters:
                                       parentLocationId: "@=location.id"
                                       type: ["article"]
                                   assign_results: "children"
                              <strong>other_result_set</strong>:
                                   query_type: "CRParameterProvider:GetChildrenBis"
                                   parameters:
                                       parentLocationId: "@=location.id"
                                       type: ["sausage"]
                                   assign_results: "sausages" 
   

Use it and feel free to comment this article and give us some feedback, I'm pretty sure that you will change your mind when using eZ Platform for website development.

Cheers ;)