This tutorial assumes the reader has successfully installed eZ Publish 5 based on the eZ Publish 5.0 Platform (enterprise) or the most current version available on Github (but not eZ Publish community edition 2012.9, which uses an older directory structure) and understands the eZ Publish 5 terminology. Take a look and get yourself acquainted with the new changes in terminology before proceeding.



We’ll go through the steps it takes to create a controller/view bundle and in the end we’ll have a framework that can execute custom PHP code and generate a view.

Here is an outline of the major steps in this tutorial:

  • Create the Bundle
  • Register the Bundle
  • Include the Bundle’s Route File
  • Add the Bundle Definition File
  • Add the Routes
  • Create the Dependency Injection File
  • Create the Controller
  • Create the Twig Template(s)
  • Gotchas and Tips
  • Conclusion


File Hierarchy

Here’s the file hierarchy for the bundle in this tutorial. There are some things that are needed for other types of bundles but this should cover the requirements of a Controller/View Bundle.



Note: Creating bundles automatically

It might be worth noting the ezpublish/console tool can create a bundle framework for you, but for the purpose of this tutorial we’ll go through the steps of creating the bundle ourselves.


Step 1: Register the Bundle

Your <ez-publish-5-root>/ezpublish/EzPublishKernel.php file should have an array called $bundleswhich is where we’ll register our new bundle with the kernel. You can do so by adding a single entry to the bottom of the $bundles array. Registering your bundle might look similar to this:
$bundles = array(
            new FrameworkBundle(),
            new TwigBundle(),
            new AsseticBundle(),
            new SensioGeneratorBundle(),
            new EzPublishCoreBundle(),
            new EzPublishLegacyBundle(),
            new EzSystemsDemoBundle(),
            new GH\TutorialBundle\GHTutorialBundle()
This new entry in the array points to our bundle’s namespace and the GHTutorialBunde method which will be later defined in the GHTutorialBundle.php file. The naming convention is the concatenation of namespace (e.g. CompanyName or ProductName) and BundleName. If you name this differently than CompanyName+BundleName I guarantee you will start running into a host of problems like kernel errors and PHP Fatal Errors. Keeping a consistent naming convention will help you avoid issues like these.


Step 2: Include the Bundle’s Route File

While Symfony’s idea of routes may be a new concept for many of us, the purpose is nothing new for eZ Publish developers. We are used to the pretty URLs for SEO purposes and we understand the necessity of creating human-readable URLs. Routes, in a nutshell, tell the application what route (/hello/world) should be controlled by (GHController::helloIndex). Read more on routing in the Symfony2 documentation.
Though you can define routes globally in <ez-publish-5-root>/ezpublish/config/routing.yml, to make things cleaner and more portable it is better to place the routes in our bundle. Include a reference to the routing file that resides in  <ez-publish-5-root>/src/GH/TutorialBundle/Resources/config directory, in the <ez-publish-5-root>/ezpublish/config/routing.yml file.
If you choose to include a reference to the routing file in your bundle, as is recommended, your <ez-publish-5-root>/ezpublish/config/routing.yml file should contain this new line (position should not matter):
    resource: @GHTutorialBundle/Resources/config/routing.yml
“@GHTutorialBundle” tells Symfony to look here: <ez-publish-5-root>/src/GH/TutorialBundle.


Step 3: Add the Bundle Definition File

Now make the directories <ez-publish-5-root>/src/GH and ez-publish-5-root>/src/GH/TutorialBundle and then create the bundle definition file <ez-publish-5-root>/src/GH/TutorialBundle/GHTutorialBundle.php. This will make referencing resources elsewhere much easier. The naming convention is usually the concatenation of CompanyName and BundleName. Thus we get ‘GHTutorialBundle’ for ours;
namespace GH\TutorialBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class GHTutorialBundle extends Bundle


Step 4: Add the Routes

While the URL alias system still gets used, in eZPublish 5 you will need to define some routes. If they aren’t defined then the system will currently fall back to the eZPublish Legacy stack (and content). If that content doesn’t exist you will get the familiar legacy “Module Not Found” error.


Syntax and Structure of Route

There currently isn’t a complete reference of all options that are available to you, but you’ll at least be able to create basic support for routes, accept slugs as variables and create regex matching for routes. The main options available are resource, pattern, defaults, requirements, type and prefix.
I’ve created four separate routes for this tutorial. The first, “GHHelloWorld,” will accept requests like “/gh/hello/Brandon Chambers” and the second, “GHHelloWorld2,” will accept the same request with a second name such as “/gh/hello/Brandon Chambers/Foo Bar.” Two more do the same but without using eZ Publish legacy’s pagelayout.tpl, providing raw output.
In an eZ Publish Legacy module/view you only get to create one PHP script per view you define. The Symfony2 framework will allow us to easily map “many” routes to one action or maintain a one-to-one route-action relationship. For example, we are mapping both routes to one controller and one action,GHTutorialBundle:GH:hello . In this case the controller is GH (i.e. class GHController extends EzController), and the action is “hello” (public function helloAction), and they will be built in the bundle called “GH/TutorialBundle” (the path will be <ez-publish-5-root>/src/GH/TutorialBundle/Controller/GHController.php). If we wanted to we could have created a separate PHP action method, but that’s not necessary. This will make more sense when you see the PHP code and the routes below.
Make sure you create your Resources and Resources/config directories under <ez-publish-5-root>/src/GH/TutorialBundle/ and drop in this code for the routing.yml file. Here’s what the <ez-publish-5-root>/src/GH/TutorialBundle/Resources/config/routing.yml should look like:
    pattern: /gh/helloPlain/{name}/{another_name}/
    defaults: { _controller: GHTutorialBundle:GH:helloPlain }
    pattern: /gh/helloPlain/{name}/
    defaults: { _controller: GHTutorialBundle:GH:helloPlain }
    pattern: /gh/hello/{name}/{another_name}/
    defaults: { _controller: GHTutorialBundle:GH:hello }
    pattern: /gh/hello/{name}/
    defaults: { _controller: GHTutorialBundle:GH:hello }

  • Placeholders ‘name’ and ‘another_name’ represent text passed to eZ Publish as part of the URI. These placeholder names need to match the PHP method’s parameter names.
  • The ‘pattern’ variable will allow us to create a regex pattern for the URI using the requirementsoption.
  • The ‘defaults’ variable lets us define a set of default items to use for our pattern. This will also allow us to set default values to our variables if we choose. All entries are separated by commas.
  • The entry names, such as “GHHelloWorld”,  don’t need to be named anything specific but they must be unique.
  • The first route that is matched will be executed and all others will be ignored.


Step 5: Create the Controller

The controller class file is where our custom PHP is housed.
We want to make sure we extend the Ez Controller and create our actions here. The names of your actions in this PHP class must match what is in your <ez-publish-5-root>/src/GH/TutorialBundle/Resources/config/routing.yml file, without the “action” keyword.
Create the <ez-publish-5-root>/src/GH/TutorialBundle/Controller directory and here’s what the <ez-publish-5-root>/src/GH/TutorialBundle/Controller/GHController.php looks like:
namespace GH\TutorialBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use eZ\Bundle\EzPublishCoreBundle\Controller as EzController;
class GHController extends EzController
    public function helloAction( $name, $another_name = null )
       return $this->render(
           array( “name” => $name,  “another_name” => $another_name )
    public function helloPlainAction( $name, $another_name = null )
       return $this->render(
           array( “name” => $name,  “another_name” => $another_name )
The framework is fairly simple. But we need to make sure we’re passing the same variables to the helloAction and helloPlainAction methods as we have defined in the routing.yml file or we will get nasty errors.
For simplicity and less confusion, define the template variables, route variables, and the PHP variables with the same names. This will make your life easier and your installation less error-prone, I promise. For example:
YAML Variable Definition: pattern: /gh/hello/{name}/{another_name}
PHP Variable Definition: public function helloAction($name, $another_name )
Template Variable Definition: array( “name” => $name, “another_name” => $another_name )


Step 6: Create the Twig Template(s)

The templates for our module reside in <ez-publish-5-root>/src/GH/TutorialBundle/Resources/views/GH. So you’ll need to create the directories for that path then create the hello.html.twig template file.
These template resources directly correlate to the PHP definition in the controller file:
Hence, the GH folder under <ez-publish-5-root>/src/GH/TutorialBundle/Resources/views/ contains the hello.html.twig file. So if you want to put your template files in the base views directory you can and then you use GHTutorialBundle::hello.html.twig in your controller when calling the template file. There’s more documentation on template naming conventions so take a look when you get a chance.
Here’s an example of a full page  with no special design wrapping it. We’ll consider this the <ez-publish-5-root>/src/GH/TutorialBundle/Resources/views/GH/helloPlain.html.twig template file:
<!DOCTYPE html>
        <title>Hello {{ name }} – Demo</title>
    <h1>Hello {{ name }}!</h1>
    <br />
    {% if another_name %}
      <h2>Hello also to {{ another_name }}!</h2>
    {% else %}
      <h2>The second part of the path is not set!</h2>
    {% endif %}
If you ended up using the eZ Demo site installation you can extend the pagelayout template and drop in your own content block. We’ll consider this the <ez-publish-5-root>/src/GH/TutorialBundle/Resources/views/GH/hello.html.twig:
{% extends “eZDemoBundle::pagelayout.html.twig” %}
{% block content %}
    <h1>Hello {{ name }}!</h1>
    <br />
    {% if another_name and another_name is not null %}
     <h2>Hello also to {{ another_name }}!</h2>
    {% else %}
     <h2>The second part of the path is not set!</h2>
    {% endif %}
{% endblock %}
Try it!
Step 7: Clear Caches
You’ll want to make sure all caches are cleared by using:
cd /path/to/ezpublish5-root
php ezpublish/console cache:clear
and then
cd ezpublish_legacy
php bin/php/ezcache.php –clear-all –purge


Step 8: Test

Now that you’ve finished this tutorial you should have some basic output such as “Hello Brandon” as a <h1> tag when accessing your installation with:
For Host-Based matching you’ll use:
Also if you decide to set a second name in the URI such as:
Then you should see “Hello also to Greg” in the output:


Otherwise the template should tell you the second part of the path is not set as defined in your routing.yml file.
For URI-Based matching you’ll use something like:
Congratulations! You now have the knowledge of creating custom PHP controllers for eZ Publish 5.x sites.


Gotchas & Tips

The ezpublish/console tool is a great resource, learn how to use it. For example, to see if you have valid routes defined try this: php ezpublish/console route:debug . You can also test if your route matches a URL encoded string with php ezpublish/console route:match /gh/hello/brandon%20chambers . Depending on permissions you may need to run this with sudo.
Additional route formats are available (html, json, xml, etc.) so check out how to add more support to the type of content you want to display.
Always make sure the file permissions are correct.


Further Reading


Special Thanks

Thank you Greg McAvoy-Jensen of Granite Horizon for his edits and suggestions for this tutorial. Jérôme Vieilledent of eZ Systems Engineering let me use his brain in the technical review and editing of this tutorial. Finally, thanks to eZ Systems V.P. for Community Nicolas Pastorino for posting this tutorial to!