+ All Categories
Home > Documents > Symfony Cmf Master

Symfony Cmf Master

Date post: 21-Jan-2016
Category:
Upload: jorgetemd
View: 873 times
Download: 5 times
Share this document with a friend
Popular Tags:
169
The Symfony CMF Book for Symfony master generated on July 18, 2013
Transcript
Page 1: Symfony Cmf Master

The Symfony CMF Bookfor Symfony master

generated on July 18, 2013

Page 2: Symfony Cmf Master

The Symfony CMF Book (master)

This work is licensed under the “Attribution-Share Alike 3.0 Unported” license (http://creativecommons.org/licenses/by-sa/3.0/).

You are free to share (to copy, distribute and transmit the work), and to remix (to adapt the work) under thefollowing conditions:

• Attribution: You must attribute the work in the manner specified by the author or licensor (butnot in any way that suggests that they endorse you or your use of the work).

• Share Alike: If you alter, transform, or build upon this work, you may distribute the resulting workonly under the same, similar or a compatible license. For any reuse or distribution, you must makeclear to others the license terms of this work.

The information in this book is distributed on an “as is” basis, without warranty. Although every precautionhas been taken in the preparation of this work, neither the author(s) nor SensioLabs shall have any liability toany person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly bythe information contained in this work.

If you find typos or errors, feel free to report them by creating a ticket on the Symfony ticketing system(http://github.com/symfony/symfony-docs/issues). Based on tickets and users feedback, this book iscontinuously updated.

Page 3: Symfony Cmf Master

Contents at a Glance

Installing the Symfony CMF Standard Edition .....................................................................................5Routing ..............................................................................................................................................9Content ............................................................................................................................................15Menu................................................................................................................................................17SimpleCMS.......................................................................................................................................20Choosing a Storage Layer ..................................................................................................................25Installing and Configuring the CMF Core ..........................................................................................29Installing and Configuring Doctrine PHPCR-ODM............................................................................32Installing and Configuring Inline Editing ...........................................................................................39Creating a CMS using CMF and Sonata .............................................................................................41Using the BlockBundle and ContentBundle with PHPCR ...................................................................45Handling Multi-Language Documents ...............................................................................................56The BlockBundle ..............................................................................................................................60Block Types ......................................................................................................................................66Create your own Blocks ....................................................................................................................71Cache ...............................................................................................................................................73Relation to Sonata Block Bundle........................................................................................................78BlogBundle .......................................................................................................................................79ContentBundle .................................................................................................................................83CoreBundle ......................................................................................................................................84CreateBundle ....................................................................................................................................92DoctrinePHPCRBundle.....................................................................................................................99MenuBundle ................................................................................................................................... 112RoutingBundle................................................................................................................................ 118RoutingAutoBundle ........................................................................................................................ 125SearchBundle.................................................................................................................................. 133SimpleCmsBundle........................................................................................................................... 134SonataDoctrinePhpcrAdminBundle ................................................................................................. 138TreeBrowserBundle......................................................................................................................... 140Using a Custom Document Class Mapper with PHPCR-ODM ......................................................... 144Using a Custom Route Repository with Dynamic Router.................................................................. 145Installing the CMF sandbox ............................................................................................................ 147Routing .......................................................................................................................................... 152Testing ........................................................................................................................................... 157Contributing................................................................................................................................... 163The Symfony CMF Release Process.................................................................................................. 164

PDF brought to you bygenerated on July 18, 2013

Contents at a Glance | iii

Page 4: Symfony Cmf Master

Licensing ........................................................................................................................................ 166

iv | Contents at a Glance Contents at a Glance | 4

Page 5: Symfony Cmf Master

Chapter 1

Installing the Symfony CMF Standard Edition

The Symfony CMF Standard Edition (SE) is a distribution based on all the main components needed formost common use cases.

The goal of this tutorial is to install the CMF components, with the minimum necessary configurationand some very simple examples, into a working Symfony2 application.

We will then walk you through the components you have installed. This can be used to familiarizeyourself with the CMF or as a starting point for a new custom application.

If this is your first encounter with the Symfony CMF it would be a good idea to first take a look at:

• The Big Picture1

• The online sandbox demo at cmf.liip.ch2

For other Symfony CMF installation guides, please read:

• The cookbook entry on Installing the CMF sandbox for instructions on how to install amore complete demo instance of Symfony CMF.

• Installing and Configuring the CMF Core for step-by-step installation and configurationdetails of just the core components into an existing Symfony application.

PreconditionsAs Symfony CMF is based on Symfony2, you should make sure you meet the Requirements for runningSymfony23. Additionally, you need to have SQLite4 PDO extension (pdo_sqlite) installed, since it is usedas the default storage medium.

1. http://slides.liip.ch/static/2012-01-17_symfony_cmf_big_picture.html#1

2. http://cmf.liip.ch

3. http://symfony.com/doc/current/reference/requirements.html

4. http://www.sqlite.org/

PDF brought to you bygenerated on July 18, 2013

Chapter 1: Installing the Symfony CMF Standard Edition | 5

Page 6: Symfony Cmf Master

Listing 1-1

Listing 1-2

Listing 1-3

Listing 1-4

Listing 1-5

By default, Symfony CMF uses Jackalope + Doctrine DBAL and SQLite as the underlying DB.However, Symfony CMF is storage agnostic, which means you can use one of several available datastorage mechanisms without having to rewrite your code. For more information on the differentavailable mechanisms and how to install and configure them, refer to Installing and ConfiguringDoctrine PHPCR-ODM

Git5 and Curl6 are also needed to follow the installation steps listed below.

Installation

The easiest way to install Symfony CMF is is using Composer7. Get it using

12

$ curl -sS https://getcomposer.org/installer | php$ sudo mv composer.phar /usr/local/bin/composer

and then get the Symfony CMF code with it (this may take a while)

12

$ php composer.phar create-project symfony-cmf/standard-edition <path-to-install>--stability=dev$ cd <path-to-install>

The path <path-to-install> should either inside your web server doc root or configure a virtualhost for <path-to-install>.

This will clone the standard edition and install all the dependencies and run some initial commands.These commands require write permissions to the app/cache and app/logs directory. In case the finalcommands end up giving permissions errors, please follow the guidelines in the symfony book8 forconfiguring the permissions and then run the composer.phar install command mentioned below.

If you prefer you can also just clone the project:

12

$ git clone git://github.com/symfony-cmf/symfony-cmf-standard.git <dir-name>$ cd <dir-name>

If there were problems during the create-project command, if you used git clone or if you updatedthe checkout later, always run the following command to update the dependencies:

1 $ php composer.phar install

The next step is to set up the database. If you want to use SQLite as your database backend just go aheadand run the following:

12

$ php app/console doctrine:database:create$ php app/console doctrine:phpcr:init:dbal

5. http://git-scm.com/

6. http://curl.haxx.se/

7. http://getcomposer.org/

8. http://symfony.com/doc/master/book/installation.html#configuration-and-setup

PDF brought to you bygenerated on July 18, 2013

Chapter 1: Installing the Symfony CMF Standard Edition | 6

Page 7: Symfony Cmf Master

Listing 1-6

Listing 1-7

34

$ php app/console doctrine:phpcr:repository:init$ php app/console doctrine:phpcr:fixtures:load

This will create a file called app.sqlite inside your app folder, containing the database content.

The project should now be accessible on your web server. If you have PHP 5.4 installed you canalternatively use the PHP internal web server:

1 $ php app/console server:run

And then access the CMF via:

1 http://localhost:8000

If you prefer to use another database backend, for example MySQL, run the configurator (point yourbrowser to /web/config.php) or set your database connection parameters in app/config/parameters.yml. Make sure you leave the database_path property at null in order to use another driverthan SQLite. Leaving the field blank in the web-configurator should set it to null.

OverviewThis section will help you understand the basic parts of Symfony CMF Standard Edition (SE) and howthey work together to provide the default pages you can see when browsing the Symfony CMF SEinstallation.

It assumes you have already installed Symfony CMF SE and have carefully read the Symfony2 book9.

AcmeMainBundle and SimpleCMSBundle

Symfony CMF SE comes with a default AcmeMainBundle to help you get started, similar to theAcmeDemoBundle provided by Symfony2. This gives you some demo pages viewable in your browser.

Where are the Controllers?

AcmeMainBundle doesn't include controllers or configuration files as you might expect. It containslittle more than a Twig file and Fixtures10 data that was loaded into your database duringinstallation.

The controller logic is actually provided by the relevant CMF bundles, as described below.

There are several bundles working together in order to turn the fixture data into a browsable website.The overall, simplified process is:

• When a request is received, the Symfony CMF Routing's Dynamic Router is used to handle theincoming request;

• The Dynamic Router is able to match the requested URL with a specific ContentBundle'sContent stored in the database;

• The retrieved content's information is used to determine which controller to pass it on to, andwhich template to use;

• As configured, the retrieved content is passed to ContentBundle's ContentController, whichwill handle it and render AcmeMainBundle's layout.html.twig.

9. http://symfony.com/doc/current/book/

10. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 1: Installing the Symfony CMF Standard Edition | 7

Page 8: Symfony Cmf Master

Listing 1-8

Listing 1-9

Listing 1-10

Listing 1-11

Again, this is simplified view of a very simple CMS built on top of Symfony CMF. To fully understand allthe possibilities of the CMF, a careful look into each component is needed.

If you want to review the contents of the PHPCR database you can use the following commands:

123

$ php app/console doctrine:phpcr:dump$ php app/console doctrine:phpcr:dump --props$ php app/console doctrine:phpcr:dump /path/to/node

The above examples respectively show a summary, a detailed view, and a summary of a node and all itschildren (instead of starting at the root node).

Don't forget to look at the --help output for more possibilities:

1 $ php app/console doctrine:phpcr:dump

Adding new pages

Symfony CMF SE does not provide any admin tools to create new pages. If you are interested in adding anadmin UI have a look at Creating a CMS using CMF and Sonata. However if all you want is a simple wayto add new pages that you can then edit via the inline editing, then you can use the SimpleCmsBundlepage migrator. The Symfony CMF SE ships with an example YAML file stored in app/Resources/data/pages/test.yml. The contents of this file can be loaded into the PHPCR database by calling:

1 $ php app/console doctrine:phpcr:migrator page --identifier=/cms/simple/test

Note that the above identifier is mapped to app/Resources/data/pages/test.yml by stripping off thebasepath configuration of the SimpleCmsBundle, which defaults to /cms/simple. Therefore if you wantto define a child page foo for /cms/simple/test you would need to create a file app/Resources/data/pages/test/foo.yml and then run the following command:

1 $ php app/console doctrine:phpcr:migrator page --identifier=/cms/simple/test/foo

PDF brought to you bygenerated on July 18, 2013

Chapter 1: Installing the Symfony CMF Standard Edition | 8

Page 9: Symfony Cmf Master

Chapter 2

Routing

This is an introduction to understand the concepts behind CMF routing. For the referencedocumentation please see Routing and RoutingBundle.

Concept

Why a new Routing Mechanism?

CMS are highly dynamic sites, where most of the content is managed by the administrators rather thandevelopers. The number of available pages can easily reach the thousands, which is usually multipliedby the number of available translations. Best accessibility and SEO practices, as well as user preferencesdictate that the URLs should be definable by the content managers.

The default Symfony2 routing mechanism, with its configuration file approach, is not the best solutionfor this problem. It does not provide a way of handling dynamic, user-defined routes, nor does it scalewell to a large number of routes.

The Solution

In order to address these issues, a new routing system was developed that takes into account the typicalneeds of CMS routing:

• User-defined URLs;• Multi-site;• Multi-language;• Tree-like structure for easier management;• Content, Menu and Route separation for added flexibility.

With these requirements in mind, the Symfony CMF Routing component was developed.

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 9

Page 10: Symfony Cmf Master

Listing 2-1

Listing 2-2

Listing 2-3

The ChainRouterAt the core of Symfony CMF's Routing component sits the ChainRouter. It is used as a replacement forSymfony2's default routing system and, like the Symfony2 router, is responsible for determining whichController will handle each request.

The ChainRouter works by accepting a set of prioritized routing strategies, RouterInterface1

implementations, commonly referred to as "Routers". The routers are responsible for matching anincoming request to an actual Controller and, to do so, the ChainRouter iterates over the configuredRouters according to their configured priority:

123456789

10

# app/config/config.ymlcmf_routing:

chain:routers_by_id:

# enable the DynamicRouter with high priority to allow overwriting# configured routes with contentcmf_routing.dynamic_router: 200

# enable the symfony default router with a lower priorityrouter.default: 100

You can also load Routers using tagged services, by using the router tag and an optional priority. Thehigher the priority, the earlier your router will be asked to match the route. If you do not specify thepriority, your router will come last. If there are several routers with the same priority, the order betweenthem is undetermined. The tagged service will look like this:

12345

services:my_namespace.my_router:

class: "%my_namespace.my_router_class%"tags:

- { name: router, priority: 300 }

The Symfony CMF Routing system adds a new DynamicRouter, which complements the default Routerfound in Symfony2.

The Default Symfony2 RouterAlthough it replaces the default routing mechanism, Symfony CMF Routing allows you to keep using theexisting system. In fact, the default routing is enabled by default, so you can keep using the routes youdeclared in your configuration files, or as declared by other bundles.

The DynamicRouterThis Router can dynamically load Route instances from a given provider. It then uses a matching processto the incoming request to a specific Route, which in turn is used to determine which Controller toforward the request to.

The bundle's default configuration states that DynamicRouter is disabled by default. To activate it, justadd the following to your configuration file:

1. http://api.symfony.com/master/Symfony/Component/Routing/RouterInterface.html

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 10

Page 11: Symfony Cmf Master

1234

# app/config/config.ymlcmf_routing:

dynamic:enabled: true

This is the minimum configuration required to load the DynamicRouter as a service, thus makingit capable of performing any routing. Actually, when you browse the default pages that come withthe Symfony CMF SE, it is the DynamicRouter that matches your requests with the Controllers andTemplates.

Getting the Route Object

The provider to use can be configured to best suit each implementation's needs, and must implementthe RouteProviderInterface. As part of this bundle, an implementation for PHPCR-ODM2 is provided.Also, you can easily create your own, as the Router itself is storage agnostic. The default provider loadsthe route at the path in the request and all parent paths to allow for some of the path segments beingparameters.

For more detailed information on this implementation and how you can customize or extend it, refer toRoutingBundle.

The DynamicRouter is able to match the incoming request to a Route object from the underlyingprovider. The details on how this matching process is carried out can be found in the Routing.

To have the route provider find routes, you also need to provide the data in your storage. WithPHPCR-ODM, this is either done through the admin interface (see at the bottom) or with fixtures.

However, before we can explain how to do that, you need to understand how the DynamicRouterworks. An example will come later in this document.

Getting the Controller and Template

A Route needs to specify which Controller should handle a specific Request. The DynamicRouter usesone of several possible methods to determine it (in order of precedence):

• Explicit: The stored Route document itself can explicitly declare the target Controller byspecifying the '_controller' value in getRouteDefaults().

• By alias: the Route returns a 'type' value in getRouteDefaults(), which is then matchedagainst the provided configuration from config.yml

• By class: requires the Route instance to implement RouteObjectInterface and return anobject for getRouteContent(). The returned class type is then matched against the providedconfiguration from config.yml.

• Default: if configured, a default Controller will be used.

Apart from this, the DynamicRouter is also capable of dynamically specifying which Template will beused, in a similar way to the one used to determine the Controller (in order of precedence):

• Explicit: The stored Route document itself can explicitly declare the target Template ingetRouteDefaults().

• By class: requires the Route instance to implement RouteObjectInterface and return anobject for getRouteContent(). The returned class type is then matched against the providedconfiguration from config.yml.

Here's an example of how to configure the above mentioned options:

2. https://github.com/doctrine/phpcr-odm

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 11

Page 12: Symfony Cmf Master

Listing 2-4

Listing 2-5

123456789

10

# app/config/config.ymlcmf_routing:

dynamic:generic_controller: cmf_content.controller:indexActioncontrollers_by_type:

editablestatic: sandbox_main.controller:indexActioncontrollers_by_class:

Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent:cmf_content.controller::indexAction

templates_by_class:Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent:

CmfContentBundle:StaticContent:index.html.twig

Notice that enabled: true is no longer present. It's only required if no other configuration parameter isprovided. The router is automatically enabled as soon as you add any other configuration to the dynamicentry.

Internally, the routing component maps these configuration options to severalRouteEnhancerInterface instances. The actual scope of these enhancers is much wider, and youcan find more information about them in the Routing documentation page.

Linking a Route with a Model InstanceDepending on you application's logic, a requested URL may have an associated model instance fromthe database. Those Routes can implement the RouteObjectInterface, and optionally return a modelinstance, that will be automatically passed to the Controller as the $contentDocument variable, ifdeclared as parameter.

Note that a Route can implement the above mentioned interface but still not return any model instance,in which case no associated object will be provided.

Furthermore, Routes that implement this interface can also have a custom Route name, instead of thedefault Symfony core compatible name, and can contain any characters. This allows you, for example, toset a path as the route name.

RedirectsYou can build redirects by implementing the RedirectRouteInterface. If you are using the defaultPHPCR-ODM route provider, a ready to use implementation is provided in the RedirectRoute Document.It can redirect either to an absolute URI, to a named Route that can be generated by any Router in thechain or to another Route object known to the route provider. The actual redirection is handled by aspecific Controller that can be configured as follows:

1234

# app/config/config.ymlcmf_routing:

controllers_by_class:Symfony\Cmf\Component\Routing\RedirectRouteInterface:

cmf_routing.redirect_controller:redirectAction

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 12

Page 13: Symfony Cmf Master

Listing 2-6

The actual configuration for this association exists as a service, not as part of a config.yml file. Asdiscussed before, any of the approaches can be used.

URL GenerationSymfony CMF's Routing component uses the default Symfony2 components to handle route generation,so you can use the default methods for generating your URLs with a few added possibilities:

• Pass an implementation of either RouteObjectInterface or RouteAwareInterface as thename parameter

• Alternatively, supply an implementation of ContentRepositoryInterface and the id of themodel instance as parameter content_id

The route generation handles locales as well, see ContentAwareGenerator and locales.

The PHPCR-ODM Route DocumentAs mentioned above, you can use any route provider. The example in this section applies if youuse the default PHPCR-ODM route provider(Symfony\Cmf\Bundle\RoutingBundle\Document\RouteProvider).

All routes are located under a configured root path, for example /cms/routes. A new route can becreated in PHP code as follows:

123456789

1011121314

use Symfony\Cmf\Bundle\RoutingBundle\Document\Route;

$route = new Route;$route->setParent($dm->find(null, '/routes'));$route->setName('projects');

// link a content to the route$content = new Content('my content');$route->setRouteContent($content);

// now define an id parameter; do not forget the leading slash if you want /projects/{id}and not /projects{id}$route->setVariablePattern('/{id}');$route->setRequirement('id', '\d+');$route->setDefault('id', 1);

This will give you a document that matches the URL /projects/<number> but also /projects as thereis a default for the id parameter.

Because you defined the {id} route parameter, your controller can expect an $id parameter.Additionally, because you called setRouteContent on the route, your controller can expect the$contentDocument parameter. The content could be used to define an intro section that is the same foreach project or other shared data. If you don't need content, you can just not set it in the document.

For more details, see the route document section in the RoutingBundle documentation.

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 13

Page 14: Symfony Cmf Master

Listing 2-7

Listing 2-8

Integrating with SonataAdminIf sonata-project/doctrine-phpcr-admin-bundle is added to the composer.json require section, theroute documents are exposed in the SonataDoctrinePhpcrAdminBundle. For instructions on how toconfigure this Bundle see SonataDoctrinePhpcrAdminBundle.

By default, use_sonata_admin is automatically set based on whether theSonataDoctrinePhpcrAdminBundle is available but you can explicitly disable it to not have it even ifSonata is enabled, or explicitly enable to get an error if Sonata becomes unavailable.

There are a couple of configuration options for the admin. The content_basepath points to the root ofyour content documents.

1234567

# app/config/config.ymlcmf_routing:

# use true/false to force using / not using sonata adminuse_sonata_admin: auto

# used with Sonata Admin to manage content; defaults to cmf_core.content_basepathcontent_basepath: ~

Terms Form TypeThe Routing bundle defines a form type that can be used for classical "accept terms" checkboxes whereyou place URLs in the label. Simply specify cmf_routing_terms_form_type as the form type name andspecify a label and an array with content_ids in the options:

1234567

$form->add('terms', 'cmf_routing_terms_form_type', array('label' => 'I have seen the <a href="%team%">Team</a> and <a href="%more%">More</a>

pages ...','content_ids' => array(

'%team%' => '/cms/content/static/team','%more%' => '/cms/content/static/more'

),));

The form type automatically generates the routes for the specified content and passes the routes to thetrans twig helper for replacement in the label.

Further NotesFor more information on the Routing component of Symfony CMF, please refer to:

• Routing for most of the actual functionality implementation• RoutingBundle for Symfony2 integration bundle for Routing Bundle• Symfony2's Routing3 component page• Handling Multi-Language Documents for some notes on multilingual routing

3. http://symfony.com/doc/current/components/routing/introduction.html

PDF brought to you bygenerated on July 18, 2013

Chapter 2: Routing | 14

Page 15: Symfony Cmf Master

Chapter 3

Content

ConceptAt the heart of every CMS stands the content, an abstraction that the publishers can manipulate and thatwill later be presented to the page's users. The content's structure greatly depends on the project's needs,and it will have a significant impact on future development and use of the platform.

Symfony CMF SE comes with the ContentBundle: a basic implementation of a content structure,including support for multiple languages and database storage of Routes.

Static ContentThe StaticContent class declares the basic content's structure. Its structure is very similar to the onesused on Symfony2's ORM systems. Most of its fields are self explanatory and are what you would expectfrom a basic CMS: title, body, publishing information and a parent reference, to accommodate a tree-likehierarchy. It also includes a Block reference (more on that later).

The two implemented interfaces reveal two of the features included in this implementation:

• RouteAwareInterface means that the content has associated Routes.• PublishWorkflowInterfacePublishWorkflowInterface means that the content has publishing and

unpublishing dates, which will be handled by Symfony CMF's core to determine whetheror not to display the content from StaticContent.

Multilang Static ContentThe MultilangStaticContent class extends StaticContent, offering the same functionality with multilanguage support. It specifies which fields are to be translated (title, body and tags) as well as a variableto declare the locale.

It also specifies the translation strategy:

PDF brought to you bygenerated on July 18, 2013

Chapter 3: Content | 15

Page 16: Symfony Cmf Master

Listing 3-1 12345

use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;

/*** @PHPCRODM\Document(translator="child", referenceable=true)*/

For information on the available translation strategies, refer to the Doctrine page regarding multilanguagesupport in PHPCR-ODM1.

Content ControllerA controller is also included that can render either of the above content document types. Its single action,indexAction, accepts a content instance and optionally the path of the template to be used for rendering.If no template path is provided, it uses a pre-configured default.

The controller action also takes into account the document's publishing status and language (forMultilangStaticContent). Both the content instance and the optional template are provided to thecontroller by the DynamicRouter of the RoutingBundle. More information on this is available on theRouting system getting started page page.

Admin SupportThe last component needed to handle the included content types is an administration panel. SymfonyCMF can optionally support SonataDoctrinePHPCRAdminBundle2, a back office generation tool. Formore information about it, please refer to the bundle's documentation section3.

In ContentBundle, the required administration panels are already declared in the Admin folder andconfigured in Resources/config/admin.xml, and will automatically be loaded if you install theSonataDoctrinePHPCRAdminBundle (refer to Creating a CMS using CMF and Sonata for instructions onthat).

ConfigurationThe ContentBundle also supports a set of optional configuration parameters. Refer to ContentBundle forthe full configuration reference.

Final ThoughtsWhile this small bundle includes some vital components to a fully working CMS, it often will not provideall you need. The main idea behind it is to provide developers with a small and easy to understandstarting point you can extend or use as inspiration to develop your own content types, Controllers andAdmin panels.

1. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/multilang.html

2. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle

3. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/tree/master/Resources/doc

PDF brought to you bygenerated on July 18, 2013

Chapter 3: Content | 16

Page 17: Symfony Cmf Master

Listing 4-1

Chapter 4

Menu

ConceptNo CMS system is complete without a menu system that allows users to navigate between content pagesand perform certain actions. While it usually maps the actual content tree structure, menus often havea logic of their own, include options not mapped by content or exist in multiple contexts with multipleoptions, thus making them a complex problem themselves.

Symfony CMF Menu SystemSymfony CMF SE includes the MenuBundle, a tool that allow you to dynamically define your menus. Itextends the KnpMenuBundle1, with a set of hierarchical, multi language menu elements, along with thetools to persist them in the chosen content store. It also includes the administration panel definitions andrelated services needed for integration with the SonataDoctrinePhpcrAdminBundle2.

The MenuBundle extends and greatly relies on the KnpMenuBundle3, so you should carefully readKnpMenuBundle's documentation4. For the rest of this page we assume you have done so and arefamiliar with concepts like Menu Providers and Menu Factories.

Usage

The MenuBundle uses KnpMenuBundle's default renderers and helpers to print out menus. You can referto the respective documentation page5 for more information on the subject, but a basic call would be:

1. https://github.com/knplabs/KnpMenuBundle

2. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle

3. https://github.com/knplabs/KnpMenuBundle

4. https://github.com/KnpLabs/KnpMenuBundle/blob/master/Resources/doc/index.md

5. https://github.com/KnpLabs/KnpMenuBundle/blob/master/Resources/doc/index.md#rendering-menus

PDF brought to you bygenerated on July 18, 2013

Chapter 4: Menu | 17

Page 18: Symfony Cmf Master

Listing 4-2

1 {{ knp_menu_render('simple') }}

The provided menu name will be passed on to MenuProviderInterface implementation, which will useit to identify which menu you want rendered in this specific section.

The Provider

The core of the MenuBundle is PHPCRMenuProvider, a MenuProviderInterface implementation that'sresponsible for dynamically loading menus from a PHPCR database. The default provider service isconfigured with a menu_basepath to know where in the PHPCR tree it will find menus. The menu nameis given when rendering the menu and must be a direct child of the menu base path. This allows thePHPCRMenuProvider to handle several menu hierarchies using a single storage mechanism.

To give a concrete example, if we have the configuration as given below and render the menu simple, themenu root node must be stored at /cms/menu/simple.

12

cmf_menu:menu_basepath: /cms/menu

If you need multiple menu roots, you can create further PHPCRMenuProvider instances and registerthem with KnpMenu - see the CMF MenuBundle DependencyInjection code for the details.

The menu element fetched using this process is used as the menu root node, and its children will beloaded progressively as the full menu structure is rendered by the MenuFactory.

The Factory

The ContentAwareFactory is a FactoryInterface implementation, which generates the full MenuItemhierarchy from the provided MenuNode. The data generated this way is later used to generate the actualHTML representation of the menu.

The included implementation focuses on generating MenuItem instances from NodeInterface instances,as this is usually the best approach to handle tree-like structures typically used by a CMS. Otherapproaches are implemented in the base classes, and their respective documentation pages can be foundin KnpMenuBundle6's page.

ContentAwareFactory is responsible for loading the full menu hierarchy and transforming the MenuNodeinstances from the root node it receives from the MenuProviderInterface implementation. It is alsoresponsible for determining which (if any) menu item is currently being viewed by the user. It supportsa voter mechanism to have custom code decide what menu item is the current item. KnpMenu alreadyincludes a specific factory targeted at Symfony2's Routing component, which this bundle extends, to addsupport for:

• Route instances stored in a database (refer to RoutingBundle's RouteProvider for more detailson this)

• Route instances with associated content (more on this on respective RoutingBundle's section)

As mentioned before, ContentAwareFactory is responsible for loading all the menu nodes from theprovided root element. The actual loaded nodes can be of any class, even if it's different from the root's,but all must implement NodeInterface in order to be included in the generated menu.

6. https://github.com/knplabs/KnpMenuBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 4: Menu | 18

Page 19: Symfony Cmf Master

Listing 4-3

The Menu Nodes

Also included in the MenuBundle are two menu node content types: MenuNode and MultilangMenuNode.If you have read the documentation page regarding Content, you'll find this implementation somewhatfamiliar.

MenuNode implements the above mentioned NodeInterface, and holds the information regarding a singlemenu entry: a label and a uri, a children list, plus some attributes for the node and its children thatwill allow the rendering process to be customized. It also includes a Route field and two references toContents. These are used to store an associated Route object, plus one (not two, despite the fact that twofields exist) Content element. The MenuNode can have a strong (integrity ensured) or weak (integrity notensured) reference to the actual Content element it points to; it's up to you to choose which best fits yourscenario. You can find more information on references on the Doctrine PHPCR documentation page7.

MultilangMenuNode extends MenuNode with multilanguage support. It adds a locale field to identifywhich translation set it belongs to, plus label and uri fields marked as translated=true. This meansthey will differ between translations, unlike the other fields.

MultilangMenuNode also specifies the strategy used to persist multiple translations:

123

/*** @PHPCRODM\Document(translator="attribute")*/

For information on the available translation strategies, refer to the Doctrine page regarding Multilanguage support in PHPCR-ODM8

Admin SupportThe MenuBundle also includes the administration panels and respective services needed for integrationwith the backend admin tool SonataDoctrinePhpcrAdminBundle

The included administration panels are automatically available but need to be explicitly put on thedashboard if you want to use them. See Creating a CMS using CMF and Sonata for instructions on howto install SonataDoctrinePhpcrAdminBundle.

ConfigurationThis bundle is configurable using a set of parameters, but all of them are optional. You can go to theMenuBundle reference page for the full configuration options list and additional information.

Further NotesFor more information on the MenuBundle of Symfony CMF, please refer to:

• MenuBundle for advanced details and configuration reference• KnpMenuBundle9 page for information on the bundle on which the MenuBundle• relies KnpMenu10 page for information on the underlying library used by the KnpMenuBundle

7. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/association-mapping.html#references

8. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/multilang.html

9. https://github.com/knplabs/KnpMenuBundle

10. https://github.com/knplabs/KnpMenu

PDF brought to you bygenerated on July 18, 2013

Chapter 4: Menu | 19

Page 20: Symfony Cmf Master

Chapter 5

SimpleCMS

ConceptIn the previous documentation pages all the basic components of Symfony CMF have been analysed: theRouting that allows you to associate URLs with your Content, which users can browse using a Menu.

These three components complement each other but are independent: they work without each other,allowing you to choose which ones you want to use, extend or ignore. In some cases, however, you mightjust want a simple implementation that gathers all those capabilities into a ready-to-go package. For thatpurpose, the SimpleCMSBundle was created.

SimpleCMSBundleThe SimpleCMSBundle is implemented on top of most of the other Symfony CMF Bundles, combiningthem into a functional CMS. It is a simple solution, but you will find it very useful when you startimplementing your own CMS using Symfony CMF. Whether you decide to extend or replace it, it's up toyou, but in both cases, it's a good place to start developing your first CMS.

Page Document

Instead of separate documents for the content, routing and the menu system, the SimpleCMSBundleprovides the Page document which provides all those roles in one class:

• It has properties for title and text body;• It extends the Route class from the CMF RoutingBundle to work with the CMF router

component, returning $this in getRouteContent();• It implements the RouteAwareInterface with getRoutes simply returning array($this) to

allow the CMF router to generate the URL to a page;• It implements NodeInterface, which means it can be used by CMF MenuBundle to generate a

menu structure;• It implements the PublishWorkflowInterface to be used with the publish workflow checker.

PDF brought to you bygenerated on July 18, 2013

Chapter 5: SimpleCMS | 20

Page 21: Symfony Cmf Master

Listing 5-1

Here's how that works in practice:

• The routing component receives a request that it matches to a Route instance loaded frompersistent storage. That Route is a Page instance;

• The route enhancer asks the page for its content and will receive $this, putting the page intothe request attributes;

• Other route enhancers determine the controller to use with this class and optionally thetemplate to use (either a specific template stored with the page or one configured in theapplication configuration for the SimpleCmsBundle);

• The controller renders the page using the template, usually generating HTML content.• The template might also render the menu, which will load all Pages and build a menu with

them.

This three-in-one approach is the key concept behind the bundle.

MultilangPage

As you would expect, a multilanguage version of Page is also included. MultilangPage defines a localevariable and which fields will be translated (title, label and body). It also includes getStaticPrefix()to handle the path prefix of the Page. This is part of the route handling mechanism, and will be discussedbelow.

The MultilangPage class uses the attribute strategy for translation: several translations can coexist inthe same database entry, and several translated versions of each field can be stored as different attributesin that same entry.

As the routing is not separated from the content, it is not possible to create different routes for differentlanguages. This is one of the main disadvantages of the SimpleCmsBundle.

Configuring the Content Class

SimpleCMSBundle will use Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page as the contentclass if multilanguage is not enabled (this is the default). If no other class is chosen, and multilanguagesupport is enabled, it will automatically switch toSymfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangPage. You can explicitly specify yourcontent class and/or enable multilanguage support using the configuration parameters:

1234567

# app/config/config.ymlcmf_simple_cms:

# defaults to Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page or MultilangPage (seeabove)

document_class: ~multilang:

# defaults to [] - declare your locales here to enable multilanguagelocales: ~

SimpleCMSBundle in DetailNow that you understand what the SimpleCMSBundle does, we'll detail how it does it. Several othercomponents are part of this bundle that change the default behaviour of its dependencies.

Routing

The SimpleCMSBundle mostly relies on RoutingBundle and its set of configurable capabilities to meet itsrequirements. It declares an independent DynamicRouter service, with its own specific RouteProvider,

PDF brought to you bygenerated on July 18, 2013

Chapter 5: SimpleCMS | 21

Page 22: Symfony Cmf Master

Listing 5-2

Listing 5-3

NestedMatcher, Enhancers set and other useful services, all of them instances of the classes bundled withRoutingBundle.

The only exception to this is RouteProvider: the SimpleCMSBundle has its own strategy to retrieveRoute instances from persistent storage. This is related to the way Route instances are stored byRoutingBundle. By default, the path parameter will hold the prefixed full URI, including the localeidentifier. This would mean an independent Route instance should exist for each translation of thesame Content. However, as we've seen, MultilangPage```stores all translations in the sameentry. So, to avoid duplication, the locale prefix is stripped from the URI prior topersistence, and SimpleCMSBundle includes ``MultilangRouteProvider, which is responsible forfetching Route instances taking that into account.

When rendering the actual URL from Route, the locale prefix needs to be replaced, otherwise theresulting addresses would not specify the locale they refer to. To do so, MultilangPage uses the alreadymentioned getStaticPrefix() implementation.

Example: An incoming request for contact would be prefixed with the /cms/simple basepath, and thestorage would be queried for /cms/simple/contact/. However, in a multilanguage setup, the localeis prefixed to the URI, resulting in a query either for /cms/simple/en/contact/ or /cms/simple/de/contact/, which would require two independent entries to exist for the same actual content. With theabove mentioned approach, the locale is stripped from the URI prior to basepath prepending, resultingin a query for /cms/simple/contact/ in both cases.

Routes and Redirects

SimpleCMSBundle includes MultilangRoute and MultilangRedirectRoute. These are extensions tothe Route and RedirectRoute found in RoutingBundle, but with the necessary changes to handle theprefix strategy discussed earlier.

Content Handling

Route instances are responsible for determining which Controller will handle the current request.Getting the Controller and Template shows how Symfony CMF SE can determine which Controller touse when rendering a certain content document, and the SimpleCMSBundle uses these mechanisms todo so.

1234

# app/config/config.ymlcmf_simple_cms:

# defaults to cmf_content.controller:indexActiongeneric_controller: ~

Unless you specify otherwise, the ContentController from SimpleCMSBundle is used for all Documents.The default configuration associates all document_class instances with this Controller, and specifiesno default template. However, you can configure several controllers_by_class andtemplates_by_class rules, which will associate, respectively, Controller and templates to a specificContent type. Symfony CMF SE includes an example of both in its default configuration.

1234567

# app/config/config.ymlcmf_simple_cms:

routing:templates_by_class:

Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page:CmfSimpleCmsBundle:Page:index.html.twig

controllers_by_class:Symfony\Cmf\Bundle\RoutingBundle\Document\RedirectRoute:

cmf_routing.redirect_controller:redirectAction

PDF brought to you bygenerated on July 18, 2013

Chapter 5: SimpleCMS | 22

Page 23: Symfony Cmf Master

Listing 5-4

Listing 5-5

These configuration parameters will be used to instantiate Route Enhancers. More information aboutthem can be found in the Routing component documentation page.

The specific example above determines that content instances of class Page will be rendered using theabove template, if none other is explicitly provided by the associated Route (which, in this case, is Pageitself). It also states that all content documents that instantiate RedirectRoute will be rendered using thespecified Controller instead of the the default. Again, the actual Route can provided a controller thatwill take priority over this one. Both the template and the controller are part of SimpleCMSBundle.

Menu Generation

As mentioned before, Page implements NodeInterface, which means it can be used to generate aMenuItem that will, in turn, be rendered into HTML menus.

To do so, the default MenuBundle mechanisms are used, only a custom basepath is provided to thePHPCRMenuProvider instance. This is defined in the SimpleCMSBundle configuration options, and usedwhen handling content storage to support functionality as described in Menu documentation. Thisparameter is optional, and can be configured as follows:

1234567

# app/config/config.ymlcmf_simple_cms:

# defaults to auto; true/false can be used to force providing/not providing a menuuse_menu: ~

# defaults to /cms/simplebasepath: ~

Admin SupportThe SimpleCMSBundle also includes the administration panel and respective service needed forintegration with SonataDoctrinePHPCRAdminBundle1, a backend editing bundle. For more informationabout it, please refer to the bundle's documentation section2.

The included administration panels will automatically be loaded if you install theSonataDoctrinePHPCRAdminBundle (refer to Creating a CMS using CMF and Sonata for instructions onhow to do so). You can change this behaviour with the following configuration option:

1234

# app/config/config.ymlcmf_simple_cms:

# defaults to auto; true/false can be used to force using/not using SonataAdminuse_sonata_admin: ~

Fixtures

The SimpleCMSBundle includes a support class for integration with DoctrineFixturesBundle3, aimed atmaking loading initial data easier. A working example is provided in Symfony CMF SE that illustrateshow you can easily generate MultilangPage and MultilangMenuNode instances from YAML files.

1. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle

2. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/tree/master/Resources/doc

3. http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 5: SimpleCMS | 23

Page 24: Symfony Cmf Master

ConfigurationThis bundle is configurable using a set of parameters, but all of them are optional. You can go to theSimpleCmsBundle reference page for the full configuration options list and aditional information.

Further NotesFor more information on the SimpleCMSBundle, please refer to:

• SimpleCmsBundle for configuration reference and advanced details about the bundle.• Routing for information about the routing component in which the SimpleCMSBundle is based

on.• Content for information about the base content bundle that the SimpleCMSBundle depends

on.• Menu for information about the menu system used by the SimpleCMSBundle.

PDF brought to you bygenerated on July 18, 2013

Chapter 5: SimpleCMS | 24

Page 25: Symfony Cmf Master

Chapter 6

Choosing a Storage Layer

When building a CMS, the choice of storage layer is one of the key decisions to take. Many factors mustbe considered, the good news is that with all the components and Bundles in the CMF, it takes extra careto provide the necessary extension points to ensure the CMF remains storage layer agnostic.

The goal of this tutorial is to explain the considerations and why Symfony CMF suggest PHPCR1 andPHPCR-ODM2 as the ideal basis for a CMS. However, all components and Bundles can be integratedwith other solutions with a fairly small amount of work.

Requirements for a CMS Storage LayerAt the most fundamental level a CMS is about storing, so the first requirement is that a CMS must providemeans to store content with different properties.

A CMS has very different storage needs than for example a system for processing orders. Do note howeverthat it is entirely possible and very intended of the CMF initiative to enable developers to combine theCMF with a system for processing orders. So for example one could create a shopping solution using theCMF for storing the product catalog, while using another system for maintaining the inventory, customerdata and orders. This leads to the second requirement, a CMS must provide means to reference content,both content stored inside the CMS, but also in other systems.

The actual content in a CMS tends to be organized in a tree like structure, mimicking a file system. Notethat content authors might want to use different structures for how to organize the content and how toorganize other aspects like the menu and the routing. This leads to the third requirement, a CMS mustprovide means to represent the content as a tree structure. Furthermore a fourth requirement is that a CMSshould allow maintaining several independent tree structures.

In general data inside a CMS tends to be unstructured. So while several pages inside the CMS mightbe very similar, there is a good chance that there will be many permutations needing different extrafields, therefore a CMS must not enforce a singular schema for content. That being said, in order to bettermaintain the content structure and enabling UI layers from generically displaying content elements itis important to optionally be able to express rules that must be followed and that can also help attach

1. http://phpcr.github.com

2. http://www.doctrine-project.org/projects/phpcr-odm.html

PDF brought to you bygenerated on July 18, 2013

Chapter 6: Choosing a Storage Layer | 25

Page 26: Symfony Cmf Master

additional semantic meaning. So a CMS must provide means to optionally define a schema for contentelements.

This requirement actually also relates to another need, in that a CMS must make it easy for contentauthors to prepare a series of changes in a staging environment that then needs to go online in a singlestep. This means another requirement is that it is necessary that a CMS should support moving andexporting content between independent tree structures. Note that exporting can be useful also for backups.

When making changes it would however also be useful to be able to version the change sets, so that theyremain available for historical purposes, but also to be able to revert whenever needed. Therefore the nextrequirement is that a CMS should provide the ability to version content.

As we live in a globalized world, websites need to provide content in multiple languages addressingdifferent regions. However not all pieces of content need to be translated and others might only beeventually translated but until then the user should be presented the content in one of the availablelanguages, so a CMS should provide the ability to store content in different languages, with optional fallbackrules.

As a CMS usually tends to store an increasing amount of content it will become necessary to providesome way for users to search the content even when the user has only a very fuzzy idea about the contentthey are looking for, leading to the requirement that a CMS must provide full text search capabilities,ideally leveraging both the contents tree structure and the data schema.

Another popular need is limiting read and/or write access of content to specific users or groups. Ideallythis solution would also integrate with the tree structure. So it would be useful if a CMS providescapabilities to define access controls that leverage the tree structure to quickly manage access for entiresubtrees.

Finally not all steps in the content authoring process will be done by the same person. As a matter offact there might be multiple steps all of which might not even be done by a person. Instead some of thesteps might even be executed by a machine. So for example a photographer might upload a new image, acontent author might attach the photo to some text, then the system automatically generates thumbnailsand web optimized renditions and finally an editor decides on the final publication. Therefore a CMSshould provide capabilities to assist in the management of workflows.

SummaryHere is a summary of the above requirements. Note some of the requirements have a must, while othersonly have a should. Obviously depending on your use case you might prioritize features differently:

• a CMS must provide means to store content with different properties;• a CMS must provide means to reference content;• a CMS must provide means to represent the content as a tree structure;• a CMS must provide full text search capabilities;• a CMS must not enforce a singular schema for content;• a CMS must provide means to optionally define a schema for content elements;• a CMS should allow maintaining several independent tree structures;• a CMS should support moving and exporting content between independent tree structures;• a CMS should provide the ability to version content;• a CMS should provide the ability to store content in different languages, with optional fallback

rules;• a CMS should provides capabilities to define access controls;• a CMS should provide capabilities to assist in the management of workflows.

PDF brought to you bygenerated on July 18, 2013

Chapter 6: Choosing a Storage Layer | 26

Page 27: Symfony Cmf Master

RDBMSLooking at the above requirements it becomes apparent that out the box an RDBMS is ill-suited toaddress the needs of a CMS. RDBMS were never intended to store tree structures of unstructured content.Really the only requirement RDBMS cover from the above list is the ability to store content, some wayto reference content, keep multiple separate content structures and a basic level of access controls andtriggers.

This is not a failing of RDBMS in the sense that they were simply designed for a different use case: theability to store, manipulate and aggregate structured data. This makes them ideal for storing inventoryand orders.

That is not to say that it is impossible to build a system on top of an RDBMS that addresses more oreven all of the above topics. Some RDBMS natively support recursive queries, which can be useful forretrieving tree structures. Even if such native support is missing, there are algorithms like materializedpath and nested sets that can enable efficient storage and retrieval of tree structures for different use cases.

The point is however that these all require algorithms and code on top of an RDBMS which also tightlybind your business logic to a particular RDBMS and/or algorithm even if some of them can be abstracted.So again using an ORM one could create a pluggable system for managing tree structures with differentalgorithms which prevent binding the business logic of the CMS to a particular algorithm.

However it should be said once more, that all Bundles and Components in the CMF are developedto enable any persistent storage API and we welcome contributions for adding implementations forother storage systems. So for example CMF RoutingBundle currently only provides Document classes forPHPCR ODM, but the interfaces defined in the Routing component are storage agnostic and we wouldaccept a contribution to add Doctrine ORM support.

PHPCR

PHPCR3 essentially is a set of interfaces addressing most of the requirements from the above list. Thismeans that PHPCR is totally storage agnostic in the sense that it is possible to really put any persistencesolution behind PHPCR. So in the same way as an ORM can support different tree storage algorithmsvia some plugin, PHPCR aims to provide an API for the entire breath of CMS needs, therefore cleanlyseparating the entire business logic of your CMS from the persistence choice. As a matter of fact the onlyfeature above not natively supported by PHPCR is support for translations.

Thanks to the availability of several PHPCR implementations supporting various kinds of persistencechoices, creating a CMS on top of PHPCR means that end users are enabled to pick and choose whatworks best for them, their available resources, their expertise and their scalability requirements.

So for the simplest use cases there is for example a Doctrine DBAL based solution provided by theJackalope4 PHPCR implementation that can use the SQLite RDBMS shipped with PHP itself. At the otherend of the spectrum Jackalope also supports Jackrabbit5 which supports clustering and can efficientlyhandle data into the hundreds of gigabytes. By default Jackrabbit simply uses the file system forpersistence, but it can also use an RDBMS. However future versions will support MongoDB and supportfor other NoSQL solutions like CouchDB or Cassandra is entirely possible. Again, switching thepersistence solution would require no code changes as the business logic is only bound to the PHPCRinterfaces.

Please see Installing and Configuring Doctrine PHPCR-ODM for more details on the available PHPCRimplementations and their requirements and how to setup Symfony2 with one of them.

3. http://phpcr.github.com

4. https://github.com/jackalope/jackalope

5. http://jackrabbit.apache.org

PDF brought to you bygenerated on July 18, 2013

Chapter 6: Choosing a Storage Layer | 27

Page 28: Symfony Cmf Master

PHPCR ODMAs mentioned above using PHPCR does not mean giving up on RDBMS. In many ways, PHPCR can beconsidered a specialized ORM solution for CMS. However while PHPCR works with so called nodes,in an ORM people expect to be able to map class instances to a persistence layer. This is exactly whatPHPCR ODM provides. It follows the same interface classes as Doctrine ORM while also exposing all theadditional capabilities of PHPCR, like trees and versioning. Furthermore, it also provides native supportfor translations, covering the only omission of PHPCR for the above mentioned requirements list of aCMS storage solution.

PDF brought to you bygenerated on July 18, 2013

Chapter 6: Choosing a Storage Layer | 28

Page 29: Symfony Cmf Master

Listing 7-1

Chapter 7

Installing and Configuring the CMF Core

The goal of this tutorial is to install the minimal CMF components ("core") with the minimum necessaryconfiguration. From there, you can begin incorporating CMF functionality into your application asneeded.

This is aimed at experienced user who want to know all about the Symfony CMF details. If this is yourfirst encounter with the Symfony CMF it would be a good idea to start with:

• Installing the Symfony CMF Standard Edition page for instructions on how to quickly installthe CMF (recommended for development)

• Installing the CMF sandbox for instructions on how to install a demonstration sandbox.

Preconditions

• Installation of Symfony21 (2.1.x)• Installing and Configuring Doctrine PHPCR-ODM

Installation

Download the Bundles

Add the following to your composer.json file:

12345

"minimum-stability": "dev","require": {

..."symfony-cmf/symfony-cmf": "1.0.*"

}

And then run:

1. http://symfony.com/doc/2.1/book/installation.html

PDF brought to you bygenerated on July 18, 2013

Chapter 7: Installing and Configuring the CMF Core | 29

Page 30: Symfony Cmf Master

Listing 7-2

Listing 7-3

Listing 7-4

1 $ php composer.phar update

Initialize bundles

Next, initialize the bundles in AppKernel.php by adding them to the registerBundles method:

123456789

1011121314151617181920212223

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...

new Symfony\Cmf\Bundle\RoutingBundle\CmfRoutingBundle(),new Symfony\Cmf\Bundle\CoreBundle\CmfCoreBundle(),new Symfony\Cmf\Bundle\MenuBundle\CmfMenuBundle(),new Symfony\Cmf\Bundle\ContentBundle\CmfContentBundle(),new Symfony\Cmf\Bundle\BlockBundle\CmfBlockBundle(),

// Dependencies of the CmfMenuBundlenew Knp\Bundle\MenuBundle\KnpMenuBundle(),

// Dependencies of the CmfBlockBundlenew Sonata\BlockBundle\SonataBlockBundle(),

);

// ...}

This also enables the PHPCR-ODM and related dependencies; setup instructions can be found inthe dedicated documentation.

ConfigurationTo get your application running, very little configuration is needed.

Minimum Configuration

These steps are needed to ensure your AppKernel still runs.

If you haven't done so already, make sure you have followed these steps from Installing and ConfiguringDoctrine PHPCR-ODM:

• Initialize DoctrinePHPCRBundle in app/AppKernel.php• Ensure there is a doctrine_phpcr: section in app/config/config.yml• Add the AnnotationRegistry::registerFile line to app/autoload.php

Configure the BlockBundle in your config.yml:

PDF brought to you bygenerated on July 18, 2013

Chapter 7: Installing and Configuring the CMF Core | 30

Page 31: Symfony Cmf Master

Listing 7-5

123

# app/config/config.ymlsonata_block:

default_contexts: [cms]

Additional Configuration

Because most CMF components use the DynamicRouter from the RoutingBundle, which by default isnot loaded, you will need to enable it as follows:

12345678

# app/config/config.ymlcmf_routing:

chain:routers_by_id:

cmf_routing.dynamic_router: 200router.default: 100

dynamic:enabled: true

You might want to configure more on the dynamic router, i.e. to automatically choose controllers basedon content. See RoutingBundle for details.

For now this is the only configuration we need. Mastering the configuration of the different bundles willbe handled in further tutorials. If you're looking for the configuration of a specific bundle take a look atthe corresponding bundles entry.

PDF brought to you bygenerated on July 18, 2013

Chapter 7: Installing and Configuring the CMF Core | 31

Page 32: Symfony Cmf Master

Chapter 8

Installing and Configuring Doctrine PHPCR-ODM

The Symfony2 CMF needs somewhere to store the content. Many of the bundles provide documentsand mappings for the PHP Content Repository - Object Document Mapper (PHPCR-ODM), and thedocumentation is currently based around using this. However, it should also be possible to use any otherform of content storage, such as another ORM/ODM or MongoDB.

The goal of this tutorial is to install and configure Doctrine PHPCR-ODM, ready for you to get startedwith the CMF.

For more details see the full PHPCR-ODM documentation1. Some additional information can be found inthe DoctrinePHPCRBundle reference, which for the most part mimics the standard DoctrineBundle2.

Finally for information about PHPCR see the official PHPCR website3.

If you just want to use PHPCR but not the PHPCR-ODM, you can skip the step about registeringannotations and the part of the configuration section starting with odm.

Requirements• Symfony2 (version 2.1 or newer)• phpunit >= 3.6 (if you want to run the tests)• When using jackalope-jackrabbit: Java, Apache Jackalope and libxml version >= 2.7.0 (due

to a bug in libxml4)• When using jackalope-doctrine-dbal with MySQL: MySQL >= 5.1.5 (as you need the xml

function ExtractValue)

1. http://www.doctrine-project.org/projects/phpcr-odm.html

2. https://github.com/doctrine/DoctrineBundle

3. http://phpcr.github.com

4. http://bugs.php.net/bug.php?id=36501)

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 32

Page 33: Symfony Cmf Master

Listing 8-1

Listing 8-2

Listing 8-3

Installation

Choosing a Content Repository

The first thing to decide is what content repository to use. A content repository is essentially a databasethat will be responsible for storing all the content you want to persist. It provides an API that is optimizedfor the needs of a CMS (tree structures, references, versioning, full text search etc.). While every contentrepository can have very different requirements and performance characteristics, the API is the same forall of them.

Furthermore, since the API defines an export/import format, you can always switch to a different contentrepository implementation later on.

These are the available choices:

• Jackalope with Jackrabbit (Jackrabbit requires Java, it can persist into the file system, adatabase etc.)

• Jackalope with Doctrine DBAL (requires an RDBMS like MySQL, PostgreSQL or SQLite)• Midgard (requires the midgard2 PHP extension and an RDBMS like MySQL, PostgreSQL or

SQLite)

The following documentation includes examples for all of the above options.

If you are just getting started with the CMF, it is best to choose a content repository based on astorage engine that you are already familiar with. For example, Jackalope with Doctrine DBALwill work with your existing RDBMS and does not require you to install Java or the midgard2 PHPextension. Once you have a working application it should be easy to switch to another option.

Download the Bundles

Add the following to your composer.json file, depending on your chosen content repository.

Jackalope with Jackrabbit

1234567

"minimum-stability": "dev","require": {

..."jackalope/jackalope-jackrabbit": "1.0.*","doctrine/phpcr-bundle": "1.0.*","doctrine/phpcr-odm": "1.0.*"

}

Jackalope with Doctrine DBAL

1234567

"minimum-stability": "dev","require": {

..."jackalope/jackalope-doctrine-dbal": "dev-master","doctrine/phpcr-bundle": "1.0.*","doctrine/phpcr-odm": "1.0.*"

}

Midgard

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 33

Page 34: Symfony Cmf Master

Listing 8-4

Listing 8-5

Listing 8-6

1234567

"minimum-stability": "dev","require": {

..."midgard/phpcr": "dev-master","doctrine/phpcr-bundle": "1.0.*","doctrine/phpcr-odm": "1.0.*"

}

For all of the above, if you are also using Doctrine ORM, make sure to use "doctrine/orm":"2.3.*", otherwise composer can't resolve the dependencies as Doctrine PHPCR-ODM dependson the newer 2.3 Doctrine Commons. (Symfony2.1 standard edition uses 2.2.*.)

To install the above dependencies, run:

1 $ php composer.phar update

Register Annotations

PHPCR-ODM uses annotations and these need to be registered in your app/autoload.php file. Add thefollowing line, immediately after the last AnnotationRegistry::registerFile line:

12345

// app/autoload.php

// ...AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');// ...

Initialize Bundles

Next, initialize the bundles in app/AppKernel.php by adding them to the registerBundle method:

123456789

101112131415

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...

// Doctrine PHPCRnew Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(),

);

// ...}

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 34

Page 35: Symfony Cmf Master

Listing 8-7

Listing 8-8

Listing 8-9

ConfigurationNext step is to configure the bundles.

PHPCR Session

Basic configuration for each content repository is shown below; add the appropriate lines to your app/config/config.yml. More information on configuring this bundle can be found in the reference chapterDoctrinePHPCRBundle.

The workspace, username and password parameters are for the PHPCR repository and should not beconfused with possible database credentials. They come from your content repository setup. If you wantto use a different workspace than default you have to create it first in your repository.

If you want to use the PHPCR-ODM as well, please also see the next section.

Jackalope with Jackrabbit

123456789

10

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: jackrabbiturl: http://localhost:8080/server/

workspace: defaultusername: adminpassword: admin

# odm configuration see below

Jackalope with Doctrine DBAL

123456789

10

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: doctrinedbalconnection: doctrine.dbal.default_connection

workspace: defaultusername: adminpassword: admin

# odm configuration see below

Make sure you also configure the main doctrine: section for your chosen RDBMS. If you wantto use a different than the default connection, configure it in the dbal section and specify it in theconnection parameter. A typical example configuration is:

doctrine:dbal:

driver: %database_driver%host: %database_host%port: %database_port%dbname: %database_name%user: %database_user%password: %database_password%charset: UTF8

See `Databases and Doctrine`_ for more information.

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 35

Page 36: Symfony Cmf Master

Listing 8-10

Listing 8-11

Listing 8-12

Midgard

123456789

1011121314151617

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: midgard2db_type: MySQLdb_name: midgard2_testdb_host: "0.0.0.0"db_port: 3306db_username: ""db_password: ""db_init: trueblobdir: /tmp/cmf-blobs

workspace: defaultusername: adminpassword: admin

# odm configuration see below

Doctrine PHPCR-ODM

Any of the above configurations will give you a valid PHPCR session. If you want to use the Object-Document manager, you need to configure it as well. The simplest is to set auto_mapping: true tomake the PHPCR bundle recognize documents in the <Bundle>/Document folder and look for mappingsin <Bundle>/Resources/config/doctrine/<Document>.phpcr.xml resp. ...yml. Otherwise you needto manually configure the mappings section. See the configuration reference of the PHPCR-ODM bundlefor details.

123456

# app/config/config.ymldoctrine_phpcr:

session:# ...

odm:auto_mapping: true

Setting up the Content RepositoryJackalope Jackrabbit

These are the steps necessary to install Apache Jackrabbit:

• Make sure you have Java Virtual Machine installed on your box. If not, you can grab one fromhere: http://www.java.com/en/download/manual.jsp5

• Download the latest version from the Jackrabbit Downloads page6

• Run the server. Go to the folder where you downloaded the .jar file and launch it

1 $ java -jar jackrabbit-standalone-*.jar

Going to http://localhost:8080/ should now display a Apache Jackrabbit page.

More information about running a Jackrabbit server7 can be found on the Jackalope wiki.

5. http://www.java.com/en/download/manual.jsp

6. http://jackrabbit.apache.org/downloads.html

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 36

Page 37: Symfony Cmf Master

Listing 8-13

Listing 8-14

Listing 8-15

Jackalope Doctrine DBAL

Run the following commands to create the database and set up a default schema:

12

$ php app/console doctrine:database:create$ php app/console doctrine:phpcr:init:dbal

For more information on how to configure Doctrine DBAL with Symfony2, see the "Databases andDoctrine8" and the explanations in the reference of the PHPCR-ODM bundle.

Midgard

Midgard is a C extension that implements the PHPCR API on top of a standard RDBMS.

See the official Midgard PHPCR documentation9.

Registering System Node TypesPHPCR-ODM uses a `custom node type <>`_ to track meta information without interfering with yourcontent. There is a command that makes it trivial to register this type and the PHPCR namespace, as wellas all base paths of bundles:

1 $ php app/console doctrine:phpcr:repository:init

Using the ValidPhpcrOdm Constraint ValidatorThe bundle provides a ValidPhpcrOdm constraint validator you can use to check if your document Id orNodename and Parent fields are correct:

123456789

10111213141516171819202122

<?php

namespace Acme\DemoBundle\Document;

use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;use Doctrine\Bundle\PHPCRBundle\Validator\Constraints as Assert;

/*** @PHPCRODM\Document* @Assert\ValidPhpcrOdm*/class MyDocument{

/** @PHPCRODM\Id(strategy="parent") */protected $id;

/** @PHPCRODM\Nodename */protected $name;

/** @PHPCRODM\ParentDocument */protected $parent;

7. https://github.com/jackalope/jackalope/wiki/Running-a-jackrabbit-server

8. http://symfony.com/doc/current/book/doctrine.html

9. http://midgard-project.org/phpcr/

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 37

Page 38: Symfony Cmf Master

2324

// ...}

PDF brought to you bygenerated on July 18, 2013

Chapter 8: Installing and Configuring Doctrine PHPCR-ODM | 38

Page 39: Symfony Cmf Master

Listing 9-1

Listing 9-2

Chapter 9

Installing and Configuring Inline Editing

The goal of this tutorial is to install and configure the inline editing support.

This provides a solution to easily integrate with VIE.js1 and create.js2 to provide inline editing based onRDFa3 output.

For more information for now see the documentation of the CreateBundle

Installation

Download the Bundles

Add the following to your composer.json file:

123456789

1011121314

"require": {..."symfony-cmf/create-bundle": "1.0.*"

},"scripts": {

"post-install-cmd": ["Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreate",...

],"post-update-cmd": [

"Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreate",...

]},

And then run:

1. http://viejs.org

2. http://createjs.org

3. http://rdfa.info

PDF brought to you bygenerated on July 18, 2013

Chapter 9: Installing and Configuring Inline Editing | 39

Page 40: Symfony Cmf Master

Listing 9-3

Listing 9-4

1 $ php composer.phar update symfony-cmf/create-bundle

See Using CKEditor Instead if you want to use "CKEditor" instead of the default "hallo" editor.

Initialize Bundles

Next, initialize the bundles in the AppKernel by adding them to the registerBundle method:

123456789

1011121314

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...

new Symfony\Cmf\Bundle\CreateBundle\CmfCreateBundle(),new FOS\RestBundle\FOSRestBundle(),new JMS\SerializerBundle\JMSSerializerBundle($this),

);// ...

}

ConfigurationNext step is to configure the bundles.

Basic configuration, add to your application configuration:

12345678

# app/config/config.ymlcmf_create:

phpcr_odm: truemap:

'<http://rdfs.org/sioc/ns#Post>':'Symfony\Cmf\Bundle\MultilangContentBundle\Document\MultilangStaticContent'

image:model_class: Symfony\Cmf\Bundle\CreateBundle\Document\Imagecontroller_class: Symfony\Cmf\Bundle\CreateBundle\Controller\PHPCRImageController

If you have your own documents, add them to the mapping and place the RDFa mappings in Resources/rdf-mappings either inside the app directory or inside any Bundle. The filename is the full class nameincluding namespace with the backslashes \\ replaced by a dot ..

PDF brought to you bygenerated on July 18, 2013

Chapter 9: Installing and Configuring Inline Editing | 40

Page 41: Symfony Cmf Master

Listing 10-1

Listing 10-2

Chapter 10

Creating a CMS using CMF and Sonata

The goal of this tutorial is to create a simple content management system using the CMF as well asSonataAdminBundle1 and SonataDoctrinePhpcrAdminBundle.

Preconditions• installing-cmf-core• Symfony SecurityBundle2 (required by the SonataAdminBundle default templates)

Installation

Download the Bundles

Add the following to your composer.json file:

1234

"require": {..."sonata-project/doctrine-phpcr-admin-bundle": "1.0.*",

}

And then run:

1 $ php composer.phar update

Initialize Bundles

Next, initialize the bundles in app/AppKernel.php by adding them to the registerBundle method:

1. https://github.com/sonata-project/SonataAdminBundle

2. http://symfony.com/doc/master/book/security.html

PDF brought to you bygenerated on July 18, 2013

Chapter 10: Creating a CMS using CMF and Sonata | 41

Page 42: Symfony Cmf Master

Listing 10-3

Listing 10-4

123456789

10111213141516

public function registerBundles(){

$bundles = array(// ...

// support for the adminnew Symfony\Cmf\Bundle\TreeBrowserBundle\CmfTreeBrowserBundle(),new Sonata\jQueryBundle\SonatajQueryBundle(),new Sonata\BlockBundle\SonataBlockBundle(),new Sonata\AdminBundle\SonataAdminBundle(),new Sonata\DoctrinePHPCRAdminBundle\SonataDoctrinePHPCRAdminBundle(),new FOS\JsRoutingBundle\FOSJsRoutingBundle(),

);

// ...}

ConfigurationAdd the sonata bundles to your application configuration:

123456789

1011121314151617181920212223242526272829303132333435

# app/config/config.ymlsonata_block:

default_contexts: [cms]blocks:

sonata.admin.block.admin_list:contexts: [admin]

sonata_admin_doctrine_phpcr.tree_block:settings:

id: '/cms'contexts: [admin]

sonata_admin:templates:

# default global templatesajax: SonataAdminBundle::ajax_layout.html.twig

dashboard:blocks:

# display a dashboard block- { position: right, type: sonata.admin.block.admin_list }- { position: left, type: sonata_admin_doctrine_phpcr.tree_block }

sonata_doctrine_phpcr_admin:document_tree:

Doctrine\ODM\PHPCR\Document\Generic:valid_children:

- allSymfony\Cmf\Bundle\SimpleCmsBundle\Document\Page: ~Symfony\Cmf\Bundle\RoutingBundle\Document\Route:

valid_children:- Symfony\Cmf\Bundle\RoutingBundle\Document\Route- Symfony\Cmf\Bundle\RoutingBundle\Document\RedirectRoute

Symfony\Cmf\Bundle\RoutingBundle\Document\RedirectRoute:valid_children: []

Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode:valid_children:

PDF brought to you bygenerated on July 18, 2013

Chapter 10: Creating a CMS using CMF and Sonata | 42

Page 43: Symfony Cmf Master

Listing 10-5

Listing 10-6

363738394041

- Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode- Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode

Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode:valid_children:

- Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode- Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode

Add route in to your routing configuration:

123456789

10111213141516171819

# app/config/routing.ymladmin:

resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'prefix: /admin

sonata_admin:resource: .type: sonata_adminprefix: /admin

doctrine_phpcr_admin_bundle_odm_browser:resource: "@SonataDoctrinePHPCRAdminBundle/Resources/config/routing/

phpcrodmbrowser.xml"

fos_js_routing:resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"

cmf_tree:resource: .type: 'cmf_tree'

The FOSJsRoutingBundle is used to export sonata routes to javascript, to be used with the tree. Allrelevant routes have the expose option set. If you do custom routes that need to be used with the tree,you need to do that or configure the js routing bundle manually.

Sonata Assets

1 $ php app/console assets:install --symlink

Defining own Admin ClassesThe CMF bundles come with predefined admin classes which will be activated automatically if SonataPHPCR-ODM Admin is loaded. If you need to write different admins and do not want to load thedefaults, you can deactivate the loading - see the documentation of the respective bundles.

To load your own Admin service, you need to declare it as a service, tag with sonata.admin withmanager_type="doctrine_phpcr". For the admin to work properly, you need to add a call for methodsetRouteBuilder to set it to the service sonata.admin.route.path_info_slashes, or your Admin willnot work.

The constructor expects three arguments, code, document class and controller name. You can pass anempty argument for the code, the document class must be the fully qualified class name of the documentthis admin is for and the third argument can be used to set a custom controller that does additionaloperations over the default sonata CRUD controller.

PDF brought to you bygenerated on July 18, 2013

Chapter 10: Creating a CMS using CMF and Sonata | 43

Page 44: Symfony Cmf Master

Listing 10-7 123456789

10

<service id="my_bundle.admin" class="%my_bundle.admin_class%"><tag name="sonata.admin" manager_type="doctrine_phpcr" group="dashboard.group_content"

label_catalogue="MyBundle" label="dashboard.label_my_admin"label_translator_strategy="sonata.admin.label.strategy.underscore" />

<argument/><argument>%my_bundle.document_class%</argument><argument>SonataAdminBundle:CRUD</argument>

<call method="setRouteBuilder"><argument type="service" id="sonata.admin.route.path_info_slashes" />

</call></service>

FinallyNow Sonata is configured to work with the PHPCR you can access the dashboard using via /admin/dashboard in your site.

Tree ProblemsIf you have not yet added anything to the content repository, the tree view will not load as it cannot find aroot node. To fix this, load some data as fixtures by following "Using the BlockBundle and ContentBundlewith PHPCR"

Further Reading• SonataDoctrinePhpcrAdminBundle• handling-multilang-documents

PDF brought to you bygenerated on July 18, 2013

Chapter 10: Creating a CMS using CMF and Sonata | 44

Page 45: Symfony Cmf Master

Chapter 11

Using the BlockBundle and ContentBundlewith PHPCR

The goal of this tutorial is to demonstrate how the CMF BlockBundle and ContentBundle can be used asstand-alone components, and to show how they fit into the PHPCR.

This tutorial demonstrates the simplest possible usage, to get you up and running quickly. Once you arefamiliar with basic usage, the in-depth documentation of both bundles will help you to adapt these basicexamples to serve more advanced use cases.

We will begin with using only BlockBundle, with content blocks linked directly into the PHPCR. Next,we will introduce the ContentBundle to show how it can represent content pages containing blocks.

Although not a requirement for using BlockBundle or ContentBundle, this tutorial will also makeuse of DoctrineFixturesBundle1. This is because it provides an easy way to load in some testcontent.

Preconditions

• Installation of Symfony22 (2.1.x)• Installing and Configuring Doctrine PHPCR-ODM

This tutorial is based on using PHPCR-ODM set up with Jackalope, Doctrine DBAL and a MySQLdatabase. It should be easy to adapt this to work with one of the other PHPCR options documentedin Installing and Configuring Doctrine PHPCR-ODM.

1. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

2. http://symfony.com/doc/2.1/book/installation.html

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 45

Page 46: Symfony Cmf Master

Listing 11-1

Listing 11-2

Listing 11-3

Listing 11-4

Listing 11-5

Create and Configure the DatabaseYou can use an existing database, or create one now to help you follow this tutorial. For a new database,run these commands in MySQL:

123

CREATE DATABASE symfony DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;CREATE USER 'symfony'@'localhost' IDENTIFIED BY 'UseABetterPassword';GRANT ALL ON symfony.* TO 'symfony'@'localhost';

Your parameters.yml file needs to match the above, for example:

12345678

# app/config/parameters.ymlparameters:

database_driver: pdo_mysqldatabase_host: localhostdatabase_port: ~database_name: symfonydatabase_user: symfonydatabase_password: UseABetterPassword

Configure the Doctrine PHPCR Component

If you have followed Installing and Configuring Doctrine PHPCR-ODM, you can skip this section.

You need to install the PHPCR-ODM components. Add the following to your composer.json file:

1234567

"require": {..."jackalope/jackalope-jackrabbit": "1.0.*","jackalope/jackalope-doctrine-dbal": "dev-master","doctrine/phpcr-bundle": "1.0.*","doctrine/phpcr-odm": "1.0.*"

}

To install the above, run:

1 $ php composer.phar update

In your config.yml file, add following configuration for doctrine_phpcr:

123456789

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: doctrinedbalconnection: doctrine.dbal.default_connection

workspace: defaultodm:

auto_mapping: true

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 46

Page 47: Symfony Cmf Master

Listing 11-6

Listing 11-7

Listing 11-8

Listing 11-9

Listing 11-10

Add the following line to the registerBundles() method of the AppKernel:

123456789

101112

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(),

);

// ...}

Add the following line to your autoload.php file, immediately after the lastAnnotationRegistry::registerFile line:

12345

// app/autoload.php

// ...AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');// ...

Create the database schema and register the PHPCR node types using the following console commands:

12

$ php app/console doctrine:phpcr:init:dbal$ php app/console doctrine:phpcr:repository:init

Now you should have a number of tables in your MySQL database with the phpcr_ prefix.

Install the needed Symfony CMF ComponentsTo install the BlockBundle, run:

1 $ php composer.phar require symfony-cmf/block-bundle:master

Add the following lines to AppKernel:

123456789

10111213

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...new Sonata\BlockBundle\SonataBlockBundle(),new Symfony\Cmf\Bundle\BlockBundle\CmfBlockBundle(),

);

// ...}

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 47

Page 48: Symfony Cmf Master

Listing 11-11

Listing 11-12

Listing 11-13

Listing 11-14

SonataBlockBundle is a dependency of the CMF BlockBundle and needs to be configured. Add thefollowing to your config.yml:

123

# app/config/config.ymlsonata_block:

default_contexts: [cms]

Install DoctrineFixturesBundle

As mentioned at the start, this is not a requirement for BlockBundle or ContentBundle;nevertheless it is a good way to manage example or default content.

To install the DoctrineFixturesBundle, run:

1 $ php composer.phar require doctrine/doctrine-fixtures-bundle:dev-master

Add the following line to the registerBundles() method of the AppKernel:

123456789

101112

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),

);

// ...}

Loading Fixtures

Based on the DoctrineFixturesBundle documentation3, you will need to create a fixtures class.

To start with, create a DataFixtures directory inside your own bundle (e.g. "MainBundle"), and insidethere, create a directory named PHPCR. As you follow the examples further below, theDoctrineFixturesBundle will automatically load the fixtures classes placed here.

Within a fixtures loader, an example of creating a content block might look like this:

1234567

$myBlock = new SimpleBlock();$myBlock->setParentDocument($parentPage);$myBlock->setName('sidebarBlock');$myBlock->setTitle('My first block');$myBlock->setContent('Hello block world!');

$documentManager->persist($myBlock);

3. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 48

Page 49: Symfony Cmf Master

Listing 11-15

The above on its own will not be enough however, because there is no parent ($parentPage) to link theblocks to. There are several possible options that you can use as the parent:

• Link the blocks directly to the root document (not shown)• Create a document from the PHPCR bundle (shown below using the Generic document type)• Create a document from the CMF ContentBundle (shown below using StaticContent

document type)

Using the PHPCRTo store a CMF block directly in the PHPCR, create the following class inside your DataFixtures/PHPCRdirectory:

123456789

1011121314151617181920212223242526272829303132333435363738394041

<?php// src/Acme/MainBundle/DataFixtures/PHPCR/LoadBlockWithPhpcrParent.phpnamespace Acme\MainBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\Persistence\ObjectManager;use Doctrine\ODM\PHPCR\Document\Generic;use Symfony\Component\DependencyInjection\ContainerAwareInterface;use Symfony\Component\DependencyInjection\ContainerInterface;use Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock;

class LoadBlockWithPhpcrParent extends AbstractFixture implements ContainerAwareInterface{

public function load(ObjectManager $manager){

// Get the root document from the PHPCR$rootDocument = $manager->find(null, '/');

// Create a generic PHPCR document under the root, to use as a kind of categoryfor the blocks

$document = new Generic();$document->setParent($rootDocument);$document->setNodename('blocks');$manager->persist($document);

// Create a new SimpleBlock (see http://symfony.com/doc/master/cmf/bundles/block.html#block-types)

$myBlock = new SimpleBlock();$myBlock->setParentDocument($document);$myBlock->setName('testBlock');$myBlock->setTitle('CMF BlockBundle only');$myBlock->setContent('Block from CMF BlockBundle, parent from the PHPCR (Generic

document).');$manager->persist($myBlock);

// Commit $document and $block to the database$manager->flush();

}

public function setContainer(ContainerInterface $container = null){

$this->container = $container;}

}

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 49

Page 50: Symfony Cmf Master

Listing 11-16

Listing 11-17

Listing 11-18

Listing 11-19

Listing 11-20

Listing 11-21

This class loads an example content block using the CMF BlockBundle (without needing any other CMFbundle). To ensure the block has a parent in the repository, the loader also creates a Generic documentnamed 'blocks' within the PHPCR.

Now load the fixtures using the console:

1 $ php app/console doctrine:phpcr:fixtures:load

The content in your database should now look something like this:

1 SELECT path, parent, local_name FROM phpcr_nodes;

path parent local_name

/

/blocks / blocks

/blocks/testBlock / blocks testBlock

Using the CMF ContentBundleFollow this example to use both the CMF Block and Content components together.

The ContentBundle is best used together with the RoutingBundle. Add the following to composer.json:

12345

"require": {..."symfony-cmf/content-bundle": "dev-master","symfony-cmf/routing-bundle": "dev-master"

}

Install as before:

1 $ php composer.phar update

Add the following line to AppKernel:

123456789

101112

// app/AppKernel.php

// ...public function registerBundles(){

$bundles = array(// ...new Symfony\Cmf\Bundle\ContentBundle\CmfContentBundle(),

);

// ...}

Now you should have everything needed to load a sample content page with a sample block, so createthe LoadBlockWithCmfParent.php class:

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 50

Page 51: Symfony Cmf Master

Listing 11-22

123456789

101112131415161718192021222324252627282930313233343536373839404142434445

<?php// src/Acme/Bundle/MainBundle/DataFixtures/PHPCR/LoadBlockWithCmfParent.phpnamespace Acme\MainBundle\DataFixtures\PHPCR;

use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\Persistence\ObjectManager;use Symfony\Component\DependencyInjection\ContainerAwareInterface;use Symfony\Component\DependencyInjection\ContainerInterface;use PHPCR\Util\NodeHelper;use Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock;use Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent;

class LoadBlockWithCmfParent extends AbstractFixture implements ContainerAwareInterface{

public function load(ObjectManager $manager){

// Get the base path name to use from the configuration$session = $manager->getPhpcrSession();$basepath = $this->container->getParameter('cmf_content.static_basepath');

// Create the path in the repositoryNodeHelper::createPath($session, $basepath);

// Create a new document using StaticContent from the CMF ContentBundle$document = new StaticContent();$document->setPath($basepath . '/blocks');$manager->persist($document);

// Create a new SimpleBlock (see http://symfony.com/doc/master/cmf/bundles/block.html#block-types)

$myBlock = new SimpleBlock();$myBlock->setParentDocument($document);$myBlock->setName('testBlock');$myBlock->setTitle('CMF BlockBundle and ContentBundle');$myBlock->setContent('Block from CMF BlockBundle, parent from CMF ContentBundle

(StaticContent).');$manager->persist($myBlock);

// Commit $document and $block to the database$manager->flush();

}

public function setContainer(ContainerInterface $container = null){

$this->container = $container;}

}

This class creates an example content page using the CMF ContentBundle. It then loads our exampleblock as before, using the new content page as its parent.

By default, the base path for the content is /cms/content/static. To show how it can be configured toany path, add the following, optional entry to your config.yml:

123

# app/config/config.ymlcmf_content:

static_basepath: /content

Now it should be possible to load in the above fixtures:

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 51

Page 52: Symfony Cmf Master

Listing 11-23

Listing 11-24

Listing 11-25

Listing 11-26

1 $ php app/console doctrine:phpcr:fixtures:load

All being well, the content in your database should look something like this (if you also followed theLoadBlockWithPhpcrParent example, you should still have two /blocks entries as well):

1 SELECT path, parent, local_name FROM phpcr_nodes;

path parent local_name

/

/content / content

/content/blocks /content blocks

/content/blocks/testBlock /content/blocks testBlock

Rendering the BlocksThis is handled by the Sonata BlockBundle. sonata_block_render is already registered as a Twigextension by including SonataBlockBundle in AppKernel.php. Therefore, you can render any blockwithin any template by referring to its path.

The following code shows the rendering of both testBlock instances from the examples above. If youonly followed one of the examples, make sure to only include that block:

123456789

1011

{# src/Acme/Bundle/MainBundle/resources/views/Default/index.html.twig #}

{# include this if you followed the BlockBundle with PHPCR example #}{{ sonata_block_render({

'name': '/blocks/testBlock'}) }}

{# include this if you followed the BlockBundle with ContentBundle example #}{{ sonata_block_render({

'name': '/content/blocks/testBlock'}) }}

Now your index page should show the following (assuming you followed both examples):

12345

CMF BlockBundle onlyBlock from CMF BlockBundle, parent from the PHPCR (Generic document).

CMF BlockBundle and ContentBundleBlock from CMF BlockBundle, parent from CMF ContentBundle (StaticContent).

This happens when a block is rendered, see the BlockBundle for more details:

• a document is loaded based on the name• if caching is configured, the cache is checked and content is returned if found• each block document also has a block service, the execute method of it is called:

• you can put here logic like in a controller• it calls a template• the result is a Response object

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 52

Page 53: Symfony Cmf Master

Listing 11-27

Listing 11-28

Listing 11-29

A block can also be configured using settings, this allows you to create more advanced blocks andreuse it. The default settings are configured in the block service and can be altered in the bundleconfiguration, the twig helper and the block document. An example is an rss reader block, the urland title are stored in the settings of the block document, the maximum amount of items to displayis specified when calling sonata_block_render.

Embedding Blocks in WYSIWYG ContentThe CmfBlockBundle provides a twig filter cmf_embed_blocks that looks through the content and looksfor special tags to render blocks. To use the tag, you need to apply the cmf_embed_blocks filter to youroutput. If you can, render your blocks directly in the template. This feature is only a cheap solution forweb editors to place blocks anywhere in their HTML content. A better solution to build composed pagesis to build it from blocks (there might be a CMF bundle at some point for this).

12

{# template.twig.html #}{{ page.content|cmf_embed_blocks }}

When you apply the filter, your users can use this tag to embed a block in their HTML content:

123

<span>%embed-block:"/absolute/path/to/block"%</span>

<span>%embed-block:"local-block"%</span>

The path to the block is either absolute or relative to the current main content. The actual path to theblock must be enclosed with double quotes ". But the prefix and postfix are configurable. The defaultprefix is <span>%embed-block: and the default postfix is %</span>. Say you want to write %%%block:"/absolute/path"%%% and no <span> tag then you do:

# app/config/config.ymlcmf_block:

twig:cmf_embed_blocks:

prefix: %%%block:postfix: %%%

Currently there is no limitation built into this feature. Only enable it on content for which you aresure only trusted users may edit it. Restrictions about what block can be where that are built intoan admin interface are not respected here.

Next StepsYou should now be ready to use the BlockBundle and/or the ContentBundle in your application, or toexplore the other available CMF bundles.

• See the BlockBundle and ContentBundle documentation to learn about more advanced usage ofthese bundles

• To see a better way of loading fixtures, look at the fixtures in the CMF Sandbox4

4. https://github.com/symfony-cmf/cmf-sandbox/tree/master/src/Sandbox/MainBundle/DataFixtures/PHPCR

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 53

Page 54: Symfony Cmf Master

Listing 11-30

Listing 11-31

Listing 11-32

Listing 11-33

Listing 11-34

• Take a look at the PHPCR Tutorial5 for a better understanding of the underlying contentrepository

TroubleshootingIf you run into problems, it might be easiest to start with a fresh Symfony2 installation. You can also tryrunning and modifying the code in the external CMF Block Sandbox6 working example.

Doctrine configuration

If you started with the standard Symfony2 distribution (version 2.1.x), this should already be configuredcorrectly in your config.yml file. If not, try using the following section:

123456789

10111213

# app/config/config.ymldoctrine:

dbal:driver: "%database_driver%"host: "%database_host%"port: "%database_port%"dbname: "%database_name%"user: "%database_user%"password: "%database_password%"charset: UTF8

orm:auto_generate_proxy_classes: "%kernel.debug%"auto_mapping: true

"No commands defined" when loading fixtures

12

[InvalidArgumentException]There are no commands defined in the "doctrine:phpcr:fixtures" namespace.

Make sure AppKernel.php contains the following lines:

12

new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(),

"You did not configure a session"

12

[InvalidArgumentException]You did not configure a session for the document managers

Make sure you have the following in your config file:

123

# app/config.ymldoctrine_phpcr:

session:

5. https://github.com/phpcr/phpcr-docs/blob/master/tutorial/Tutorial.md

6. https://github.com/fazy/cmf-block-sandbox

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 54

Page 55: Symfony Cmf Master

Listing 11-35

Listing 11-36

Listing 11-37

Listing 11-38

Listing 11-39

Listing 11-40

456789

backend:type: doctrinedbalconnection: doctrine.dbal.default_connection

workspace: defaultodm:

auto_mapping: true

"Annotation does not exist"

12

[Doctrine\Common\Annotations\AnnotationException][Semantical Error] The annotation "@Doctrine\ODM\PHPCR\Mapping\Annotations\Document" inclass Doctrine\ODM\PHPCR\Document\Generic does not exist, or could not be auto-loaded.

Make sure you add this line to your app/autoload.php (immediately after theAnnotationRegistry::registerLoader line):

1 AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');

SimpleBlock class not found

12

[Doctrine\Common\Persistence\Mapping\MappingException]The class 'Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock' was not found in the chainconfigured namespaces Doctrine\ODM\PHPCR\Document, Sonata\UserBundle\Document,FOS\UserBundle\Document

Make sure the CMF BlockBundle is installed and loaded in app/AppKernel.php:

1 new Symfony\Cmf\Bundle\BlockBundle\CmfBlockBundle(),

RouteAwareInterface not found

1 Fatal error: Interface 'Symfony\Cmf\Component\Routing\RouteAwareInterface' not found in/var/www/your-site/vendor/symfony-cmf/content-bundle/Symfony/Cmf/Bundle/ContentBundle/Document/StaticContent.php on line 15

If you are using ContentBundle, make sure you have also installed the RoutingBundle:

1 $ php composer.phar require symfony-cmf/routing-bundle:dev-master

PDF brought to you bygenerated on July 18, 2013

Chapter 11: Using the BlockBundle and ContentBundle with PHPCR | 55

Page 56: Symfony Cmf Master

Listing 12-1

Chapter 12

Handling Multi-Language Documents

The goal of the tutorial is to describe all the steps that are needed to be taken to use multi-languagedocuments as clearly as possible.

Please read this firstYou need to understand how to use PHPCR-ODM. You find an introduction in Documentation on thePHPCR-ODM Doctrine bundle

Lunetics LocaleBundle

The CMF recommends to rely on the LuneticsLocaleBundle1 to handle initial locale selection when a userfirst visits the site, and to provide a locale switcher.

To install the bundle, require it in your project with:

1 $ php composer.phar require lunetics/locale-bundle

and then instantiate Lunetics\LocaleBundle\LuneticsLocaleBundle in your AppKernel.php.

You also need the intl php extension installed and enabled. (Otherwise composer will tell you it can'tfind ext-intl.) If you get issues that some locales can not be loaded, have a look at this discussion aboutICU2.

Then configure it in the main application configuration file. As there are several CMF bundles wantingthe list of allowed locales, we recommend putting them into a parameter %locales%, see the cmf-sandboxconfig.yml file3 for an example.

1. https://github.com/lunetics/LocaleBundle/

2. https://github.com/symfony/symfony/issues/5279#issuecomment-11710480

3. https://github.com/symfony-cmf/cmf-sandbox/blob/master/app/config/config.yml

PDF brought to you bygenerated on July 18, 2013

Chapter 12: Handling Multi-Language Documents | 56

Page 57: Symfony Cmf Master

Listing 12-2

Whenever you do a sub-request, for example to call a controller from a twig template, do notforget to pass the app.request.locale along or you will lose the request locale and fall back to thedefault. See for example the action to include the create.js javascript files in the create.js reference.

PHPCR-ODM multi-language DocumentsYou can mark any properties as being translatable and have the document manager store and load thecorrect language for you. Note that translation always happens on a document level, not on the individualtranslatable fields.

123456789

101112131415

<?php

/*** @PHPCRODM\Document(translator="attribute")*/class MyPersistentClass{

/*** Translated property* @String(translated=true)*/private $topic;

// ...}

Read more about multi-language documents in the PHPCR-ODM documentation on multi-language4 andsee Translation Configuration to configure PHPCR-ODM correctly.

Most of the CMF bundles provide multi-language documents, for example MultilangStaticContent,MultilangMenuNode or MultilangSimpleBlock. The routing is different, as explained in the nextsection.

RoutingThe DynamicRouter uses a route source to get routes that could match a request. The concept of thedefault PHPCR-ODM source is to map the request URL onto an id, which in PHPCR terms is therepository path to a node. This allows for a very efficient lookup without needing a full search over therepository. But a PHPCR node has exactly one path, therefore we need a separate route document foreach locale. The cool thing with this is that we can localize the URL for each language. Simply create oneroute document per locale, and set a default value for _locale to point to the locale of that route.

As all routes point to the same content, the route generator can handle picking the correct route for youwhen you generate the route from the content. See also ContentAwareGenerator and locales.

Sonata PHPCR-ODM AdminThis section explains how to make Sonata Admin handle multi-language documents. You should alreadyhave set up Sonata PHPCR-ODM Admin and understand how it works, see Creating a CMS using theCMF and Sonata.

4. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/multilang.html

PDF brought to you bygenerated on July 18, 2013

Chapter 12: Handling Multi-Language Documents | 57

Page 58: Symfony Cmf Master

Listing 12-3

Listing 12-4

Listing 12-5

Listing 12-6

The following assumes that you installed the LuneticsLocaleBundle as explained above. If youwant to use something else or write your own locale handling, first think if it would not make senseto give the Lunetics bundle a try. If you are still convinced you will need to adapt the followingtemplate examples to your way of building a locale switcher.

The first step is to configure sonata admin. We are going to place the LuneticsLocaleBundle languageswitcher in the topnav bar. To do this we need to configure the template for the user_block as shownbelow:

12345

# app/config/config.ymlsonata_admin:

# ...templates:

user_block: AcmeCoreBundle:Admin:admin_topnav.html.twig

And the template looks like this:

1234567

{# src/Acme/CoreBundle/Resources/views/Admin/admin_topnav.html.twig #}{% extends 'SonataAdminBundle:Core:user_block.html.twig' %}

{% block user_block %}{{ locale_switcher(null, null, 'AcmeCoreBundle:Admin:switcher_links.html.twig') }}{{ parent() }}

{% endblock %}

You need to tell the locale_switcher to use a custom template to display the links, which looks likethis:

12345

{# src/Acme/CoreBundle/Resources/views/Admin/switcher_links.html.twig #}Switch to :{% for locale in locales %}

{% if loop.index > 1 %} | {% endif %}<a href="{{ locale.link }}" title="{{locale.locale_target_language }}">{{ locale.locale_target_language }}</a>{% endfor %}

Now what is left to do is to update the sonata routes to become locale aware:

123456789

101112131415161718

# app/config/routing.yml

admin_dashboard:pattern: /{_locale}/admin/defaults:

_controller: FrameworkBundle:Redirect:redirectroute: sonata_admin_dashboardpermanent: true # this for 301

admin:resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'prefix: /{_locale}/admin

sonata_admin:resource: .type: sonata_adminprefix: /{_locale}/admin

PDF brought to you bygenerated on July 18, 2013

Chapter 12: Handling Multi-Language Documents | 58

Page 59: Symfony Cmf Master

1920212223242526272829303132

# redirect routes for the non-locale routesadmin_without_locale:

pattern: /admindefaults:

_controller: FrameworkBundle:Redirect:redirectroute: sonata_admin_dashboardpermanent: true # this for 301

admin_dashboard_without_locale:pattern: /admin/dashboarddefaults:

_controller: FrameworkBundle:Redirect:redirectroute: sonata_admin_dashboardpermanent: true # this for 301

When we now reload the admin dashboard, the url should be prefixed with our default locale, forexample /de/admin/dashboard. When clicking on the language switcher the page reloads and displaysthe correct content for the requested language.

The provided sonata admin classes map the locale field of the multi-language documents to the form.You need to do the same in your admins, in order to create new translations. Otherwise the languagefallback of PHPCR-ODM will make you update the original language, even when you request a differentlocale. With the mapped locale field, the editor can chose if he needs to create a new language version orupdates the loaded one.

Frontend Editing and multi-languageWhen using the CreateBundle, you do not need to do anything at all to get multi-language support.PHPCR-ODM will deliver the document in the requested language, and the callback URL is generated inthe request locale, leading to save the edited document in the same language as it was loaded.

If a translation is missing, language fallback kicks in, both when viewing the page but also whensaving the changes, so you always update the current locale.

It would make sense to offer the user the choice whether he wants to create a new translation orupdate the existing one. There is this issue5 in the CreateBundle issue tracker.

5. https://github.com/symfony-cmf/CreateBundle/issues/39

PDF brought to you bygenerated on July 18, 2013

Chapter 12: Handling Multi-Language Documents | 59

Page 60: Symfony Cmf Master

Chapter 13

The BlockBundle

The BlockBundle1 provides integration with SonataBlockBundle. It assists you in managing fragments ofcontents, so-called blocks. What the BlockBundle does is similar to what Twig does, but for blocks thatare persisted in a DB. Thus, the blocks can be made editable for an editor. Also the BlockBundle providesthe logic to determine which block should be rendered on which pages.

The BlockBundle does not provide an editing functionality for blocks itself. However, you can findexamples on how making blocks editable in the Symfony CMF Sandbox2.

Dependencies

This bundle is based on the SonataBlockBundle3.

Configuration

1. https://github.com/symfony-cmf/BlockBundle#readme

2. https://github.com/symfony-cmf/cmf-sandbox

3. https://github.com/sonata-project/SonataBlockBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 60

Page 61: Symfony Cmf Master

Listing 13-1

Listing 13-2

Listing 13-3

Updated SonataBlockBundle defaults

Prepended Configuration

The BlockBundle automatically changes some defaults and adds configuration to theSonataBlockBundle4 to make it work nicely. This is done using the prepended configuration5

option of Symfony available since version 2.2. SeeDependencyInjection\CmfBlockExtension::prepend.

Updated defaults:

• templates.block_base the cmf base template wraps the block output in a div anddashifies the PHPCR path as id; The base template is kept compatible with the Sonatabase template for non-cmf blocks;

• RssBlock configuration adds the default RssBlock settings.

Settings are only prepended, define the settings explicitly inside the app/config/config.yml to override them.

The configuration key for this bundle is cmf_block:

123

# app/config/config.ymlcmf_block:

manager_name: default

The default settings of a block are defined in the block service. If you use a 3rd party block you mightwant to alter these for your application. Use the sonata_block key for this. You can define defaultsettings for a block service type or more specific for a block class. The later is usefull as a block servicecan be used by multiple block classes and sometimes you only want specific settings for one of the blockclasses.

123456789

10

# app/config/config.ymlsonata_block:

blocks:acme_main.block.news:

settings:maxItems: 3

blocks_by_class:Symfony\Cmf\Bundle\BlockBundle\Document\RssBlock:

settings:maxItems: 3

Block DocumentBefore you can render a block, you need to create a data object representing your block in the repository.You can do so with the following code snippet:

4. https://github.com/sonata-project/SonataBlockBundle

5. http://symfony.com/doc/current/components/dependency_injection/compilation.html#prepending-configuration-passed-to-the-extension

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 61

Page 62: Symfony Cmf Master

123456789

1011

use Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock;

// ...

$myBlock = new SimpleBlock();$myBlock->setParentDocument($parentDocument);$myBlock->setName('sidebarBlock');$myBlock->setTitle('My first block');$myBlock->setContent('Hello block world!');

$documentManager->persist($myBlock);

Note the sidebarBlock is the identifier we chose for the block. Together with the parent documentof the block, this makes the block unique. The other properties are specific toSymfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock.

The simple block is now ready to be rendered, see Block rendering.

Always make sure you implement the interface Sonata\BlockBundle\Model\BlockInterface oran existing block document like Symfony\Cmf\Bundle\BlockBundle\Document\BaseBlock.

Block ContextThe BlockContext contains all information and the block document needed to render the block. Itaggregates and merges all settings from configuration, the block service, the block document and settingspassed to the twig template helper. Therefore use the BlockContext to get or alter a setting if needed.

Block ServiceIf you look inside the SimpleBlock class, you will notice the method getType. This defines the name ofthe block service that processes the block when it is rendered.

A block service contains:

• An execute method;• Default settings;• Dorm configuration;• Cache configuration;• Javascript and stylesheet assets to be loaded;• A load method.

Take a look at the block services in Symfony\Cmf\Bundle\BlockBundle\Block to see some examples.

Always make sure you implement the interfaceSonata\BlockBundle\Block\BlockServiceInterface or an existing block service likeSonata\BlockBundle\Block\BaseBlockService.

The Execute Method

This method contains controller logic:

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 62

Page 63: Symfony Cmf Master

Listing 13-4

Listing 13-5

123456789

1011121314

// ...if ($block->getEnabled()) {

$feed = false;if ($blockContext->getSetting('url', false)) {

$feed = $this->feedReader->import($block);}

return $this->renderResponse($blockContext->getTemplate(), array('feed' => $feed,'block' => $blockContext->getBlock(),'settings' => $blockContext->getSettings(),

), $response);}// ...

If you have much logic to be used, you can move that to a specific service and inject it in the blockservice. Then use this specific service in the execute method.

Default Settings

The method setDefaultSettings specifies the default settings for a block. Settings can be altered onmultiple places afterwards, it cascades like this:

• Default settings are stored in the block service;• If you use a 3rd party bundle you might want to change them in the bundle configuration for

your application see Configuration;• Settings can be altered through template helpers (see example);• And settings can also be altered in a block document, the advantage is that settings are stored

in PHPCR and allows to implement a frontend or backend UI to change some or all settings.

Example of how settings can be specified through a template helper:

1234

{{ sonata_block_render({'name': 'rssBlock'}, {'title': 'Symfony2 CMF news','url': 'http://cmf.symfony.com/news.rss'

}) }}

Form Configuration

The methods buildEditForm and buildCreateForm specify how to build the the forms for editing usinga frontend or backend UI. The method validateBlock contains the validation configuration.

Cache Configuration

The method getCacheKeys contains cache keys to be used for caching the block.

Javascript and Stylesheets

The methods getJavascripts and getStylesheets can be used to define javascript and stylesheetassets. Use the twig helpers sonata_block_include_javascripts andsonata_block_include_stylesheets to render them:

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 63

Page 64: Symfony Cmf Master

Listing 13-6

Listing 13-7

Listing 13-8

12

{{ sonata_block_include_javascripts() }}{{ sonata_block_include_stylesheets() }}

This will output the javascripts and stylesheets for all blocks loaded in the service container of yourapplication.

The Load Method

The method load can be used to load additional data. It is called each time a block is rendered before theexecute method is called.

Block renderingTo render the example from the Block Document section, just add the following code to your Twigtemplate:

1 {{ sonata_block_render({'name': '/cms/content/blocks/sidebarBlock'}) }}

In this example we specify an absolute path, however, if the block is a child of a content document, thenyou can simply specify the name of the block as follows:

1 {{ sonata_block_render({'name': 'sidebarBlock'}) }}

This will make the BlockBundle render the specified block on every page that has a child block documentnamed sidebarBlock. Of course, the actual page needs to be rendered by the template that contains thesnippet above.

When a block is rendered the following things happen:

• The block document is loaded based on its name or absolute path;• If caching is configured, the cache is checked and content is returned if found;• The execute method of the corresponding block service is called.

The execute method is the equivalent of a normal Symfony controller. It receives the block object(equivalent to a Request object) and a Response object. The purpose of the execute method to set thecontent of the response object - typically by rendering a Twig template.

You can also embed blocks in content using the cmf_embed_blocks filter.

Block typesThe block bundle comes with a couple of predefined blocks. You may write your own blocks, but often,the supplied implementations will be sufficient. This is just a quick overview, more details on each blocktype can be found in the Block Types section.

There are five general purpose blocks:

• StringBlock: A block only containing a string that is rendered without any decoration. Usefulfor page fragments;

• SimpleBlock: A simple block with nothing but a title and a field of hypertext. This wouldusually be what an editor edits directly, for example contact information;

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 64

Page 65: Symfony Cmf Master

• ContainerBlock: A block that contains zero, one or many child blocks;• ReferenceBlock: A block that references a block stored somewhere else in the content tree.

For example you might want; to refer parts of the contact information from the homepage• ActionBlock: A block that calls a Symfony2 action.

The BlockBundle also provides a couple of blocks for specific tasks, integrating third party libraries. Youshould to read the Block Types section relevant to those blocks to figure out what third party libraries youneed to load into your project.

• RssBlock: This block extends the ActionBlock, the block document saves the feed url and thecontroller action fetches the feed items. The default implementation uses the EkoFeedBundle6

to read the feed items.• ImagineBlock: A block containing an image child, the imagine filter name and optional link

url and title.• SlideshowBlock: A special case of a container block suitable for building a slideshow of

blocks. Note that this block doesn't provide any Javascript code to make the slideshow workin the frontend. You can use your favourite Javascript library to do the animation.

Examples

You can find example usages of this bundle in the Symfony CMF Sandbox7 (have a look at theBlockBundle). It also shows you how to make blocks editable using the CreateBundle.

6. https://github.com/eko/FeedBundle

7. https://github.com/symfony-cmf/cmf-sandbox

PDF brought to you bygenerated on July 18, 2013

Chapter 13: The BlockBundle | 65

Page 66: Symfony Cmf Master

Chapter 14

Block Types

The BlockBundle provides a couple of default block types for general use cases. It also has a couple ofmore specific blocks that integrate third party libraries. Those can be handy for some use cases and alsoserve as examples to build your own blocks.

StringBlockThis is a very simple block that just provides one string field called content and the default templaterenders the content as raw to allow HTML in the field. The template outputs no HTML tags around thestring at all.

SimpleBlockJust a text block with a title and a content. The default template renders both title and content as raw,meaning HTML is allowed in those fields.

This block also exists in a MultilangSimpleBlock variant that can be translated.

This block is useful to edit static text fragments and for example display it in several places using theReferenceBlock.

ContainerBlockA container can hold a list of arbitrary child blocks (even other ContainerBlocks) and just renders onechild after the other.

This block has the methods setChildren to overwrite the current children with a new list and addChildand removeChild to individually add resp. remove child blocks.

PDF brought to you bygenerated on July 18, 2013

Chapter 14: Block Types | 66

Page 67: Symfony Cmf Master

Listing 14-1

ReferenceBlockThis block has no content of its own but points to a target block. When rendered, this block renders thetarget node as if the target node was directly used in that place.

This block simply has the method setReferencedBlock that accepts any block mapped by thepersistence layer as argument. If you set this to something that is not a valid block, the problem is onlydetected when rendering the block.

ActionBlockThe action block allows to configure a controller action that will be called in a subrequest whenrendering the block. Instead of directly calling the action from a template, your CMS users can define andparametrize their own actions, and decide where to put this block.

This block is also a good base to implement specific actions if you need something more user friendly.See the RssBlock below for an example.

As the ActionBlock does a subrequest, you may also need to control the parameters that are passedto the subrequest. The block service calls resolveRequestParams($request, $blockContext) to letthe block decide what needs to be passed to the subrequest. The ActionBlock implementation letsyou configure the fields with setRequestParams and persists them in the database. It does not matterwhether the field is found in the request attributes or the request parameters, it is found in both by using$request->get(). The only request attribute propagated by default is the _locale.

RssBlockThe RssBlock extends the ActionBlock and allows you to read feed items and display them in a list.

Create a document:

123456789

101112

use Symfony\Cmf\Bundle\BlockBundle\Document\RssBlock;

// ...

$myRssBlock = new RssBlock();$myRssBlock->setParentDocument($parentPage);$myRssBlock->setName('rssBlock');$myRssBlock->setSetting('title', 'Symfony2 CMF news');$myRssBlock->setSetting('url', 'http://cmf.symfony.com/news.rss');$myRssBlock->setSetting('maxItems', 3);

$documentManager->persist($myRssBlock);

All available settings are:

• url: the url of the rss feed (required)• title: the title for the list (default: Insert the rss title)• maxItems: the maximum amount of items to return to the template (default: 10)• template: the template to render the feed items (default:

CmfBlockBundle:Block:block_rss.html.twig)• ItemClass: the class used for the item objects that are passed to the template (default:

Symfony\Cmf\Bundle\BlockBundle\Model\FeedItem)

The controller to get the feed items can also be changed:

PDF brought to you bygenerated on July 18, 2013

Chapter 14: Block Types | 67

Page 68: Symfony Cmf Master

Listing 14-2

Listing 14-3

• Define a different class for the controller service in your configuration using the DI serviceparameter cmf_block.rss_controller_class

• or set the actionName of your RssBlock document

The Symfony CMF Sandbox1 contains an example of the RssBlock.

ImagineBlock

The imagine block uses the LiipImagineBundle2 to display images directly out of PHPCR. The block hasa child of type nt:file and fields for the name of the imagine filter to use, an URL and an image caption.To use this block, you need to add liip/imagine-bundle to your composer.json and define the imaginefilter you specify in the block. The default name is cmf_block. The filter must use the phpcr driver:

123456789

10

# app/config/config.ymlliip_imagine:

# ...filter_sets:

cmf_block:data_loader: phpcrquality: 85filters:

thumbnail: { size: [616, 419], mode: outbound }# ...

Refer to the LiipImagineBundle documentation3 for further information.

See the example below for how to create an ImagineBlock programmatically.

SlideshowBlockThe SlideshowBlock is just a special kind of ContainerBlock. It can contain any kind of blocks that willbe rendered with a wrapper div to help a javascript slideshow library to slide them. The ImagineBlock isparticularly suited if you want to do an image slideshow but the SlideshowBlock can handle any kind ofblocks, also mixed types of blocks in the same slideshow.

This bundle does not attempt to provide a javascript library for animating the slideshow. Choseyour preferred library that plays well with the rest of your site and hook it on the slideshows. (Seealso below).

Create your first Slideshow

Creating a slideshow consists of creating the container SlideshowBlock and adding blocks to it. Thoseblocks can be anything, but an image makes a lot of sense:

1. https://github.com/symfony-cmf/cmf-sandbox

2. https://github.com/liip/LiipImagineBundle

3. https://github.com/liip/LiipImagineBundle/tree/master/Resources/doc

PDF brought to you bygenerated on July 18, 2013

Chapter 14: Block Types | 68

Page 69: Symfony Cmf Master

Listing 14-4

Listing 14-5

123456789

10111213141516171819202122232425

use Symfony\Cmf\Bundle\BlockBundle\Document\SlideshowBlock;use Symfony\Cmf\Bundle\BlockBundle\Document\ImagineBlock;// the Image will be moved to Symfony\Cmf\Bundle\MediaBundle\Model\Imageuse Doctrine\ODM\PHPCR\Document\Image;use Doctrine\ODM\PHPCR\Document\File;

// create slideshow$mySlideshow = new SlideshowBlock();$mySlideshow->setName('slideshow');$mySlideshow->setParentDocument($parentPage);$mySlideshow->setTitle('My first Slideshow');$documentManager->persist($mySlideshow);

// add first slide to slideshow$mySlideshowItem = new ImagineBlock();$mySlideshowItem->setName('first_item');$mySlideshowItem->setLabel('label of first item');$mySlideshowItem->setParentDocument($mySlideshow);$manager->persist($mySlideshowItem);

$file = new File();$file->setFileContentFromFilesystem('path/to/my/image.jpg');$image = new Image();$image->setFile($file);$mySlideshowItem->setImage($image);

Render the slideshow

Rendering your slideshow is as easy as just rendering the according block in your template. If yourcontentDocument has a field slideshow that contains a SlideshowBlock object, you can simply renderit with:

123

{{ sonata_block_render({'name': 'slideshow'

}) }}

Make the slideshow work in the frontend

Since the BlockBundle doesn't contain anything to make the slideshow work in the frontend, youneed to do this yourself. Just use your favourite JS library to make the slideshow interactive. If specialmarkup is needed for your slideshow code to work, just overrideBlockBundle:Block:block_slideshow.html.twig or the templates of the blocks you use as slideshowitems and adapt them to your needs.

Use the Sonata admin class

The BlockBundle comes with an admin class for managing slideshow blocks. All you need to do toadministrate slideshows in your project is to add the following line to your sonata admin configuration:

12345

sonata_admin:dashboard:

groups:blocks:

label: Blocks

PDF brought to you bygenerated on July 18, 2013

Chapter 14: Block Types | 69

Page 70: Symfony Cmf Master

67

items:- cmf_block.slideshow_admin

However, you can also embed the slideshow administration directly into other admin classes using thesonata_type_admin form type. The admin service to use in that case is cmf_block.slideshow_admin.Please refer to the Sonata Admin documentation4 for further information.

4. http://sonata-project.org/bundles/admin/master/doc/reference/form_types.html

PDF brought to you bygenerated on July 18, 2013

Chapter 14: Block Types | 70

Page 71: Symfony Cmf Master

Listing 15-1

Chapter 15

Create your own Blocks

Follow these steps to create a block:

• create a block document;• create a block service and declare it (optional);• create a data object representing your block in the repository, see Block Document;• render the block, see Block rendering;

Lets say you are working on a project where you have to integrate data received from several RSS feeds.Of course you could create an ActionBlock for each of these feeds, but wouldn't this be silly? In fact allthose actions would look similar: Receive data from a feed, sanitize it and pass the data to a template. Soinstead you decide to create your own block, the RSSBlock.

Create a block documentThe first thing you need is an document that contains the data. It is recommended to extendSymfony\Cmf\Bundle\BlockBundle\Document\BaseBlock contained in this bundle (however you arenot forced to do so, as long as you implement Sonata\BlockBundle\Model\BlockInterface). In yourdocument, you need to define the getType method which just returns acme_main.block.rss.

123456789

1011121314

// src/Acme/MainBundle/Document/RssBlock.phpnamespace Acme\MainBundle\Document;

use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;use Symfony\Cmf\Bundle\BlockBundle\Document\BaseBlock;

/*** Rss Block** @PHPCRODM\Document(referenceable=true)*/class RssBlock extends BaseBlock{

public function getType()

PDF brought to you bygenerated on July 18, 2013

Chapter 15: Create your own Blocks | 71

Page 72: Symfony Cmf Master

Listing 15-2

Listing 15-3

15161718

{return 'acme_main.block.rss';

}}

Create a Block ServiceYou could choose to use a an already existing block service because the configuration and logic alreadysatisfy your needs. For our rss block we create a service that knows how to handle RSSBlocks:

• The method setDefaultSettings configures a template, title, url and the maximum amountof items:

123456789

1011

// ...public function setDefaultSettings(OptionsResolverInterface $resolver){

$resolver->setDefaults(array('template' => 'AcmeMainBundle::Block::block_rss.html.twig','url' => false,'title' => 'Insert the rss title','maxItems' => 10,

));}// ...

• The execute method passes the settings to an rss reader service and forwards

• The feed items to a template, see The Execute Method

Make sure you implement the interface Sonata\BlockBundle\Block\BlockServiceInterface or anexisting block service like Sonata\BlockBundle\Block\BaseBlockService.

Define the service in a config file. It is important to tag your BlockService with sonata.block, otherwiseit will not be known by the Bundle.

123456789

101112

acme_main.rss_reader:class: Acme\MainBundle\Feed\SimpleReader

sandbox_main.block.rss:class: Acme\MainBundle\Block\RssBlockServicearguments:

- "acme_main.block.rss"- "@templating"- "@sonata.block.renderer"- "@acme_main.rss_reader"

tags:- {name: "sonata.block"}

PDF brought to you bygenerated on July 18, 2013

Chapter 15: Create your own Blocks | 72

Page 73: Symfony Cmf Master

Chapter 16

Cache

The BlockBundle integrates with the SonataCacheBundle1 to provide several caching solutions. Have alook at the available adapters in the SonataCacheBundle to see all options.

The BlockBundle additionally provides its own adapters for:

• ESI2

• SSI3

• Asynchronous javascript• Synchronous javascript

It is advised to store all settings in the block document when using cache. See also Block Rendering.

Dependencies

The cache functionality is optional and depends on the SonataCacheBundle4.

InstallationThe installation is split between the SonataCacheBundle, the CmfBlockBundle and theSonataBlockBundle:

1. SonataCacheBundle - Follow the installation instructions from the SonataCacheBundledocumentation5.

2. CmfBlockBundle - At the end of your routing file, add the following lines:

1. https://github.com/sonata-project/SonataCacheBundle

2. http://wikipedia.org/wiki/Edge_Side_Includes

3. http://wikipedia.org/wiki/Server_Side_Includes

4. https://github.com/sonata-project/SonataCacheBundle

5. http://sonata-project.org/bundles/cache/master/doc/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 16: Cache | 73

Page 74: Symfony Cmf Master

Listing 16-1

Listing 16-2

1234567

# app/config/routing.yml

# ...# routes CmfBlockBundle cache adaptersblock_cache:

resource: "@CmfBlockBundle/Resources/config/routing/cache.xml"prefix: /

3. SonataBlockBundle - Use the sonata_block key to configure the cache adapter for each blockservice:

123456789

1011

# app/config/config.ymlsonata_block:# ...

blocks:acme_main.block.news:

# use the service id of the cache adaptercache: cmf.block.cache.js_async

blocks_by_class:# cache only the RssBlock and not all action blocksSymfony\Cmf\Bundle\BlockBundle\Document\RssBlock:

cache: cmf.block.cache.js_async

WorkflowThe following happens when a block is rendered using cache:

• A document is loaded based on the name• If caching is configured, the cache is checked and content is returned if found

• Cache keys are computed using:

• The cache keys of the block service• The extraCacheKeys passed from the template

• The cache adapter is asked for a cache element

• The ESI and SSI adapter add a specific tag and a url to retrieve the blockcontent

• The Javascript adapter adds javascript and a url to retrieve the blockcontent

• If the cache element is not expired and has data it is returned

• The template is rendered:

• For ESI and SSI the url is called to retrieve the block content• For Javascript the browser calls a url and replaces a placeholder with the• returned block content

The additional cache adapters of the BlockBundle always return that the cache is found, have alook at the has method of the adapters in the SonataCacheBundle to see how they respond.

PDF brought to you bygenerated on July 18, 2013

Chapter 16: Cache | 74

Page 75: Symfony Cmf Master

If cache is checked and the cache adapter returned that no cache was found, the workflow proceeds likethis:

• Each block document also has a block service, the execute method of it is called to render theblock and return a response

• If the response is cacheable the configured adapter creates a cache element, it contains

• The computed cache keys• The ttl of the response• The response• And additional contextual keys

• The template is rendered

Cache KeysThe block service has the responsibility to generate the cache keys, the method getCacheKeys returnsthese keys, see Block Service.

The block services shipped with the BlockBunde use the getCacheKeys method of theSonata\BlockBundle\Block\BaseBlockService, and return:

• block_id• updated_at

If block settings need to be persisted between requests it is advised to store them in the blockdocument. Alternatively they can be added to the cache keys. However be very cautious because,depending on the adapter, the cache keys can be send to the browser and are not secure.

Extra Cache Keys

The extra cache keys array is used to store metadata along the cache element. The metadata can be usedto invalidate a set of cache elements.

Contextual Keys

The contextual cache array hold the object class and id used inside the template. This contextual cachearray is then added to the extra cache key.

This feature can be use like this $cacheManager->remove(array('objectId' => 'id')).

Of course not all cache adapters support this feature, varnish and MongoDB do.

The BlockBundle also has a cache invalidation listener that calls the flush method of a cache adapterautomatically when a cached block document is updated or removed.

Block RenderingThe following parameters can be used in the sonata_block_render code in your Twig template whenusing cache:

• use_cache: use the configured cache for a block (default: true)• extra_cache_keys: expects an array with extra cache keys (default: empty array)

PDF brought to you bygenerated on July 18, 2013

Chapter 16: Cache | 75

Page 76: Symfony Cmf Master

Listing 16-3

Listing 16-4

Listing 16-5

1234

{{ sonata_block_render({ 'name': 'rssBlock' }, {use_cache: true,extra_cache_keys: { 'extra_key': 'my_block' }

}) }}

When using the Esi, Ssi or Js cache adapters the settings passed here are remembered:

12345

{{ sonata_block_render({ 'name': 'rssBlock' }, {'title': 'Symfony2 CMF news','url': 'http://cmf.symfony.com/news.rss','maxItems': 2

}) }}

The default BlockContextManager of the SonataBlockBundle automatically adds settings passed fromthe template to the extra_cache_keys with the key context. This allows the cache adapters to rebuildthe BlockContext. See also the SonataBlockBundle Advanced usage6 documentation.

Secure the cache adapter url if needed as the settings from sonata_block_render are added to theurl as parameters.

Because, as mentioned above, settings can be added to the url as parameters avoid exposingsensitive settings from sonata_block_render and try to store them in the block document.

Adapters

ESI

This extends the default VarnishCache adapter of the SonataCacheBundle.

Configuration

123456789

1011121314

# app/config/config.ymlframework:

# ...esi: { enabled: true }# enable FragmentListener to automatically validate and secure fragmentsfragments: { path: /_fragment }# add varnish server ip-address(es)trusted_proxies: [192.0.0.1, 10.0.0.0/8]

cmf_block:# ...caches:

varnish:token: a unique security key # a random one is generated by default

6. http://sonata-project.org/bundles/block/master/doc/reference/advanced_usage.html#block-context-manager-context-cache

PDF brought to you bygenerated on July 18, 2013

Chapter 16: Cache | 76

Page 77: Symfony Cmf Master

Listing 16-6

1516

servers:- varnishadm -T 127.0.0.1:2000 {{ COMMAND }} "{{ EXPRESSION }}"

SSI

This extends the default SsiCache adapter of the SonataCacheBundle.

Configuration

123456

# app/config/config.ymlcmf_block:

# ...caches:

ssi:token: a unique security key # a random one is generated by default

Javascript

Renders the block using javascript, the page is loaded and not waiting for the block to be finishedrendering or retrieving data. The block is then asynchronously or synchronously loaded and added to thepage.

PDF brought to you bygenerated on July 18, 2013

Chapter 16: Cache | 77

Page 78: Symfony Cmf Master

Chapter 17

Relation to Sonata Block Bundle

The BlockBundle is based on the SonataBlockBundle1. It replaces components of the bundle whereneeded to be compatible with PHPCR.

ClassdiagramThe following picture shows where we use our own components (blue):

../../../_images/classdiagram.jpg

1. https://github.com/sonata-project/SonataBlockBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 17: Relation to Sonata Block Bundle | 78

Page 79: Symfony Cmf Master

Listing 18-1

Chapter 18

BlogBundle

This bundle aims to provide everything you need to create a full blog or blog-like system. It also includesin-built support for the Sonata Admin bundle to help you get up-and-running quickly.

Current features:

• Host multiple blogs within a single website;• Place blogs anywhere within your route hierarchy;• Sonata Admin integration.

Pending features:

• Full tag support;• Frontend pagination (using knp-paginator);• RSS/ATOM feed;• Comments;• User support (FOSUserBundle).

Notes on this document• The XML configuration examples may be formatted incorrectly.

Dependencies• CmfRoutingBundle is used to manage the routing;• CmfRoutingAutoBundle is used to manage automatically generate routes;• PHPCR-ODM is used to persist the bundles documents.

ConfigurationExample:

PDF brought to you bygenerated on July 18, 2013

Chapter 18: BlogBundle | 79

Page 80: Symfony Cmf Master

Listing 18-2

Listing 18-3

Listing 18-4

123456789

# app/config.ymlcmf_blog:

use_sonata_admin: autoblog_basepath: /cms/blogclass:

blog_admin: Symfony\Cmf\Bundle\BlogBundle\Admin\BlogAdmin # Optionalpost_admin: Symfony\Cmf\Bundle\BlogBundle\Admin\PostAdmin # Optionalblog: Symfony\Cmf\Bundle\BlogBundle\Document\Blog # Optionalpost: Symfony\Cmf\Bundle\BlogBundle\Document\Post # Optional

Explanation:

• use_sonata_admin - Specify whether to attempt to integrate with sonata admin;• blog_basepath - required Specify the path where the blog content should be placed when

using sonata admin;• class - Allows you to specify custom classes for sonata admin and documents; * blog_admin:

FQN of the sonata admin class to use for managing Blog's; * post_admin: FQN of the sonataadmin class to use for managing Post's; * blog: FQN of the document class that sonata adminwill use for Blog's; * post: FQN of the document class that sonata admin will use for Post's.

If you change the default documents it is necessary to update the auto routing configuration, asthe auto routing system will not recognize your new classes and consequently will not generate anyroutes.

Auto Routing

The blog bundle uses the CmfRoutingAuto bundle to generate a route for each content. You will need anauto routing configuration for this to work.

You can include the default in the main configuration file as follows:

# app/config/config.ymlimports:

# ...- { resource: @CmfBlogBundle/Resources/config/routing/autoroute_default.yml }

# ...

The default configuration will produce URLs like the following:

1 http://www.example.com/blogs/dtls-blog/2013-04-14/this-is-my-post

Refer to the RoutingAutoBundle documentation for more information.

Content Routing

To enable the routing system to automatically forward requests to the blog controller when a Blog orPost content is associated with a route, add the following under the controllers_by_class section ofcmf_routing_extra in the main configuration file:

12345

# app/config/config.ymlcmf_routing_extra:

# ...dynamic:

# ...

PDF brought to you bygenerated on July 18, 2013

Chapter 18: BlogBundle | 80

Page 81: Symfony Cmf Master

Listing 18-5

Listing 18-6

Listing 18-7

6789

controllers_by_class:# ...Symfony\Cmf\Bundle\BlogBundle\Document\Blog: cmf_blog.blog_controller:listActionSymfony\Cmf\Bundle\BlogBundle\Document\Post:

cmf_blog.blog_controller:viewPostAction

Sonata Admin

The BlogBundle has admin services defined for Sonata Admin, to make the blog system visible on yourdashboard, add the following to the sonata_admin section:

123456789

1011

# app/config/config.ymlsonata_admin:

# ...dashboard:

groups:# ...blog:

label: blogitems:

- cmf_blog.admin- cmf_post.admin

Tree Browser Bundle

If you use the Symfony CMF Tree Browser bundle you can expose the blog routes to enable blog editionfrom the tree browser. Expose the routes in the fos_js_routing section of the configuration file:

1234567

# app/config/config.ymlfos_js_routing:

routes_to_expose:# ...- admin_bundle_blog_blog_create- admin_bundle_blog_blog_delete- admin_bundle_blog_blog_edit

Integration

Templating

The default templates are marked up for Twitter Bootstrap1. But it is easy to completely customize thetemplates by overriding them.

The one template you will have to override is the default layout, you will need to change it and make itextend your applications layout. The easiest way to do this is to create the following file:

123

{# app/Resources/CmfBlogBundle/views/default_layout.html.twig #}{% extends "MyApplicationBundle::my_layout.html.twig" %}

1. http://twitter.github.com/bootstrap/

PDF brought to you bygenerated on July 18, 2013

Chapter 18: BlogBundle | 81

Page 82: Symfony Cmf Master

45

{% block content %}{% endblock %}

The blog will now use MyApplicationBundle::my_layout.html.twig instead ofCmfBlogBundle::default_layout.html.twig.

See `Overriding Bundle Templates`_ in the Symfony documentation for more information.

PDF brought to you bygenerated on July 18, 2013

Chapter 18: BlogBundle | 82

Page 83: Symfony Cmf Master

Listing 19-1

Chapter 19

ContentBundle

This bundle provides a document for static content and the controller to render it.

For an introduction see the Content article in the "Getting started" section.

ConfigurationThe configuration key for this bundle is cmf_content:

123456789

10111213

# app/config/config.ymlcmf_content:

admin_class: ~document_class: ~default_template: ~content_basepath: /cms/contentstatic_basepath: /cms/content/staticuse_sonata_admin: automultilang: # the whole multilang section is optional

admin_class: ~document_class: ~use_sonata_admin: autolocales: [] # if you use multilang, you have to define at least one

locale

PDF brought to you bygenerated on July 18, 2013

Chapter 19: ContentBundle | 83

Page 84: Symfony Cmf Master

Listing 20-1

Chapter 20

CoreBundle

This is the CoreBundle1 for the Symfony2 content management framework. This bundle providescommon functionality, helpers and utilities for the other CMF bundles.

One of the provided features is an interface and implementation of a publish workflow checker with anaccompanying interface that models can implement that want to support this checker.

Furthermore it provides a twig helper exposing several useful functions for twig templates to interact withPHPCR-ODM documents.

Configuration

1234567

# app/config/config.ymlcmf_core:

document_manager_name: ~ # used for the twig functions to fetch documentspublish_workflow:

enabled: trueview_non_published_role: ROLE_CAN_VIEW_NON_PUBLISHEDrequest_listener: true

The publish workflow is enabled by default. If you do not want to use it, you can setcmf_core.publish_workflow.enabled: false to gain some performance.

Publish WorkflowThe publish workflow system allows to control what content is available on the site. This is similarto the Symfony2 Security component2. But contrary to the security context, the publish check can beexecuted even when no firewall is in place and the security context thus has no token (see Symfony2Authorization3).

1. https://github.com/symfony-cmf/CoreBundle#readme

2. http://www.symfony.com/doc/current/components/security/index.html

3. http://www.symfony.com/doc/current/components/security/authorization.html

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 84

Page 85: Symfony Cmf Master

Listing 20-2

Listing 20-3

Listing 20-4

The publish workflow is also tied into the security workflow: The core bundle registers a security voterthat forwards security checks to the publish workflow. This means that if you always have a firewall, youcan just use the normal security context and the twig function is_granted to check for publication.

A good introduction to the Symfony core security is the Security Chapter4 in the Symfony2 book.

Check if Content is Published

The Bundle provides the cmf_core.publish_workflow.checker service which implements theSecurityContextInterface5 of the Symfony security component. The method to check publication is,like with the security context, isGranted()6.

This method is used as when doing ACL checks7: The first argument is the desired action, the second thecontent object you want to do the action on.

In 1.0, the only actions supported by the default voters are VIEW and VIEW_ANONYMOUS. Having the right toview means that the current user is allowed to see this content either because it is published or because ofhis specific permissions. In some contexts, your application might not want to show unpublished contenteven to a privileged user so as not to confuse him. For this, the "view anonymous" permission is used.

The workflow checker is configured with a role that is allowed to bypass publication checks so thatit can see unpublished content. This role should be given to editors. The default name of the role isROLE_CAN_VIEW_NON_PUBLISHED.

1234

# app/config/security.ymlsecurity:

role_hierarchy:ROLE_EDITOR: ROLE_CAN_VIEW_NON_PUBLISHED

Once a user with ROLE_EDITOR is logged in - meaning there is a firewall in place for the path in question- he will have the permission to view unpublished content as well:

123456789

10111213141516171819

use Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowChecker;

// check if current user is allowed to see this document$publishWorkflowChecker = $container->get('cmf_core.publish_workflow.checker');if ($publishWorkflowChecker->isGranted(

PublishWorkflowChecker::VIEW_ATTRIBUTE,$document)

) {// ...

}// check if the document is published. even if the current role would allow// to see the document, this will still return false if the documet is not// publishedif ($publishWorkflowChecker->isGranted(

PublishWorkflowChecker::VIEW_ANONYMOUS_ATTRIBUTE,$document

)) {// ...

}

To check publication in a template, use the twig function cmf_is_published:

4. http://www.symfony.com/doc/current/book/security.html

5. http://api.symfony.com/master/Symfony/Component/Security/Core/SecurityContextInterface.html

6. http://api.symfony.com/master/Symfony/Component/Security/Core/SecurityContextInterface.html#isGranted()

7. http://www.symfony.com/doc/current/cookbook/security/acl.html

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 85

Page 86: Symfony Cmf Master

123456789

10111213

{# check if document is published, regardless of current users role #}{% if cmf_is_published(page) %}

{# ... output the document #}{% endif %}

{#check if current logged in user is allowed to view the document eitherbecause it is published or because the current user may view unpublisheddocuments.

#}{% if is_granted('VIEW', page) %}

{# ... output the document #}{% endif %}

Code that loads content should do the publish checks. Note that the twig functions already check forpublication. Thanks to a request listener, routes and the main content provided by the DynamicRouterare checked automatically as well.

It is possible to set the security token explicitly on the workflow checker. But by default, the checkerwill acquire the token from the default security context, and if there is none (typically when there is nofirewall in place for that URL), an AnonymousToken8 is created on the fly.

If you check for VIEW and not VIEW_ANONYMOUS, the first check is whether the security context knows thecurrent user and if that user is granted the bypass role. If so, access is granted, otherwise the decision isdelegated to a AccessDecisionManager9 which calls all voters with the requested attributes, the objectand the token.

The decision manager is configured for an unanimous vote with "allow if all abstain". This means a singlevoter saying ACCESS_DENIED is enough for the content to be considered not published. If all voters abstain(for example when the content in question does not implement any workflow features) the content is stillconsidered published.

Publish Voters

A voter has to implement the VoterInterface10. It will get passed a content object and has to decidewhether it is published according to its rules. The CoreBundle provides a couple of generic voters thatcheck the content for having an interface exposing the methods they need. If the content implementsthe interface, they check the parameter and return ACCESS_GRANTED or ACCESS_DENIED, otherwise theyreturn ACCESS_ABSTAIN.

As voting is unanimous, each voter returns ACCESS_GRANTED if its criteria is met, but if a single voterreturns ACCESS_DENIED, the content is considered not published.

You can also implement your own voters for additional publication behaviour.

PublishableVoter

This voter checks on the PublishableInterface which simply has a method to return a boolean value.

• isPublishable: If the object should be considered for publication or not.

TimePeriodVoter

This voter checks on the PublishTimePeriodInterface which defines a start and end date. A date maybe null to indicate "always started" resp. "never ending".

8. http://api.symfony.com/master/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.html

9. http://api.symfony.com/master/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.html

10. http://api.symfony.com/master/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 86

Page 87: Symfony Cmf Master

Listing 20-5

Listing 20-6

• getPublishStartDate: If non-null, the date from which the document should start beingpublished;

• getPublishEndDate: If non-null, the date from which the document should stop beingpublished.

Custom Voters

To build voters with custom logic, you need to implement VoterInterface11 and define a service withthe tag cmf_published_voter. This is similar to the security.voter tag, but adds your voter to thepublish workflow. As with the security voters, you can specify a priority, though it is of limited use asthe access decision must be unanimous. If you have more expensive checks, you can lower the priority ofthose voters.

services:acme.security.publishable_voter:

class: %my_namespace.security.publishable_voter.class%tags:

- { name: cmf_published_voter, priority: 30 }

As the workflow checker will create an AnonymousToken12 on the fly if the security context has none,voters must be able to handle this situation when accessing the user. Also when accessing the securitycontext, they first must check if it has a token and otherwise not call it to avoid triggering an exception.If a voter only gives access if there is a current user fulfills some requirement, it simply has to returnACCESS_DENIED if there is no current user.

Publication Request Listener

The DynamicRouter places the route object and the main content - if the route has a main content -into the request attributes. Unless you disable the cmf_core.publish_workflow.request_listener,this listener will listen on all requests and check publication of both the route object and the main contentobject.

This means that custom templates for templates_by_class and the controllers ofcontrollers_by_class need not check for publication explicitly as its already done.

Editing publication information: Publish Workflow Sonata Admin Extension

There is a write interface for each publish workflow too, defining setter methods. The core bundleprovides extensions for SonataAdminBundle to easily add editing of the publish workflow fields to all orselected admins.

Instead of implementing PublishableInterface resp. PublishTimePeriodInterface you modelsinstead need to implement the PublishableWriteInterface and / orPublishTimePeriodWriteInterface.

To enable the extensions in your admin classes, simply define the extension configuration in thesonata_admin section of your project configuration:

1234567

# app/config/config.ymlsonata_admin:

# ...extensions:

symfony_cmf_core.publish_workflow.admin_extension.publishable:implements:

- Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishableWriteInterface

11. http://api.symfony.com/master/Symfony/Component/Security/Core/Authentication/Voter/VoterInterface.html

12. http://api.symfony.com/master/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.html

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 87

Page 88: Symfony Cmf Master

Listing 20-7

89

10

symfony_cmf_core.publish_workflow.admin_extension.time_period:implements:

-Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishTimePeriodWriteInterface

See the Sonata Admin extension documentation13 for more information.

Dependency Injection Tags

cmf_request_aware

If you have services that need the request (e.g. for the publishing workflow or current menu item voters),you can tag them with cmf_request_aware to have a kernel listener inject the request. Any class used insuch a tagged service must have the setRequest method or you will get a fatal error:

123456789

1011

use Symfony\Component\HttpFoundation\Request;

class MyClass{

private $request;

public function setRequest(Request $request){

$this->request = $request;}

}

You should only use this tag on services that will be needed on every request. If you use thistag excessively you will run into performance issues. For seldom used services, you can injectthe container in the service definition and call $this->container->get('request') in your codewhen you actually need the request.

For Symfony 2.3, this tag is automatically translated to a synchronized service14 but as Symfony 2.2 doesnot have that feature, you can use this tag for bundles that you want to be able to use with Symfony 2.2.In custom applications that run with Symfony 2.3, there is no need for this tag, just use the synchronizedservice feature.

cmf_published_voter

Used to activate custom voters for the publish workflow . Tagging a service with cmf_published_voterintegrates it into the access decision of the publish workflow.

This tag has the attribute priority. The lower the priority number, the earlier the voter gets to vote.

13. http://sonata-project.org/bundles/admin/master/doc/reference/extensions.html

14. http://symfony.com/doc/current/cookbook/service_container/scopes.html#using-a-synchronized-service

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 88

Page 89: Symfony Cmf Master

Listing 20-8

Templating

Twig

<<<<<<< HEAD The core bundle contains a Twig extension that provides a set of useful functions foryour templates. The functions respect the publish workflow if it is

• cmf_find: returns the document for the provided path• cmf_find_many: returns an array of documents for the provided paths• cmf_is_published: checks if the provided document is published, see bundle-core-

publish_workflow-twig_function.• cmf_prev: returns the previous document by examining the child nodes of the provided parent• cmf_prev_linkable: returns the previous linkable document by examining the child nodes of

the provided parent• cmf_next: returns the next document by examining the child nodes of the provided parent• cmf_next_linkable: returns the next linkable document by examining the child nodes of the

provided parent• cmf_child: returns a child documents of the provided parent document and child node• cmf_children: returns an array of all the children documents of the provided parent• cmf_linkable_children: returns an array of all the linkable children documents of the

provided parent• cmf_descendants: returns an array of all descendants paths of the provided parent• cmf_document_locales: gets the locales of the provided document• cmf_nodename: returns the node name of the provided document• cmf_parent_path: returns the parent path of the provided document• cmf_path: returns the path of the provided document

123456789

1011121314151617181920212223242526272829

{% set page = cmf_find('/some/path') %}

{% if cmf_is_published(page) %}{% set prev = cmf_prev(page) %}{% if prev %}

<a href="{{ path(prev) }}">prev</a>{% endif %}

{% set next = cmf_next(page) %}{% if next %}

<span style="float: right; padding-right: 40px;"><a href="{{ path(next)}}">next</a></span>

{% endif %}

{% for news in cmf_children(page)|reverse %}<li><a href="{{ path(news) }}">{{ news.title }}</a> ({{ news.publishStartDate |

date('Y-m-d') }})</li>{% endfor %}

{% if 'de' in cmf_document_locales(page) %}<a href="{{ path(

app.request.attributes.get('_route'),

app.request.attributes.get('_route_params')|merge(app.request.query.all)|merge({'_locale': 'de'

})) }}">DE</a>

{% endif %}{% if 'fr' in cmf_document_locales(page) %}

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 89

Page 90: Symfony Cmf Master

Listing 20-9

3031323334

<a href="{{ path(app.request.attributes.get('_route'),

app.request.attributes.get('_route_params')|merge(app.request.query.all)|merge({'_locale': 'fr'

})) }}">FR</a>

{% endif %}{% endif %}

PHP

The bundle also provides a templating helper to use in PHP templates, it contains the following methods:

• find: returns the document for the provided path• findMany: returns an array of documents for the provided paths• isPublished: checks if the provided document is published• getPrev: returns the previous document by examining the child nodes of the provided parent• getPrevLinkable: returns the previous linkable document by examining the child nodes of the

provided parent• getNext: returns the next document by examining the child nodes of the provided parent• getNextLinkable: returns the next linkable document by examining the child nodes of the

provided parent• getChild: returns a child documents of the provided parent document and child node• getChildren: returns an array of all the children documents of the provided parent• getLinkableChildren: returns an array of all the linkable children documents of the provided

parent• getDescendants: returns an array of all descendants paths of the provided parent• getLocalesFor: gets the locales of the provided document• getNodeName: returns the node name of the provided document• getParentPath: returns the parent path of the provided document• getPath: returns the path of the provided document

123456789

10111213141516171819202122

<?php $page = $view['cmf']->find('/some/path') ?>

<?php if $view['cmf']->isPublished($page) : ?><?php $prev = $view['cmf']->getPrev($page) ?><?php if ($prev) : ?>

<a href="<?php echo $view['router']->generate($prev) ?>">prev</a><?php endif ?>

<?php $next = $view['cmf']->getNext($page) ?><?php if ($next) : ?>

<span style="float: right; padding-right: 40px;"><a href="<?php echo $view['router']->generate($next) ?>">next</a>

</span><?php endif ?>

<?php foreach (array_reverse($view['cmf']->getChildren($page)) as $news) : ?><li>

<a href="<?php echo $view['router']->generate($news) ?>"><?php echo$news->getTitle() ?></a>

(<?php echo date('Y-m-d', $news->getPublishStartDate()) ?>)</li>

<?php endforeach ?>

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 90

Page 91: Symfony Cmf Master

23242526272829303132333435363738394041424344454647

<?php if (in_array('de', $view['cmf']->getLocalesFor($page))) : ?><a href="<?php $view['router']->generate

$app->getRequest()->attributes->get('_route'),array_merge(

$app->getRequest()->attributes->get('_route_params'),array_merge(

$app->getRequest()->query->all(),array('_locale' => 'de')

))

?>">DE</a><?php endif ?><?php if (in_array('fr', $view['cmf']->getLocalesFor($page))) : ?>

<a href="<?php $view['router']->generate$app->getRequest()->attributes->get('_route'),array_merge(

$app->getRequest()->attributes->get('_route_params'),array_merge(

$app->getRequest()->query->all(),array('_locale' => 'fr')

))

?>">FR</a><?php endif ?>

<?php endif ?>

PDF brought to you bygenerated on July 18, 2013

Chapter 20: CoreBundle | 91

Page 92: Symfony Cmf Master

Listing 21-1

Chapter 21

CreateBundle

The CreateBundle1 integrates create.js and the createphp helper library into Symfony2.

Create.js is a comprehensive web editing interface for Content Management Systems. It is designedto provide a modern, fully browser-based HTML5 environment for managing content. Create can beadapted to work on almost any content management backend. The default editor is the Hallo Editor, butyou can also use CKEditor. See http://createjs.org/2 for more information.

Createphp is a PHP library to help with RDFa annotating your documents/entities. Seehttps://github.com/flack/createphp3 for documentation on how it works.

DependenciesThis bundle includes create.js (which bundles all its dependencies like jquery, vie, hallo, backbone etc) asa git submodule. Do not forget to add the composer script handler to your composer.json as describedbelow.

PHP dependencies are managed through composer. We use createphp as well as AsseticBundle,FOSRestBundle and by inference also JmsSerializerBundle. Make sure you instantiate all those bundlesin your kernel and properly configure assetic.

InstallationThis bundle is best included using Composer.

Edit your project composer file to add a new require for symfony-cmf/create-bundle. Then create ascripts section or add to the existing one:

12

{"scripts": {

1. https://github.com/symfony-cmf/CreateBundle

2. http://createjs.org/

3. https://github.com/flack/createphp

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 92

Page 93: Symfony Cmf Master

Listing 21-2

Listing 21-3

Listing 21-4

Listing 21-5

3456789

101112

"post-install-cmd": ["Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreate",...

],"post-update-cmd": [

"Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreate",...

]}

}

It is possible to specify another directory, repository or commit id in the extra parameters ofcomposer.json file (here are the default values):

1234567

{"extra": {

"create-directory": "vendor/symfony-cmf/create-bundle/Symfony/Cmf/Bundle/CreateBundle/Resources/public/vendor/create",

"create-repository": "https://github.com/bergie/create.git","create-commit": "271e0114a039ab256ffcceacdf7f361803995e05"

}}

Add this bundle (and its dependencies, if they are not already there) to your application's kernel:

123456789

101112

// application/ApplicationKernel.phppublic function registerBundles(){

return array(// ...new Symfony\Bundle\AsseticBundle\AsseticBundle(),new JMS\SerializerBundle\JMSSerializerBundle($this),new FOS\RestBundle\FOSRestBundle(),new Symfony\Cmf\Bundle\CreateBundle\CmfCreateBundle(),// ...

);}

You also need to configure FOSRestBundle to handle json:

1234

fos_rest:view:

formats:json: true

Using CKEditor Instead

If you want to use CKEditor, edit the composer.json file to call downloadCreateAndCkeditor instead ofdownloadCreate:

12345

{"scripts": {

"post-install-cmd": [

"Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreateAndCkeditor",

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 93

Page 94: Symfony Cmf Master

Listing 21-6

Listing 21-7

Listing 21-8

Listing 21-9

6789

101112

...],"post-update-cmd": [

"Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::downloadCreateAndCkeditor",...

]}

}

and re-run composer:

1 $ php composer.phar update nothing

In your application config file, define the editor base path:

12

cmf_create:editor_base_path: /bundles/cmfcreate/vendor/ckeditor/

In your template, load the javascript files using:

1234

{% render controller("cmf_create.jsloader.controller:includeJSFilesAction",{"editor": "ckeditor"}

) %}

As for create.js, you can override the source of CKEditor to a different target directory, source repositoryor commit id in the extra parameters of the composer.json file (here are the default values):

1234567

{"extra": {

"ckeditor-directory": "vendor/symfony-cmf/create-bundle/Symfony/Cmf/Bundle/CreateBundle/Resources/public/vendor/ckeditor",

"ckeditor-repository": "https://github.com/ckeditor/ckeditor-releases.git","ckeditor-commit": "bba29309f93a1ace1e2e3a3bd086025975abbad0"

}}

ConceptCreatephp uses RDFa metadata about your domain classes, much like doctrine knows the metadata howan object is stored in the database. The metadata is modelled by the type class and can come from anysource. Createphp provides metadata drivers that read XML, php arrays and one that just introspectsobjects and creates non-semantical metadata that will be enough for create.js to edit.

The RdfMapper is used to translate between your storage layer and createphp. It is passed the domainobject and the relevant metadata object.

With the metadata and the twig helper, the content is rendered with RDFa annotations. create.js isloaded and enables editing on the entities. Save operations happen in ajax calls to the backend.

The REST controller handles those ajax calls, and if you want to be able to upload images, an imagecontroller saves uploaded images and tells the image location.

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 94

Page 95: Symfony Cmf Master

Listing 21-10

Configuration

123456789

101112131415161718192021222324252627282930313233343536373839404142

# app/config/config.ymlcmf_create:

# metadata loading

# directory list to look for metadatardf_config_dirs:

- "%kernel.root_dir%/Resources/rdf-mappings"# look for mappings in <Bundle>/Resources/rdf-mappings# auto_mapping: true

# use a different class for the REST handler# rest_controller_class: FQN\Classname

# image handlingimage:

model_class: ~controller_class: ~

# access check role for js inclusion, default REST and image controllers# role: IS_AUTHENTICATED_ANONYMOUSLY

# enable the doctrine PHPCR-ODM mapperphpcr_odm: true

# mapping from rdf type name => class name used when adding items to collectionsmap:

rdfname: FQN\Classname

# stanbol url for semantic enhancement, otherwise defaults to the demo install# stanbol_url: http://dev.iks-project.eu:8081

# fix the Hallo editor toolbar on top of the page# fixed_toolbar: true

# RDFa types used for elements to be edited in plain text# plain_text_types: ['dcterms:title']

# RDFa types for which to create the corresponding routes after# content of these types has been added with Create.js. This is# not necessary with the SimpleCmsBundle, as the content and the# routes are in the same repository tree.# create_routes_types: ['http://schema.org/NewsArticle']

The provided javascript file configures create.js and the hallo editor. It enables some plugins like the tageditor to edit skos:related collections of attributes. We hope to add some configuration options totweak the configuration of create.js but you can also use the file as a template and do your own if youneed larger customizations.

Metadata

Createphp needs metadata information for each class of your domain model. By default, the createbundle uses the XML metadata driver and looks for metadata in the enabled bundles at <Bundle>/Resources/rdf-mappings. If you use a bundle that has no RDFa mapping, you can specify a list ofrdf_config_dirs that will additionally be checked for metadata.

See the documentation of createphp4 for the format of the XML metadata format.

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 95

Page 96: Symfony Cmf Master

Listing 21-11

Listing 21-12

Listing 21-13

Access Control

If you use the default REST controller, everybody can edit content once you enabled the create bundle.To restrict access, specify a role other than the default IS_AUTHENTICATED_ANONYMOUSLY to thebundle. If you specify a different role, create.js will only be loaded if the user has that role and the RESThandler (and image handler if enabled) will check the role.

If you need more fine grained access control, look into the mapper isEditable method. You can extendthe mapper you use and overwrite isEditable to answer whether the passed domain object is editable.

Image Handling

Enable the default simplistic image handler with the image > model_class | controller_class settings. Thisimage handler just throws images into the PHPCR-ODM repository and also serves them in requests.

If you need different image handling, you can either overwrite image.model_class and/orimage.controller_class, or implement a custom ImageController and override thecmf_create.image.controller service with it.

Mapping Requests to Objects

For now, the bundle only provides a service to map to doctrine PHPCR-ODM. Enable it by settingphpcr_odm to true. If you need something else, you need to provide a servicecmf_create.object_mapper. (If you need a wrapper for doctrine ORM, look at the mappers in thecreatephp library and do a pull request on that library, and another one to expose the ORM mapper asservice in the create bundle).

Also note that createphp would support different mappers for different RDFa types. If you need that, diginto the createphp and create bundle and do a pull request to enable this feature.

To be able to create new objects, you need to provide a map between the RDFa types and the class names.

Routing

Finally add the relevant routing to your configuration

1234

create:resource: "@CmfCreateBundle/Resources/config/routing/rest.xml"

create_image:resource: "@CmfCreateBundle/Resources/config/routing/image.xml"

UsageAdjust your template to load the editor js files if the current session is allowed to edit content.

If you are using Symfony 2.2 or higher:

1 {% render controller("cmf_create.jsloader.controller:includeJSFilesAction", {'_locale':app.request.locale}) %}

For versions prior to 2.2, this will do:

4. https://github.com/flack/createphp

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 96

Page 97: Symfony Cmf Master

Listing 21-14

Listing 21-15

Listing 21-16

Listing 21-17

1 {% render "cmf_create.jsloader.controller:includeJSFilesAction" with {'_locale':app.request.locale} %}

Plus make sure that assetic is rewriting paths in your css files, then include the base css files (andcustomize with your css as needed) with

1 {% include "CmfCreateBundle::includecssfiles.html.twig" %}

The other thing you have to do is provide RDFa mappings for your model classes and adjust yourtemplates to render with createphp so that create.js knows what content is editable.

Create XML metadata mappings in <Bundle>/Resources/rdf-mappings or a path you configured inrdf_config_dirs named after the full classname of your model classes with \\ replaced by a dot (.), i.e.Symfony.Cmf.Bundle.SimpleCmsBundle.Document.MultilangPage.xml. For an example mapping seethe files in the cmf-sandbox. Reference documentation is in the createphp library repository5.

To render your model, use the createphp twig tag:

123

{% createphp page as="rdf" %}{{ rdf|raw }}{% endcreatephp %}

Or if you need more control over the generated HTML:

123456

{% createphp page as="rdf" %}<div {{ createphp_attributes(rdf) }}>

<h1 class="my-title" {{ createphp_attributes( rdf.title ) }}>{{ createphp_content(rdf.title ) }}</h1>

<div {{ createphp_attributes( rdf.body ) }}>{{ createphp_content( rdf.body ) }}</div></div>{% endcreatephp %}

Alternative Editors

You can write your own templates to load a javascript editor. They have to follow the naming patternCmfCreateBundle::includejsfiles-%editor%.html.twig to be loaded. In the includeJSFilesAction,you specify the editor parameter. (Do not forget to add the controller call around the controller nameinside render for Symfony 2.2, as in the example above.)

1 {% render "cmf_create.jsloader.controller:includeJSFilesAction" with {'editor': 'aloha','_locale': app.request.locale } %}

Create.js has built in support for Aloha and ckeditor, as well as the default hallo editor. Thoseshould be supported by the CreateBundle as well. See these github issue for ckeditor6 and alhoa7

integration.

If you wrote the necessary code for one of those editors, or another editor that could be useful forothers, please send a pull request.

5. https://github.com/flack/createphp

6. https://github.com/symfony-cmf/CreateBundle/issues/33

7. https://github.com/symfony-cmf/CreateBundle/issues/32

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 97

Page 98: Symfony Cmf Master

Listing 21-18

Listing 21-19

Listing 21-20

Listing 21-21

Developing the Hallo Wysiwyg EditorYou can develop the hallo editor inside the Create bundle. By default, a minimized version of hallo thatis bundled with create is used. To develop the actual code, you will need to checkout the full hallorepository first. You can do this by running the following command from the command line:

1 $ php app/console cmf:create:init-hallo-devel

There is a special template to load the coffee script files. To load this, just use the hallo-coffee editorwith the includeJSFilesAction. (Do not forget to add the controller call around the controller nameinside render for Symfony 2.2, as in the example above.)

1 {% render "cmf_create.jsloader.controller:includeJSFilesAction" with {'editor':'hallo-coffee', '_locale': app.request.locale } %}

The hallo-coffee template uses assetic to load the coffee script files from Resources/public/vendor/hallo/src, rather than the precompiled javascript from Resources/public/vendor/create/deps/hallo-min.js. This also means that you need to add a mapping for coffeescript in your asseticconfiguration and you need the coffee compiler set up correctly8.

assetic:filters:

cssrewrite: ~coffee:

bin: %coffee.bin%node: %coffee.node%apply_to: %coffee.extension%

In the cmf sandbox we did a little hack to not alwas trigger coffee script compiling. In config.yml wemake the coffee extension configurable. Now if the parameters.yml sets coffee.extension to \.coffeethe coffeescript is compiled and the coffee compiler needs to be installed. If you set it to anything else like\.nocoffee then you do not need the coffee compiler installed.

The default values for the three parameters are

123456

# app/config/parameters.yml

# ...coffee.bin: /usr/local/bin/coffeecoffee.node: /usr/local/bin/nodecoffee.extension: \.coffee

8. http://coffeescript.org/#installation

PDF brought to you bygenerated on July 18, 2013

Chapter 21: CreateBundle | 98

Page 99: Symfony Cmf Master

Chapter 22

DoctrinePHPCRBundle

The DoctrinePHPCRBundle1 provides integration with the PHP content repository and optionally withDoctrine PHPCR-ODM to provide the ODM document manager in symfony.

Out of the box, this bundle supports the following PHPCR implementations:

• Jackalope2 (both jackrabbit and doctrine-dbal transports)• Midgard23

This reference only explains the Symfony2 integration of PHPCR and PHPCR-ODM. To learn howto use PHPCR refer to the PHPCR website4 and for Doctrine PHPCR-ODM to the PHPCR-ODMdocumentation5.

This bundle is based on the AbstractDoctrineBundle and thus is similar to the configuration of theDoctrine ORM and MongoDB bundles.

Setup and RequirementsSee Installing and Configuring Doctrine PHPCR-ODM

Configuration

If you want to only use plain PHPCR without the PHPCR-ODM, you can simply not configurethe odm section to avoid loading the services at all. Note that most CMF bundles by default usePHPCR-ODM documents and thus need ODM enabled.

1. https://github.com/doctrine/DoctrinePHPCRBundle

2. http://jackalope.github.com/

3. http://midgard-project.org/phpcr/

4. http://phpcr.github.com/

5. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 99

Page 100: Symfony Cmf Master

Listing 22-1

Listing 22-2

PHPCR Session Configuration

The session needs a PHPCR implementation specified in the backend section by the type field, alongwith configuration options to bootstrap the implementation. Currently we support jackrabbit,doctrinedbal and midgard2. Regardless of the backend, every PHPCR session needs a workspace,username and password.

Every PHPCR implementation should provide the workspace called default, but you can choosea different one. There is the doctrine:phpcr:workspace:create command to initialize a newworkspace. See also Services.

The username and password you specify here are what is used on the PHPCR layer in thePHPCR\SimpleCredentials. They will usually be different from the username and password used byMidgard2 or Doctrine DBAL to connect to the underlying RDBMS where the data is actually stored.

If you are using one of the Jackalope backends, you can also specify options. They will be set onthe Jackalope session. Currently this can be used to tune pre-fetching nodes by settingjackalope.fetch_depth to something bigger than 0.

123456789

1011

# app/config/config.ymldoctrine_phpcr:

session:backend:

# see below for how to configure the backend of your choiceworkspace: defaultusername: adminpassword: admin## tweak options for jackrabbit and doctrinedbal (all jackalope versions)# options:# 'jackalope.fetch_depth': 1

PHPCR Session with Jackalope Jackrabbit

The only setup required is to install Apache Jackrabbit (see installing Jackrabbit).

The configuration needs the url parameter to point to your jackrabbit. Additionally you can tune someother jackrabbit-specific options, for example to use it in a load-balanced setup or to fail early for theprice of some round trips to the backend.

123456789

10111213141516

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: jackrabbiturl: http://localhost:8080/server/## jackrabbit only, optional. see https://github.com/jackalope/jackalope/blob/

master/src/Jackalope/RepositoryFactoryJackrabbit.php# default_header: ...# expect: 'Expect: 100-continue'# enable if you want to have an exception right away if PHPCR login fails# check_login_on_server: false# enable if you experience segmentation faults while working with binary data

in documents# disable_stream_wrapper: true# enable if you do not want to use transactions and you neither want the odm

to automatically use transactions

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 100

Page 101: Symfony Cmf Master

Listing 22-3

Listing 22-4

# its highly recommended NOT to disable transactions# disable_transactions: true

PHPCR Session with Jackalope Doctrine DBAL

This type uses Jackalope with a Doctrine database abstraction layer transport to provide PHPCR withoutany installation requirements beyond any of the RDBMS supported by Doctrine.

You need to configure a Doctrine connection according to the DBAL section in the Symfony2 Doctrinedocumentation6.

123456789

10111213

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: doctrinedbalconnection: doctrine.dbal.default_connection# enable if you want to have an exception right away if PHPCR login fails# check_login_on_server: false# enable if you experience segmentation faults while working with binary data

in documents# disable_stream_wrapper: true# enable if you do not want to use transactions and you neither want the odm

to automatically use transactions# its highly recommended NOT to disable transactions# disable_transactions: true

Once the connection is configured, you can create the database and you need to initialize the databasewith the doctrine:phpcr:init:dbal command.

12

$ php app/console doctrine:database:create$ php app/console doctrine:phpcr:init:dbal

Of course, you can also use a different connection instead of the default. It is recommended touse a separate connection to a separate database if you also use Doctrine ORM or direct DBALaccess to data, rather than mixing this data with the tables generated by jackalope-doctrine-dbal. If you have a separate connection, you need to pass the alternate connection name tothe doctrine:database:create command with the --connection option. For doctrine PHPCRcommands, this parameter is not needed as you configured the connection to use.

PHPCR Session with Midgard2

Midgard2 is an application that provides a compiled PHP extension. It implements the PHPCR API ontop of a standard RDBMS.

To use the Midgard2 PHPCR provider, you must have both the midgard2 PHP extension7 and themidgard/phpcr package8 installed. The settings here correspond to Midgard2 repository parameters asexplained in the getting started document9.

The session backend configuration looks as follows:

6. http://symfony.com/doc/current/book/doctrine.html

7. http://midgard-project.org/midgard2/#download

8. http://packagist.org/packages/midgard/phpcr

9. http://midgard-project.org/phpcr/#getting_started

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 101

Page 102: Symfony Cmf Master

Listing 22-5

Listing 22-6

123456789

10111213

# app/config/config.ymldoctrine_phpcr:

session:backend:

type: midgard2db_type: MySQLdb_name: midgard2_testdb_host: "0.0.0.0"db_port: 3306db_username: ""db_password: ""db_init: trueblobdir: /tmp/cmf-blobs

For more information, please refer to the official Midgard PHPCR documentation10.

Doctrine PHPCR-ODM Configuration

This configuration section manages the Doctrine PHPCR-ODM system. If you do not configure anythinghere, the ODM services will not be loaded.

If you enable auto_mapping, you can place your mappings in <Bundle>/Resources/config/doctrine/<Document>.phpcr.xml resp. ...yml to configure mappings for documents you provide in the<Bundle>/Document folder. Otherwise you need to manually configure the mappings section.

If auto_generate_proxy_classes is false, you need to run the cache:warmup command in order to havethe proxy classes generated after you modified a document. You can also tune how and where to generatethe proxy classes with the proxy_dir and proxy_namespace settings. The the defaults are usually finehere.

You can also enable metadata caching11.

# app/config/config.ymldoctrine_phpcr:

odm:configuration_id: ~auto_mapping: truemappings:

<name>:mapping: truetype: ~dir: ~alias: ~prefix: ~is_bundle: ~

auto_generate_proxy_classes: %kernel.debug%proxy_dir: %kernel.cache_dir%/doctrine/PHPCRProxiesproxy_namespace: PHPCRProxies

metadata_cache_driver:type: arrayhost: ~port: ~instance_class: ~class: ~id: ~

10. http://midgard-project.org/phpcr/

11. http://symfony.com/doc/master/reference/configuration/doctrine.html

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 102

Page 103: Symfony Cmf Master

Listing 22-7

Listing 22-8

Listing 22-9

Listing 22-10

Translation Configuration

If you are using multilingual documents, you need to configure the available languages. For moreinformation on multilingual documents, see the PHPCR-ODM documentation on Multilanguage.

12345678

# app/config/config.ymldoctrine_phpcr:

odm:...locales:

en: [e, fr]de: [en, fr]fr: [en, de]

This block defines the order of alternative locales to look up if a document is not translated to therequested locale.

General Settings

If the jackrabbit_jar path is set, you can use the doctrine:phpcr:jackrabbit console command to startand stop jackrabbit.

You can tune the output of the doctrine:phpcr:dump command with dump_max_line_length.

1234

# app/config/config.ymldoctrine_phpcr:

jackrabbit_jar: /path/to/jackrabbit.jardump_max_line_length: 120

Configuring Multiple SessionsIf you need more than one PHPCR backend, you can define sessions as child of the sessioninformation. Each session has a name and the configuration as you can use directly in session. You canalso overwrite which session to use as default_session.

123456789

10111213

# app/config/config.ymldoctrine_phpcr:

session:default_session: ~sessions:

<name>:workspace: ~ # Requiredusername: ~password: ~backend:

# as aboveoptions:

# as above

If you are using the ODM, you will also want to configure multiple document managers.

Inside the odm section, you can add named entries in the document_managers. To use the non-defaultsession, specify the session attribute.

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 103

Page 104: Symfony Cmf Master

Listing 22-11

123456

odm:default_document_manager: ~document_managers:

<name>:# same keys as directly in odm, see above.session: <sessionname>

A full example looks as follows:

doctrine_phpcr:# configure the PHPCR sessionssession:

sessions:

default:backend: %phpcr_backend%workspace: %phpcr_workspace%username: %phpcr_user%password: %phpcr_pass%

website:backend:

type: jackrabbiturl: %magnolia_url%

workspace: websiteusername: %magnolia_user%password: %magnolia_pass%

dms:backend:

type: jackrabbiturl: %magnolia_url%

workspace: dmsusername: %magnolia_user%password: %magnolia_pass%

# enable the ODM layerodm:

document_managers:default:

session: defaultmappings:

SandboxMainBundle: ~CmfContentBundle: ~CmfMenuBundle: ~CmfRoutingBundle: ~

website:session: websiteconfiguration_id: sandbox_magnolia.odm_configurationmappings:

SandboxMagnoliaBundle: ~

dms:session: dmsconfiguration_id: sandbox_magnolia.odm_configurationmappings:

SandboxMagnoliaBundle: ~

auto_generate_proxy_classes: %kernel.debug%

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 104

Page 105: Symfony Cmf Master

Listing 22-12

Listing 22-13

This example also uses different configurations per repository (see the repository_id attribute).This case is explained in Using a Custom Document Class Mapper with PHPCR-ODM.

ServicesYou can access the PHPCR services like this:

123456789

101112131415161718

<?php

namespace Acme\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller{

public function indexAction(){

// ManagerRegistry instance with references to all sessions and document managerinstances

$registry = $this->container->get('doctrine_phpcr');// PHPCR session instance$session = $this->container->get('doctrine_phpcr.default_session');// PHPCR ODM document manager instance$documentManager =

$this->container->get('doctrine_phpcr.odm.default_document_manager');}

}

EventsYou can tag services to listen to Doctrine PHPCR-ODM events. It works the same way as for DoctrineORM. The only differences are:

• use the tag name doctrine_phpcr.event_listener resp.doctrine_phpcr.event_subscriber instead of doctrine.event_listener.

• expect the argument to be of class• Doctrine\ODM\PHPCR\Event\LifecycleEventArgs rather than in the ORM namespace. (this

is subject to change, as doctrine commons 2.4 provides a common class for this event).

You can register for the events as described in the PHPCR-ODM documentation12. Or you can tag yourservices as event listeners resp. event subscribers.

1234567

services:my.listener:

class: Acme\SearchBundle\EventListener\SearchIndexertags:

- { name: doctrine_phpcr.event_listener, event: postPersist }

my.subscriber:

12. http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/events.html

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 105

Page 106: Symfony Cmf Master

Listing 22-14

Listing 22-15

89

10

class: Acme\SearchBundle\EventSubscriber\MySubscribertags:

- { name: doctrine_phpcr.event_subscriber }

Doctrine event subscribers (both ORM and PHPCR-ODM) can not return a flexible array ofmethods to call like the Symfony event subscriber13 can do. Doctrine event subscribers must returna simple array of the event names they subscribe to. Doctrine will then expect methods on thesubscriber with the names of the subscribed events, just as when using an event listener.

More information with PHP code examples for the doctrine event system integration is in this Symfonycookbook entry14.

Constraint ValidatorThe bundle provides a ValidPhpcrOdm constraint validator you can use to check if your document Id orNodename and Parent fields are correct.

1234

# src/Acme/BlogBundle/Resources/config/validation.ymlAcme\BlogBundle\Entity\Author:

constraints:- Doctrine\Bundle\PHPCRBundle\Validator\Constraints\ValidPhpcrOdm

Form TypesThe bundle provides a couple of handy form types for PHPCR and PHPCR-ODM specific cases, alongwith form type guessers.

phpcr_odm_image

The phpcr_odm_image form maps to a document of type Doctrine\ODM\PHPCR\Document\Image andprovides a preview of the uploaded image. To use it, you need to include the LiipImagineBundle15 in yourproject and define an imagine filter for thumbnails.

This form type is only available if explicitly enabled in your application configuration by defining theimagine section under the odm section with at least enabled: true. You can also configure the imaginefilter to use for the preview, as well as additional filters to remove from cache when the image is replaced.If the filter is not specified, it defaults to image_upload_thumbnail.

12345678

doctrine_phpcr:# ...odm:

imagine:enabled: true# filter: image_upload_thumbnail# extra_filters:# - imagine_filter_name1

13. http://symfony.com/doc/master/components/event_dispatcher/introduction.html#using-event-subscribers

14. http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html

15. https://github.com/liip/LiipImagineBundle/

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 106

Page 107: Symfony Cmf Master

Listing 22-16

Listing 22-17

Listing 22-18

910111213141516171819

# - imagine_filter_name2

# Imagine Configurationliip_imagine:

# ...filter_sets:

# define the filter to be used with the image previewimage_upload_thumbnail:

data_loader: phpcrfilters:

thumbnail: { size: [100, 100], mode: outbound }

Then you can add images to document forms as follows:

12345678

use Symfony\Component\Form\FormBuilderInterface;

protected function configureFormFields(FormBuilderInterface $formBuilder){

$formBuilder->add('image', 'phpcr_odm_image', array('required' => false))

;}

If you set required to true for the image, the user must re-upload a new image each time he editsthe form. If the document must have an image, it makes sense to require the field when creatinga new document, but make it optional when editing an existing document. We are trying to makethis automatic16.

Next you will need to add the fields.html.twig template from the DoctrinePHPCRBundle to theform.resources, to actually see the preview of the uploaded image in the backend.

12345

# Twig Configurationtwig:

form:resources:

- 'DoctrinePHPCRBundle:Form:fields.html.twig'

The document that should contain the Image document has to implement a setter method. To profitfrom the automatic guesser of the form layer, the name in the form element and this method name haveto match:

123456789

10

public function setImage($image){

if (!$image) {// this is normal and happens when no new image is uploadedreturn;

} elseif ($this->image && $this->image->getFile()) {// TODO: needed until this bug in PHPCRODM has been fixed: https://github.com/

doctrine/phpcr-odm/pull/262$this->image->getFile()->setFileContent($image->getFile()->getFileContent());

} else {$this->image = $image;

16. https://groups.google.com/forum/?fromgroups=#!topic/symfony2/CrooBoaAlO4

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 107

Page 108: Symfony Cmf Master

Listing 22-19

1112

}}

To delete an image, you need to delete the document containing the image. (There is a proposal toimprove the user experience for that in a DoctrinePHPCRBundle issue17.)

There is a doctrine listener to invalidate the imagine cache for the filters you specified. This listenerwill only operate when an Image is changed in a web request, but not when a CLI commandchanges images. When changing images with commands, you should handle cache invalidation inthe command or manually remove the imagine cache afterwards.

phpcr_odm_reference_collection

This form type handles editing ReferenceMany collections on PHPCR-ODM documents. It is a choicefield with an added referenced_class required option that specifies the class of the referenced targetdocument.

To use this form type, you also need to specify the list of possible reference targets as an array of PHPCR-ODM ids or PHPCR paths.

The minimal code required to use this type looks as follows:

123456789

101112

$dataArr = array('/some/phpcr/path/item_1' => 'first item','/some/phpcr/path/item_2' => 'second item',

);

$formMapper->with('form.group_general')

->add('myCollection', 'phpcr_odm_reference_collection', array('choices' => $dataArr,'referenced_class' => 'Class\Of\My\Referenced\Documents',

))->end();

When building an admin interface with Sonata Admin there is also the sonata_type_model thatis more powerful, allowing to add to the referenced documents on the fly. Unfortunately it iscurrently broken18.

phpcr_reference

The phpcr_reference represents a PHPCR Property of type REFERENCE or WEAKREFERENCEwithin a form. The input will be rendered as a text field containing either the PATH or the UUID as perthe configuration. The form will resolve the path or id back to a PHPCR node to set the reference.

This type extends the text form type. It adds an option transformer_type that can be set to either pathor uuid.

17. https://github.com/doctrine/DoctrinePHPCRBundle/issues/40

18. https://github.com/sonata-project/SonataDoctrineORMAdminBundle/issues/145

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 108

Page 109: Symfony Cmf Master

Listing 22-20

Listing 22-21

Repository InitializersThe Initializer is the PHPCR equivalent of the ORM schema tools. It is used to let bundles registerPHPCR node types and to create required base paths in the repository. Initializers have to implementDoctrine\Bundle\PHPCRBundle\Initializer. If you don't need any special logic, you can simplyconfigure the GenericInitializer as service and don't need to write any code. The generic initializerexpects an array of base paths it will create if they do not exist, and an optional string definingnamespaces and primary / mixin node types in the CND language.

A service to use the generic initializer looks like this:

12345678

# src/Acme/ContentBundle/Resources/config/services.ymlacme.phpcr.initializer:

class: Doctrine\Bundle\PHPCRBundle\Initializer\GenericInitializerarguments:

- { "%acme.content_basepath%", "%acme.menu_basepath%" }- { "%acme.cnd%" }

tags:- { name: "doctrine_phpcr.initializer" }

The doctrine:phpcr:repository:init command runs all tagged initializers.

Fixture LoadingTo use the doctrine:phpcr:fixtures:load command, you additionally need to install theDoctrineFixturesBundle19 which brings the Doctrine data-fixtures into Symfony2.

Fixtures work the same way they work for Doctrine ORM. You write fixture classes implementingDoctrine\Common\DataFixtures\FixtureInterface. If you place them in<Bundle>DataFixturesPHPCR, they will be auto detected if you specify no path to the fixture loadingcommand.

A simple example fixture class looks like this:

123456789

1011121314

<?php

namespace MyBundle\DataFixtures\PHPCR;

use Doctrine\Common\Persistence\ObjectManager;use Doctrine\Common\DataFixtures\FixtureInterface;

class LoadMyData implements FixtureInterface{

public function load(ObjectManager $manager){

// Create and persist your data here...}

}

For more on fixtures, see the documentation of the DoctrineFixturesBundle20.

19. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

20. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 109

Page 110: Symfony Cmf Master

Listing 22-22

Migration LoadingThe DoctrinePHPCRBundle also ships with a simple command to run migration scripts. Migrationsshould implement the Doctrine\Bundle\PHPCRBundle\Migrator\MigratorInterface and registeredas a service with a doctrine_phpcr.migrator tag contains an alias attribute uniquely identifying themigrator. There is an optional Doctrine\Bundle\PHPCRBundle\Migrator\AbstractMigrator class touse as a basis. To find out available migrations run:

1 $ php app/console doctrine:phpcr:migrator

Then pass in the name of the migrator to run it, optionally passing in an --identifier, --depth or --session argument. The later argument determines which session name to set on the migrator, while thefirst two arguments will simply be passed to the migrate() method. You can find an example migratorin the SimpleCmsBundle.

Doctrine PHPCR CommandsAll commands about PHPCR are prefixed with doctrine:phpcr and you can use the --session argumentto use a non-default session if you configured several PHPCR sessions.

Some of these commands are specific to a backend or to the ODM. Those commands will only beavailable if such a backend is configured.

Use app/console help <command> to see all options each of the commands has.

• doctrine:phpcr:workspace:create: Create a workspace in the configured repository;• doctrine:phpcr:workspace:list: List all available workspaces in the configured repository;• doctrine:phpcr:type:register: Register node types from a .cnd file in the PHPCR repository;• doctrine:phpcr:type:list: List all node types in the PHPCR repository;• doctrine:phpcr:purge: Remove a subtree or all content from the repository;• doctrine:phpcr:repository:init: Register node types and create base paths. See above how to

define custom initializers;• doctrine:phpcr:fixtures:load: Load data fixtures to your PHPCR database;• doctrine:phpcr:import: Import xml data into the repository, either in JCR system view

format or arbitrary xml;• doctrine:phpcr:export: Export nodes from the repository, either to the JCR system view

format or the document view format;• doctrine:phpcr:dump: Output all or some content of the repository;• doctrine:phpcr:touch: Create or modify a node at the specified path;• doctrine:phpcr:move: Move a node from one path to another;• doctrine:phpcr:query: Execute a JCR SQL2 statement;• doctrine:phpcr:mapping:info: Shows basic information about all mapped documents.

To use the doctrine:phpcr:fixtures:load command, you additionally need to install theDoctrineFixturesBundle21 and its dependencies. See that documentation page for how to usefixtures.

Jackrabbit Specific Commands

If you are using jackalope-jackrabbit, you also have a command to start and stop the jackrabbitserver:

21. http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 110

Page 111: Symfony Cmf Master

Listing 22-23

Listing 22-24

• jackalope:run:jackrabbit Start and stop the Jackrabbit server

Doctrine DBAL Specific Commands

If you are using jackalope-doctrine-dbal, you have a command to initialize the database:

• jackalope:init:dbal Prepare the database for Jackalope Doctrine DBAL

Note that you can also use the doctrine dbal command to create the database.

Some Example Command Runs

Running SQL2 queries22 against the repository:

1 $ php app/console doctrine:phpcr:query "SELECT title FROM [nt:unstructured] WHERE NAME() ='home'"

Dumping nodes under /cms/simple including their properties:

1 $ php app/console doctrine:phpcr:dump /cms/simple --props

22. http://www.h2database.com/jcr/grammar.html

PDF brought to you bygenerated on July 18, 2013

Chapter 22: DoctrinePHPCRBundle | 111

Page 112: Symfony Cmf Master

Listing 23-1

Chapter 23

MenuBundle

The MenuBundle1 provides menus from a doctrine object manager with the help of KnpMenuBundle2.

Dependencies

This bundle is extending the KnpMenuBundle3.

Unless you change defaults and provide your own implementations, this bundle also depends on

• SymfonyRoutingBundle for the router service cmf_routing.dynamic_router. Note that youneed to explicitly enable the dynamic router as per default it is not loaded. See thedocumentation of the cmf routing bundle for how to do this.

• PHPCR-ODM to load route documents from the content repository

ConfigurationIf you want to use default configurations, you do not need to change anything. The values are:

123456789

101112

cmf_menu:menu_basepath: /cms/menudocument_manager_name: defaultadmin_class: ~document_class: ~content_url_generator: routerroute_name: ~ # cmf routes are created by content instead of namecontent_basepath: ~ # defaults to cmf_core.content_basepathallow_empty_items: ~ # defaults to falsevoters:

uri_prefix: false # enable the UriPrefixVoter for current menu itemcontent_identity: not set # enable the RequestContentIdentityVoter

1. https://github.com/symfony-cmf/MenuBundle#readme

2. https://github.com/knplabs/KnpMenuBundle

3. https://github.com/knplabs/KnpMenuBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 112

Page 113: Symfony Cmf Master

Listing 23-2

13141516171819

content_key: not set # override DynamicRouter::CONTENT_KEYuse_sonata_admin: auto # use true/false to force using / not using sonata adminmultilang: # the whole multilang section is optional

use_sonata_admin: auto # use true/false to force using / not using sonata adminadmin_class: ~document_class: ~locales: [] # if you use multilang, you have to define at least one

locale

If you want to render the menu from twig, make sure you have not disabled twig in the knp_menuconfiguration section.

If sonata-project/doctrine-phpcr-admin-bundle is added to the composer.json require section, themenu documents are exposed in the SonataDoctrinePhpcrAdminBundle. For instructions on how toconfigure this Bundle see SonataDoctrinePhpcrAdminBundle.

By default, use_sonata_admin is automatically set based on whether SonataDoctrinePhpcrAdminBundleis available but you can explicitly disable it to not have it even if sonata is enabled, or explicitly enable toget an error if Sonata becomes unavailable.

By default, menu nodes that have neither the uri nor the route field set and no route can be generatedfrom the content they link to are skipped by the ContentAwareFactory. This also leads to theirdescendants not showing up. If you want to generate menu items without a link instead, set theallow_empty_items parameter to true to make the menu items show up as static text instead.

Menu EntriesA MenuItem document defines menu entries. You can build menu items based on symfony routes,absolute or relative urls or referenceable PHPCR-ODM content documents.

The menu tree is built from documents under [menu_basepath]/[menuname]. You can use differentdocument classes for menu items as long as they implement Knp\Menu\NodeInterface to integratewith KnpMenuBundle. The default MenuNode document discards children that do not implement thisinterface.

Examples:

123456789

1011121314151617181920

<?php

// get document manager$dm = ...;$rootNode = $dm->find(null, ...); // retrieve parent menu item

// using referenceable content document$blogContent = $dm->find(null, '/my/cms/content/blog');

$blogNode = new MenuNode();$blogNode->setName('blog');$blogNode->setParent($parent);$blogNode->setContent($blogDocument);$blogNode->setLabel('Blog');

$dm->persist($blogNode);

// using a route document$timelineRoute = $dm->find(null, '/my/cms/routes/timeline');

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 113

Page 114: Symfony Cmf Master

Listing 23-3

212223242526272829303132333435363738394041

$timelineNode = new MenuNode();$timelineNode->setContent($timelineRoute);// ...

$dm->persist($timelineNode);

// using a symfony route$sfRouteNode = new MenuNode();$sfRouteNode->setRoute('my_hard_coded_symfony_route');// ...

$dm->persist($sfRouteNode);

// using URL$urlNode = new MenuNode();$urlNode->setUri('http://www.example.com');// ...

$dm->persist($urlNode);

$dm->flush();

By default content documents are created using a weak reference (this means you will be able to deletethe referenced content). You can specify a strong reference by using setWeak(false):

12345

<?php

$node = new MenuNode;// ...$node->setWeak(false);

When content is referenced weakly and subsequently deleted the rendered menu will not providea link to the content.

Current Menu ItemA menu item can be the current item. If it is the current item, this information is passed to the twigtemplate which by default adds the css class current and all menu items that are ancestors of that itemget the class current_ancestor. This will typically used in CSS to highlight menu entries.

The decision about being current item happens by comparing the URI associated with the menu itemwith the request URI. Additionally, the CMF menu bundle supports voters that can look at the MenuItemand optionally the request.

There are two voter services configured but not enabled by default, another voter that you can use toconfigure services and you can write your own voter classes.

The CMF MenuBundle is based on Knp Menu 1.x. The 2.0 rewrite of Knp Menu will add currentitem voters in the core Knp library. The CMF bundle voters are interface compatible and followthe same mechanism as Knp Menu to ease upgrading.

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 114

Page 115: Symfony Cmf Master

Listing 23-4

Listing 23-5

RequestContentIdentityVoter

This voter looks at the content field of the menu item extras and compares it with the main contentattribute of the request. The name for the main content attribute in the request is configurable with thecontent_key option - if not set it defaults to the constant DynamicRouter::CONTENT_KEY.

You can enable this voter by setting cmf_menu.voters.content_identity to ~ in your config.yml to usea custom content_key for the main content attribute name, set it explicitly:

1234

cmf_menu:voters:

content_identity:content_key: myKey

UriPrefixVoter

The uri prefix voter looks at the content field of the menu item extras and checks if it contains aninstance of the symfony Route class. If so, it compares the route option currentUriPrefix with therequest URI. This allows you to make a whole sub-path of your site trigger the same menu item ascurrent, but you need to configure the prefix option on your route documents.

To enable the prefix voter, set the configuration key cmf_menu.voters.uri_prefix: ~.

RequestParentContentIdentityVoter

This voter has the same logic of looking for a request attribute to get the current content, but callsgetParent on it. This parent is compared to the content of the menu item extras. That way, content thatdoes not have its own menu entry but a parent that does have a menu item can trigger the right menuentry to be highlighted.

To use this voter you need to configure a custom service with the name of the content in the request andyour model class to avoid calling getParent on objects that do not have that method. You need to tagthe service as cmf_menu.voter and also as cmf_request_aware because it depends on the request. Theservice looks the same as for complete custom voters (see below), except you do not need to write yourown PHP code:

services:my_bundle.menu_voter.parent:

class: Symfony\Cmf\Bundle\MenuBundle\Voter\RequestParentContentIdentityVoterarguments:

- contentDocument- %my_bundle.my_model_class%

tags:- { name: "cmf_menu.voter" }- { name: "cmf_request_aware" }

Custom Voter

Voters must implement the Symfony\Cmf\MenuBundle\Voter\VoterInterface. To make the menubundle notice the voter, tag it with cmf_menu.voter. If the voter needs the request, add the tagcmf_request_aware to have a listener calling setRequest on the voter before it votes for the first time.

If you need to know the content the menu item points to, look in the content field of the menu itemextras: $item->getExtra('content');. The ContentAwareFactory places the content referenced by theroute there - if it does reference a content. Your voter should handle the case where the content is null.

For an example service definition see the section above for RequestParentIdentityVoter.

A voter will look something like this:

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 115

Page 116: Symfony Cmf Master

Listing 23-6

Listing 23-7

Listing 23-8

123456789

10111213141516171819202122232425262728293031323334

<?phpnamespace Acme\MenuBundle\Voter;

use Symfony\Cmf\Bundle\MenuBundle\Voter\VoterInterface;use Knp\Menu\ItemInterface;

class MyVoter implements VoterInterface{

private $request;

public function setRequest(Request $request){

$this->request = $request;}

/*** {@inheritDoc}*/public function matchItem(ItemInterface $item){

if (...) {// $item is the current menu itemreturn true;

}if (...) {

// $item for sure is NOT the current menu item// even if other voters might matchreturn false;

}

// we dont know if this is the current itemreturn null;

}}

Rendering MenusAdjust your twig template to load the menu.

1 {{ knp_menu_render('simple') }}

The menu name is the name of the node under menu_basepath. For example if your repository stores themenu nodes under /cms/menu , rendering "main" would mean to render the menu that is at /cms/menu/main

How to use Non-Default Other ComponentsIf you use the cmf menu with PHPCR-ODM, you just need to store Route documents undermenu_basepath. If you use a different object manager, you need to make sure that the menu rootdocument is found with:

1 $dm->find($menu_document_class, $menu_basepath . $menu_name)

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 116

Page 117: Symfony Cmf Master

The route document must implement Knp\Menu\NodeInterface - see MenuNode document for anexample. You probably need to specify menu_document_class too, as only PHPCR-ODM can determinethe document from the database content.

If you use the cmf menu with the DynamicRouter, you need no route name as the menu document justneeds to provide a field content_key in the options. If you want to use a different service to generateURLs, you need to make sure your menu entries provide information in your selected content_key thatthe url generator can use to generate the url. Depending on your generator, you might need to specify aroute_name too.

Note that if you just want to generate normal symfony routes with a menu that is in the database, youcan pass the core router service as content_url_generator, make sure the content_key never matches andmake your menu documents provide the route name and eventual routeParameters.

Publish Workflow InterfaceMenu nodes implement the write interfaces for publishable and publish time period, see the publishworkflow documentation for more information.

PDF brought to you bygenerated on July 18, 2013

Chapter 23: MenuBundle | 117

Page 118: Symfony Cmf Master

Chapter 24

RoutingBundle

The RoutingBundle1 integrates dynamic routing into Symfony using Routing.

The ChainRouter is meant to replace the default Symfony Router. All it does is collect a prioritized listof routers and try to match requests and generate URLs with all of them. One of the routers in that chaincan of course be the default router so you can still use the standard way for some of your routes.

Additionally, this bundle delivers useful router implementations. Currently, there is the DynamicRouterthat routes based on a custom loader logic for Symfony2 Route objects. The provider can be implementedusing a database, for example with Doctrine PHPCR-ODM2 or Doctrine ORM. This bundle provides adefault implementation for Doctrine PHPCR-ODM3.

The DynamicRouter service is only made available when explicitly enabled in the applicationconfiguration.

Finally this bundles provides route documents for Doctrine PHPCR-ODM4 and a controller forredirection routes.

Dependencies

• Symfony CMF routing5

ChainRouterThe ChainRouter can replace the default symfony routing system with a chain- enabled implementation.It does not route anything on its own, but only loops through all chained routers. To handle standardconfigured symfony routes, the symfony default router can be put into the chain.

1. https://github.com/symfony-cmf/RoutingBundle#readme

2. https://github.com/doctrine/phpcr-odm

3. https://github.com/doctrine/phpcr-odm

4. https://github.com/doctrine/phpcr-odm

5. https://github.com/symfony-cmf/Routing#readme

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 118

Page 119: Symfony Cmf Master

Listing 24-1

Listing 24-2

ConfigurationIn your app/config/config.yml, you can specify which router services you want to use. If you do notspecify the routers_by_id map at all, by default the chain router will just load the built-in symfonyrouter. When you specify the routers_by_id list, you need to have an entry for router.default if youwant the Symfony2 router (that reads the routes from app/config/routing.yml).

The format is service_name: priority - the higher the priority number the earlier this router service isasked to match a route or to generate a url

123456789

101112

# app/config/config.ymlcmf_routing:

chain:routers_by_id:

# enable the DynamicRouter with high priority to allow overwriting configuredroutes with content

cmf_routing.dynamic_router: 200# enable the symfony default router with a lower priorityrouter.default: 100

# whether the chain router should replace the default router. defaults to true# if you set this to false, the router is just available as service# cmf_routing.router and you need to do something to trigger it# replace_symfony_router: true

Loading Routers with Tagging

Your routers can automatically register, just add it as a service tagged with router and an optionalpriority. The higher the priority, the earlier your router will be asked to match the route. If you donot specify the priority, your router will come last. If there are several routers with the same priority, theorder between them is undetermined. The tagged service will look like this

services:my_namespace.my_router:

class: %my_namespace.my_router_class%tags:

- { name: router, priority: 300 }

See also official Symfony2 documentation for DependencyInjection tags6

Dynamic RouterThis implementation of a router uses the NestedMatcher which loads routes from aRouteProviderInterface. The provider interface can be easily implemented with Doctrine.

The router works with extended UrlMatcher and UrlGenerator classes that add loading routes from thedatabase and the concept of referenced content.

The NestedMatcher service is set up with a route provider. See the configuration section for how tochange the route_repository_service and the following section on more details for the default PHPCR-ODM7 based implementation.

You may want to configure route enhancers to decide what controller is used to handle the request, toavoid hard coding controller names into your route documents.

6. http://symfony.com/doc/2.1/reference/dic_tags.html

7. https://github.com/doctrine/phpcr-odm

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 119

Page 120: Symfony Cmf Master

Listing 24-3

The minimum configuration required to load the dynamic router as servicecmf_routing.dynamic_router is to have enabled: true in your config.yml (the router is automaticallyenabled as soon as you add any other configuration to the dynamic entry). Without enabling it, thedynamic router service will not be loaded at all, allowing you to use the ChainRouter with your ownrouters

1234

# app/config/config.ymlcmf_routing:

dynamic:enabled: true

PHPCR-ODM integration

This bundle comes with a route repository implementation for PHPCR-ODM8. PHPCR is well suited tothe tree nature of the data. If you use PHPCR-ODM9 with a route document like the one provided, youcan just leave the repository service at the default.

The default repository loads the route at the path in the request and all parent paths to allow for someof the path segments being parameters. If you need a different way to load routes or for example neveruse parameters, you can write your own repository implementation to optimize (see cmf_routing.xmlfor how to configure the service).

Match Process

Most of the match process is described in the documentation of the CMF Routing component. The onlydifference is that the bundle will place the contentDocument in the request attributes instead of the routedefaults.

Your controllers can (and should) declare the parameter $contentDocument in their Action methodsif they are supposed to work with content referenced by the routes. SeeSymfony\Cmf\Bundle\ContentBundle\Controller\ContentController for an example.

Configuration

To configure what controller is used for which content, you can specify route enhancers. Presenceof each of any enhancer configuration makes the DI container inject the respective enhancer into theDynamicRouter.

The possible enhancements are (in order of precedence):

• (Explicit controller): If there is a _controller set in getRouteDefaults(), no enhancer willoverwrite it.

• Explicit template: requires the route document to return a '_template' parameter ingetRouteDefaults. The configured generic controller is set by the enhancer.

• Controller by alias: requires the route document to return a 'type' value ingetRouteDefaults()

• Controller by class: requires the route document to return an object for getRouteContent().The content document is checked for being instanceof the class names in the map and ifmatched that controller is used. Instanceof is used instead of direct comparison to work withproxy classes and other extending classes.

• Template by class: requires the route document to return an object for getRouteContent().The content document is checked for being instanceof the class names in the map and if

8. https://github.com/doctrine/phpcr-odm

9. https://github.com/doctrine/phpcr-odm

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 120

Page 121: Symfony Cmf Master

Listing 24-4

matched that template will be set as '_template' in the $defaults and the generic controllerused as controller.

123456789

101112131415161718192021222324252627282930

# app/config/config.ymlcmf_routing:

dynamic:generic_controller: cmf_content.controller:indexActioncontrollers_by_type:

editablestatic: sandbox_main.controller:indexActioncontrollers_by_class:

Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent:cmf_content.controller:indexAction

templates_by_class:Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent:

CmfContentBundle:StaticContent:index.html.twig

# the route provider is responsible for loading routes.manager_registry: doctrine_phpcrmanager_name: default

# if you use the default doctrine route repository service, you# can use this to customize the root path for the `PHPCR-ODM`_# RouteProvider. This base path will be injected by the# Listener\IdPrefix - but only to routes matching the prefix,# to allow for more than one route source.routing_repositoryroot: /cms/routes

# If you want to replace the default route provider or content repository# you can specify their service IDs here.route_provider_service_id: my_bundle.provider.endpointcontent_repository_service_id: my_bundle.repository.endpoint

# an orm provider might need different configuration. look at# cmf_routing.xml for an example if you need to define your own# service

To see some examples, please look at the CMF sandbox10 and specifically the routing fixtures loading.

You can also define your own RouteEnhancer classes for specific use cases. See Customize.

Using the PHPCR-ODM route documentAll route classes must extend the Symfony core Route class. The documents can either be created by code(for example a fixtures script) or with a web interface like the one provided for Sonata PHPCR-ODMadmin (see below).

PHPCR-ODM maps all features of the core route to the storage, so you can use setDefault,setRequirement, setOption and setHostnamePattern like normal. Additionally when creating a route, youcan define whether .{_format} should be appended to the pattern and configure the required _formatwith a requirements. The other constructor option lets you control whether the route should append atrailing slash because this can not be expressed with a PHPCR name. The default is to have no trailingslash.

10. https://github.com/symfony-cmf/cmf-sandbox

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 121

Page 122: Symfony Cmf Master

Listing 24-5

Listing 24-6

Listing 24-7

Listing 24-8

All routes are located under a configured root path, for example '/cms/routes'. A new route can be createdin PHP code as follows:

123456

use Symfony\Cmf\Bundle\RoutingBundle\Document\Route;$route = new Route;$route->setParent($dm->find(null, '/routes'));$route->setName('projects');// set explicit controller (both service and Bundle:Name:action syntax work)$route->setDefault('_controller', 'sandbox_main.controller:specialAction');

The above example should probably be done as a route configured in a Symfony xml/yml file however,unless the end user is supposed to change the URL or the controller.

To link a content to this route, simply set it on the document.

12

$content = new Content('my content'); // Content must be a mapped class$route->setRouteContent($content);

This will put the document into the request parameters and if your controller specifies a parameter called$contentDocument, it will be passed this document.

You can also use variable patterns for the URL and define requirements and defaults.

1234

// do not forget leading slash if you want /projects/{id} and not /projects{id}$route->setVariablePattern('/{id}');$route->setRequirement('id', '\d+');$route->setDefault('id', 1);

This will give you a route that matches the URL /projects/<number> but also /projects as there is adefault for the id parameter. This will match /projects/7 as well as /projects but not /projects/x-4. The document is still stored at /routes/projects. This will work because, as mentioned above, theroute provider will look for route documents at all possible paths and pick the first that matches. In ourexample, if there is a route document at /routes/projects/7 that matches (no further parameters) it isselected. Otherwise we check if /routes/projects has a pattern that matches. If not, the top document at/routes is checked.

Of course you can also have several parameters, like with normal Symfony routes. The semantics andrules for patterns, defaults and requirements are exactly the same as in core routes.

Your controller can expect the $id parameter as well as the $contentDocument as we set a content onthe route. The content could be used to define an intro section that is the same for each project or othershared data. If you don't need content, you can just not set it in the document.

Sonata Admin ConfigurationIf sonata-project/doctrine-phpcr-admin-bundle is added to the composer.json require section andthe SonataDoctrinePhpcrAdminBundle is loaded in the application kernel, the route documents areexposed in the SonataDoctrinePhpcrAdminBundle. For instructions on how to configure this Bundle seeSonataDoctrinePhpcrAdminBundle.

By default, use_sonata_admin is automatically set based on whetherSonataDoctrinePhpcrAdminBundle is available, but you can explicitly disable it to not have it even ifsonata is enabled, or explicitly enable to get an error if Sonata becomes unavailable.

If you want to use the admin, you want to configure the content_basepath to point to the root of yourcontent documents.

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 122

Page 123: Symfony Cmf Master

Listing 24-9

Listing 24-10

1234

# app/config/config.ymlcmf_routing:

use_sonata_admin: auto # use true/false to force using / not using sonata admincontent_basepath: ~ # used with sonata admin to manage content, defaults to

cmf_core.content_basepath

Form TypeThe bundle defines a form type that can be used for classical "accept terms" checkboxes where you placeurls in the label. Simply specify cmf_routing_terms_form_type as the form type name and specify alabel and an array with content_ids in the options:

1234

$form->add('terms', 'cmf_routing_terms_form_type', array('label' => 'I have seen the <a href="%team%">Team</a> and <a href="%more%">More</a>

pages ...','content_ids' => array('%team%' => '/cms/content/static/team', '%more%' => '/cms/

content/static/more')));

The form type automatically generates the routes for the specified content and passes the routes to thetrans twig helper for replacement in the label.

Further notes

See the documentation of the CMF Routing component11 for information on the RouteObjectInterface,redirections and locales.

Notes:

• RouteObjectInterface: The provided documents implement this interface to map content toroutes and to (optional) provide a custom route name instead of the symfony core compatibleroute name.

• Redirections: This bundle provides a controller to handle redirections.

1234

# app/config/config.ymlcmf_routing:

controllers_by_class:Symfony\Cmf\Component\Routing\RedirectRouteInterface:

cmf_routing.redirect_controller:redirectAction

CustomizeYou can add more RouteEnhancerInterface implementations if you have a case not handled by theprovided ones. Simply define services for your enhancers and tag them withdynamic_router_route_enhancer to have them added to the routing.

11. https://github.com/symfony-cmf/Routing

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 123

Page 124: Symfony Cmf Master

If you use an ODM / ORM different to PHPCR-ODM12, you probably need to specify the class for theroute entity (in PHPCR-ODM13, the class is automatically detected). For more specific needs, have a lookat DynamicRouter and see if you want to extend it. You can also write your own routers to hook into thechain.

Learn more from the Cookbook• Using a Custom Route Repository with Dynamic Router

Further notesFor more information on the Routing component of Symfony CMF, please refer to:

• Routing for an introductory guide on Routing bundle• Routing for most of the actual functionality implementation• Symfony2's Routing14 component page

12. https://github.com/doctrine/phpcr-odm

13. https://github.com/doctrine/phpcr-odm

14. http://symfony.com/doc/current/components/routing/introduction.html

PDF brought to you bygenerated on July 18, 2013

Chapter 24: RoutingBundle | 124

Page 125: Symfony Cmf Master

Chapter 25

RoutingAutoBundle

The RoutingAutoBundle allows you to define automatically created routes for documents. This implies aseparation of the Route and Content documents. If your needs are simple this bundle may not be for youand you should have a look at SimpleCmsBundle.

For the sake of example, we will imagine a blog application that has two routeable contents, the blogitself, and the posts for the blog. We will call these documents Blog and Post, and we will class them ascontent documents.

In our example we add an auto route for the blog, but in reality, as a blog is something you createrarely, you will probably want to create routes for your blog manually, but its up to you.

If we create a new Blog with the title "My New Blog" the bundle could automatically create the route/blogs/my-new-blog. For each new Post it could create a route such as /blogs/my-new-blog/my-posts-title. This URL resolves to a special type of route that we will call the auto route.

By default, when we update a content document that has an auto route the corresponding auto route willalso be updated, when deleting a content document the corresponding auto route will also be deleted.

If required, the bundle can also be configured to do extra stuff, like, for example, leaving aRedirectRoute when the location of a content document changes or automatically displaying an indexpage when an unconfigured intermediate path is accessed (for example, listing all the children under/blogs instead of returning a 404).

Why not simply use a single route?Of course, our fictional blog application could use a single route with a pattern /blogs/my-new-blog/{slug} which could be handled by a controller. Why not just do this?

1. By having a route for each page in the system the application has a knowledge of which URLsare accessible, this can be very useful, for example, when specifying endpoints for menu itemsare generating a site map.

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 125

Page 126: Symfony Cmf Master

Listing 25-1

2. By separating the route from the content we allow the route to be customized independently ofthe content, for example, a blog post may have the same title as another post but might need adifferent URL.

3. Separate route documents are translateable - this means we can have a URL for each language,"/welcome" and "/bienvenue" would each reference the

same document in English and French respectively. This would be difficult if the slugwere embedded in the content document.

4. By decoupling route and content the application doesn't care what is referenced in the routedocument. This means that we can easily replace the class of document referenced.

Anatomy of an automatic URLThe diagram below shows a fictional URL for a blog post. The first 6 elements of the URL are what wewill call the content path. The last element we will call the content name.

../../_images/routing_auto_post_schema.png

The content path is further broken down into route stacks and routes. A route stack is a group of routesand routes are simply documents in the PHPCR tree.

Although routes in this case can be of any document class, only objects which extend the Route1

object will be considered when matching a URL.

The default behavior is to use Generic documents when generating a content path, and thesedocuments will result in a 404 when accessed directly.

Internally each route stack is built up by a builder unit. Builder units contain one path provider class andtwo actions classes one action to take if the provided path exists in the PHPCR tree, the other if it doesnot. The goal of each builder unit is to generate a path and then provide a route object for each elementin that path.

The configuration for the example above could be as follows:

123456789

10111213141516171819

cmf_routing_auto:

auto_route_mapping:My\Namespace\Bundle\BlogBundle\Document\Post:

content_path:# corresponds first route stack in diagram: my, blog, my-blogblog_path:

provider:name: content_objectmethod: getBlog

exists_action:strategy: use

not_exists_action:strategy: throw_exception

# corresponds to second route stack: 2013,04,06date:

provider:name: content_datetime

1. http://api.symfony.com/master/Symfony/Component/Routing/Route.html

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 126

Page 127: Symfony Cmf Master

Listing 25-2

Listing 25-3

20212223242526272829303132333435

method: getPublishedDateexists_action:

strategy: usenot_exists_action:

strategy: create

# corresponds to the content name: My Post Titlecontent_name:

provider:name: content_methodmethod: getTitle

exists_action:strategy: auto_incrementpattern: -%d

not_exists_action:strategy: create

The Post document would then need to implement the methods named above as follows:

123456789

1011121314151617181920

<?php

class Post{

public function getBlog(){

// return the blog object associated with the postreturn $this->blog;

}

public function getPublishedDate(){

return new \DateTime('2013/04/06');}

public function getTitle(){

return "My post title";}

}

Path ProvidersPath providers specify a target path which is used by the subsequent path actions to provide the actualroute documents.

Base providers must be the first configured as the first builder in the content path chain. This is becausethe paths that they provide correspond directly to an existing path, i.e. they have an absolute reference.

specified (base provider)

This is the most basic path provider and allows you to specify an exact (fixed) path.

123

path_provider:name: specifiedpath: this/is/a/path

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 127

Page 128: Symfony Cmf Master

Listing 25-4

Listing 25-5

Listing 25-6

Options:

• path - required The path to provide.

You never specifiy absolute paths in the auto route system. If the builder unit is the first contentpath chain it is understood that it is the base of an absolute path.

content_object (base provider)

The content object provider will try and provide a path from an object implementingRouteAwareInterface provided by a designated method on the content document. For example, if youhave a Post class, which has a getBlog method, using this provider you can tell the Post auto route touse the route of the blog as a base.

So basically, if your blog content has a path of /this/is/my/blog you can use this path as the base ofyour Post auto-route.

Example:

123

provider:name: content_objectmethod: getBlog

At the time of writing translated objects are not supported. This isn't hard to do, but well, I justhavn't done it yet.

Options:

• method: required Method used to return the document whose route path we wishto use.

content_method

The content_method provider allows the content object (e.g. a blog Post) to specify a path using one ofits methods. This is quite a powerful method as it allows the content document to do whatever it can toproduce the route, the disadvantage is that your content document will have extra code in it.

Example 1:

123

path_provider:name: content_methodmethod: getTitle

This example will use the existing method "getTitle" of the Post document to retrieve the title. By defaultall strings are slugified.

The method can return the path either as a single string or an array of path elements as shown in thefollowing example:

123

<?php

class Post

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 128

Page 129: Symfony Cmf Master

Listing 25-7

Listing 25-8

456789

1011121314

{public function getTitle(){

return "This is a post";}

public function getPathElements(){

return array('this', 'is', 'a', 'path');}

}

Options:

• method: required Method used to return the route name/path/path elements.• slugify: If we should use the slugifier, default is true.

content_datetime

The content_datettime provider will provide a path from a DateTime object provided by a designatedmethod on the content document.

Example 1:

123

provider:name: content_datetimemethod: getDate

Example 2:

1234

provider:name: content_datetimemethod: getDatedate_format: Y/m/d

This method extends content_method and inherits the slugify feature. Internally we return a stringusing the DateTime->format() method. This means that you can specify your date in anyway youlike and it will be automatically slugified, also, by adding path separators in the date_format youare effectively creating routes for each date component as slugify applies to each element of thepath.

Options:

• method: required Method used to return the route name/path/path elements.• slugify: If we should use the slugifier, default is true.• date_format: Any date format accepted by the DateTime class, default Y-m-d.

Path Exists ActionsThese are the default actions available to take if the path provided by a path_provider already exists andso creating a new path would create a conflict.

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 129

Page 130: Symfony Cmf Master

Listing 25-9

Listing 25-10

Listing 25-11

Listing 25-12

auto_increment

The auto_increment action will add a numerical suffix to the path, for example my/path would firstbecome my/path-1 and if that path also exists it will try my/path-2, my/path-3 and so on into infinityuntil it finds a path which doesn't exist.

This action should typically be used in the content_name builder unit to resolve conflicts. Using it in thecontent_path builder chain would not make much sense (I can't imagine any use cases at the moment).

Example:

12

exists_action:name: auto_increment

use

The use action will simply take the existing path and use it. For example, in our post example the firstbuilder unit must first determine the blogs path, /my/blog, if this path exists (and it should) then we willuse it in the stack.

This action should typically be used in one of the content path builder units to specify that we should usethe existing route, on the other hand, using this as the content name builder action should cause the oldroute to be overwritten.

Example:

12

exists_action:name: use

Path not Exists ActionsThese are the default actions available to take if the path provided by a path_provider does not exist.

create

The create action will create the path. currently all routes provided by the content path build unitswill be created as Generic documents, whilst the content name route will be created as an AutoRoutedocument.

12

not_exists_action:name: create

throw_exception

This action will throw an exception if the route provided by the path provider does not exist. You shouldtake this action if you are sure that the route should exist.

12

not_exists_action:name: create

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 130

Page 131: Symfony Cmf Master

Listing 25-13

Listing 25-14

Listing 25-15

Customization

Adding Path Providers

The goal of a PathProvider class is to add one or several path elements to the route stack. For example,the following provider will add the path foo/bar to the route stack:

123456789

101112

<?php

use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathProviderInterface;use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack;

class FoobarProvider implements PathProviderInterface{

public function providePath(RouteStack $routeStack){

$routeStack->addPathElements(array('foo', 'bar'));}

}

To use the path provider you must register it in the DIC and add the cmf_routing_auto.provider tagand set the alias accordingly.

1234567

<serviceid="my_cms.some_bundle.path_provider.foobar"class="FoobarProvider"scope="prototype"

><tag name="cmf_routing_auto.provider" alias="foobar"/>

</service>

The foobar path provider is now available as foobar.

The that both path providers and path actions need to be defined with a scope of "prototype". Thisensures that each time the auto routing system requests the class a new one is given and we do nothave any state problems.

Adding Path Actions

In the auto routing system, a "path action" is an action to take if the path provided by the "path provider"exists or not.

You can add a path action by extending the PathActionInterface and registering your new classcorrectly in the DI configuration.

This is a very simple implementation from the bundle - it is used to throw an exception when a pathalready exists:

1234567

<?php

namespace Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathNotExists;

use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathActionInterface;use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\Exception\CouldNotFindRouteException;use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack;

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 131

Page 132: Symfony Cmf Master

Listing 25-16

89

10111213141516171819

class ThrowException implements PathActionInterface{

public function init(array $options){}

public function execute(RouteStack $routeStack){

throw new CouldNotFindRouteException('/'.$routeStack->getFullPath());}

}

It is registered in the DI configuration as follows:

1234567

<serviceid="my_cms.not_exists_action.throw_exception"class="My\Cms\AutoRoute\PathNotExists\ThrowException"scope="prototype"><tag name="cmf_routing_auto.not_exists_action" alias="throw_exception"/>

</service>

Note the following:

• Scope: Must always be set to prototype;• Tag: The tag registers the service with the auto routing system, it can be one of the

following;

• cmf_routing_auto.exists.action - if the action is to be used when a pathexists;

• cmf_routing_auto.not_exists.action - if the action is to be used when apath does not exist;

• Alias: The alias of the tag is the name by which you will reference this action in the autorouting schema.

PDF brought to you bygenerated on July 18, 2013

Chapter 25: RoutingAutoBundle | 132

Page 133: Symfony Cmf Master

Listing 26-1

Chapter 26

SearchBundle

The SearchBundle1 provides integration with LiipSearchBundle2 to provide a site wide search.

Dependencies

• LiipSearchBundle3

ConfigurationThe configuration key for this bundle is cmf_search

123456789

# app/config/config.ymlcmf_search:

document_manager_name: defaulttranslation_strategy: child # can also be set to an empty string or attributetranslation_strategy: attributesearch_path: /cms/contentsearch_fields:

title: titlesummary: body

1. https://github.com/symfony-cmf/SearchBundle#readme

2. https://github.com/liip/LiipSearchBundle

3. https://github.com/liip/LiipSearchBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 26: SearchBundle | 133

Page 134: Symfony Cmf Master

Chapter 27

SimpleCmsBundle

The SimpleCmsBundle1 provides a simplistic CMS on top of the CMF components and bundles.

While the core CMF components focus on flexibility, the simple CMS trades away some of that flexibilityin favor of simplicity.

The SimpleCmsBundle provides a solution to easily map content, routes and menu items based on asingle tree structure in the content repository.

For a simple example installation of the bundle check out the Symfony CMF Standard Edition2

You can find an introduction to the bundle in the Getting started3 section.

The CMF website4 is another application using the SimpleCmsBundle.

DependenciesAs specified in the bundle composer.json this bundle depends on most CMF bundles.

ConfigurationThe configuration key for this bundle is cmf_simple_cms

The use_menu option automatically enables a service to provide menus out of the simple cms if theMenuBundle is enabled. You can also explicitly disable it if you have the menu bundle but do not wantto use the default service, or explicitly enable to get an error if the menu bundle becomes unavailable.

The routing section is configuring what template or controller to use for a content class. This is reusingwhat the cmf routing bundle does, please see the corresponding routing configuration section. It alsoexplains the generic_controller.

See the section below for multilanguage support.

1. https://github.com/symfony-cmf/SimpleCmsBundle#readme

2. https://github.com/symfony-cmf/symfony-cmf-standard

3. #cmf-getting_started-simplecms

4. https://github.com/symfony-cmf/cmf-website/

PDF brought to you bygenerated on July 18, 2013

Chapter 27: SimpleCmsBundle | 134

Page 135: Symfony Cmf Master

Listing 27-1

Listing 27-2

123456789

10111213141516171819

# app/config/config.ymlcmf_simple_cms:

use_menu: auto # use true/false to force providing / not providing a menuuse_sonata_admin: auto # use true/false to force using / not using sonata adminsonata_admin:

sort: false # set to asc|desc to sort children by publication datedocument_class: Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page# controller to use to render documents with just custom templategeneric_controller: cmf_content.controller:indexAction# where in the PHPCR tree to store the pagesbasepath: /cms/simplerouting:

content_repository_id: cmf_routing.content_repositorycontrollers_by_class:

# ...templates_by_class:

# ...multilang:

locales: []

If you have the Sonata PHPCR-ODM admin bundle enabled but do NOT want to show the defaultadmin provided by this bundle, you can add the following to your configuration

12

cmf_simple_cms:use_sonata_admin: false

Multi-language supportThe multi-language-mode is enabled by providing the list of allowed locales in the multilang > localesfield.

In multi-language-mode the Bundle will automatically use theSymfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangPage as the document_class unless adifferent class is configured explicitly.

This class will by default prefix all routes with /{_locale}. This behavior can be disabled by setting thesecond parameter in the constructor of the model to false.

Furthermore the routing layer will be configured to useSymfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangRouteRepository which will ensurethat even with the locale prefix the right content node will be found. Furthermore it will automaticallyadd a _locale requirement listing the current available locales for the matched route.

Since SimpleCmsBundle only provides a single tree structure, all nodes will have the same nodename for all languages. So a url http://foo.com/en/bar for english content will look likehttp://foo.com/de/bar for german content. At times it might be most feasible to use integers asthe node names and simple append the title of the node in the given locale as an anchor. So forexample http://foo.com/de/1#my title and http://foo.com/de/1#mein title. If you needlanguage specific URLs, you want to use the CMF routing bundle and content bundle directly tohave a separate route document per language.

PDF brought to you bygenerated on July 18, 2013

Chapter 27: SimpleCmsBundle | 135

Page 136: Symfony Cmf Master

Listing 27-3

Listing 27-4

Listing 27-5

Listing 27-6

RenderingYou can specify the template to render a SimpleCms page, or use a controller where you then give thepage document to the template. A simple example for such a template is:

123456789

1011

{% block content %}<h1>{{ page.title }}</h1>

<div>{{ page.body|raw }}</div>

<ul>{% for tag in page.tags %}

<li>{{ tag }}</li>{% endfor %}</ul>

{% endblock %}

If you have the CreateBundle enabled, you can also output the document with RDFa annotations,allowing you to edit the content as well as the tags in the frontend. The most simple form is the followingtwig block:

12345

{% block content %}{% createphp page as="rdf" %}

{{ rdf|raw }}{% endcreatephp %}

{% endblock %}

If you want to control more detailed what should be shown with RDFa, see chapter CreateBundle.

Extending the Page classThe default Page document Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page is relativelysimple, shipping with a handful of the most common properties for building a typical page: title, body,tags, publish dates etc.

If this is not enough for your project you can easily provide your own document by extending the defaultPage document and explicitly setting the configuration parameter to your own document class:

12345

# app/config/config.ymlcmf_simple_cms:

# ...document_class: Acme\DemoBundle\Document\MySuperPage# ...

Alternatively, the default Page document contains an extras property. This is a key - value store (wherevalue must be string or null) which can be used for small trivial additions, without having to extend thedefault Page document.

For example:

12345

$page = new Page();

$page->setTitle('Hello World!');$page->setBody('Really interesting stuff...');

PDF brought to you bygenerated on July 18, 2013

Chapter 27: SimpleCmsBundle | 136

Page 137: Symfony Cmf Master

6789

1011121314

// set extras$extras = array(

'subtext' => 'Add CMS functionality to applications built with the Symfony2 PHPframework.',

'headline-icon' => 'exclamation.png',);

$page->setExtras($extras);

$documentManager->persist($page);

These properties can then be accessed in your controller or templates via the getExtras() orgetExtra($key) methods.

PDF brought to you bygenerated on July 18, 2013

Chapter 27: SimpleCmsBundle | 137

Page 138: Symfony Cmf Master

Listing 28-1

Chapter 28

SonataDoctrinePhpcrAdminBundle

The SonataDoctrinePhpcrAdminBundle1 provides integration with the SonataAdminBundle to enableeasy creation of admin UIs.

Dependencies

• SonataAdminBundle2

• TreeBundle3

Configuration

123456789

1011121314151617

sonata_doctrine_phpcr_admin:templates:

form:

# Default:- SonataDoctrinePHPCRAdminBundle:Form:form_admin_fields.html.twig

filter:

# Default:- SonataDoctrinePHPCRAdminBundle:Form:filter_admin_fields.html.twig

types:list:

# Prototypename: []

document_tree:# Prototype

1. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle#readme

2. https://github.com/sonata-project/SonataAdminBundle

3. https://github.com/symfony-cmf/TreeBundle#readme

PDF brought to you bygenerated on July 18, 2013

Chapter 28: SonataDoctrinePhpcrAdminBundle | 138

Page 139: Symfony Cmf Master

18192021

class: # name of the class# class names of valid children, manage tree operations for them and hide

other childrenvalid_children: []image:

PDF brought to you bygenerated on July 18, 2013

Chapter 28: SonataDoctrinePhpcrAdminBundle | 139

Page 140: Symfony Cmf Master

Listing 29-1

Listing 29-2

Chapter 29

TreeBrowserBundle

The TreeBrowserBundle1 provides a tree navigation on top of a PHPCR repository.

This bundle consists of two parts:

• Generic Tree Browser with a TreeInterface• PHPCR tree implementation and GUI for a PHPCR browser

Dependencies

• FOSJsRoutingBundle2

• Install jQuery. SonatajQueryBundle3 strongly suggested.

ConfigurationThe configuration key for this bundle is cmf_tree_browser:

123

# app/config/config.ymlcmf_tree_browser:

session: default

RoutingThe bundle will create routes for each tree implementation found. In order to make those routes availableyou need to include the following in your routing configuration:

1. https://github.com/symfony-cmf/TreeBrowserBundle#readme

2. https://github.com/FriendsOfSymfony/FOSJsRoutingBundle

3. https://github.com/sonata-project/SonatajQueryBundle

PDF brought to you bygenerated on July 18, 2013

Chapter 29: TreeBrowserBundle | 140

Page 141: Symfony Cmf Master

1234

# app/config/routing.ymlcmf_tree:

resource: .type: 'cmf_tree'

UsageYou have select.js and init.js which are a wrapper to build a jquery tree. Use them withSelectTree.initTree resp. AdminTree.initTree

• SelectTree in select.js is a tree to select a node to put its id into a field• AdminTree in init.js is a tree to create, move and edit nodes

Both have the following options when creating:

• config.selector: jquery selector where to hook in the js tree• config.rootNode: id to the root node of your tree, defaults to "/"• config.selected: id of the selected node• config.ajax.children_url: Url to the controller that provides the children of a node• config.routing_defaults: array for route parameters (such as _locale etc.)• config.path.expanded: tree path where the tree should be expanded to at the moment• config.path.preloaded: tree path what node should be preloaded for faster user experience

select.js only

• config.output: where to write the id of the selected node

init.js only

• config.labels: array containing the translations for the labels of the context menu (keyscreateItem and deleteItem)

• config.ajax.move_url: Url to the controller for moving a child (i.e. giving it a new parentnode)

• config.ajax.reorder_url: Url to the controller for reordering siblings• config.types: array indexed with the node types containing information about valid_children,

icons and available routes, used for the creation of context menus and checking during moveoperations.

ExamplesLook at the templates in the Sonata Admin Bundle for examples how to build the tree:

• init.js4

• select.js5 (look for doctrine_phpcr_type_tree_model_widget)

In the same bundle the PhpcrOdmTree6 implements the tree interface and gives an example how toimplement the methods.

Here are some common tips about TreeBrowser utilization:

4. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/blob/master/Resources/views/Tree/tree.html.twig

5. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/blob/master/Resources/views/Form/form_admin_fields.html.twig

6. https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/blob/master/Tree/PhpcrOdmTree.php

PDF brought to you bygenerated on July 18, 2013

Chapter 29: TreeBrowserBundle | 141

Page 142: Symfony Cmf Master

Listing 29-3

Listing 29-4

Listing 29-5

Define Tree Elements

The first step, is to define all the elements allowed in the tree and their children. Have a look at the cmf-sandbox configuration7, the section document_tree in sonata_doctrine_phpcr_admin.

This configuration is set for all your application trees regardless their type (admin or select).

123456789

101112131415

sonata_doctrine_phpcr_admin:document_tree_defaults: [locale]document_tree:

Doctrine\ODM\PHPCR\Document\Generic:valid_children:

- allSymfony\Cmf\Bundle\ContentBundle\Document\MultilangStaticContent:

valid_children:- Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock- Symfony\Cmf\Bundle\BlockBundle\Document\ContainerBlock- Symfony\Cmf\Bundle\BlockBundle\Document\ReferenceBlock- Symfony\Cmf\Bundle\BlockBundle\Document\ActionBlock

Symfony\Cmf\Bundle\BlockBundle\Document\ReferenceBlock:valid_children: []

# ...

How to add an Admin Tree to Your Page

This can be done either in an action template or in a custom block.

You have to specify the tree root and the selected item, this allows you to have different type of contentin your tree.

In this example, we will have the menu elements.

For Symfony 2.2 and later:

12345

{% render(controller('sonata.admin.doctrine_phpcr.tree_controller:treeAction')) with {'root': websiteId ~ "/menu",'selected': menuNodeId,'_locale': app.request.locale

} %}

For Symfony 2.1:

12345

{% render 'sonata.admin.doctrine_phpcr.tree_controller:treeAction' with {'root': websiteId ~ "/menu",'selected': menuNodeId,'_locale': app.request.locale

} %}

How to Customize the Tree Behaviour

The TreeBrowserBundle is based on jsTree8. jsTree works with events, dispatched everytime the user doesan action.

A simple way to customize the tree behavior is to bind your actions to those events.

7. https://github.com/symfony-cmf/cmf-sandbox/blob/master/app/config/config.yml

8. http://www.jstree.com/documentation

PDF brought to you bygenerated on July 18, 2013

Chapter 29: TreeBrowserBundle | 142

Page 143: Symfony Cmf Master

Listing 29-6

Listing 29-7

Listing 29-8

Listing 29-9

If you have a look at init.js and select.js, you will notice that actions are already bound to some of thetree events. If the default behavior is not what you need, jQuery provide the unbind function to solve theproblem.

Here is a simple way to remove the context menu from the admin tree (add the controller call aroundthe controller name inside render for Symfony 2.2):

{% render 'sonata.admin.doctrine_phpcr.tree_controller:treeAction' with {'root': websiteId ~ "/menu",'selected': menuNodeId,'_locale': app.request.locale

} %}<script type="text/javascript">

$(document).ready(function() {$('#tree').bind("before.jstree", function (e, data) {

if (data.plugin === "contextmenu") {e.stopImmediatePropagation();return false;

}});

});</script>

By default, the item selection open the edit route of the admin class of the element. This action is bind tothe select_node.jstree.

If you want to remove it, you just need to call the unbind function on this event:

12345

<script type="text/javascript">$(document).ready(function() {

$('#tree').unbind('select_node.jstree');});

</script>

Then you can bind it on another action.

For example, if your want to open a custom action:

$('#tree').bind("select_node.jstree", function (event, data) {if ((data.rslt.obj.attr("rel") == 'Symfony_Cmf_Bundle_MenuBundle_Document_MenuNode'

|| data.rslt.obj.attr("rel") =='Symfony_Cmf_Bundle_MenuBundle_Document_MultilangMenuNode')

&& data.rslt.obj.attr("id") != '{{ menuNodeId }}') {

var routing_defaults = {'locale': '{{ locale }}', '_locale': '{{ _locale }}'};routing_defaults["id"] = data.rslt.obj.attr("url_safe_id");window.location = Routing.generate('presta_cms_page_edit', routing_defaults);

}});

Don't forget to add your custom route to the fos_js_routing.routes_to_expose configuration:

1234567

fos_js_routing:routes_to_expose:

- cmf_tree_browser.phpcr_children- cmf_tree_browser.phpcr_move- sonata.admin.doctrine_phpcr.phpcrodm_children- sonata.admin.doctrine_phpcr.phpcrodm_move- presta_cms_page_edit

PDF brought to you bygenerated on July 18, 2013

Chapter 29: TreeBrowserBundle | 143

Page 144: Symfony Cmf Master

Listing 30-1

Chapter 30

Using a Custom Document Class Mapper withPHPCR-ODM

The default document class mapper of PHPCR-ODM uses the attribute phpcr:class to store andretrieve the document class of a node. When accessing an existing PHPCR repository, you might needdifferent logic to decide on the class.

You can extend the DocumentClassMapper or implement DocumentClassMapperInterface fromscratch. The important methods are getClassName that needs to find the class name and writeMetadatathat needs to make sure the class of a newly stored document can be determined when loading it again.

Then you can overwrite the doctrine.odm_configuration service to call setDocumentClassMapper onit. An example from the symfony cmf sandbox1 (magnolia_integration branch):

# if you want to overwrite default configuration, otherwise use a# custom name and specify in odm configuration block

doctrine.odm_configuration:class: %doctrine_phpcr.odm.configuration.class%calls:

- [ setDocumentClassMapper, [@sandbox_magnolia.odm_mapper] ]

sandbox_magnolia.odm_mapper:class: "Sandbox\MagnoliaBundle\Document\MagnoliaDocumentClassMapper"arguments:

- 'standard-templating-kit:pages/stkSection': 'Sandbox\MagnoliaBundle\Document\Section'

Here you create a mapper that uses a configuration to read node information and map that onto adocument class.

If you have several repositories, you can use one configuration per repository. See Configuring MultipleSessions.

1. https://github.com/symfony-cmf/cmf-sandbox/tree/magnolia_integration

PDF brought to you bygenerated on July 18, 2013

Chapter 30: Using a Custom Document Class Mapper with PHPCR-ODM | 144

Page 145: Symfony Cmf Master

Listing 31-1

Chapter 31

Using a Custom Route Repository withDynamic Router

The Dynamic Router allows you to customize the Route Provider (i.e. the class responsible for retrievingroutes from the database) and, by extension, the Route objects.

Creating the Route ProviderThe route provider must implement the RouteProviderInterface. The following class provides a simplesolution using an ODM Repository.

123456789

1011121314151617181920212223

// src/Acme/DemoBundle/Repository/RouteProvider.phpnamespace Acme\DemoBundle\Repository;

use Doctrine\ODM\PHPCR\DocumentRepository;use Symfony\Cmf\Component\Routing\RouteProviderInterface;use Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route as SymfonyRoute;

class RouteProvider extends DocumentRepository implements RouteProviderInterface{

/*** This method is used to find routes matching the given URL.*/public function findManyByUrl($url){

// for simplicity we retrieve one route$document = $this->findOneBy(array(

'url' => $url,));

$pattern = $document->getUrl(); // e.g. "/this/is/a/url"

$collection = new RouteCollection();

PDF brought to you bygenerated on July 18, 2013

Chapter 31: Using a Custom Route Repository with Dynamic Router | 145

Page 146: Symfony Cmf Master

Listing 31-2

2425262728293031323334353637383940414243444546474849505152535455

// create a new Route and set our document as// a default (so that we can retrieve it from the request)$route = new SymfonyRoute($pattern, array(

'document' => $document,));

// add the route to the RouteCollection using// a unique ID as the key.$collection->add('my_route_'.uniqid(), $route);

return $collection;}

/*** This method is used to generate URLs, e.g. {{ path('foobar') }}.*/public function getRouteByName($name, $params = array()){

$document = $this->findOneBy(array('name' => $name,

));

if ($route) {$route = new SymfonyRoute($route->getPattern(), array(

'document' => $document,));

}

return $route;}

}

As you may have noticed we return a RouteCollection object - why not return a single Route?The Dynamic Router allows us to return many candidate routes, in other words, routes that mightmatch the incoming URL. This is important to enable the possibility of matching dynamic routes,/page/{page_id}/edit for example. In our example we match the given URL exactly and onlyever return a single Route.

Replacing the Default CMF Route ProviderTo replace the default RouteProvider, it is necessary to modify your configuration as follows:

12345

# app/config/config.ymlcmf_routing:

dynamic:enabled: trueroute_provider_service_id: acme_demo.provider.endpoint

Where acme_demo.provider.endpoint is the service ID of your route provider. See Creating andconfiguring services in the container1 for information on creating custom services.

1. http://symfony.com/doc/current/book/service_container.html#creating-configuring-services-in-the-container/

PDF brought to you bygenerated on July 18, 2013

Chapter 31: Using a Custom Route Repository with Dynamic Router | 146

Page 147: Symfony Cmf Master

Chapter 32

Installing the CMF sandbox

This tutorial shows how to install the Symfony CMF Sandbox, a demo platform aimed at showing thetool's basic features running on a demo environment. This can be used to evaluate the platform or to seeactual code in action, helping you understand the tool's internals.

While it can be used as such, this sandbox does not intend to be a development platform. If you arelooking for installation instructions for a development setup, please refer to:

• Installing the Symfony CMF Standard Edition page for instructions on how to quickly installthe CMF (recommended for development)

• Installing and Configuring the CMF Core for step-by-step installation and configuration details(if you want to know all the details)

RequirementsAs Symfony CMF Sandbox is based on Symfony2, you should make sure you meet the Requirements forrunning Symfony21. Git 1.6+2, Curl3 and PHP Intl are also needed to follow the installation steps listedbelow.

For the PHPCR repository requirements, see PHPCR-ODM requirements

Installation

Apache Jackrabbit

The Symfony CMF Sandbox uses Jackalope with Apache JackRabbit by default. Alternative storagemethods can be configured, but this is the most tested, and should be the easiest to setup.

You can get the latest Apache Jackrabbit version from the project's official download page4. To start it,use the following command

1. http://symfony.com/doc/current/reference/requirements.html

2. http://git-scm.com/

3. http://curl.haxx.se/

4. http://jackrabbit.apache.org/downloads.html

PDF brought to you bygenerated on July 18, 2013

Chapter 32: Installing the CMF sandbox | 147

Page 148: Symfony Cmf Master

Listing 32-1

Listing 32-2

Listing 32-3

Listing 32-4

Listing 32-5

Listing 32-6

1 $ java -jar jackrabbit-standalone-*.jar

By default the server is listening on the 8080 port, you can change this by specifying the port on thecommand line.

1 $ java -jar jackrabbit-standalone-*.jar --port 8888

For unix systems, you can get the start-stop script for /etc/init.d here5.

Getting the Sandbox Code

The Symfony CMF Sandbox source code is available on github. To get it use:

1 $ git clone git://github.com/symfony-cmf/cmf-sandbox.git

Move into the folder and copy the default configuration files:

123

$ cd cmf-sandbox$ cp app/config/parameters.yml.dist app/config/parameters.yml$ cp app/config/phpcr_jackrabbit.yml.dist app/config/phpcr.yml

These two files include the default configuration parameters for the sandbox storage mechanism. Youcan modify them to better fit your needs

The second configuration file refers to specific jackalope + jackrabbit configuration. There areother files available for different stack setups.

Next, get composer and install and the necessary bundles (this may take a while):

12345

# get composer$ curl -s http://getcomposer.org/installer | php --

# install bundles$ php composer.phar install

On Windows you need to run the shell as Administrator or edit the composer.json and changethe line "symfony-assets-install": "symlink" to "symfony-assets-install": "". If you failto do this you might receive:

123

[Symfony\Component\Filesystem\Exception\IOException]Unable to create symlink due to error code 1314: 'A required privilege is not heldby the client'. Do you have the required Administrator-rights?

Preparing the PHPCR Repository

Now that you have all the code, you need to setup your PHPCR repository. PHPCR organizes data inworkspaces and sandbox uses the "default" workspace, which is exists by default in Jackrabbit. If you use

5. https://github.com/sixty-nine/Jackrabbit-startup-script

PDF brought to you bygenerated on July 18, 2013

Chapter 32: Installing the CMF sandbox | 148

Page 149: Symfony Cmf Master

Listing 32-7

Listing 32-8

Listing 32-9

Listing 32-10

Listing 32-11

other applications that require Jackrabbit or if you just wish to change the workspace name, you can doso in app/config/phpcr.yml. The following command will create a new workspace named "sandbox" inJackrabbit. If you decide to use the "default" workspace, you can skip it.

1 $ php app/console doctrine:phpcr:workspace:create sandbox

Once your workspace is set up, you need to register the node types6 for phpcr-odm:

1 $ php app/console doctrine:phpcr:repository:init

Import the Fixtures

The sandbox provides a set of demo content to show various use cases. They are loaded using the fixtureloading concept of PHPCR-ODM.

1 $ php app/console -v doctrine:phpcr:fixtures:load

This command loads fixtures from all bundles that provide them in the DataFixtures/PHPCR folder. Thesandbox has fixtures in the MainBundle. Note that loading fixtures from non-default locations is possibleas well, just not needed in this case.

Accessing your Sandbox

The sandbox should now be accessible on your web server.

1 http://localhost/app_dev.php

In order to run the sandbox in production mode you need to generate the doctrine proxies and dump theassetic assets:

12

$ php app/console cache:clear --env=prod --no-debug$ php app/console assetic:dump --env=prod --no-debug

Alternative Storage MechanismsSymfony CMF and the sandbox are storage agnostic, which means you can change the storagemechanism without having to change your code. The default storage mechanism for the sandbox isJackalope + Apache Jackrabbit, as it's the most tested and stable setup. However, other alternatives areavailable.

Jackalope + Doctrine DBAL

By default, when using Doctrine DBAL, data is stored using a Sqlite7 database. Refer to theproject's page for installation instructions. If you wish to use other database systems, change theconfiguration parameters in app/config/parameters.yml. Refer to Symfony's page on DoctrineDBAL configuration8 or Doctrine's documentation9 for more information.

6. https://github.com/doctrine/phpcr-odm/wiki/Custom-node-type-phpcr%3Amanaged

7. http://www.sqlite.org/

PDF brought to you bygenerated on July 18, 2013

Chapter 32: Installing the CMF sandbox | 149

Page 150: Symfony Cmf Master

Listing 32-12

Listing 32-13

Listing 32-14

Listing 32-15

Listing 32-16

Listing 32-17

Listing 32-18

Move into the sandbox folder and copy the default configuration file for Doctrine DBAL setup:

12

$ cd cmf-sandbox$ cp app/config/phpcr_doctrine_dbal.yml.dist app/config/phpcr.yml

Next, you need to install the actual Doctrine DBAL bundle required by jackalope:

1 $ php composer.phar require jackalope/jackalope-doctrine-dbal:dev-master

And create and init your database:

12

$ php app/console doctrine:database:create$ php app/console doctrine:phpcr:init:dbal

After this, your should follow the steps in Preparing the PHPCR repository.

Doctrine caching

Optionally, to improve performance and enable the meta data, you can install LiipDoctrineCacheBundleby typing the following command:

1 $ php composer.phar require liip/doctrine-cache-bundle:dev-master

And adding the following entry to your app/AppKernel.php:

123456789

1011

// app/AppKernel.php

// ...public function registerBundles(){$bundles = array(

// ...new Liip\DoctrineCacheBundle\LiipDoctrineCacheBundle(),// ...

);}

Finally, uncomment the caches settings in the phpcr.yml as well as the liip_doctrine_cache settingsin config.yml.

1234

# app/config/phpcr.ymlcaches:

meta: liip_doctrine_cache.ns.metanodes: liip_doctrine_cache.ns.nodes

1234567

# app/config/config.yml

# ...

# jackalope doctrine cachingliip_doctrine_cache:

namespaces:

8. http://symfony.com/doc/current/reference/configuration/doctrine.html#doctrine-dbal-configuration

9. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html

PDF brought to you bygenerated on July 18, 2013

Chapter 32: Installing the CMF sandbox | 150

Page 151: Symfony Cmf Master

Listing 32-19

Listing 32-20

Listing 32-21

Listing 32-22

Listing 32-23

Listing 32-24

89

1011

meta:type: file_system

nodes:type: file_system

Midgard2 PHPCR Provider

If you want to run the CMF sandbox with the Midgard2 PHPCR10 provider instead of Jackrabbit, youneed to install the midgard2 PHP extension. On current Debian/Ubuntu systems, this is simply donewith:

1 $ sudo apt-get install php5-midgard2

On OS X you can install it using either Homebrew11 with:

1 $ brew install midgard2-php

or MacPorts12 with:

1 $ sudo port install php5-midgard2

You also need to download the midgard_tree_node.xml13 and midgard_namespace_registery.xml14 schemafiles and place them into <your-midgard2-folder>/schema (defaults to "/usr/share/midgard2/schema")

To have the Midgard2 PHPCR implementation installed run the following additional command:

1 $ php composer.phar require midgard/phpcr:dev-master

Finally, switch to one of the Midgard2 configuration file:

1 $ cp app/config/phpcr_midgard_mysql.yml.dist app/config/phpcr.yml

or:

1 $ cp app/config/phpcr_midgard_sqlite.yml.dist app/config/phpcr.yml

After this, your should follow the steps in Preparing the PHPCR repository to continue the installationprocess.

10. http://midgard-project.org/phpcr/

11. http://mxcl.github.com/homebrew/

12. http://www.macports.org/

13. https://raw.github.com/midgardproject/phpcr-midgard2/master/data/share/schema/midgard_tree_node.xml

14. https://github.com/midgardproject/phpcr-midgard2/raw/master/data/share/schema/midgard_namespace_registry.xml

PDF brought to you bygenerated on July 18, 2013

Chapter 32: Installing the CMF sandbox | 151

Page 152: Symfony Cmf Master

Chapter 33

Routing

The Symfony CMF Routing component1 library extends the Symfony2 core routing component. Eventhough it has Symfony in its name, it does not need the full Symfony2 framework and can be used instandalone projects. For integration with Symfony we provide RoutingBundle.

At the core of the Symfony CMF Routing component is the ChainRouter, that is used instead ofthe Symfony2's default routing system. The ChainRouter can chain several RouterInterfaceimplementations, one after the other, to determine what should handle each request. The defaultSymfony2 router can be added to this chain, so the standard routing mechanism can still be used.

Additionally, this component is meant to provide useful implementations of the routing interfaces.Currently, it provides the DynamicRouter, which uses a RequestMatcherInterface to dynamically loadRoutes, and can apply RouteEnhancerInterface strategies in order to manipulate them. The providedNestedMatcher can dynamically retrieve Symfony2 Route2 objects from a RouteProviderInterface.This interfaces abstracts a collection of Routes, that can be stored in a database, like Doctrine PHPCR-ODM or Doctrine ORM. The DynamicRouter also uses a UrlGenerator instance to generate Routes andan implementation is provided under ProviderBasedGenerator that can generate routes loaded from aRouteProviderInterface instance, and the ContentAwareGenerator on top of it to determine the routeobject from a content object.

To use this component outside of the Symfony2 framework context, have a look at the coreSymfony2 Routing3 to get a fundamental understanding of the component. CMF Routing justextends the basic behaviour.

Dependencies

This component uses Composer4. It needs the Symfony2 Routing5 component and the Symfony2HttpKernel6 (for the logger interface and cache warm-up interface).

1. https://github.com/symfony/symfony-docs/issues?milestone=1&state=open

2. http://api.symfony.com/master/Symfony/Component/Routing/Route.html

3. http://symfony.com/doc/current/components/routing/introduction.html

4. http://getcomposer.org

5. http://symfony.com/doc/current/components/routing/introduction.html

PDF brought to you bygenerated on July 18, 2013

Chapter 33: Routing | 152

Page 153: Symfony Cmf Master

For the DynamicRouter you will need something to implement the RouteProviderInterface with. Wesuggest using Doctrine as this provides an easy way to map classes into a database.

ChainRouterAt the core of Symfony CMF's Routing component sits the ChainRouter. It's used as a replacement forSymfony2's default routing system, and is responsible for determining the parameters for each request.Typically you need to determine which Controller will handle this request - in the full stack Symfony2framework, this is identified by the _controller field of the parameters.

The ChainRouter works by accepting a set of prioritized routing strategies, RouterInterface7

implementations, commonly referred to as "Routers".

When handling an incoming request, the ChainRouter iterates over the configured routers, by theirconfigured priority, until one of them is able to match()8 the request and provide the request parameters.

RoutersThe ChainRouter is incapable of, by itself, making any actual routing decisions. Its sole responsibilityis managing the given set of Routers, which are responsible for matching a request and determining itsparameters.

You can easily create your own routers by implementing RouterInterface9 but Symfony CMF alreadyincludes a powerful route matching system that you can extend to your needs.

If you are using this as part of a full Symfony CMF project, please refer to RoutingBundle forinstructions on how to add Routers to the ChainRouter. Otherwise, use the ChainRouter's addmethod to configure new routers.

Symfony2 Default Router

The Symfony2 routing mechanism is itself a RouterInterface implementation, which means you canuse it as a Router in the ChainRouter. This allows you to use the default routing declaration system.

Dynamic Router

The Symfony2 default Router was developed to handle static Route definitions, as they are traditionallydeclared in configuration files, prior to execution. This makes it a poor choice to handle dynamicallydefined routes, and to handle those situations, this bundle comes with the DynamicRouter. It is capableof handling Routes from more dynamic data sources, like database storage, and modify the resultingparameters using a set of enhancers that can be easily configured, greatly extending Symfony2's defaultfunctionality.

Matcher

The DynamicRouter uses a RequestMatcherInterface or UrlMatcherInterface instance to match thereceived Request or URL, respectively, to a parameters array. The actual matching logic depends on theunderlying implementation you choose. You can easily use you own matching strategy by passing it to

6. http://symfony.com/doc/current/components/http_kernel/introduction.html

7. http://api.symfony.com/master/Symfony/Component/Routing/RouterInterface.html

8. http://api.symfony.com/master/Symfony/Component/Routing/RouterInterface.html#match()

9. http://api.symfony.com/master/Symfony/Component/Routing/RouterInterface.html

PDF brought to you bygenerated on July 18, 2013

Chapter 33: Routing | 153

Page 154: Symfony Cmf Master

the DynamicRouter constructor. As part of this bundle, a NestedMatcher is already provided which youcan use straight away, or as reference for your own implementation.

Its other feature are the RouteEnhancerInterface strategies used to infer routing parameters from theinformation provided by the match (see below).

NestedMatcher

The provided RequestMatcherInterface implementation is NestedMatcher. It is suitable for use withDynamicRouter, and it uses a multiple step matching process to determine the resulting routingparameters from a given Request10.

It uses a RouteProviderInterface implementation, which is capable of loading candidate Route11

objects for a Request dynamically from a data source. Although it can be used in other ways, theRouteProviderInterface's main goal is to be easily implemented on top of Doctrine PHPCR ODM or arelational database, effectively allowing you to store and manage routes dynamically from database.

The NestedMatcher uses a 3-step matching process to determine which Route to use when handling thecurrent Request:

• Ask the RouteProviderInterface for the collection of Route instances potentially matchingthe Request Apply all RouteFilterInterface to filter down this collection

• Let the FinalMatcherInterface instance decide on the best match among the remainingRoute instances and transform it into the parameter array.

RouteProviderInterface

Based on the Request, the NestedMatcher will retrieve an ordered collection of Route objects from theRouteProviderInterface. The idea of this provider is to provide all routes that could potentially match,but not to do any elaborate matching operations yet - this is the job of the later steps.

The underlying implementation of the RouteProviderInterface is not in the scope of this bundle.Please refer to the interface declaration for more information. For a functional example, seeRoutingBundle.

RouteFilterInterface

The NestedMatcher can apply user provided RouteFilterInterface implementations to reduce theprovided Route objects, e.g. for doing content negotiation. It is the responsibility of each filter to throwthe ResourceNotFoundException if no more routes are left in the collection.

FinalMatcherInterface

The FinalMatcherInterface implementation has to determine exactly one Route as the best matchor throw an exception if no adequate match could be found. The default implementation uses theUrlMatcher12 of the Symfony Routing Component.

Route Enhancers

Optionally, and following the matching process, a set of RouteEnhancerInterface instances can beapplied by the DynamicRouter. The aim of these is to allow you to manipulate the parameters from thematched route. They can be used, for example, to dynamically assign a controller or template to a Routeor to "upcast" a request parameter to an object. Some simple Enhancers are already packed with thebundle, documentation can be found inside each class file.

10. http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html

11. http://api.symfony.com/master/Symfony/Component/Routing/Route.html

12. http://api.symfony.com/master/Symfony/Component/Routing/Matcher/UrlMatcher.html

PDF brought to you bygenerated on July 18, 2013

Chapter 33: Routing | 154

Page 155: Symfony Cmf Master

Linking a Route with a Content

Depending on your application's logic, a requested url may have an associated content from the database.Those Routes should implement the RouteObjectInterface, and can optionally return a modelinstance. If you configure the RouteContentEnhancer, it will included that content in the match array,with the _content key. Notice that a Route can implement the above mentioned interface but still not toreturn any model instance, in which case no associated object will be returned.

Furthermore, routes that implement this interface can also provide a custom Route name. The keyreturned by getRouteKey will be used as route name instead of the Symfony core compatible route nameand can contain any characters. This allows you, for example, to set a path as the route name. BothUrlMatchers provided with the NestedMatcher replace the _route key with the route instance and putthe provided name into _route_name.

All routes still need to extend the base class Symfony\Component\Routing\Route.

Redirections

You can build redirections by implementing the RedirectRouteInterface. It can redirect either to anabsolute URI, to a named Route that can be generated by any Router in the chain or to another Routeobject provided by the Route.

Notice that the actual redirection logic is not handled by the bundle. You should implement your ownlogic to handle the redirection. For an example on implementing that redirection under the full Symfony2stack, refer to RoutingBundle.

Generating URLs

Apart from matching an incoming request to a set of parameters, a Router is also responsible forgenerating an URL from a Route and its parameters. The ChainRouter iterates over its known routersuntil one of them is able to generate a matching URL.

Apart from using RequestMatcherInterface or UrlMatcherInterface to match a Request/URL to itscorresponding parameters, the DynamicRouter also uses an UrlGeneratorInterface instance, whichallows it to generate an URL from a Route.

The included ProviderBasedGenerator extends Symfony2's default UrlGenerator13 (which, in turn,implements UrlGeneratorInterface) and - if $name is not already a Route object - loads the route fromthe RouteProviderInterface. It then lets the core logic generate the URL from that Route.

The bundle also include the ContentAwareGenerator, which extends the ProviderBasedGenerator tocheck if $name is an object implementing RouteAwareInterface and, if so, gets the Route from thecontent. Using the ContentAwareGenerator, you can generate urls for your content in three ways:

• Either pass a Route object as $name• Or pass a RouteAwareInterface object that is your content as $name• Or provide an implementation of ContentRepositoryInterface and pass the id of the

content object as parameter content_id and null as $name.

ContentAwareGenerator and locales

You can use the _locale default value in a Route to create one Route per locale, all referencing thesame multilingual content instance. The ContentAwareGenerator respects the _locale when generatingroutes from content instances. When resolving the route, the _locale gets into the request and is pickedup by the Symfony2 locale system.

13. http://api.symfony.com/master/Symfony/Component/routing/Generator/UrlGenerator.html

PDF brought to you bygenerated on July 18, 2013

Chapter 33: Routing | 155

Page 156: Symfony Cmf Master

Under PHPCR-ODM, Routes should never be translatable documents, as one Route documentrepresents one single url, and serving several translations under the same url is not recommended.

If you need translated URLs, make the locale part of the route name.

CustomizationThe Routing bundles allows for several customization options, depending on your specific needs:

• You can implement your own RouteProvider to load routes from a different source• Your Route parameters can be easily manipulated using the existing Enhancers• You can also add your own Enhancers to the DynamicRouter• You can add RouteFilterInterface instances to the NestedMatcher• The DynamicRouter or its components can be extended to allow modifications• You can implement your own Routers and add them to the ChainRouter

If you feel like your specific Enhancer or Router can be useful to others, get in touch with us andwe'll try to include it in the bundle itself

Symfony2 integrationLike mentioned before, this bundle was designed to only require certain parts of Symfony2. However,if you wish to use it as part of your Symfony CMF project, an integration bundle is also available. Westrongly recommend that you take a look at RoutingBundle.

For a starter's guide to the Routing bundle and its integration with Symfony2, refer to Routing

We strongly recommend reading Symfony2's Routing14 component documentation page, as it's the baseof this bundle's implementation.

Authors• Filippo De Santis (p16)• Henrik Bjornskov (henrikbjorn)• Claudio Beatrice (omissis)• Lukas Kahwe Smith (lsmith77)• David Buchmann (dbu)• Larry Garfield (Crell)• And others15

The original code for the chain router was contributed by Magnus Nordlander.

14. http://symfony.com/doc/current/components/routing/introduction.html

15. https://github.com/symfony/symfony-docs/issues?milestone=1&state=open

PDF brought to you bygenerated on July 18, 2013

Chapter 33: Routing | 156

Page 157: Symfony Cmf Master

Listing 34-1

Listing 34-2

Chapter 34

Testing

The Testing component is an internal tool for testing Symfony CMF bundles. It provides a way to easilybootstrap a consistent functional test environment.

Configuration

composer

Add the folowing dependency to the require-dev section of composer.json:

123

"require-dev": {"symfony-cmf/testing": "1.0.*"

},

The Testing component does not automatically include the SonataAdminBundle. You will need tomanually add this dependency if required.

phpunit

The following file should be placed in the root directory of your bundle and named phpunit.xml.dist:

123456789

<?xml version="1.0" encoding="UTF-8"?><phpunit

colors="true"bootstrap="vendor/symfony-cmf/testing/bootstrap/bootstrap.php">

<testsuites><testsuite name="Symfony <your bundle>Bundle Test Suite">

<directory>./Tests</directory>

PDF brought to you bygenerated on July 18, 2013

Chapter 34: Testing | 157

Page 158: Symfony Cmf Master

Listing 34-3

101112131415161718192021222324252627282930

</testsuite></testsuites>

<filter><whitelist addUncoveredFilesFromWhitelist="true">

<directory>.</directory><exclude>

<file>*Bundle.php</file><directory>Resources/</directory><directory>Admin/</directory><directory>Tests/</directory><directory>vendor/</directory>

</exclude></whitelist>

</filter>

<php><server name="KERNEL_DIR" value="Tests/Resources/app" />

</php>

</phpunit>

AppKernel

The AppKernel should be placed in the ./Tests/Resources/app folder.

Below is the minimal AppKernel.php:

123456789

1011121314151617181920212223

<?php

use Symfony\Cmf\Component\Testing\HttpKernel\TestKernel;use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends TestKernel{

public function configure(){

$this->requireBundleSets(array('default',

));

$this->addBundles(array(new \Symfony\Cmf\Bundle\MyBundle\CmfMyBundle(),

));}

public function registerContainerConfiguration(LoaderInterface $loader){

$loader->load(__DIR__.'/config/config.php');}

}

Use $this->requireBundleSets('bundle_set_name') to include pre-configured sets of bundles:

• default: Symfony's FrameworkBundle, TwigBundle and MonologBundle;• phpcr_odm: Doctrines DoctrineBundle and DoctrinePHPCRBundle;• sonata_admin: Sonata AdminBundle, BlockBundle and

SonataDoctrinePHPCRAdminBundle.

PDF brought to you bygenerated on July 18, 2013

Chapter 34: Testing | 158

Page 159: Symfony Cmf Master

Listing 34-4

Listing 34-5

For any other bundle requirements simply use $this->addBundles(array()) as in the example above.

git

Place the following .gitignore file in your root directory:

1234

Tests/Resources/app/cacheTests/Resources/app/logscomposer.lockvendor

travis

The following file should be named .travis.yml (note the leading ".") and placed in the root directoryof your bundle:

123456789

1011121314151617181920

language: php

php:- 5.3- 5.4- 5.5

env:- SYMFONY_VERSION=2.2.*- SYMFONY_VERSION=2.3.*

before_script:- composer require symfony/framework-bundle:${SYMFONY_VERSION}- vendor/symfony-cmf/testing/bin/travis/phpcr_odm_doctrine_dbal.sh

script: phpunit --coverage-text

notifications:irc: "irc.freenode.org#symfony-cmf"email: "[email protected]"

Implementing the ComponentYou should try and build a working application for testing your bundle. The application can be accessedusing the server command detailed in this document.

Test Types

• Unit - The scope of a unit test should be limited testing a single class instance. All otherdependencies should be mocked;

• Functional - Functional tests will test a single service as retrieved from the dependencyinjection container;

• Web - Web test cases are the most holistic tests. They use the browser kit to make webrequests on the kernel, testing the whole stack.

PDF brought to you bygenerated on July 18, 2013

Chapter 34: Testing | 159

Page 160: Symfony Cmf Master

Listing 34-6

Listing 34-7

Listing 34-8

Test File Organization

Test files and tests should be organized as follows:

123456789

1011121314151617

./Tests/./Unit

./Full/Namespace/<test>Test.php

./Document/BlogTest.php

./Document/PostTest.php[...]

./Functional./MyService/SomeServiceTest.php[...]

./WebTest./Admin/SomeAdminTest.php./Controller/MyControllerTest.php

./Resources./app

./AppKernel.php

./config/./config.php

Custom Documents

The testing component will automatically include PHPCR-ODM documents that are placed in Tests/Resources/Document.

Configuration

The testing component includes some pre-defined configurations to get things going with a minimum ofeffort and repetition.

To implement the default configurations create the following PHP file:

12345

<?php// Tests/Resources/app/config/config.php

$loader->import(CMF_TEST_CONFIG_DIR.'/default.php');$loader->import(__DIR__.'/mybundleconfig.yml');

Here you include the testing components default configuration, which will get everything up-and-running. You can then optionally import configurations specific to your bundle.

The available default configurations are as follows, and correspond to the bundle sets above:

• default.php: framework, doctrine, security;• sonata_admin.php: sonata_admin, sonata_block;• phpcr-odm.php: doctrine_phpcr.

Note that each must be prefixed with the CMF_TEST_CONFIG_DIR constant.

Routing Configuration

You must include a routing.php file in the same directory as the configuration above:

12

<?php

PDF brought to you bygenerated on July 18, 2013

Chapter 34: Testing | 160

Page 161: Symfony Cmf Master

Listing 34-9

Listing 34-10

Listing 34-11

Listing 34-12

Listing 34-13

3456789

10111213

use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();$collection->addCollection(

$loader->import(CMF_TEST_CONFIG_DIR.'/routing/sonata_routing.yml'));$collection->addCollection(

$loader->import(__DIR__.'/routing/my_test_routing.yml'));

return $collection;

The following default routing configurations are available:

• sonata_routing.yml: sonata admin and dashboard.

The above files must be prefixed with CMF_TEST_CONFIG_DIR.'routing' as in the example above.

The Console

The console for your test application can be accessed as follows:

1 $ php vendor/symfony-cmf/testing/bin/console

Test Web Server

The testing component provides a wrapper for the Symfony server:run command.

1 $ php vendor/symfony-cmf/testing/bin/server

Which basically does the following:

123

$ php vendor/symfony-cmf/testing/bin/console server:run \--router=vendor/symfony-cmf/testing/resources/web/router.php \--docroot=vendor/symfony-cmf/testing/resources/web

You can then access your test application in your browser at http://localhost:8000.

Publish assets in the directory named above using the testing console as follows:

12

$ php vendor/symfony-cmf/testing/bin/cosole assets:install \vendor/symfony-cmf/testing/resources/web

Initializing the Test Environment

Before running your (functional) tests you will need to initialize the test environment (i.e. the database).You could do this manually, but it is easier to do this the same way that travis will do it, as follows:

1 $ ./vendor/symfony-cmf/testing/bin/travis/phpcr_odm_doctrine_dbal.sh

PDF brought to you bygenerated on July 18, 2013

Chapter 34: Testing | 161

Page 162: Symfony Cmf Master

Listing 35-14

Chapter 35

Functional and Web Testing

In general your functional tests should extendSymfony\Cmf\Component\Testing\Functional\BaseTestCase. This class will provide you with somehelpers to make testing easier.

PHPCR-ODM

Accessing the Document Manager

Access as:

123456789

101112

<?php

$manager = $this->db('PHPCR');$documentManager = $this->db('PHPCR')->getOm();

// create a test node /test$this->db('PHPCR')->createTestNode();

// load fixtures$this->db('PHPCR')->loadFixtures(array(

// ... fixture classes here));

Support Files

The testing component includes some basic documents which will automatically be mapped by PHPCR-ODM:

• Symfony\Cmf\Testing\Document\Content: Minimal referenceable content document.

PDF brought to you bygenerated on July 18, 2013

Chapter 35: Functional and Web Testing | 162

Page 163: Symfony Cmf Master

Chapter 36

Contributing

The Symfony2 CMF team follows all the rules and guidelines of the core Symfony2 development process1.

When creating Pull Requests, please follow the Symfony Submitting a Patch2 guidlines.

Resources / Links

• GitHub3

• Website4

• Wiki5

• Issue Tracker6

• IRC channel7

• Users mailing list8

• Devs mailing list9

1. http://symfony.com/doc/current/contributing/index.html

2. http://symfony.com/doc/current/contributing/code/patches.html

3. https://github.com/symfony-cmf

4. http://cmf.symfony.com/

5. https://github.com/symfony-cmf/symfony-cmf/wiki

6. http://github.com/symfony-cmf/symfony-cmf/issues

7. #cmf-contributing-irc:--freenode--symfony-cmf

8. http://groups.google.com/group/symfony-cmf-users

9. http://groups.google.com/group/symfony-cmf-devs

PDF brought to you bygenerated on July 18, 2013

Chapter 36: Contributing | 163

Page 164: Symfony Cmf Master

Chapter 37

The Symfony CMF Release Process

This document explains the release process for the Symfony Content Management Framework. See thecore documentation for the core Symfony release process1.

Symfony CMF manages its releases through a time-based model; a new Symfony CMF release comes outevery six months. We want to synchronize with the core Symfony release dates and release a version aboutone month after Symfony. This should lead to releases in June and December.

This release cycle is for the ``symfony-cmf/symfony-cmf`` repository and the symfony-cmf StandardEdition. The individual CMF bundles and components may release minor versions more often, ifneeded. symfony-cmf/symfony-cmf will always point to a working combination and only integratenewer minor versions when a minor release of the CMF is scheduled.

Point releases (i.e. 1.0.1) are used to quickly provide important fixes. New features are never added inthe point releases, but only in minor releases. With the release of 1.0, we will create a branch 1.0 tomaintain such fixes, and master becomes aliased to 1.1.x-dev.

The CMF Standard Edition and symfony-cmf will get point releases whenever one of the includedbundles does a point release.

The CMF is quite new. As with Symfony 2.0, we don't want to promise BC at all cost yet. Ifreasonably doable, we will keep the code BC or use deprecations, but there might be exceptionswhere BC is too cumbersome. The UPGRADE.md document will help you in those cases.

DevelopmentThe six-months period is divided into two phases:

• Development: Four months to add new features and to enhance existing ones;• Stabilisation: Two months to fix bugs and prepare the release.

During the development phase, any new feature can be reverted if it won't be finished in time or if it won'tbe stable enough to be included in the current final release.

1. http://symfony.com/doc/current/contributing/community/releases.html

PDF brought to you bygenerated on July 18, 2013

Chapter 37: The Symfony CMF Release Process | 164

Page 165: Symfony Cmf Master

MaintenanceThe CMF is a community effort and as such, no maintenance can be guaranteed. If you need maintenancecontracts, please get in contact with the lead developers. They work at internet agencies and will be ableto offer support and maintenance contracts.

Backward CompatibilityFor the next releases, we will maintain BC if possible. If something needs to be broken, theUPGRADE.md file will help you to update the project. We will switch to a BC-at-all-cost model later,once the CMF has stabilized and matured.

The work on Symfony CMF 2.0 will start whenever enough major features breaking backwardcompatibility are waiting on the todo-list.

DeprecationsWhen a feature implementation cannot be replaced with a better one without breaking backwardcompatibility, there is still the possibility to deprecate the old implementation and add a new preferredone along side. Read the conventions document to learn more about how deprecations are handled inSymfony.

RationaleThis release process was adopted to give more predictability and transparency. It is heavily inspired by thecore Symfony release process2.

2. http://symfony.com/doc/current/contributing/community/releases.html

PDF brought to you bygenerated on July 18, 2013

Chapter 37: The Symfony CMF Release Process | 165

Page 166: Symfony Cmf Master

Chapter 38

Licensing

The Symfony2 CMF aims to provide liberal open source licenses for its entire stack.

Code

The code stack is covered by the Apache license1 for Jackalope and PHPCR, the rest of the stack, notablythe Symfony2 code, PHPCR-ODM, create.js and Hallo.js are MIT licensed2. Please refer to the relevantLICENSE files in the given source packages.

DocumentationThe Symfony CMF documentation is licensed under a Creative Commons Attribution-Share Alike 3.0Unported License3.

You are free:

• to Share — to copy, distribute and transmit the work;• to Remix — to adapt the work.

Under the following conditions:

• Attribution — You must attribute the work in the manner specified by the author or licensor(but not in any way that suggests that they endorse you or your use of the work);

• Share Alike — If you alter, transform, or build upon this work, you may distribute the resultingwork only under the same or similar license to this one.

With the understanding that:

• Waiver — Any of the above conditions can be waived if you get permission from the copyrightholder;

1. http://en.wikipedia.org/wiki/Apache_license

2. http://en.wikipedia.org/wiki/MIT_License

3. http://creativecommons.org/licenses/by-sa/3.0/

PDF brought to you bygenerated on July 18, 2013

Chapter 38: Licensing | 166

Page 167: Symfony Cmf Master

• Public Domain — Where the work or any of its elements is in the public domain underapplicable law, that status is in no way affected by the license;

• Other Rights — In no way are any of the following rights affected by the license:

• Your fair dealing or fair use rights, or other applicable copyrightexceptions and limitations;

• The author's moral rights;• Rights other persons may have either in the work itself or in how the work

is used, such as publicity or privacy rights.

• Notice — For any reuse or distribution, you must make clear to others the license terms of thiswork. The best way to do this is with a link to this web page.

This is a human-readable summary of the Legal Code (the full license)4.

4. http://creativecommons.org/licenses/by-sa/3.0/legalcode

PDF brought to you bygenerated on July 18, 2013

Chapter 38: Licensing | 167

Page 168: Symfony Cmf Master
Page 169: Symfony Cmf Master

Recommended