Partial Content

Home

CMS Bake-offs - Contrasting eZ Publish and Drupal

A few years ago we ran a bake-off project, contrasting eZ Publish and Drupal for the same task. Are you starting a big project where the CMS will be a major factor? Consider doing some research first to look outside your normal set of tools and understand how you might implement the same solution in multiple systems.

Unpacking Now What 2013

 The Now What Conference was a long time coming. I wasn't involved in the planning or setup, so I can say with humility intact that I thought it was a fantastic conference. The selection of speakers was excellent, the production values were top notch, and the audience seemed engaged and appreciative. I'm always proud of our team at Blend, but the conference put it over the top.

There are a lot of conferences you attend for the networking opportunities, and a few you attend to really learn something from the talks. For me, NowWhat was the latter. I was helping with the production and that meant following what each speaker said very closely. You pay more attention when you have to help advance the slides. 

The speakers didn't coordinate their presentations, but that might be hard to believe if you attended, since there were a few themes that were constants across most of the presentations. That points to strong trends within the industry, and it's worth taking a look at them:

It's your content, but it's your audience's attention

Businesses spend a lot of time thinking about their message. "What do we want to say to our potential consumers?" And that's an important question to answer. You should know what you're going to say, and how and when you're going to say it. But audience behavior on the web has shifted in the past few years. Prior to Twitter and Facebook, when users were consuming daily content, they were seeking it out by visiting a regular set of sites, or using RSS. But increasingly, audiences are using social media streams to have a selection of customized content brought to them in addition to (or in place of) this seeking behavior. We've gone from hunters to gatherers of news.

As a brand, if you want to be a part of that stream, you need to think about not just what you want to say, but what your audience wants to hear. People are only going to subscribe to sources of information that interest them, and the bullet points in your 12-point marketing plan may not be the first thing on their minds. But they are your customers, so you do share some common interests. Use your position of expertise in areas around your industry or related topics to come up with things to talk about that may not directly involve you, but are interesting to your audience. If you were to sit down with one of your clients for a casual lunch, what are some things you would talk about? What can you do to create some interesting and valuable information based on the resources you have and what you do? The best summary I heard was, "Give a lot, take a little." 

<blockquote class="twitter-tweet"><p>Theme for <a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a> - build content around what customers want to talk about, not what your business wants to say.</p>&mdash; Joe Kepley (@joekepley) <a href="https://twitter.com/joekepley/status/324963644783861761">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p><a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a> enjoyed this quote. Brands aren't expected to be flawless anymore. <a href="http://t.co/UyFhIBe3Yj" title="http://twitter.com/BrittanyReith/status/324909146484899840/photo/1">twitter.com/BrittanyReith/…</a></p>&mdash; Brittany Reith (@BrittanyReith) <a href="https://twitter.com/BrittanyReith/status/324909146484899840">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p>@<a href="https://twitter.com/melissarach">melissarach</a> - Let conversation lead content. <a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a></p>&mdash; Bryan Ruby (@cmsreport) <a href="https://twitter.com/cmsreport/status/324904827983364098">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

Successful websites are made of people! PEOPLE!

The other big recurring theme was the focus on "soft skills" within a project - goal alignment and management of project intent not just project milestones. In my years working on project for the web in various employment positions, I've seen a few projects fail. It's almost never due to technology. Web projects fail because requirements were wrong, expectations weren't managed, or important stakeholders weren't considered at the right time. Project management is as much a process of political alignment as it is a process of managing time and technology. Everyone has to agree on the overall vision for the organizational structure to work. 

 Governance is another area where 'people management' was heavily discussed. It's clear that to maintain proper order on a large web property, everyone involved needs to understand the importance of providing the right content and maintaining it. You don't simply write something for the web site and then forget it exists. Every piece of content on your site needs to be there for a purpose, and it needs to always be right, even when things change. This means that someone is responsible for owning it and taking pride in it.

At the same time, governance shouldn't be there to enforce a rigid tone or suck the soul out of your content. Write your website for people, because people like to be treated like people, not 'end users' or 'uniques' or 'prospects'. The Golden Rule still applies. Don't make something you wouldn't want to read.

<blockquote class="twitter-tweet"><p><a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a> Theme 2 - get everyone involved that needs to be at the BEGINNING. <a href="https://twitter.com/search/%23nosecrets">#nosecrets</a> <a href="https://twitter.com/search/%23nosurprises">#nosurprises</a></p>&mdash; Cathy McKnight (@cathymcknight) <a href="https://twitter.com/cathymcknight/status/324977086970359808">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p>B/c you cut the lawn doesn't mean you own the yard. Just b/c you maintain company's web presence doesn't mean you own it. The org owns...</p>&mdash; Cathy McKnight (@cathymcknight) <a href="https://twitter.com/cathymcknight/status/324999702783328256">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p><a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a> @<a href="https://twitter.com/mrvilhauer">mrvilhauer</a> Talk not only about content strategy but also people strategy.</p>&mdash; Bryan Ruby (@cmsreport) <a href="https://twitter.com/cmsreport/status/324975408326012931">April 18, 2013</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

Recharged

My biggest takeaway from NowWhat is that it gave me another chance to get fired up about the web. Lisa Welchman's keynote was the perfect capper to the day. One of the coolest things about a new medium is that it's new. And the web is perpetually a new medium. Even though it's been around for 20 years, we are constantly getting new tools to work with. And that means that best practices are constantly shifting. It's an environment where there are some generally-understood best practices, but any of them could be trumped tomorrow by a better approach. It makes the web exciting and engaging for both users and those of us who work to build it. As web professionals, it's easy to think that we're working day to day in an established field. But the truth is that at 20 years in, its' still one of the newest professions out there. We're still the '49ers working the gold rush, and the things we're doing now are going to be How It's Done for the next generation, until a new wrinkle comes along to change it once again.

<blockquote class="twitter-tweet"><p>If you are working on the digital today, you are making a difference. You have a responsibility to get it right. @<a href="https://twitter.com/lwelchman">lwelchman</a> <a href="https://twitter.com/search/%23nowwhat13">#nowwhat13</a></p>&mdash; melissarach (@melissarach) <a href="https://twitter.com/melissarach/status/324995432180760576">April 18, 2013</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script> 

Looking back at eZ UnConference #1 and ahead to #2

 

eZ Uncon 2013

With eZ UnConference #2 fast approaching, I thought it might be a good idea to share some thoughts on last year's eZ Unconference #1. What follows is my own reflections on the event, but hopefully it's helpful for someone who might be trying to decide whether they should make the trip to Montpelier.

The first UnConference was a break from a long tradition of the annual eZ Publish Summit as a two-track "business" and "developer" combined event. Instead, there were two days of developer-aimed talks, and an optional extra day of Symfony training, and a business-focused Summit a few months later. As a US partner, Cologne is a long trip, so I was concerned that I'd be able to get enough out of the conference to justify the travel. The eZ Community team put on a useful and informative event, and really made it worth the trip.

Talks

The main draw of any conference, of course, is the talks, and the eZ Community Team did a good job here. I've worked with eZ Publish for a while, and a few things were covered that I was already familiar with, which is the case with most conferences. But more importantly, there were one or two really critical presentations that have changed the way I work with eZ Publish since. Unlike a larger conference, there was also plenty of opportunity to interact with the speakers.

Interaction

The talks were good, but the really valuable things I took away from eZ UnConference #1 were the result of things that happened between sessions and in smaller groups. I picked up several tips while speaking with other eZ Developers that I brought home and immediately applied to active projects. I also met a lot of folks who were experts in specific areas (like caching, search engines, etc), so I know who to call if I face challenges in those areas. Those are all things I never would have picked up by watching talks on YouTube.For me, networking is one of the most important parts of a conference, and the "unconference" style allowed us ample time to break into small groups and talk about various development challenges with people who have a lot of experience with those challenges. It was a rare opportunity to (for example) sit down with the eZ development team and talk about the architecture of eZ Publish 5 with the people who were building it every day.

Time Out

The release of eZ Publish 5 started an exciting and challenging period for eZ Publish developers. Those of us who spent a lot of time working with eZ Publish 4 have to absorb a lot of change with eZ Publish 5. In some ways, we're no longer experts because we still need to write the new rules of the platform. That's exciting, because there's never been a time to contribute to eZ Publish, but it also means taking the time to really dig in and learn some new tools. I'm sure it's possible to stay at home and learn Symfony and eZ Publish 5 by reading documentation, but the problem is finding the time. I had wanted to learn Symfony for a couple months, but there were always too many distractions and other work at home to dive in. The Symfony training day at the end of the eZ UnConference #1 was great, since it provided a day with no distractions to really sit down and dig in to Symfony. Since then, I've become very comfortable with Symfony and I'm really enjoying the new tools. Having some 'classroom' time to really focus sped up the learning curve significantly.

Looking Ahead

eZ Unconference #2 looks like it will continue to focus on learning opportunities for eZ Publish 5, which is a great theme given the continuing transition to the new Symfony-based stack. The talks have been divided up into information for those just learning Symfony, those new to eZ 5, backward compatibility, and the REST API, so there should be something for developers at any stage of development. While it's a large open source project, eZ Publish is a rare skill compared to other professions. You'll meet a lot more dentists, for instance, than you will eZ Publish developers. And dentistry is a very rare skill compared to cooking or construction work. Dentists get together at conferences all the time to compare notes and discuss techniques, because it provides an opportunity to see other dentists and talk about things that they can't discuss on a daily basis. If that's an important opportunity for dentists, then it's an even more important opportunity for eZ Publish developers, since there are fewer of us and our jobs are (in some ways) even more complicated.

Working with eZ Publish 5 - Listeners

Our blog is starting to look a lot like a blog, but we'd like to do better than "This Site's Name" and "sidebar goes here" for the surround.

You could hardcode the site name and sidebar into the templates, but I'd prefer to have those things editable from the admin interface with the rest of the site. It's common practice on an eZ Publish site to have a 'Design' or 'Site Surround' object that holds some of your common settings, and that's what we'll do here. So how do we get this object in to our template?

One approach would be to use sub-requests, and in fact that's what eZ's demo bundle does. But if we're pulling a lot of different pieces, that's a lot of controller methods, sub-requests, and templates. I'd prefer to just have the surround object available all the time in my template. Based on what we've looked at so far, though, the only way to do that would be to write controller methods for all my pages. No thanks.

Fortunately, there's another new piece of functionality available that we haven't examined yet. eZ Publish 5 provides a number of events that can be hooked to during the page generation process to provide your own code. In particular, there's a PreContentView event that eZ Publish triggers just before a page renders that gives you the opportunity to insert more variables into a page. That sounds like a good approach, so let's hook that event and add our surround object to the template.

First, let's do the usual work-resetting dance of updating git to the next tutorial step:

git checkout -- *
git clean -f -d
git checkout -b tutorial-5 origin/tutorial-5

We'll need to write a listener that accesses the content repository using the Public API (just like our controller), gets the surround object, and adds it to the variables in the template. But how will it know which object to retrieve? We could hardcode that into the listener, but instead let's also make a new config setting to tell our listener which object type is the surround object.

Let's add the following to ezpublish/config/parameters.yml, under and in line withe the existing parameters there:

     ez5tutorial.default.surround_type: template_look

'template_look' is the name for this surround/design object in most eZ Publish repositories.

Add a new directory to your project, src/Blend/TutorialBlogBundle/EventListener, and create a new php file in it, PreContentViewListener.php. Add the following code:

?php
namespace Blend\TutorialBlogBundle\EventListener;

use eZ\Publish\Core\MVC\ConfigResolverInterface,
    eZ\Publish\Core\MVC\Symfony\Event\PreContentViewEvent,
    eZ\Publish\API\Repository\Values\Content\Query,
    eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator,
    eZ\Publish\API\Repository\Values\Content\Query\Criterion,
    eZ\Publish\API\Repository\Values\Content\Query\SortClause,
    eZ\Publish\API\Repository\Repository;

/**
 * PreContentViewListener hooks the PreContentView Event to provide extra data to the template
 */
class PreContentViewListener
{
    /**
     * @var \eZ\Publish\Core\MVC\ConfigResolverInterface
     */
    protected $configResolver;

    /**
     * @var \eZ\Publish\API\Repository\Repository
     */
    protected $repository;


    /**
     * Constructs our listener and loads it with access to the eZ Publish repository and config
     * @param \eZ\Publish\API\Repository\Repository $repository
     * @param \eZ\Publish\Core\MVC\ConfigResolverInterface $configResolver
     */
    public function __construct( Repository $repository, ConfigResolverInterface $configResolver )
    {
        //Add these to the class so we have them when the event method is triggered
        $this->repository = $repository;
        $this->configResolver = $configResolver;
    }

    /**
     * Fires just before the page is rendered
     * @param \eZ\Publish\Core\MVC\Symfony\Event\PreContentViewEvent $event
     */
    public function onPreContentView( PreContentViewEvent $event )
    {
        //What's our design/surround object in the repository called? Check the config
        //This reads the setting we added to parameters.yml
        $surroundTypeIdentifier = $this->configResolver->getParameter('surround_type', 'ez5tutorial');

        //To retrieve the surround object, first access the repository
        $searchService = $this->repository->getSearchService();

        //Find the first object that matched the name from our config
        //(We only expect there to be one in the DB)
        $surround = $searchService->findSingle(
            new Criterion\ContentTypeIdentifier($surroundTypeIdentifier)
        );

        //Retrieve the view context from the event
        $contentView = $event->getContentView();

        //Add the surround variable to the context
        $contentView->addParameters( array('surround' => $surround) );
    }
}

Some of this should look familiar from the Controller we made earlier. We access the repository's Search service, give it our criteria, and find an object. Some of these pieces, though, are new. The ConfigResolver is your way of accessing the configuration variables defined in the various yml files. The service helpfully works out for you whether you're in dev or prod, what siteaccess you may be running (etc), and returns the correct value. In this case, we're retrieving the ini setting we added above.

The constructor poses a puzzle, though: how do we construct this listener and make sure it's loading the services it needs? Here the dependency injection container comes to the rescue again. Let's update src/Blend/TutorialBlogBundle/Resources/config/services.yml with some new instructions. Add this line to the parameters section: 

    blend_tutorial_blog.pre_content_view_listener.class: Blend\TutorialBlogBundle\EventListener\PreContentViewListener

and add this to the service section:

    #define our event listener
    blend_tutorial_blog.pre_content_view_listener:
        class: %blend_tutorial_blog.pre_content_view_listener.class%
        #These services will be passed to the constructor
        arguments: [@ezpublish.api.repository, @ezpublish.config.resolver]
        #This tag tells the framework we're interested in the PreContentView event
        tags:
            - {name: kernel.event_listener, event: ezpublish.pre_content_view, method: onPreContentView}

With these pieces added, Symfony will now automatically instantiate our class and give it the right components. In the arguments section, the '@ezpublish.api.repository' and '@ezpublish.config.resolver' are service markers that tell Symfony to insert the objects for those services. So as far as our code is concerned, those services could be easily replaced with something else, as long as the interfaces stayed the same.

Let's give it a try: add this somewhere within the body on your pagelayout.html.twig:

{{ dump(surround) }} 

You should get a long variable dump listing, including a list of fields. This is your surround object, inserted by the listener. Go ahead and remove the dump command, then let's modify the template to take advantage of it (here's the whole pagelayout.html.twig again, with tags added for {{surround}}.

{# Base HTML based on HTML Boilerplate and Twitter Bootstrap #}
<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>{{ location.contentInfo.name }} - {{ surround.contentInfo.name }}</title>
    <meta name="description" content="{{ surround.fields.meta_description['eng-US'].text }}">
    <meta name="viewport" content="width=device-width">

    {% stylesheets "bundles/blendtutorialblog/css/*" %}
        <link rel="stylesheet" href="{{ asset_url }}" />
    {% endstylesheets %}

    {% javascripts
        "bundles/blendtutorialblog/js/modernizr.js"
    %}
        <script type="text/javascript" src="{{ asset_url }}"></script>
    {% endjavascripts %}
</head>
<body>
<header class="main-header">
    {# The block defines a piece of the template that can be overridden by other templates #}
    {# See http://twig.sensiolabs.org/doc/templates.html#template-inheritance #}
    {% block header %}
        <div class="branding jumbotron subhead">
            <div class="container">
                <h1>{{ ez_render_field(surround, "title") }}</h1>
            </div>
        </div>
        <nav class="navbar navbar-inverse">
            <div class="navbar-inner">
                <div class="container">
                    {# You can nest blocks #}
                    {% block topnav %}
                        Top Menu will go here
                    {% endblock %}
                </div>
            </div>
        </nav>
    {% endblock %}
</header>

<div class="container">
    <div class="body">
        <div class="row">
            <div class="main span9">
                {# eZ Publish uses the 'content' block as the main content of the page #}
                {% block content %}{% endblock %}
            </div>
            <aside class="sidebar span3">
                {% block sidebar %}
                    {{ ez_render_field(surround, "sidebar") }}
                {% endblock %}
            </aside>
        </div>
    </div>
</div>

<footer class="footer">
    {% block footer %}
        {{ ez_render_field(surround, "footer") }}
    {% endblock %}
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
{% javascripts
    "bundles/blendtutorialblog/js/bootstrap.js"
    "bundles/blendtutorialblog/js/main.js"
%}
    <script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}

</body>
</html>

Let's shift-reload the site and see how things came together: 

Screenshot with surround object

Not bad! You can edit the surround settings under the 'Design' top link in the admin. Note that as you make edits, the frontend may not change. This is because the surround object isn't taken into account by the modification dates and Etags sent out by our controllers. You could modify the code to factor it in, but it isn't likely to change often, so it's probably easier to just manually clear the cache (ezpublish/console cache:clear -e dev)

Working with eZ Publish 5 - Subrequests

OK, now the real fun starts. 

So far, most of what we've done has been pretty analogous to eZ Publish 4, but we'll start to see some real differences here. 

At this point, our blog isn't much of a blog, because while it's displaying the blog object, it's isn't displaying the blog posts it contains. 

In eZ Publish 4, we'd just do something like this in the template:

{def $posts = fetch('content','tree', hash(
      'class_filter_type','include',
      'class_filter_array',array('blog_post')
))}
{foreach $posts as $post}
{node_view_gui content_node=$post view='summary'}
{/foreach}

That worked fine, but it also tightly binds our template architecture to the the repository. And if you want to run any additional code, the only option you really have is to write the code right in the template. eZ Template language isn't well-suited to this role, so it can lead to some real spaghetti, and ultimately maintenance problems. 

The solution is to use the right tool for the right job: PHP for retrieving and preparing the data, and templates for formatting the output. So how do we do this?

Recall that symfony uses a Model-View-Controller architecture. A request comes in, gets routed to an action on a Controller, where data is assembled from the Model and then handed off to a View for display. We've been using this structure all along, it's just that we've been using a Controller action provided by eZ Publish. If you want, you can take a look at it yourself on github (the viewLocation function). 

Symfony provides a sub-request system, which is basically a way of sending a request to a second MVC action from inside your first, and that's what we'll use here. To do that, we'll need to:

  • create a new controller
  • tell symfony about it
  • build the templates we need

To get started on this step, you can bring your tutorial code up to date with the following commands. As before, this will wipe out any local changes you've made in the tutorial, so take care.

git checkout -- *
git clean -f -d
git checkout -b tutorial-4 origin/tutorial-4

The "Hello World" Controller

The first thing we'll do is create the controller. This will be the most complicated piece, but it's all downhill from here.

Add a new PHP file to src/Blend/TutorialBlogBundle/Controllers called 'BlogController.php', and add the following code:

<?php
namespace Blend\TutorialBlogBundle\Controller;
//The above defines our PHP namespace

//This declares the components we'll be using 
use Symfony\Component\HttpFoundation\Response,
 eZ\Publish\Core\MVC\Symfony\Controller\Content\ViewController as APIViewController;
 
/**
 * BlogController provides basic sub-request methods used by the tutorial blog
 * Extends APIViewController which provides some convenience methods for handling request/response objects
 */
class BlogController extends APIViewController
{
    public function postsByDate()
    {
     //Response is a symfony class that defines all the data of an HTTP response
        return new Response("<h1>HI FROM BlogController</h1>");
    }
}

We've defined a controller with one method, postsByDate. We'll use this to get a templated set of blog post summaries sorted by date. For now, though, we're just going to get back our text. 

Wiring it up

Now that we have our controller action, we need to let Symfony know about it, and call it from our template.

Open up src/Blend/TutorialBlogBundle/Resources/config/services.yml. This is where we define the services that your bundle provides. Symfony uses a Dependency Injection Container, which is a fancy way of saying that rather than manually instantiating your classes in code, you can define the pieces they need and how they should be instantiated in the configuration. The end result is that code components only depend on interfaces rather than directly depending on other classes, making the entire framework (your code included) more maintainable and easier to unit test.

Right now your services.yml probably just has some commented-out sample lines. Delete that stuff and put this in instead:

#These parameters are defined like variables. They can prevent you from needing to 
#change a bunch of code just because you changed your class name.
parameters:
    blend_tutorial_blog.blogcontroller.class: Blend\TutorialBlogBundle\Controller\BlogController

services:
    #This tells symfony what is needed to instantiate our controller
    blend_tutorial_blog.controller:
        class: %blend_tutorial_blog.blogcontroller.class%
        arguments: [@ezpublish.view_manager]
        calls:
            - [setContainer, [@service_container] ]

    #This adds a short name 'tblog' for our controller, which is nicer to type in templates
    tblog:
        alias: blend_tutorial_blog.controller

Now add this line to your src/Blend/TutorialBlogBundle/Resources/view/full/blog.html.twig template, right below the ez_render_field line:

{{ render( controller( "tblog:postsByDate" ) ) }} 

The twig 'render' function retrieves the result of another MVC request and includes it in the template. If you refresh your page (you will need to shift/cmd-reload to bypass the browser cache), you should see your controller in action: 

Hello World Controller Screenshot

Bingo!

Do Something Useful

Of course, if we just wanted text we could have typed it in the template. Let's retrieve those blog posts. 

To do that, we'll use the eZ Publish Public API. One of the engineering problems that eZ Publish 5 attempts to address is figuring out which classes and methods in the core system were safe for authors to call, and which parts were going to change in future releases. The public API defines the interaction layer, and allows for more flexibility behind the scenes for things like pluggable storage engines and other future enhancements, without breaking existing code.

From an architectural standpoint, this is great. Unfortunately, the Public API can often be pretty verbose in the case of simple actions, and could use a bit more syntactic sugar. But we'll focus on how it works here, and I'll save the editorializing for a future post.

(If you don't feel like typing, you can reach into the future and copy and paste all this from the next tutorial step on github)

The first thing you'll need to do is add a few more classes to your controllers 'use' statement: 

use Symfony\Component\HttpFoundation\Response,
    eZ\Publish\Core\MVC\Symfony\Controller\Content\ViewController as APIViewController,
    eZ\Publish\API\Repository\Values\Content,
    eZ\Publish\API\Repository\Values\Content\Query,
    eZ\Publish\API\Repository\Values\Content\Query\Criterion,
    eZ\Publish\API\Repository\Values\Content\Search\SearchResult,
    eZ\Publish\API\Repository\Values\Content\Query\SortClause;

I've broken the controller code up in to two methods: The controller action itself (replacing our 'hello' method) ...

    /**
     * postsByDate returns a formatted list of all posts beneath a location id(aka node id)
     * Posts are retrieved from the repository and returned in reverse chronological order
     * @param $subTreeLocationId The location ID (node ID) to look under
     * @param string $viewType What type of view template should render each result
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function postsByDate($subTreeLocationId, $viewType='summary')
    {
        //Retrieve the location service from the Symfony container
        $locationService = $this->getRepository()->getLocationService();
 
        //Load the called location (node) from the repository based on the ID
        $root = $locationService->loadLocation( $subTreeLocationId );
 
        //Get the modification time from the content object
        $modificationDate = $root->contentInfo->modificationDate;
 
        //Retrieve a subtree fetch of the latest posts
        $postResults = $this->fetchSubTree(
            $root,
            array('blog_post'),
            array(new SortClause\Field('blog_post','publication_date',Query::SORT_DESC))
        );

        //Convert the results from a search result object into a simple array
        $posts = array();
        foreach ( $postResults->searchHits as $hit )
        {
            $posts[] = $hit->valueObject;
 
            //If any of the posts is newer than the root, use that post's modification date
            if ($hit->valueObject->contentInfo->modificationDate > $modificationDate) {
                $modificationDate = $hit->valueObject->contentInfo->modificationDate;
            }
        }
 
        //Set the etag and modification date on the response
        $response = $this->buildResponse(
            __METHOD__ . $subTreeLocationId,
            $modificationDate
        );
 
        //If nothing has been modified, return a 304
        if ( $response->isNotModified( $this->getRequest() ) )
        {
            return $response;
        }
 
        //Render the output
        return $this->render(
            'BlendTutorialBlogBundle::posts_list.html.twig',
            array( 'posts' => $posts, 'viewType' => $viewType ),
            $response
        );
    }

... and a convenience method to fetch a tree of content, keeping our controller code more legible.

    /**
     * A convenience method to provide a simple method for retrieving selected objects.
     * Returns all content object from a subtree of content by type, based on the location
     * @param Location $subTreeLocation The location object representing a location (node) in the repository
     * @param array $typeIdentifiers an array of string containing identifiers for ContentTypes
     * @param array $sortMethods An array of sort methods
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
     * @todo Factor this method out as a service to be used by other controllers
     */
    protected function fetchSubTree(
        \eZ\Publish\API\Repository\Values\Content\Location $subTreeLocation,
        array $typeIdentifiers=array(),
        array $sortMethods=array()
    )
    {
 
        //Access the search service provided by the eZ Repository (Public API)
        $searchService = $this->getRepository()->getSearchService();
 
        $criterion = array(
            new Criterion\ContentTypeIdentifier( $typeIdentifiers ),
   new Criterion\Subtree( $subTreeLocation->pathString )
        );
 
        //Construct a query
        $query = new Query();
        $query->criterion = new Criterion\LogicalAnd(
            $criterion
        );

        if ( !empty( $sortMethods ) )
        {
            $query->sortClauses = $sortMethods;
        }
        $query->limit = 20;
 
        //Return the content from the repository
        return $searchService->findContent( $query );
    }

That's a lot more code than we're used to for a fetch, but most of it is involved with cache control. Since eZ Publish 5 doesn't have the multiple layers of caching that 4 did, caching is easier to understand, but it also needs to be considered when generating any data. When we set up the response object for the controller, we're giving it a string unique to our output, and a time when the output was last modified: 

//Set the etag and modification date on the response
$response = $this->buildResponse(
    __METHOD__ . $subTreeLocationId,
    $modificationDate
);

These values are used to populate the HTTP ETag and Last-Modified headers, meaning they will inform cache behavior for Symfony, frontend caches like varnish or squid, intermediate proxies, and the user's browser all at once. As a developer, this gives you a great deal more control in how your output is managed in caching, and the use of the HTTP spec means that all of the layers will work in concert.

A couple of other interesting notes from this code: 

  • We added a couple of parameters to our postsByDate function. These can be called from the template via the Twig render function.
  • The line '$locationService = $this->getRepository()->getLocationService();' give you a pretty good example of the other side of dependency injection, consuming services. Here we're consuming a location service defined by the eZ Publish Core, similar to how we defined our controller in services.yml. $this->getRepository is a method provided by our parent class that just makes it easy to retrieve repository services from Symfony's service container, which is how you get all of these magically-instantiated objects. Later on we'll examine how to use the service container directly when you're running code outside of a controller.
  • If you look at the fetchSubtree method, you'll note that sort clauses and filters are defined as separate classes. This means they're easily extensible. 
  • Also in fetchSubtree, it's worth noting that this code would be identical for querying content from the database, a Solr index, or any other future storage engine that the repository might use.

More Templates

One more item of note in the controller: we've replaced our return response with a call to a template rather than a simple response. It's a template that doesn't currently exist. Let's fix that.

Add a new template, src/Blend/TutorialBlogBundle/Resources/views/posts_list.html.twig:

{# Iterate over the list of posts and render each individually #}
{% for post in posts %}
    {{
        render(
            controller(
                "ez_content:viewLocation",
                {
                    'locationId': post.contentInfo.mainLocationId,
                    'viewType': viewType
                }
            )
        )
    }}
{% endfor %}

This template uses the two variables that we provided when we called the template from our controller, 'posts' and 'viewType'. These are being passed to another render call, which calls eZ's viewLocation controller for each retrieved content. This seems like a lot of sub-request calling, but with the cache headers set properly, most of these will be very fast. 'viewType' is actually set by an input variable to our postsByDate method on the controller, so it's a variable we're passing through to the template to allow us to decide what type of presentation to give our objects without writing more code. The same controller action can be used for many different presentations, as long as the data is the same.

So far the only template we have for blog posts is a 'full' template - a full-page render. We'll want a summary view to list the posts on the blog page. Let's create that now by adding a new template: src/Blend/TutorialBlogBundle/Resources/views/summary/blog_post.html.twig:

{% extends viewbaseLayout %}
{% block content %}
    <article class="summary blog_post">
        <h2><a href="{{ path( location ) }}">{{ location.contentInfo.name }}</a></h2>
        {# If the summary is empty, render the body #}
        {# This is likely not best practice since it's language-specific #}
        {% if content.fields.summary['eng-US'].xml|xmltext_to_html5 %}
            {{ ez_render_field( content, "summary" ) }}
        {% else %}
            {{ ez_render_field( content, "body" ) }}
        {% endif %}
    </article>
{% endblock %}

We'll also need to register this new template by adding to ezpublish/config/override.yml so that eZ Publish knows when to use it: 

                summary:
                    blog_post:
                        template: BlendTutorialBlogBundle:summary:blog_post.html.twig
                        match:
                            Identifier\ContentType: blog_post

(Why didn't we register posts_list.html.twig? Because our controller called that directly. Since the eZ Publish controller didn't use it, it didn't need to be registered.

Finally, we'll modify our render statement in src/Blend/TutorialBlogBundle/Resources/views/full/blog.html.twig to take advantage of the parameters we added to the controller action:

     {{
      render (
       controller(
        "tblog:postsByDate",
        {
         'viewType': 'summary',
         'subTreeLocationId': location.id
        }
       )
   )
     }}

At long last, a shift-reload should bring us our blog posts!

Posts List screenshot

This is starting to look a lot like a web site! Next we'll see how we can insert some additional objects to help us format the page.

Working with eZ Publish 5 - Content Templates

Now that you have the surround running in Twig templates, it's time to take over those content templates from the legacy template system and create Twig templates for those too. 

First, let's catch up your tutorial code for this step by updating your branch to tutorial-2. As with last time, note that this will erase your local changes for the sake of consistency, so if you have things you want to keep, commit them as a new branch. 

Clean up your working directory:

git checkout -- *
git clean -f -d 

and pull down the next step of the tutorial:

git checkout -b tutorial-3 origin/tutorial-3 

This includes our template and asset additions from the last step.

In the legacy eZ Publish template system, there was a rules-based system that the CMS used to decide which template would be used for a given content object based on its type, location, the type of appearance requested (view) and other rules. An analogous system is in place for eZ Publish 5, but it's a bit cleaner and more extensible. 

First, let's create a template for our 'Blog' content type, which is the kind of page we've been looking at when we load the root of the site. In our bundle, create the directory Resources/views/full, and add a 'blog.html.twig' file in it. Add this content to the file: 

{# This template extends pagelayout.html.twig and just replaces the 'content' block #}
{% extends noLayout ? viewbaseLayout : "BlendPartialContentBundle::pagelayout.html.twig" %}
{% block content %}
    {# The 'location' variable contains info about the current position in the site and some metadata #}
    <h1 class="page-title">{{ location.contentInfo.name }}</h1>
    <article class="extra description">
        {# The 'content' variable contains the content object we're viewing #}
        {# ez_render_field is a twig helper function that uses either twig or legacy templates to render the
           data from the field
        #}
        {{ ez_render_field( content, "description" ) }}
    </article>
    Blog posts will go here
{% endblock %}

The 'extends' keyword declares that this template inherits from the pagelayout.html.twig, which means that our page will look just like that template, except our new template markup will be plugged in to the 'content' block. This seems similar on the surface to eZ's former templating system, but keep in mind that the template will be rendered in a single step - both the inner and outer template will have access to the same variables.

Go ahead and create a 'blog_post.html.twig' in that same 'full' folder: 

{% extends noLayout ? viewbaseLayout : "BlendPartialContentBundle::pagelayout.html.twig" %}
{% block content %}
    <article class="body">
        <h1 class="page-title">{{ location.contentInfo.name }}</h1>
        <div>
            {{ ez_render_field( content, "body" ) }}
        </div>
    </article>
{% endblock %}

Nothing new there, just more of the same.

The next step is to tell eZ Publish about our new templates. This means configuring the template system in the yml files. To keep things familiar, we'll create an override.yml and include it, sort of like the override.ini files you might be familiar with from the legacy system.

Up in the htdocs/ezpublish/config folder, create a new file, override.yml, and add these rules to it:

#This ezpublish.system preamble states the context these rules apply in
ezpublish:
    system:
        # The siteaccess
        ezdemo_site_user:
            location_view:
                # Type of the view. 'full' is when an object represents the whole page
                full:
                    # name for our rule
                    blog:
                        # template to use
                        template: BlendPartialContentBundle:full:blog.html.twig
                        # when to use this template (when the ContentType is 'blog') 
                        match:
                            Identifier\ContentType: blog
                    blog_post:
                        template: BlendPartialContentBundle:full:blog_post.html.twig
                        match:
                            Identifier\ContentType: blog_post

As you can see, you create a rule for each template to tell eZ Publish when you want to use it. Next, we need to tell eZ Publish to include our new ini file in its configuration. Add this to the top of htdocs/ezpublish/config/ezpublish.yml

imports:
    - { resource: override.yml }

This will include your new override.yml file in to the ezpublish.yml as though it were part of the file.

That's it. Refresh your page and you should see your new templates in use.

Screenshot of full view template in use

The rules for selecting templates in eZ Publish 5 are similar to those in eZ Publish 4, just with a new template language and configuration syntax. You can read more about it in the eZ Publish 5 documentation

Next we'll get some blog posts showing up by retrieving content from the repository.

Working with eZ Publish 5 - Templates

Starting Development

In Symfony, your development happens in Bundles. Bundles provide containers for your code, and can even allow you to override files from third parties or the core software itself. So we'll start off by generating a bundle to hold our work. 

Symfony provides a command line tool to help generate a new bundle. All you need to do is provide a namespace for your bundle. The namespace should be unique to your project, and the first element is usually your company or site name. Per Symfony standards, the final element in the namespace must end with the word 'Bundle'. On the command line, run the following from the ez publish root (htdocs in the sample code): 

php ezpublish/console generate:bundle --namespace="Blend/TutorialBlogBundle" 

This will kick off a wizard that will ask you a series of questions about your bundle. Just hit 'enter' for most of these, but use 'yml' for your configuration format, and let Symfony generate the whole directory structure and take all the automated steps to register your bundle. Your wizard session will look something like this: 

Bundle Generation Screenshot

Once the wizard is done, we'll have an extra directory structure under the 'src' directory. 

Contents of bundle directory - Finder screenshot

What's in here? Well, we'll come back to most of it, but a couple of things to note for now: 

  • Resources - this is where all of the non-PHP items for your bundle are stored, including config files, Twig templates, and javascript, CSS, and image assets.
  • Controller - any controllers you write will go here; we'll create one later
  • Tests - Symfony provides a testing framework for all code and encourages the creation of unit tests. This is beyond the scope of this tutorial, but I'll cover it in a future post.

Now that we have our bundle in place, let's start off by creating a Twig template that will define the structure of our site, the page layout template. This template will serve as the basis for all of our HTML pages. Twig uses a system of template inheritance, so other pages will replace specific pieces of our page layout with specific content later on. If we don't have a piece of a template available as a Twig template, eZ Publish will try to fill the content in from the legacy template system.

Create a file called 'pagelayout.html.twig' in src/Blend/PartialContentBundle/Resources/views, and add a basic template: 

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title Here</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">

</head>
<body>
<header class="main-header">
    {% block header %}
        <div class="branding jumbotron subhead">
            <div class="container">
                <h1>This Site's Name</h1>
            </div>
        </div>
        <nav class="navbar navbar-inverse">
            <div class="navbar-inner">
                <div class="container">
                    {% block topnav %}
                        Top Menu will go here
                    {% endblock %}
                </div>
            </div>
        </nav>
    {% endblock %}
</header>

<div class="container">
    <div class="body">
        <div class="row">
            <div class="main span9">
                {% block content %}{% endblock %}
            </div>
            <aside class="sidebar span3">
                {% block sidebar %}
                    Sidebar stuff goes here
                {% endblock %}
            </aside>
        </div>
    </div>
</div>

<footer class="footer">
    {% block footer %}
        Footer stuff goes here
    {% endblock %}
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

</body>
</html>

With our pagelayout template in place, we need to tell eZ Publish to start using it. Edit the ezpublish/config/parameters.yml file and modify the 'ezpublish_legacy.view.default_layout' setting like so: 

ezpublish_legacy.view.default_layout: BlendTutorialBlogBundle::pagelayout.html.twig

If you refresh http://ez5tutorial.local, you should now have a very ugly-looking site: 

Screenshot of basic site with no styling

Where's all that stuff coming from? Well, since there are no Twig templates to render the content area, the legacy eZ Publish site is trying to render a basic blog. But if you examine the HTML, you should find that it's using our page layout. You'll also notice that the page doesn't have any styles, because we haven't provided any. We'll do that next. 

Asset Management

eZ Publish 4 provided a handy system for managing stylesheets and javascript called eZJSCore. Symfony provides an even handier system called Assetic, so we'll use that to add some styles to our site. 

For this project, I've used HTML5 Boilerplate and Twitter Bootstrap. The assets for these should go in the js, css, and images directories in our bundle's Resources/public folder. To save time and frustration, I've downloaded these for you in our next tutorial step on github, and also set a few settings in the legacy eZ Publish environment to help with some of the content formatting in the admin. Let's clear out our work and switch to that branch now. Note that this will erase any work you've done so far and replace it with the tutorial version, so if you've made customizations you want to save, create a new branch for them in git before proceeding.

To switch branches, first clean up the working area by typing: 

git checkout -- *
git clean -f -d 

Then switch to the next step in the tutorial: 

git checkout -b tutorial-2 origin/tutorial-2 

Note: if you have already looked at this branch, you'll get an error saying the branch already exists. In that case, simply type 'git checkout tutorial-2'

In this new branch, we've pulled down some new javascript, css, and image files into the Resources/public folder. To use them in the template, we'll use assetic to reference them. Add this to the header of your pagelayout.html.twig just below the meta tags: 

    {% stylesheets "bundles/blendtutorialblog/css/*" %}
        <link rel="stylesheet" href="{{ asset_url }}" />
    {% endstylesheets %}
 
    {% javascripts
        "bundles/blendtutorialblog/js/modernizr.js"
        "bundles/blendtutorialblog/js/bootstrap.js"
    %}
        <script type="text/javascript" src="{{ asset_url }}"></script>
    {% endjavascripts %}

The {% stylesheets %} block will iterate over all files in "bundles/blendtutorialblog/css/*" and create the included <link> tag for each file it finds, loaded alphabetically. The {% javascripts %} block will load each of the named javascript files in turn and create the script tag. In a production environment, Assetic will also condense these into a single file. 

The paths used here are relative to the 'web' folder, since that's the Apache root. But if you look, you'll notie that there isn't a bundles/blendtutorialblog folder in the 'web' directory. You'll need to run a console command to symlink the assets there, which we haven't done. In the htdocs directory, run this from the terminal: 

php ezpublish/console assets:install --symlink 

Now the symlink for your bundle should appear, and refreshing http://ez5tutorial.local should grace us with a styled page.

Screenshot of styled page

With that out of the way, let's add some missing pieces by creating content templates.

Working with eZ Publish 5 - Installation

Part 3 of X - Getting Things up and running

Working with eZ Publish 5 - Setup

About The Project

This tutorial started because I wanted to start a blog - and one of the first things I wanted to blog about was building a site with eZ Publish 5. So it seemed natural to use building a blog as an example in the tutorial. So throughout this code we'll be building is the early version of the very blog you're reading. While I wrote the tutorial and the blog, I saved branches in the code to make it easier to follow along.

Requirements

I'm assuming that you already have a decent Apache, Mysql, and PHP setup running and configured for setting up virtual hosts. You'll want to have at least PHP 5.3.8 and MySQL 5.0.51, and some version of ImageMagick. On a Mac, Homebrew is a quick way to get most of this up and running. Vagrant is also a great way to provide a performant virtual environment to use as a development sandbox. You'll also need to have git installed to retrieve the tutorial code.

The tutorial assumes that you're running Mysql locally, and that the site is in a directory called /Sites/ez5tutorial. If you're placing it somewhere else, you'll need to make some adjustments, and I'll try to point those out.

My goal in the tutorial is to teach you how to develop with eZ, not how to install it, so the repository we're using is focused on just getting everything up and running quickly. eZ Systems provides a detailed installation guide, so look there when you start a real project.

Installing the site

To retrieve the code for the tutorial, create a '/Sites' directory, 'cd' to it in your terminal, and pull a copy of the repository from github:

git clone git://github.com/blendinteractive/ez5tutorial.git

Then, 'cd' to 'ez5tutorial' and check out the initial tutorial branch:

git checkout -b tutorial-1 origin/tutorial-1 

In the /Sites/ez5tutorial directory, you'll now have a few new subdirectories. The notable ones: 

  • apache - Apache configuration
  • ez - Here's where eZ Publish is set up
  • sql - A set of sql files for working with the tutorial
  • logs - the apache config points here to save its logs

Hosts File

Modify your computer's hosts file ('/etc/hosts' on most Linux/Mac machines) and add lines for 'ez5tutorial.local' and 'admin.ez5tutorial.local' to point at your localhost: 

127.0.0.1 ez5tutorial.local admin.ez5tutorial.local 

Apache Config

Include the Apache virtual host config stored in conf/local.conf in your Apache configuration. Edit the paths if your site isn't at /Sites/ez5tutorial. Make sure you restart Apache.

MySQL

Create a new database and user in your MySQL server:

CREATE DATABASE ez5tutorial CHARSET utf8;
GRANT ALL ON ez5tutorial.* TO ez5tutorial@localhost identified by 'ez5tutorialpassword';
FLUSH PRIVILEGES;

Load the provided sample data in the SQL folder. We'll start with 'initial.sql', which is a clean install containing demo content. 

cat sql/initial.sql | mysql -u ez5tutorial -p ez5tutorial 

Make sure to enter the password for your ez5tutorial user ('ez5tutorialpassword') at the prompt.

Symfony

First, make sure that the following folders and all their contents are writable by the web server:

  • ez/ezpublish/cache
  • ez/ezpublish/logs
  • ez/ezpublish/config
  • ez/ezpublish_legacy/design
  • ez/ezpublish_legacy/extension
  • ez/ezpublish_legacy/settings
  • ez/ezpublish_legacy/var

During development, most of these directories will need to be writable by both you and the web server. So while I don't recommend it for production installs, for this tutorial it's easiest to either set these directories as writable by everyone (777) or to run the apache user under the same user that you use in the terminal.

Next, we need to make sure all of the static assets needed by the system (javascript, css, images, etc) are available under the 'web' folder. Fortunately, eZ provides Symfony console commands to take care of this for us. Go to the root of your project in your console (the directory that contains 'ezpublish', 'ezpublish_legacy', 'web', etc) and run this command: 

php ezpublish/console 

What you'll see is a help text and a list of available commands. The ones we're looking for are the ones to install assets. Go ahead and run these two:

php ezpublish/console assets:install --symlink
php ezpublish/console ezpublish:legacy:assets_install --symlink

There are a lot more things that the console can do for us, but we'll come back to it later. For now, go ahead and access the site in your web browser.

If you played your cards right, you should have something like this running at http://ez5tutorial.local :

eZ5 Tutorial Site

 

Organization

Default eZ 5 File Structure

Go ahead and go in to the 'ez' folder. There are a lot of directories there. So what are we looking at? Let's talk about a couple of these pieces: 

  • composer.* - These are files for using the composer PHP dependency management system. It's very handy for maintaining projects with a lot of third-party libraries, but we'll ignore it for this tutorial so we're all using the same version of everything.
  • ezpublish - This is where the config, cache, and some logging components of eZ Publish live (this directory is called 'app' in most Symfony installs)
  • ezpublish_legacy - Take a peek in here - Look, it's our old friend eZ Publish 4! This is a full eZ 4 install, slightly updated.
  • src- This is where we'll be building out our code, in structures called bundles. There's a bundle already there, the EzSystemsDemoBundle, that implements a basic surround in symfony.
  • vendor - This is where most of the supporting third-party frameworks live, including components of Symfony and eZ Publish.
  • web - This is where you'll point Apache to as a web root. We'll set up Rewrite rules shortly to send most of our traffic to index.php, which is the endpoint that handles all requests to Symfony.

In your browser, you have two URLs available:

  • ez5tutorial.local, the public-facing web site
  • admin.ez5tutorial.local, the administrative site (user: admin, password: ez5tutorialpassword)

Our Plan

The goal of this project is to create a blog. This of course means that we have a series of posts displayed in reverse chronological order. The blog will be able to be divided into categories, so multiple topics can be covered. And there needs to be some way to combine multiple posts into a series of related posts. 

To accomplish all this, we'll be working with three content types: Blog, Blog Post, and Series. A Blog will contain either Blog Posts or Series, and will display all of the posts beneath it, even if those posts are themselves contained by Blog or Series objects. In this way, we can bring posts from more and more topic categories into a single stream by nesting the Blog objects. 

So our data model looks like this: 

  • Blog
    • Name
    • Tagline
    • Description (WYSIWYG)
    • URL Slug (for customizing the URL)
  • Blog Post
    • Title
    • Summary (WYSIWYG)
    • Body
    • Publication Date (Date/Time Field)
    • Comments (Comments Field)
  • Series
    • Title
    • Description (WYSIWYG)
    • Posts (Object Relation List)
    • URL Slug (for customizing the URL)

You can build this structure out manually, and enter some sample content, but to get things started I've included a SQL file with the content already configured. Note that this will break the front end view of your eZ Publish installation. That's OK! We're going to bring things in line with our new content in a moment.

You can load your installation with it by running the following command in a terminal: 

cat sql/ez5tutorial.sql | mysql -u ez5tutorial -p ez5tutorial 

(Enter the password for your 'ez5tutorial' user when prompted)

You'll need to log back in to your admin. Have a look at the 'Content Structure' to see what's there. The sample posts are the contents of this tutorial.

Now that we've got our site set up and sample content loaded, we can start developing by creating a bundle and building our layout templates.

Working with eZ Publish 5 - Symfony

Before we dig in and start building a new site, let's look over some of the core concepts in Symfony. If you've already got a good handle on Symfony, and aren't interested in some of the conceptual changes from eZ Publish 4, you can skip ahead to the next post and start getting your hands dirty.

Model-View-Controller

If you've worked with Rails, ASP.Net MVC (or to a lesser degree, CakePHP) then you're probably familiar with the Model-View-Controller pattern. It provides a standard framework for building PHP applications in a common, organized way. When a request is received, it's directed to Symfony, which routes the request to an action on a controller, which is really just a function defined by the app developer. This function gathers up whatever variables are needed to handle the request from a set of model objects. The model objects represent whatever systems the app stores data in (database calls, web APIs, punch cards, whatever). The controller hands back the data it's gathered, which is handled by the view, which is a template language that's used to format the response. 

This all sounds pretty complicated, but in practice it's fairly straightforward. If you want your app to render some database output to a page, for example, you simply write a function on a controller class that returns an array containing the data you want to use, then write a template (in Symfony, the Twig templating language is used) that contains the HTML for your page. Using eZ Publish, it's even easier since for most pages, you'll be using a controller provided by eZ Publish. 

The main principles that drive MVC architecture are separation of concerns and don't repeat yourself (DRY). Since the job of fetching the data for the response (controller) is separate from the format of the response (view), it's very easy to change the appearance of the response, or even use JSON, XML, or something else instead of javascript. 

Symfony organizes code by packaging most of the app components into Bundles, which are directories that organize code around specific aspects of the application. This makes it easier for you to re-use code that other people have written by installing their bundles in to your project.

Big Changes from eZ Publish 4

If you're coming to eZ Publish 5 from version 4, there are some major changes that may trip you up unless you're aware of them.

Wherever we're talking about 'eZ Publish 5' here, keep in mind that we're only talking about the new, Symfony parts of eZ Publish 5. eZ Publish 4 is still there and uses all of the old concepts just as they've always been.

Twig

The half-Smarty half-PHP template language PHP has used for years is being deprecated in favor of Twig, the template language closely associated with the Symfony framework. Twig templates will look pretty familiar, but function quite a bit differently under the hood. Twig provides some neat new features too, like template inheritance. Both the language itself and the means to extend it are well documented. 

No more fetch() function

In eZ Publish 4, if you wanted to pull some extra content from the repository in to your template, you simply called fetch, like so: 

 {def $posts = fetch(content, list, hash(parent_node_id, 2, class_filter_type, 'include', class_filter_array, array('blog_post')))}

This is handy, but the problem that arises is that you've no longer separated your concerns. Your view template is now talking directly to the model and handling application logic. Over the years, eZ Publish 4 templates became more and more loaded with logic code, which lead to big, hairy, hard to read templates.

In the eZ Publish 5 approach, extra data is handled as a nested request to the CMS (a sub-request). This means that you'll write the query to retrieve the data as PHP in a controller action, and provide a template to render the results. This sounds like a bit more work, but it leads to better template re-use, and it also gives you a chance to keep any of the complex logic (like turning a list of events into a calendar) out of the template.

Single-pass rendering

Most eZ Publish 4 developers know that a page in eZ 4 is rendered in two steps: the system first renders the 'module result' (the 'full view' template of a content object) and then passes the result of that as HTML to a 'pagelayout', which renders the page surround. If you want the pagelayout template to have access to variables used by the interior template, you need to either pass those variables in a specific structure, or fetch them again. 

In Twig, only one render step occurs for each request (ignoring sub-requests for the moment). The template language provides an inheritance model similar to classes and methods in PHP that still allows you to re-use your template code.

Simplified cache

In eZ Publish 4, there were a lot of pieces involved in caching a page - template cache, block cache, view cache, etc. eZ Publish 5 uses Symfony's native caching layer, and for the most part only caches entire requests. Symfony also adheres very closely to the HTTP specification to determine how this cache is managed, so if you're not up to speed on HTTP Headers like Cache-Control and ETag, you'll want to brush up before you begin any serious development - but we can skip that stuff for now.

Let's move on and set up an eZ Publish 5 installation.