eZ Platform follows the same conventions than Symfony Standard 2.8. This includes the same project structure, as described in the Symfony documentation "Creating the project" chapter.
There are a couple parts that are important to go through this tutorial.
app
contains most of eZ Platform / symfony application: configuration, cache, kernel, templates, translations...
** SCREENSOT OF THE ROOT STRUCTURE with app open**
This is where the application is configured. It contains several files, each dedicated to specific parts. Most of them are similar to the ones that come up with symfony standard: config.yml
, security.yml
, routing.yml
...
Read more: Configuration on symfony.com
parameters.yml
contains infrastructure related settings: database credentials, solr uri... it is not versioned, since it is generated based on parameters.yml.dist
.
ezplatform.yml
contains the base configuration to get your ezplatform instance to run. It defines the default siteaccess requests are executed in. You can read more about Siteaccess in the documentation, but you don't need to right now.
Files that are suffixed with
_dev
,_prod
and_behat
are per environment files. They are loaded depending on the current environment, and include the file with the same name without the prefix: indev
environment,app/config/ezplatform_dev.yml
will be loaded first, and will itself loadezplatform.yml
. This allows for configuration of the application depending on the environment being executed.
The easiest way to add templates to your applications is to store them in the app/Resources/views
folder. The hierarchy of that folder is completely up to you.
eZ Platform uses the Twig template language. If you're not familiar with it, we will explain the concepts used throughout this tutorial. You can also read the templating chapter of the Symfony doc to go more in depth.
This is the application's kernel. By default, it mostly loads the Bundles that make up the application. This is where you will enable newly installed or created bundles. AppBundle is already enabled.
When adding new bundles, we recommend that you add yours at the end of the bundles array. It will avoid conflicts when upgrading.
Symfony doc: the Kernel Class
This is where custom application code will be stored. The first use-case in this tutorial will be custom controller.
If your application grows more complex, we recommend that you create other bundles for specific areas of the domain.
This is where you can place your application's assets (css, javascript, images, ...).
You may also place assets in
src/AppBundle/Resources/public
. In that case, remember to addAppBundle
to the assetic bundles list, and runapp/console assets:install
. As long as you don't need to organize things differently, we recommend that the application's assets are placed directly inweb
, as they can be referenced by their (shorter) relative path.
We will begin by customizing global layout of our site, in order to end up with this rendering:
SCREENSHOT OF THE HOME
First, go to the root of your ezplatform site. You should view the root Folder of the clean install, without any kind of layout. You can go to /ez
, edit this Content, and see that this page changes. When /
is requested, eZ Platform renders the root Content using the ez_content:viewContent
controller. We will customize this step, by instructing Platform to use a custom template to render this particular item.
eZ Platform organizes content as a tree. Each content item is referenced by a location, and each location as a parent. The root content location has the ID
2
by default.
To use a custom template when rendering the root content, let's create a content_view
configuration block for ezpublish
.
We will use the default
namespace, but we could have used any siteaccess instead. Edit app/config/ezplatform.yml
. At the end, add the following block, right after the language configuration (pay attention to spacing: default
should be at the same level than site_group
):
default:
content_view:
full:
root_folder:
template: "content/view/full/root_folder.html.twig"
match:
Id\Location: 2
This tells Platform to use the template
when rendering any content referenced by the location with the id 2
. There is a whole set of view matchers that can be used to customize rendering depending on any kind of circumstance.
Download index.html
, and save inside app/Resources/views
as as content/view/full/root_folder.html.twig
.
Refresh the site's root, and you should see the site's structure, but without any styles or images. Let's fix this.
Edit the root_folder.html.twig
template.
The first thing to do is fix the loading of the stylesheets, scripts and design images.
Download assets.zip, and unpack its contents to the web
directory of your project. You will end up with web/assets/
, containing css
, js
and images
subfolders.
In the template, in the<html>
section, change the <style>
tags about bootstrap and custom css lines with the following code:
{% stylesheets 'assets/css/*' filter='cssrewrite' %}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
As explained on the Symfony assetic doc, this will iterate over the files in web/assets/css
, and load them as stylesheets. Refresh the page, and you should now see the static design of the site. At the bottom of the template, you will find <script>
tags that load jQuery and Bootstrap javascript. Replace them with an equivalent block for scripts:
{% javascripts 'assets/js/*' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
Let's finish this by fixing the design images. Locate the the <img>
tag with "images/128_bike_white_avenir.png"
. Change the src
to {{ asset('assets/images/128_bike_white_avenir.png') }}
:
<img alt="Brand" src="{{ asset('assets/images/128_bike_white_avenir.png') }}">
Do the same for "images/logo_just_letters.png"
, and refresh the page. The design should now be in order, with the logo, fonts and colors.
At this point, the root_folder.html.twig
template is static. It doesn't render any dynamic data from the repository.
The root is rendered by the ez_content:viewAction
controller action. This action assigns the currently viewed content as the content
twig variable. We will use that variable to display some of the fields from the root content. Replace the central section of the template, around line 90, with the following block:
<section class="buttons">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-10 col-xs-offset-1 box-style">
<h3 class="center bottom-plus new-header">{{ ez_content_name(content) }}</h3>
<div class="col-xs-10 text-justified">{{ ez_render_field(content, 'description') }}</div>
</div>
</div>
</div>
</section>
The page will now show the values of title and description fields of the root Platform Content.
The general layout of the site, with the navigation, footer, scripts... is written down in the template we use to render the root. Let's extract the part that is common to all the pages so that we can re-use it.
Twig supports a powerful template inheritance api. Templates may declare named blocks. Any template mayextend another templates, and modify the blocks defined by its parents.
Create a new app/Resources/views/pagelayout.html.twig
template, and copy the contents of the current root_folder.html.twig
into it. Change the central section from the previous chapter as follows:
<section class="buttons">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-10 col-xs-offset-1 box-style">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</section>
This defines a block named content. Other templates can add content to it, so that the result of the execution of the controller is contained within the site's general layout.
Edit root_folder.html.twig
, and remove everything. Replace it with the following code:
{% extends "pagelayout.html.twig" %}
{% block content %}
<h3 class="center bottom-plus new-header">{{ ez_content_name(content) }}</h3>
<div class="col-xs-10 text-justified">{{ ez_render_field(content, 'description') }}</div>
{% endblock %}
This will re-use pagelayout.html.twig
, and replace the content
block with the title and description from the root folder. We could easily create more blocks in the pagelayout so that templates can modify other parts of the page (footer, head, navigation), and we will over the course of this tutorial. We can now create more templates that inherit from pagelayout.html.twig
, and customize how content is rendered.
Let's do it for the Ride content type.
The first thing we need to do is access one of the ride content items. Since we don't have any navigation yet, we need to cheat a bit. Go to the backoffice (/ez
), and navigate to a Ride content in the tree. Go to the details tab, and find the content id.
Edit full/root_folder.html.twig
, and add the following line at the beginning of the content
block, replacing 1234 with your content id:
<a href="{{ path( ez_urlalias( {"contentId", 1234} ) }}">Ride content item</a>
What we did here is generate a path()
to the url alias of the content item with the id 1234. An url alias is a user friendly virtual URL mapped to a location. They are automatically generated by the Repository. When an URL alias is used to access the site, it is dynamically routed to ez_content:viewAction()
to render the mapped content.
Refresh the homepage, and click on the "Ride content item" link. You should now see the default template, without the pagelayout. Let's first create the template, and enable it for content items of type ride.
Edit app/config/ezplatform.yml
, and add a new block at the same level than root_folder
:
full_ride:
template: "content/view/full/ride.html.twig"
match:
Identifier\ContentType: "ride"
The root_folder
rule matched on the content's location id. This one matches on the "ride" content type identifier. Create the template, and override the content
block using the HTML from the static pages:
{% extends "pagelayout.html.twig" %}
{% block content %}
<!-- Content -->
<section class="below-navbar">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-10 col-xs-offset-1 row-padding">
{#% include "ride_search.html.twig" %#}
<div class="col-xs-12">
<div class="col-xs-1 text-left">
<h2>{{ "Ride:"|trans }}</h2>
</div>
<div class="col-xs-11 text-left">
<h2 class="extra-padding-left">{{ ez_content_name(content) }}</h2>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="first-below-navbar">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-10 col-xs-offset-1">
<h4 class="underscore-green"> </h4>
</div>
<div class="col-xs-10 col-xs-offset-1 padding-box">
<div class="col-xs-2">
<div class="box-ride">
<h3 class="special-number">{{ ez_field_value(content, "length").value }} Km</h3>
<p class="pre-small special-number special-number-margin"><strong>{{ "Level"|trans }}</strong></p>
<p class="text-center">
{% set difficulty = ez_field_value(content, "difficulty").selection|first %}
{% for i in 0..difficulty %}<i class="fa fa-star star-color"></i>{% endfor %}
</p>
</div>
</div>
<div class="col-xs-10 bottom-extra">
<div class="col-xs-2 text-right">
<p>{{ "Name:"|trans }}</p>
</div>
<div class="col-xs-4">
<p class="strong">
{% for rider in ez_field_value(content, "riders").authors %}
{{ rider.name }}
{% if not loop.last %}<br>{% endif %}
{% endfor %}
</p>
</div>
<div class="col-xs-1 text-right">
<p>{{ "Start:"|trans }}</p>
<p>{{ "End:"|trans }}</p>
</div>
<div class="col-xs-5">
<p class="strong">{{ ez_field_value(content, "start_point") }}</p>
<p class="strong">{{ ez_field_value(content, "end_point") }}</p>
</div>
</div>
<div class="col-xs-10">
<div class="col-xs-2 text-right">
<p>{{ "Description:"|trans }}</p>
</div>
<div class="col-xs-10 text-justify">
{{ ez_render_field(content, "description") }}
</div>
</div>
</div>
</div>
</div>
</section>
<!-- BEGIN points of interest -->
<section class="photos">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-10 col-xs-offset-1" id="wrapper">
<div class="col-xs-12">
<h4 class="underscore-green">Points of interest:</h4>
{{ ez_render_field(content, 'points_of_interest', { "template": _self }) }}
</div>
</div>
</div>
</div>
</section>
<!-- END points of interest -->
<section class="buttons">
<div class="container">
<div class="row regular-content-size">
<div class="col-xs-5 button-padding">
<a href="#" class="button pull-right"><i class="fa fa-print fa-lg"></i> {{ "PRINT RIDE"|trans }}</a>
</div>
<div class="col-xs-5 col-xs-offset-2 button-padding">
<a href="#" class="button pull-left"><i class="fa fa-download fa-lg"></i> {{ "DOWNLOAD"|trans }}</a>
</div>
</div>
</div>
</section>
{% endblock %}
{% block ezobjectrelationlist_field %}
{% if not ez_is_field_empty( content, 'points_of_interest' ) %}
{% for contentId in ez_field_value(content, 'points_of_interest').destinationContentIds %}
{% if loop.index > 0 and loop.index is divisible by(3) %}
<div class="col-xs-12 padding-box"></div>
{% endif %}
{{ render( controller( "ez_content:viewAction", {'contentId': contentId, 'viewType': 'embed'} ) ) }}
{% endfor %}
{% endif %}
{% endblock %}
- Ride list template ?
So far, that version covers:
The dev part would I guess happen after a presentation of the backoffice, and after creating at least the ride content type and a content of that type. I'm not a big fan of the content type creation part. The process is imho a bit tedious.
I'm considering writing a small console script that creates the content type, and walk the user through the result. An alternative would be to begin small, with only a couple field definitions. Then add new ones, and update the template after each addition. This could work. For instance, the map could be added as a second or third step, and the templating code introduced at the same time.