Skip to content
Supasteevo edited this page Jun 18, 2015 · 1 revision

Introduction

While working on our first eZ Publish 5.3 projects, we encountered some performance issues with multiple "viewLocation" calls when we want to show a list of contents, like that :

{{ render( controller( "ez_content:viewLocation", {"locationId": 123, "viewType": "line"} ) ) }}

(this Twig syntax has the same result as the "node_view_gui" function in Legacy template syntax)

The problem is that each "render controller" call generates a new Symfony query, and so in a listing page with 20 lines like that, we have very bad performances.

Our solution

Encapsulate object into a wrapper

We have developed a simple model encapsulating a location and its content (as both are often used together) into an "eZObjectWrapper".

This model can be extended, in order to override or add setters and getters for sepcific FieldTpes (examples : get an image coming from an object relation, get children, etc.)

So, rather than getting all the data within the main controller, we can better retriev the eZObjectWrapper for each location to display, and pass it to the view. Then, the view can access to functions to display the data, and avoid using an additionnal controller.

Extendable model

We can extend the default eZObjectWrapper class and set-up a mapping "ContentType => Wrapper class to use" within application settings. Example :

parameters:
   class_mapping:
       article: \MyVendor\MyBundle\eZObjectWrapper\Article
       news: \MyVendor\MyBundle\eZObjectWrapper\News
       definition: \MyVendor\MyBundle\eZObjectWrapper\Definition

Building eZObjectWrapper objecs

To get the eZObjectWrapper object associated to a given location, we use a factory containg following building function :

public function buildeZObjectWrapper($locationInfo)
{
   if(is_numeric($locationInfo)){
       $location = $this->repository->getLocationService()->loadLocation($locationInfo);
   } else {
      $location = $locationInfo;
   }

   $contentTypeIdentifier = $this->repository->getContentTypeService()
                                                               ->loadContentType($location->contentInfo->contentTypeId)
                                                               ->identifier;
   $mappingEntities = $this->container->getParameter('class_mapping');

   if(isset($mappingEntities[$contentTypeIdentifier])){
       $className = $mappingEntities[$contentTypeIdentifier];
   } else {
       $className = 'eZObject\WrapperBundle\Core\eZObjectWrapper';
   }

   $objectWrapper = new $className($this->container, $location->id, $location);

   return $objectWrapper;
}

Using eZ ObjectWrapper objects : a practical example

We want to display a page with a list of search results, that can be object from different ContentTypes : Article, News, or Definition. On our result page, we want to display the picture associated to each result, but, depending of the ContentType, the picture can be from differente ContentTypes :

  • for an Article : the picture is a "object's relation" pointing to an object of ContentType "Image"
  • for a News : a Field of FieldType "ezimage"
  • for a Definition : a child of ContentType "Image".

So, we create a specific wrapper class for each of these ContentTypes, to determine how we will get the "image" attribute :

class ArticleObjectWrapper extends eZObjectWrapper
{
(...)
   public function image()
   {
      if(!isset($this->image)){
         $relatedPictureContentId = $this->content()
                                                           ->getFieldValue( 'related_picture' )
                                                           ->destinationContentId;
         if ($relatedPictureContentId) {
            $this->image = $this->repository->getContentService()
                                                               ->loadContent( $relatedPictureContentId )
                                                               ->getFieldValue('image');
         }
      }
      return $this->image;
   }
(...)
}

class NewsObjectWrapper extends eZObjectWrapper
{
(...)
   public function image()
   {
      return $this->content()->getFieldValue('image');
   }
(...)
}

class DefinitionObjectWrapper extends eZObjectWrapper
{
(...)
   public function image()
   {
      if(!isset($this->image)){
         $articleRepository = $this->container->get( 'my.services.repository.content' );
         $this->image = $articleRepository->getChildrenImage($this->locationId)->content()->getFieldValue('image');
      }
      return $this->image;
   }
(...)
}

In the main controller, we load, for each search results, its related eZObjectWrapper object, and give all to the view :

function searchAction(...)
{
    $searchResult = array();
    $factory = $this->container->get('ezobject_wrapper.services.factory');
    $searchLocations = $this->doSearch();
    foreach( $searchLocations as $location ) {
       $searchResult[] = $factory->buildeZObjectWrapper($location); 
    }
    return $this->render(  'MyBundle::search.html.twig', array( 'searchResult' => $searchResult ) );
}

In the main view, we just have to display each result like that :

{% for result in searchResult %}
    {{ ez_render_field(result, "title") }}
    {{ result.image }}
{% endfor %}