The public API will give you an easy access to the eZ Publish content repository. This repository is the core component that manages content, locations (nodes in eZ Publish 4.x), sections, content types (Content Classes in eZ Publish 4.x), users, user groups, and roles. It also provides a new, clear interface for plugging in custom field types (datatypes in eZ Publish 4.x).
The public API is built on top of a layered architecture including a persistence API that abstracts storage. By using the public API, you are sure that your code will be forward compatible with future releases based on enhanced, scalable and high-performance storage engines. Applications based on the public API are also fully backwards compatible by using the included storage engine based on the current kernel and database model.
With the introduction of Symfony 2 as the framework powering eZ Publish 5, the whole eZ Publish 4.x extensions system is changing. Pretty much everything needs to be done with entirely new concepts. In this chapter, we will see two ways of customizing eZ Publish 5: command line scripts (for import scripts, for instance), and custom controllers, the eZ Publish 5 equivalent of eZ Publish 4.x custom modules.
In order to test and use Public API code, you will need to build a custom bundle. Bundles are Symfony's extensions, and are therefore also used to extend eZ Publish. Symfony 2 provides code generation tools that will let you create your own bundle and get started in a few minutes.
In this chapter, we will show how to create a custom bundle, and implement both a command line script and a custom route with its own controller action and view. All shell commands assume that you use some linux shell, but those commands would of course also work on windows systems.
Generating a new bundle
First, change directory to your eZ Publish root.
Then use the app/console application, with the generate:bundle command to start the bundle generation wizard
Let's follow the instructions provided by the ward. Our objective is to create a bundle named
EzSystems/Bundles/CookBookBundle, located in the
The wizard will first ask about our bundle's namespace. Each bundle's namespace should feature a vendor name (in our own case:
EzSystems), optionally followed by a sub-namespace (we could have chosen to use
Bundle), and end with the actual bundle's name, suffixed with Bundle:
You will then be asked about the Bundle's name, used to reference your bundle in your code. We can go with the default,
EzSystemsCookbookBundle. Just hit enter to accept the default.
The next question is your bundle's location. By default, the script offers to place it in the
src folder. This is perfectly acceptable unless you have a good reason to place it somewhere else. Just hit enter to accept the default.
Next, you need to choose the generated configuration's format, out of YAML, XML, PHP or annotations. We mostly use yaml in eZ Publish 5, and we will use it in this cookbook. Enter 'yml', and hit enter.
The last choice is to generate code snippets demonstrating the Symfony directory structure. If you're learning Symfony, it is a good idea to accept, as it will pre-create a controller, yaml files...
The generator will then summarize the previous choices, and ask for confirmation. Hit enter to confirm.
The wizard will generate the bundle, check autoloading, and ask about activation of your bundle. Hit enter to both questions to have your bundle automatically added to your Kernel (
ezpublish/EzPublishKernel.php) and routes from your bundle added to the existing routes (
Your bundle should be generated and activated. Let's now see how you can interact with the Public API by creating a command line script, and a custom controller route and action.
Creating a command line script in your bundle
Writing a command line script with Symfony 2 is very easy. The framework and its bundles ship with a few scripts. They are all started using
php ezpublish/console <command> (
app/console in a default symfony 2 application). You can get the complete list of existing command line scripts by executing
php ezpublish/console list from the eZ Publish 5 root.
In this chapter, we will create a new command, identified as
ezpublish:cookbook:hello, that takes an optionnal name argument, and greets that name. To do so, we need one thing: a class with a name ending with "Command" that extends
Symfony\Component\Console\Command\Command. Note that in our case, we use
ContainerAwareCommand instead of
Command, since we need the dependency injection container to interact with the Public API). In your bundle's directory (
src/EzSystems/CookbookBundle), create a new directory named
Command, and in this directory, a new file named
Add this code to the file:
This is the skeleton for a command line script.
One class, with a name ending with "Command" (
HelloCommand), that extends
Symfony\Bundle\FrameworkBundle\Command\Command, and is part of our bundle's Command namespace. It has two methods:
execute(). We also import several classes & interfaces with the use keyword. The first two,
OutputInterface are used to typehint the objects that will allow us to provide input & output management in our script.
Configure will be used to set your command's name, as well as its options and arguments. Execute will contain the actual implementation of your command. Let's start by creating the
First, we use setName() to set our command's name to "
We then use
setDefinition() to add an argument, named
name, to our command.
You can read more about arguments definitions and further options in the Symfony 2 Console documentation. Once this is done, if you run
php ezpublish/console list, you should see
ezpublish:cookbook:hello listed in the available commands. If you run it, it should just do nothing.
Let's just add something very simple to our execute() method so that our command actually does something.
You can now run the command from the eZ Publish 5 root.
Creating a custom route with a controller action
In this short chapter, we will see how to create a new route that will catch a custom URL, and execute a controller action. We want to create a new route, /cookbook/test, that displays a simple Hello world message. This tutorial is a simplified version of the official one that can be found on http://symfony.com/doc/current/book/controller.html.
eZ Publish 4 equivalent
This eZ Publish 5 extension would have been a custom module, with its own
module.php file, in eZ Publish 4.
During our bundle's generation, we have chosen to generate the bundle with default code snippets. Fortunately, almost everything we need is part of those default snippets. We just need to do some editing, in particular in two locations:
src/EzSystems/CookbookBundle/Controllers/DefaultController.php. The first one will be used to configure our route (
/cookbook/test) as well as the controller action the route should execute, while the latter will contain the actual action's code.
This is the file where we define our action's URL matching. The generated file contains this YAML block
We can safely remove this default code, and replace it with this
We define a route that matches the URI /cookbook/*, and executes the action hello in the Default controller of our bundle. The next step is to create this method in the controller.
This controller was generated by the bundle generator. It contains one method,
helloAction(), that matched the YAML configuration we have changed in the previous part. Let's just rename the
indexAction() method so that we end up with this code
We won't go into details about controllers in this cookbook, but let's walk through the code a bit. This method receives the parameter defined in
routing.yml. It is named "name" in the route definition, and must be named $name in the matching action. Since the action is named "hello" in
routing.yml, the expected method name is
Controller actions must return a Response object, that will contain the response's content, the headers, and various optional properties that affect the action's behaviour. In our case, we simply set the content, using
setContent(), to "Hello $name". Simple. Go to http://ezpublish5/cookbook/hello/YourName, and you should get "Hello YourName".
With both command line scripts and HTTP routes, you have the basics you need to start writing Public API code.
Browsing, Finding, Viewing
We will start by going through the various ways to find and retrieve content from eZ Publish using the API. While this will be covered in further, dedicated documentation, it is necessary to explain a few basic concepts of the Public API. In the following recipes, you will learn about the general principles of the API as they are introduced in individual recipes.
Displaying values from a Content
In this recipe, we will see how to fetch a Content instance from the repository, and obtain its Field's content.
Let's first see the full code. You can see the Command line version at https://github.com/ezsystems/CookbookBundle/blob/master/EzSystems/CookBookBundle/Command/ViewContentCommand.php.
Let's analyze this code block by block.
This is the initialization part. As explained above, everything in the Public API goes through the repository via dedicated services. We get the repository from the service container, using the method
get() of our container, obtained via
$this->getContainer(). Using our $repository variable, we fetch the two services we will need using
Everything starting from line 5 is about getting our Content, and iterating over its Fields. You can see that the whole logic is part of a
try/catch block. Since the Public API uses Exceptions for error handling, this is strongly encouraged, as it will allow you to conditionally catch the various errors that may happen. We will cover the exceptions we expect in a next paragraph.
The first thing we do is use the Content Service to load a Content using its ID, 66:
( 66 ). As you can see on the API doc page, this method expects a Content ID, and returns a Content Value Object.
This block is the one that actually displays the value.
It iterates over the Content's (Content Object) fields using the ContentType's (Content Class) FieldDefinitions (Content Class Attribute) (
For each Field Definition (Content Class Attribute), we start by displaying its identifier (
$fieldDefinition->identifier). We then get the FieldType (Datatype) instance using the FieldType Service (
$fieldTypeService->getFieldType( $fieldDefinition->fieldTypeIdentifier )). This method expects the requested FieldType's identifier, as a string (ezstring, ezxmltext...), and returns an
The Field object (Content Object Attribute) is obtained using the
getField() method of the Content Value Object we obtained using
Using this FieldType object, we can convert the Field's value to a hash using the
toHash() method, provided by every FieldType. This method returns a native type (string, hash) out of a Field instance.
With this example, you should get a first idea of how you interact with the API. Everything is done through services, each service being responsible of a specific part of the repository (Content, FieldType...).
Loading Content in different languages
Since we didn't specify any language code, our Field objects is returned in the default language, depending on your languages settings in
ezpublish.yml. If you want to use a non-default language, you can specify a language code in the
As said earlier, the Public API uses Exceptions to handle errors. Each method of the API may throw different exceptions, depending on what it does. Which exceptions can be thrown is usually documented for each method. In our case,
loadContent() may throw two type of exceptions:
NotFoundException if the requested ID wasn't found, and
UnauthorizedException if the currently logged in user isn't allowed to view the requested content.
It is good practice to cover each exception you expect to happen. In this case, since our Command takes the content ID as a parameter, this ID may either not exist, or the referenced content may not be visible to our user. Both cases are covered with explicit error messages.
This recipe shows how to browse a subtree starting from a given location. (Full code here)
Viewing Content Meta Data
This recipe shows how to read content meta data: Locations, UrlAliases, Relations, Versions, Contenttype, Section, Owner, RemoteId, Several Timestamps. (Full code here)
This script produces e.g the follwing output:
Performing a simple full text search
In this recipe a simple full text search is performed. (Full code here)
Performing an advanced search
In this recipe different criteria is combined using a logic 'AND' operation. The result is restricted additional (see recipe 9) to a given content type and subtree. (Full code here)
Creating and editing Content
The following recipes show how to create simple content. As we don't want to rely on a specific installation with predefined content types we first show how to create a content type group and a simple content type within this group. Then we will create a content object of the newly created content type. The last two recipes show how to create content containing images and xml text.
Creating a content type group
This snippet creates a content type group for a given identifier (Full code here).
If this snipped is run with the same init code from recipe 1 we will get an UnauthorizedException.
The solution is described in the next recipe.
Setting the user for authorizing actions
By default the repository assumes the anonymous user is acting. To change this the following code can be executed
If the user is identified by other mechanisms the user also can be loaded by its id via the service method
Creating a content type
With this snipped a content type with two fields of type 'ezstring' is created. (Full code here).
In this recipe content is created under a given parent location. It is assumed that the loaded content type is the one created in recipe 4. (Full code here).
In this recipe the previously created content is updated with a new title and body in the same language. (Full code here)
It is the same code as for updating (see recipe 6). The initial language should be set to the translation language.
Multiple translations at once
It is possible to to make an update in content or create content with more than one language. But there is a restriction - only one language can be assigned to the newly created version (which is displayed in the 4.x admin GUI in the translations column).
Creating Content containing an image
This recipe shows how to create an content object containing an image. (Full code here)
Create Content with XML Text
This recipe shows how to create content with xml text. As content type the folder is used where the description is filled with xml. It is also shown how to embed the previously created image in the description. The image Id is given by a command line argument.
Working with locations
Adding a new location to content
This recipe shows how to add a new location to a given content object. (Full code here)
Move or Copy Subtree
This recipe shows how to move or copy a subtree to a given parent location (Full code here)
This recipe shows how to hide/unhide a location. (Full code here)
If a content has more than one location the method
removes the location and if exists all descendants of the location. However the content itself is untouched as it has still other locations.
If the deleted location is the last one of the content the content itself is deleted. This applies also to all descendants of the location.
Alternatively a location can also moved to the trash via the method:
The result of deleting content permanently is equivalent to deleting all locations of content (see recipe 15).
It is done via the method:
Assigning section to content
On creation of content the section of the parent location's content is assigned by default to the new content. However it is possible to assign a specific section on creation by setting it in the ContentCreateStruct:
$contentCreateStruct->section = $sectionId
Later on sections can be assigned with the following code (Full code here):