Why we need Site API for eZ Platform

29 Mar 2018  | 
Petar Španja, Ivo Lukač
Why we need Site API for eZ Platform

Site API for eZ Platform is a thin layer on top of the eZ Public Content Repository API (Repository API in short). It provides simple to use features for eZ developer’s everyday work by reducing a lot of boilerplate code, thus saving time and simplifying maintenance.

How it all started

In 2016, we released the first version of Site API for eZ Platform CMS. It was introduced with an intention to simplify building multilingual websites. Of course, you could build such sites without the Site API, but the amount of code you need to implement in order to handle multiple languages is just too big. Doing trivial things takes too much effort.

Petar realized this quickly, on the first project he was using the standard Repository API. He also remembered that the discussion about a dedicated site layer above the generic Repository API started a few years before, while he was still working on the eZ Platform core. He decided to implement that layer to transparently handle the languages so that the developers don’t need to worry about it in the code and simply configure it in one place.

However, that is only one use case. Other functionalities were implemented since the first release and new requirements are popping out almost every day.  

Developing with the Repository API only is still time-consuming. Ivo blogged about these issues almost two years ago and the claims are still valid: “Developing sites on the new stack is a time-consuming process”. Site API is here to cut the time needed for daily eZ development.

Repository API vs Site API

Site API is not meant to replace Repository API. Instead, it builds on top of it to make basic, everyday site-building use cases simple. Having it as a separate layer allows us to implement features that do not really belong in Repository API.

Basic facts to know about it are:

  • It implements its own Content, Location, and Field value objects, which are of the same name as the Repository API value objects, and replace them when fetching content
  • It is read-only, meaning it provides methods for searching and loading but not for creating, updating, or deleting. If you need to do that – use Repository API.
  • It is meant to be used for building sites, therefore Content has only one translation. This will be the appropriate one to be rendered on the site, resolved from language configuration for the siteaccess. If no such translation can be found, the API will behave as if Content does not exist (404 Not Found).
  • The above-mentioned is also valid for Locations – you can load or find only those Locations that have Content in translation that can be rendered.
  • It provides basic content tree traversal from PHP code and Twig templating engine.
  • It provides forward field relation access from PHP code and Twig templating engine (we will soon support reverse field relations and Tag relations as well).
  • It lazy-loads objects and properties as they are accessed.
  • It suppresses non-accessible Content (e.g. trashed items) to prevent accidental site crashes due to editor work.

A few real-life examples

Example 1.

To get the name of Content’s parent Location, a developer needs to write some PHP code either in a:

  • Custom controller
  • Query controller
  • Twig function
  • Some other extension point

The required code is quite verbose:

use eZ\Publish\API\Repository\Repository;
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ConfigResolver;
 
/** @var Repository $repository */
$contentService = $repository->getContentService();
$locationService = $repository->getLocationService();
 
/** @var ConfigResolver $configResolver */
$languages = $configResolver->getParameter('languages');
$content = $contentService->loadContent(42, $languages);
$mainLocation = $locationService->loadLocation($content->contentInfo->mainLocationId);
$parentLocation = $locationService->loadLocation($mainLocation->parentLocationId);
$parentContent = $contentService->loadContent($parentLocation->contentId, $languages);
 
$name = $parentContent->getVersionInfo()->getName();

If you used one of the first versions of eZ kernel 6.x (eZ Platform 1.x), you also needed to use eZ translation helper to get the correctly translated name:

/** @var \eZ\Publish\Core\Helper\TranslationHelper $translationHelper **/
$name = $translationHelper->getTranslatedContentName($parentContent);

It is unacceptable to have this amount of code written for something as basic as displaying parent name.

With Site API, the same task is trivial: 

use Netgen\EzPlatformSiteApi\API\Site;
 
/** @var Site $site */
$loadService = $site->getLoadService();
$content = $loadService->loadContent(42);
$name = $content->mainLocation->parent->content->name;

 
 Similarly, in Twig:

{% set name = content.mainLocation.parent.content.name %}

Example 2.

Loading field relations of a specific ContentType is also surprisingly complicated:

/** @var $repository \eZ\Publish\API\Repository\Repository */
$contentService = $repository->getContentService();
$searchService = $repository->getSearchService();
 
/** @var $configResolver \eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ConfigResolver */
$languages = $configResolver->getParameter('languages');
$content = $locationService->loadContent(42, $languages);
$relationField = $content->getField('related_content');
$relatedContentIds = $relationField->value->destinationContentIds;
 
// To account for permissions, status, and languages, we have to use search
$query = new Query([
    'filter' => new LogcialAnd([
        new ContentId($relatedContentIds),
        new ContentTypeId(1234234512),
    ]),
]);
$searchResult = $searchService->findLocations($query, ['languages' => $languages]);
 
// Now we have the result, but the relation order is not ensured by search,
// so we need to take care of it manually
$relatedContentItems = [];
foreach ($searchResult->searchHit as $searchHit) {
    $relatedContentItems[] = $searchHit->valueObject;
}
$sortedIdList = array_flip($relatedContentIds);
$sorter = function (Content $content1, Content $content2) use ($sortedIdList) {
    if ($content1->id === $content2->id) {
        return 0;
    }
    return ($sortedIdList[$content1->id] < $sortedIdList[$content2->id]) ? -1 : 1;
};
usort($relatedContentItems, $sorter);

 
With Site API, the same task is precise, as it should be:

/** @var $site \Netgen\EzPlatformSiteApi\API\Site */
$loadService = $site->getLoadService();
$content = $loadService->loadContent(42);
$relatedContentItems = $content->filterFieldRelations('related_content', ['image']);

 Similarly, in Twig:

{% set relatedContentItems = content.filterFieldRelations('related_content', ['image']) %}

 There are more examples like these ones, check them out.

You might find that it is a job of every developer to write the code mentioned in those examples. However, in real projects, this is bad practice. In that case, the developer is not just repeating and reinventing the wheel all the time, but also the code is harder to maintain, it’s more error-prone, and it takes more time to write it, test it, fix it, and ship it. In the end, this kind of practice simply raises the cost too much.

Another angle to consider is opening eZ Platform to people like site builders and frontend developers who do not have (and do not need to have!) extensive knowledge about the internals. They should be able to use the platform for simple sites and prototyping. Previously, with the Legacy Stack, they were able to do that, but with eZ Platform we placed it out of their reach.

How to use Site API?

You can start using it in one of two ways:

  1. Inject the Site service and use loading, finding, and filtering features. Still, this doesn’t use its full potential
  2. Replace your view controller and all PHP code and Twig templates with Site API features

The solution really depends on the use case. For projects already implemented and with no big changes ahead, it doesn’t make sense to switch to Site API. On the other hand, if you are in the middle of development or an existing project requires bigger changes consider the first way – inject the Site service and start using it. As for the new projects, we recommend the second way – full Site API approach.

Even if you are going with the full Site API approach, it is very important to note that you can always:

  • Call the default view controller (configuration is separated)
  • Get original value objects:
    • content.innerContent
    • location.innerLocation
  • Implement more complex features by injecting parameters or query controllers

Others are trying to improve as well

This is how others in the eZ ecosystem are trying to improve by reducing the amount of code and increasing the development efficiency.

eZ Systems

There are several things that the eZ core team has implemented since the eZ Platform 1.0 release. Most interesting are Query controllers which offer a really nice way of plugging custom fetches to existing controllers. Our main remarks about the Query controllers:

  • No need for controller overriding
  • But limited to only one query with special query controller
  • And still lot of PHP code and configuration

Recently, eZ core team have also added translated API properties in the language requested to make it easier to develop on multilingual projects. There are downfalls to it:

  • It is a partial solution
  • Wastes resources – loads all languages from the prioritized list but only one from this list will actually be rendered
  • Injects language configuration into Repository API level

A new SiteAccessAware Repository layer is being built at the moment. It was initially intended for eZ Platform v2.0 but is still work in progress and its current status is unknown. Is also injects siteaccess configuration into Repository API level which we have already noted is not the best solution.

Kaliop

Kaliop built a lazy-loading wrapper for Content and Locations which comes in handy in custom/complex cases but does not reduce the amount of code much.

Novactive

Novactive built a set of Twig and PHP helpers and a Children Provider in their extras bundle. However we think Helpers are not a good long-term solution and Children Provider is similar to already mentioned Query controllers.

Code Rhapsodie

Rhapsodians introduced a way of enriching views which is quite interesting approach but still, a lot of code and configuration is needed for simple and common features.

All these solutions are meaningful and they might be a good option in some cases, but none of them give an overall solution that is simple to understand and focused on everyday developer efficiency. 

Conclusion

Site API for eZ Platform is a thin layer on top of the Repository API. Its goal is to provide simple to use features for 80% of use cases. You can always bypass it or extend it for the more complex 20%.

Site API reduces a lot of boilerplate code, simplifies development and maintenance. It does require some refactoring for existing code but, most importantly, it saves time!

For more information, check out the webinar we did a few months ago.

If you have any questions, you can ask us in the comments or reach us on the eZ Community Slack.

Comments

blog comments powered by Disqus

Short backstory of our blog: Sharing our experience from various web projects based on eZ Publish / eZ Platform, Symfony, PHP, HTML5, MySQL, jQuery, CSS, etc. and focusing on solving the problems we encountered.

Subscribe to RSS feed

Tags