Symfony CMF. Part 2 and the last
We continue to look at Symfony CMF , which implements the concept of a platform for building CMS from loosely coupled components. In the first part of the article, we examined in detail the storage and access to data scheme; in the second part, everything else awaits us.
The continuation of the article comes with a significant delay due to my laziness, health problems and the Internet. Over the past couple of months, the system has grown to version 1.0.0, and all subsequent edits in the master branch somehow break the system’s work without being documented. In case anyone wants to put the system in their hands, remember - rely on stable versions marked with tags.
The most impatient ones can scroll down, download a virtual machine with an installed system (VirtualBox is required) and feel it all myself, but for the sake of completeness, I would recommend reading the article first.
So. What is our plan after data storage?
Screenshot of the main page of the demo project
Everything is familiar to many here - Twig is used . Flexible, powerful, very fast and concise. Supports partitioning, inheritance, and compilation of templates into PHP code. The main page template looks like this:
As part CoreBundle goes extension pack for the Twig, that simplify working with the CMF and bypassing PHPCR-tree, for example, functions such as
There is nothing more to look at especially here, Twig - he is also in Africa Twig.
Home admin
famous generator adminok SonataAdminBundle performs exactly the same function and Symfony CMF, but through a special layer in the form of SonataDoctrinePhpcrAdminBundle . This is done so that the original bundle can abstract from the data warehouse.
To work with tree structures, the TreeBrowserBundle designed for jsTree is designed .
The components having the admin part described below are sure to connect their panels here. Therefore, to dwell on this in detail, I see no reason, detailed screenshots will be further.
Static content in CMS is the foundation of everything. In Symfony CMF, the ContentBundle is responsible for static content, which provides the basic implementation of static document classes, including multilingualism and linking with routes.
The basis of the bundle is a class
For multilingual documents is provided
The bundle relies on the controller.
In today's large sites, the amount of material can easily be measured in the thousands. Multiply by the number of translations. Add the need for ongoing edits of materials and URLs to them in the name of search engine optimization.
At the same time, note that the site administrator (webmaster, content manager, SEO) is usually involved in such things, and not the developer.
What are the requirements for routing in this case?
If you recall the standard Symfony 2 router, it becomes clear that such flexibility cannot be achieved there. Routes are explicitly registered in the config for each controller and the user simply does not allow them to be changed. The maximum that you can count on is some
Let's see what the operation scheme looked like on bare SF2:
If you do not go into the details of the configuration options, everything is pretty primitive. A request arrives, the router decides which controller to call, the controller pulls the necessary data and renders the view, then issues the coveted Response.
This is a fairly familiar scheme.
Why is this option not good enough for a CMS?
Imagine that we have a certain
In my practice, there have been cases when among the static content there were different forms that would look more harmonious as part of the site section rather than as a separate component. For example, a
Let's say it
Need something else.
We summarize the routing in SF2:
Symfony 2 had to abandon the beautiful and powerful, but inconvenient in the case of the CMS router in favor of a new concept:
Immediately a decision comes to mind: create a default route (
That still doesn't sound very good.
It provided a better solution (until it was marked as deprecated since Symfony 2.1)
To the heap - redirecting routes (to other routes or absolute URLs).
At the moment, Symfony CMF uses two components to solve all problems with routing -
Well, we have an infinite number of routers available for use.
Now remember about the search for routes in the database and
What do providers do? Upon request, providers provide an ordered subset of candidate routes that may suit the incoming request, and
The route determines which controller will process the particular request.
Similarly (explicitly or by class), the route can specify the template with which the page should be rendered.
If desired, using the above,
Redirects are also supported. In general, there is an interface
Another important feature that may be interesting for those who have not worked with Symfony is the bi-directional nature of the router. In addition to recognizing routes based on the given parameters, these routes can also be generated by passing parameters as arguments. Unlike the standard SF2 router, as a parameter for the function
If several routes are suitable for the same material, the one whose locale coincides with the query locale will be considered preferable.
Finally, back to what was written a little earlier, the separation of the navigation tree and the content tree. Take a look at the diagram, branched navigation according to certain rules transfers control to the necessary controllers and only after that requests data:
In the admin panel for routes, you can set the format (so that the URL ends with
On top of that, while the experimental RoutingAutoBundlesuggests generating routes for content based on predefined rules. By generating auto-routes, flexibility is achieved: for individual routes, it is easy to translate aliases, generate a site map and change the class of documents that the route can link to. But in most cases, for simple CMS, this bundle may not be needed.
On this with flexible routing we will end.
No CMS can do without a menu system. Although the menu structure usually follows the structure of the content, it may require its own logic that is not defined by the content or exists in several contexts with different options:
Symfony CMF includes MenuBundle, a tool that allows you to define your own menus. It extends the well-known KnpMenuBundle , complementing it with hierarchical and multilingual elements and tools for writing them to the selected repository.
When displaying the menu, the MenuBundle relies on default renderers and helpers for KnpMenuBundle. It is recommended to read the full documentation , but in general, in the simplest case, the output looks like this:
The menu name passed to the function will in turn be passed to the implementation
The bundle is based on the
The bundle supports two types of nodes:
The class
For integration with the admin panel, there are panels and services for SonataDoctrinePhpcrAdminBundle. Panels are available immediately, but to use them, you must explicitly add them to the dashboard.
The bundle is configured as usual , but all parameters are optional.
The connection between routing, menus and content is demonstrated here:
A bundle for working with blocks is also provided. Blocks can implement some kind of logic or simply return static content that can be called anywhere in the template. BlockBundle is based on the SonataBlockBundle and, where necessary, replaces the components of the parent bundle with its own, compatible with PHPCR.
Typical blocks from the main page
Inside the bundle are several typical blocks:
You can create your own blocks .
The block output caching mechanism works on top of the SonataCacheBundle , however, there are no adapters for MongoDB, Memcached and APC in the BlockBundle - you will have to be content with Varnish or SSI.
Blocks are displayed using the Twig function
On-the-fly editing is implemented using several components.
The first is RDFa markup. This is a way to describe metadata in HTML in a microformat style, but using attributes.
After that, the code above ceases to be a “dumb” set of DOM elements, because information from attributes can be conveniently extracted into JS code and linked to models and collections of Backbone.js using VIE.js - this is the second component.
The third in the chain is create.js , which saves us from having to come up with an editing interface.
Create.js workflow
create.js runs on top of VIE.js on jQuery widgets. What can he do?
All content is edited in place, and due to RDFa you don’t have to generate tons of auxiliary HTML markup, as some CMS do.
ckEditor in the service of goodness
Well, it closes the CreatePHP list , a library linking create.js calls and the backend itself. She is responsible for mapping the model properties in PHP to HTML attributes and rendering the entity. The most attentive ones have already seen that there is a Twig extension for CreatePHP and its call is displayed in the very first listing of this article: we transfer the model and specify the output format. Beauty.
The last two components are combined for convenience in CreateBundle.
One of the bundles of the most minimalistic implementation is a bundle for working with media objects. They can be documents, binary files, MP3s, videos, and whatever else the soul desires. The current version supports downloading images and downloading files, write the rest with your hands. SonataMediaBundle can help, especially since there is integration.
The bundle provides:
As well as helpers and adapters for integration:
There is a whole bunch of interfaces for creating your own media classes:
An interesting approach to file paths. In bundle terminology, a path to a media object is understood, for example,
Self-explanatory functions are available in the Twig extension:
You can attach a picture to the document through the provided Form Type:
Implemented adapters media browser elFinder , libraries Gaufrette , giving a layer of abstraction over the file system and LiipImagine , which simplifies the manipulation of the images.
As I said earlier, the bundle implementation is minimalistic. It is enough to say that the pictures are not loaded in bulk, but in order to physically delete the file attached to the document, the document itself must also be deleted. Um.
It is planned (and in some places even ready to some extent) integration with modules:
And of course, the development of new features and the elimination of current shortcomings.
I’m crucifying many kilobytes of text here, as everything in Symfony CMF is wonderful, so it’s logical to ask where is the criticism.
There are enough shortcomings.
Symfony CMF is updated infrequently - on the github it is indicated that the process of releasing new versions is similar to the SF2 release scheme, that is, every six months (four months we write new features, two months we fix bugs and prepare a release). Of course, there will be minor corrections aimed at eliminating vulnerabilities, but in general, if you want a new one, you will have to wait pretty much. At the same time, this is such a development stage when no one promises to maintain backward compatibility between releases at any cost. This means that what worked in 1.0, in 1.1, can easily break.
The documentation suffers. In the wiki of the mess project, many articles should already be deleted, outdated code is written somewhere, and in general the Symfony CMF Book is not as friendly and simple as a similar collection for SF2.
CMF has a very high entry threshold. To install a test system, it is not enough to “unzip everything in webroot and run install.php” - you need to understand the connection between the components and be able to handle each of them. Any refinement or implementation of your code will require a careful study of the insides. Although, probably, developers using SF2 will not be scared. But for users there is no documentation at all ...
The conclusion, which is obvious only by the screenshots, is that the system is crude and still far from its presentation. The usability of the admin is questionable - it seems to be a neat Bootstrap, but it seems that the designer’s hand didn’t touch anything here. Despite the cool front-end editor, for
The documentation often comes up with promises to do X or Y then. If someone wants to advertise a project, it will be difficult to convince them of the advisability of use, I think. There will be no fashionable eye-candy sheets that promise how easy and fun your life will become after installing Symfony CMF. In general, there are no “boxes” from which you can get an attractive working system.And probably will not
Separately, I note that there are no examples of industrial use of Symfony CMF yet . It is not known how the system behaves under load and what to do if you suddenly need to scale (including the backend) - these issues are not disclosed in the documentation except for Cache bundles and installing APC.
You can immediately download the virtual machine image I prepared for VirtualBox, where everything is installed and configured, including different backends. For convenience, you can register in your hosts file
Mirrors (.ova file, ≈ 1Gb):
If for some reason you don’t feel like messing around with this, I’ll give you a link to an online sandbox , but you won’t get into it by digging inside, although looking in haste will also work.
Manually installing a little dreary, so I will not dwell on each step described in the instructions in detail . Just a brief walk through the main points.
The requirements for a machine to run CMF are a little non-standard, although there is no crime.
First, you need to meet the standard needs for Symfony 2 (it is very likely that this is already all right):
Everything else (APC and so on) is optional.
Next are the requirements of Symfony CMF. By default, SQLite is used to store data, so make sure the extension is installed
To use other backends, install:
Also in the folder with the sandbox is the script I wrote
I want to warn against the temptation to type in the console
So, despite numerous “buts,” the project looks interesting. Surprisingly, some fundamental problems of content management have been successfully resolved (for example, multilingualism and routing / menu). Development is slowly carried out by an extremely limited circle of people who already have work, so now the best help is a fork on the github and a useful pull request, be it editing documents or fixing errors. Popularization of CMF is just ahead (try searching the network for materials, there are practically none), all hope is only for open source and community.
Using CMF in production right now can be risky, but you should be aware of it all the same. Who knows, maybe from those millions of euros something will fall here and in a couple of years we will see a wonderful product?
That's all. A small list of useful links:
The continuation of the article comes with a significant delay due to my laziness, health problems and the Internet. Over the past couple of months, the system has grown to version 1.0.0, and all subsequent edits in the master branch somehow break the system’s work without being documented. In case anyone wants to put the system in their hands, remember - rely on stable versions marked with tags.
The most impatient ones can scroll down, download a virtual machine with an installed system (VirtualBox is required) and feel it all myself, but for the sake of completeness, I would recommend reading the article first.
So. What is our plan after data storage?
Screenshot of the main page of the demo project
Template Engine
Everything is familiar to many here - Twig is used . Flexible, powerful, very fast and concise. Supports partitioning, inheritance, and compilation of templates into PHP code. The main page template looks like this:
{% extends "SandboxMainBundle::skeleton.html.twig" %}
{% block content %}
We are on the homepage which uses a special template
{% createphp cmfMainContent as="rdf" %}
{{ rdf|raw }}
{% endcreatephp %}
{{ sonata_block_render({ 'name': 'additionalInfoBlock' }, {
'divisible_by': 3,
'divisible_class': 'row',
'child_class': 'span3'
}) }}
Some additional links:
{% for child in cmf_children(cmf_find('/cms/simple')) %}
- {{ child.title|striptags }}
{% endfor %}
{{ sonata_block_render({
'name': 'rssBlock'
}) }}
{% endblock %}
As part CoreBundle goes extension pack for the Twig, that simplify working with the CMF and bypassing PHPCR-tree, for example, functions such as
cmf_prev
, cmf_next
, cmf_children
and others . There is nothing more to look at especially here, Twig - he is also in Africa Twig.
A few words about the admin panel
Home admin
famous generator adminok SonataAdminBundle performs exactly the same function and Symfony CMF, but through a special layer in the form of SonataDoctrinePhpcrAdminBundle . This is done so that the original bundle can abstract from the data warehouse.
To work with tree structures, the TreeBrowserBundle designed for jsTree is designed .
The components having the admin part described below are sure to connect their panels here. Therefore, to dwell on this in detail, I see no reason, detailed screenshots will be further.
Static content
Static content in CMS is the foundation of everything. In Symfony CMF, the ContentBundle is responsible for static content, which provides the basic implementation of static document classes, including multilingualism and linking with routes.
The basis of the bundle is a class
StaticContent
whose composition will be familiar to many - fields title
that speak for themselves, such as body
, a link to the parent document, and so on. In addition, it implements two interfaces:RouteReferrersInterface
provides a bunch of routesPublishWorkflowInterface
, helps show or hide content using predefined publication dates
For multilingual documents is provided
MultilangStaticContent
- all the same, but the translation of the fields and the declaration of the locale are added. How translation is done - we have already seen in the first part of the article. The bundle relies on the controller.
ContentController
consists of the only one indexAction
that accepts the desired document at the input and renders it in the desired language, if everything is in order with the publication parameters. Optionally, you can specify the template with which the page will be displayed. If not set, the one specified by default will be taken.Routing
In today's large sites, the amount of material can easily be measured in the thousands. Multiply by the number of translations. Add the need for ongoing edits of materials and URLs to them in the name of search engine optimization.
At the same time, note that the site administrator (webmaster, content manager, SEO) is usually involved in such things, and not the developer.
What are the requirements for routing in this case?
- URL set by user
- multisite support
- multilingualism support
- tree structure
- content, menus and routes should be shared
If you recall the standard Symfony 2 router, it becomes clear that such flexibility cannot be achieved there. Routes are explicitly registered in the config for each controller and the user simply does not allow them to be changed. The maximum that you can count on is some
/page/{slug}
that you can edit from the admin panel. Let's see what the operation scheme looked like on bare SF2:
If you do not go into the details of the configuration options, everything is pretty primitive. A request arrives, the router decides which controller to call, the controller pulls the necessary data and renders the view, then issues the coveted Response.
This is a fairly familiar scheme.
Why is this option not good enough for a CMS?
Imagine that we have a certain
PageController
, which takes as an argument the URL alias of the page, compares it with what is stored in the database and gives a page, or 404. In my practice, there have been cases when among the static content there were different forms that would look more harmonious as part of the site section rather than as a separate component. For example, a
/credits/cash
credit was added to the URL of the text section on one bank’s website /calculator
so that people, after reading the necessary information about loans, could figure out the numbers they needed on the spot. Let's say it
PageController
processes the first part of the URL, what to do with the calculator, which, obviously, will act as a separate controller? Add in the configpattern: /credits/cash/calculator
and specify a separate controller / action? Somehow ugly. Even if you prioritize the rest of the routes, it is obvious that flexibility doesn’t smell here - if the nickname in the database changes, you’ll have to edit the config with your hands. Need something else.
We summarize the routing in SF2:
- determines which controller is serving the request
- URL parameters are parsed
- if all else fails, the default behavior is based on a pre-configured set of routes
- either from application configuration
- either from bundles
- users cannot edit routes
- does not scale to a very large number of routes
- in CMS, the user himself wants to decide what address should lie at.
Symfony CMF Routing Concept
Symfony 2 had to abandon the beautiful and powerful, but inconvenient in the case of the CMS router in favor of a new concept:
- you need to separate the content tree from the navigation tree
- The navigation tree consists of links to content tree elements. Due to this, it is easy to implement:
- multisite (desktop, tablet, mobile versions)
- multilingualism
- Navigation overflow requires cloning the navigation tree
- when ready, the result flows back
Immediately a decision comes to mind: create a default route (
/{url}
with the required parameter url: .*
), one controller for all requests and, depending on the content, redirect the request to other controllers. But at the same time, no one cancels conflicts with other routes.navigation:
pattern: "/{url}"
defaults: { _controller: service.controller:indexAction }
requirements:
url: .*
That still doesn't sound very good.
It provided a better solution (until it was marked as deprecated since Symfony 2.1)
DoctrineRouter
. It is already much more flexible, because it looked for routes by URL in the database, while the implementation for documents through PHPCR-ODM was ready, and you can add any of your own. The controller explicitly indicated the route at will, otherwise it was used ControllerResolver
, which tried to decide for itself which controller will process the request. There were also built-in resolvers:- binding nodes of a certain type to the controller
- binding nodes of a certain type to a template and using a standard (generic) controller
To the heap - redirecting routes (to other routes or absolute URLs).
At the moment, Symfony CMF uses two components to solve all problems with routing -
ChainRouter
and DynamicRouter
. The first replaces the standard SF2 router and, despite the name, the router does not actually work (defining a controller to process the request). Instead, it allows you to add your routers to a chain list. In the chain, all configured routers will try to process the request in turn, in order of priority. Router services are searched by tags.cmf_routing:
chain:
routers_by_id:
# включаем DynamicRouter с низким приоритетом
# в этом случае нединамические маршруты сработают раньше
# чтобы не допускать лишнего похода в базу данных
cmf_routing.dynamic_router: 20
# подключаем свой роутер
acme_core.my_router: 50
# дефолтный роутер включаем с высоким приоритетом
router.default: 100
services:
acme_core.my_router:
class: %my_namespace.my_router_class%
tags:
- { name: cmf_routing.router, priority: 300 }
Well, we have an infinite number of routers available for use.
Now remember about the search for routes in the database and
DynamicRouter
. Its task is to load routes from the provider , the provider can be (and usually is) a database. In the standard package there are provider implementations for Doctrine PHPCR-ODM, Doctrine ORM and, of course, you can supplement the list of providers by implementing RouteProviderInterface . What do providers do? Upon request, providers provide an ordered subset of candidate routes that may suit the incoming request, and
DynamicRouter
makes the final decision and maps the request to a specific type object Route
.The route determines which controller will process the particular request.
DynamicRouter
uses several methods in descending order of priority:- explicitly: - the
Route
document itself accurately declares the destination controller, if any, returns from the callgetDefault('_controller')
. - by alias: the route returns a value
getDefault('type')
that matches the configuration fromconfig.yml
- by class:
Route
-document must implementRouteObjectInterface
and return an object forgetContent()
. The returned class type is again mapped to the config - default: default controller will be used, if specified configured
Similarly (explicitly or by class), the route can specify the template with which the page should be rendered.
If desired, using the above,
RouteObjectInterface
you can teach the route to return an instance of the model associated with it. Redirects are also supported. In general, there is an interface
RedirectRouteInterface
, but for PHPCR-ODM the implementation as a document is ready RedirectRoute
. It can redirect to an absolute URI and to a named route generated by any router in the chain. Another important feature that may be interesting for those who have not worked with Symfony is the bi-directional nature of the router. In addition to recognizing routes based on the given parameters, these routes can also be generated by passing parameters as arguments. Unlike the standard SF2 router, as a parameter for the function
path()
can be transmitted not only to the specified in the config file path name, but also the implementation RouteObjectInterface
, RouteReferrersInterface
(ie, the object-route), or reference to an object in the repository using its content_id:{# myRoute это объект класса Symfony\Component\Routing\Route #}
Read on
{# Создает ссылку на / для этого сервера #}
Home
{# myContent реализует RouteReferrersInterface #}
Read on
{# передаем ссылку на объект, который реализует ContentRepositoryInterface #}
Read on
If several routes are suitable for the same material, the one whose locale coincides with the query locale will be considered preferable.
Finally, back to what was written a little earlier, the separation of the navigation tree and the content tree. Take a look at the diagram, branched navigation according to certain rules transfers control to the necessary controllers and only after that requests data:
In the admin panel for routes, you can set the format (so that the URL ends with
.html
, for example) and the end slash. On top of that, while the experimental RoutingAutoBundlesuggests generating routes for content based on predefined rules. By generating auto-routes, flexibility is achieved: for individual routes, it is easy to translate aliases, generate a site map and change the class of documents that the route can link to. But in most cases, for simple CMS, this bundle may not be needed.
On this with flexible routing we will end.
Menu
No CMS can do without a menu system. Although the menu structure usually follows the structure of the content, it may require its own logic that is not defined by the content or exists in several contexts with different options:
Symfony CMF includes MenuBundle, a tool that allows you to define your own menus. It extends the well-known KnpMenuBundle , complementing it with hierarchical and multilingual elements and tools for writing them to the selected repository.
When displaying the menu, the MenuBundle relies on default renderers and helpers for KnpMenuBundle. It is recommended to read the full documentation , but in general, in the simplest case, the output looks like this:
{{ knp_menu_render('simple') }}
The menu name passed to the function will in turn be passed to the implementation
MenuProviderInterface
, which will decide which menu to display. The bundle is based on the
PhpcrMenuProvider
implementation MenuProviderInterface
responsible for dynamically loading menus from PHPCR storage. By default, the provider service is configured with a parameter menu_basepath
that indicates where to look for the menu in the PHPCR tree. When rendering a menu, a parameter is passed name
, which should be a direct descendant of the specified base path. This allows you to PhpcrMenuProvider
work with multiple menu hierarchies using a single storage engine. Recalling the above usage example, the menu simple
should be located at the address /cms/menu/simple
if the following is specified in the configuration:cmf_menu:
menu_basepath: /cms/menu
The bundle supports two types of nodes:
MenuNode
and MultilangMenuNode
. MenuNode
It contains information about the individual menu items label
, uri
a list of child items children
, refer to marshut associated Content-element, plus a list of attributes attributes
, through which you can customize the output of the menu. The class
MultilangMenuNode
extends MenuNode
to support multilingualism: a field has been added locale
to define the translation to which the item belongs and label
with uri
marked as translated=true
. These are the only fields that differ between translations. For integration with the admin panel, there are panels and services for SonataDoctrinePhpcrAdminBundle. Panels are available immediately, but to use them, you must explicitly add them to the dashboard.
The bundle is configured as usual , but all parameters are optional.
The connection between routing, menus and content is demonstrated here:
Blocks
A bundle for working with blocks is also provided. Blocks can implement some kind of logic or simply return static content that can be called anywhere in the template. BlockBundle is based on the SonataBlockBundle and, where necessary, replaces the components of the parent bundle with its own, compatible with PHPCR.
Typical blocks from the main page
Inside the bundle are several typical blocks:
StringBlock
- a block with a single fieldbody
that simply renders the line in the template, without even surrounding it with any tagsSimpleBlock
- to bebody
addedtitle
ContainerBlock
- renders a given list of blocks (including other container blocks)ReferenceBlock
- can only refer to another block. When called, it works as if the block that the link points to were called.ActionBlock
- renders the result of a specific action from the controller, you can transfer the desired request parametersRssBlock
- shows RSS feed with the specified templateImagineBlock
- used by LiipImagineBundle to display pictures directly from PHPCRSlideshowBlock
- A special kind of container block, which allows you to wrap any blocks in the markup so that you can organize slide shows. It is noteworthy that for this you need to choose the JS library yourself, it is not included in the kit.
You can create your own blocks .
The block output caching mechanism works on top of the SonataCacheBundle , however, there are no adapters for MongoDB, Memcached and APC in the BlockBundle - you will have to be content with Varnish or SSI.
Blocks are displayed using the Twig function
sonata_block_render()
, only in contrast to the original bundle, the name of the block in PHPCR is passed as arguments.Frontend / inline editing
On-the-fly editing is implemented using several components.
The first is RDFa markup. This is a way to describe metadata in HTML in a microformat style, but using attributes.
News item title
News item contents
After that, the code above ceases to be a “dumb” set of DOM elements, because information from attributes can be conveniently extracted into JS code and linked to models and collections of Backbone.js using VIE.js - this is the second component.
The third in the chain is create.js , which saves us from having to come up with an editing interface.
Create.js workflow
create.js runs on top of VIE.js on jQuery widgets. What can he do?
- modify the contents of RDF-marked elements using editors - Aloha, Hallo, Redactor, ckEditor
- using localStorage, provide support for saving and restoring edits before they leave the CMS
- manage notifications that appear during editing
- organize your toolbars with the right tools
- call custom workflow functions of the type “delete”, “remove from publication”
All content is edited in place, and due to RDFa you don’t have to generate tons of auxiliary HTML markup, as some CMS do.
ckEditor in the service of goodness
Well, it closes the CreatePHP list , a library linking create.js calls and the backend itself. She is responsible for mapping the model properties in PHP to HTML attributes and rendering the entity. The most attentive ones have already seen that there is a Twig extension for CreatePHP and its call is displayed in the very first listing of this article: we transfer the model and specify the output format. Beauty.
The last two components are combined for convenience in CreateBundle.
Mediabundle
One of the bundles of the most minimalistic implementation is a bundle for working with media objects. They can be documents, binary files, MP3s, videos, and whatever else the soul desires. The current version supports downloading images and downloading files, write the rest with your hands. SonataMediaBundle can help, especially since there is integration.
The bundle provides:
- basic documents for simple models;
- basic
FormType
for simple models; - controller for uploading and downloading files;
- helper, giving an abstraction from downloading to the server;
- controller to display the picture.
As well as helpers and adapters for integration:
- Media browsers (elFinder, ckFinder, MceFileManager, etc.);
- image manipulation libraries (Imagine, LiipImagineBundle).
There is a whole bunch of interfaces for creating your own media classes:
MediaInterface
: base class;MetadataInterface
: definition of metadata;FileInterface
: defined as a file;ImageInterface
: defined as a picture;FileSystemInterface
: the file is stored in the file system, as a media object the path to it is saved;BinaryInterface
: mainly used when a file is saved inside a media object;DirectoryInterface
: defined as a directory;HierarchyInterface
: Media objects stored directory path to the media:/path/to/file/filename.ext
.
An interesting approach to file paths. In bundle terminology, a path to a media object is understood, for example,
/path/to/my/media.jpg
and the differences between paths in Windows and * nix systems are leveled. In PHPCR, this path can be used as an identifier. Several useful methods are available:getPath
Gets the path to an object stored in PHPCR, ORM, or other Doctrine storagegetUrlSafePath
transforms the path for safe use into URLs;mapPathToId
transforms the path into an identifier to search the Doctrine repository;mapUrlSafePathToId
transforms the URL back into an identifier.
Self-explanatory functions are available in the Twig extension:
Download
You can attach a picture to the document through the provided Form Type:
use Symfony\Component\Form\FormBuilderInterface;
protected function configureFormFields(FormBuilderInterface $formBuilder)
{
$formBuilder
->add('image', 'cmf_media_image', array('required' => false))
;
}
Implemented adapters media browser elFinder , libraries Gaufrette , giving a layer of abstraction over the file system and LiipImagine , which simplifies the manipulation of the images.
As I said earlier, the bundle implementation is minimalistic. It is enough to say that the pictures are not loaded in bulk, but in order to physically delete the file attached to the document, the document itself must also be deleted. Um.
Prospects
It is planned (and in some places even ready to some extent) integration with modules:
- SymfonyCmfSearchBundle (full search, extends LiipSearchBundle)
- SymfonyCmfSimpleCms (the simplest CMS that comes with CMF)
- LuneticsLocaleBundle (automatic locale detection)
- other bundles from Sonata
And of course, the development of new features and the elimination of current shortcomings.
Is everything so good?
I’m crucifying many kilobytes of text here, as everything in Symfony CMF is wonderful, so it’s logical to ask where is the criticism.
There are enough shortcomings.
Symfony CMF is updated infrequently - on the github it is indicated that the process of releasing new versions is similar to the SF2 release scheme, that is, every six months (four months we write new features, two months we fix bugs and prepare a release). Of course, there will be minor corrections aimed at eliminating vulnerabilities, but in general, if you want a new one, you will have to wait pretty much. At the same time, this is such a development stage when no one promises to maintain backward compatibility between releases at any cost. This means that what worked in 1.0, in 1.1, can easily break.
The documentation suffers. In the wiki of the mess project, many articles should already be deleted, outdated code is written somewhere, and in general the Symfony CMF Book is not as friendly and simple as a similar collection for SF2.
CMF has a very high entry threshold. To install a test system, it is not enough to “unzip everything in webroot and run install.php” - you need to understand the connection between the components and be able to handle each of them. Any refinement or implementation of your code will require a careful study of the insides. Although, probably, developers using SF2 will not be scared. But for users there is no documentation at all ...
The conclusion, which is obvious only by the screenshots, is that the system is crude and still far from its presentation. The usability of the admin is questionable - it seems to be a neat Bootstrap, but it seems that the designer’s hand didn’t touch anything here. Despite the cool front-end editor, for
body
-elements in the admin panel only a miserable textarea with a height of two lines is provided. For typical operations, you have to make too many gestures due to ill-conceived navigation. The documentation often comes up with promises to do X or Y then. If someone wants to advertise a project, it will be difficult to convince them of the advisability of use, I think. There will be no fashionable eye-candy sheets that promise how easy and fun your life will become after installing Symfony CMF. In general, there are no “boxes” from which you can get an attractive working system.
Separately, I note that there are no examples of industrial use of Symfony CMF yet . It is not known how the system behaves under load and what to do if you suddenly need to scale (including the backend) - these issues are not disclosed in the documentation except for Cache bundles and installing APC.
Let's get down to business
You can immediately download the virtual machine image I prepared for VirtualBox, where everything is installed and configured, including different backends. For convenience, you can register in your hosts file
ip_виртуалки cmf-sandbox
and log in there through the browser, but in general you can enter by IP address, which she will try to prompt right after the login (default username and password:) symfony
. Mirrors (.ova file, ≈ 1Gb):
If for some reason you don’t feel like messing around with this, I’ll give you a link to an online sandbox , but you won’t get into it by digging inside, although looking in haste will also work.
Manually installing a little dreary, so I will not dwell on each step described in the instructions in detail . Just a brief walk through the main points.
The requirements for a machine to run CMF are a little non-standard, although there is no crime.
First, you need to meet the standard needs for Symfony 2 (it is very likely that this is already all right):
- install PHP 5.3.3+
- enable json
- enable ctype support
- to
php.ini
set correctlydate.timezone
- put PDO drivers for Doctrine
Everything else (APC and so on) is optional.
Next are the requirements of Symfony CMF. By default, SQLite is used to store data, so make sure the extension is installed
pdo_sqlite
. To use other backends, install:
- Apache Jackrabbit and, accordingly, Java (for each distribution, its own installation method). The root of the installed CMF should be a script
jack
that downloads and helps launch Jackrabbit without unnecessary gestures. You can use it, but Java is still set separately. - Midgard2 PHPCR и его расширение для PHP. К сожалению, пакетов для этого пока мало: они либо помечены, как нестабильные (я брал в sid в случае с Debian), либо собраны далеко не под все платформы, либо собраны, но для устаревших версий ОС. В целом, если поискать, можно найти и RPM, и deb-пакеты. На крайний случай расширение можно собрать из исходников, но дело бесполезное — поддержка Midgard2 в CMF все еще сломана.
Also in the folder with the sandbox is the script I wrote
switch_backends.py
, which itself will replace the config with the one you need (the original files are corrected in app/config/phpcr/
) and clean the production cache so that it all takes off. For obvious reasons, I have commented on midgard options so far - they still do not work. I want to warn against the temptation to type in the console
git pull
or composer update
, as I said at the beginning of the article, the changes in the master branch violate the system’s performance, wait for the next stable release.Summary
So, despite numerous “buts,” the project looks interesting. Surprisingly, some fundamental problems of content management have been successfully resolved (for example, multilingualism and routing / menu). Development is slowly carried out by an extremely limited circle of people who already have work, so now the best help is a fork on the github and a useful pull request, be it editing documents or fixing errors. Popularization of CMF is just ahead (try searching the network for materials, there are practically none), all hope is only for open source and community.
Using CMF in production right now can be risky, but you should be aware of it all the same. Who knows, maybe from those millions of euros something will fall here and in a couple of years we will see a wonderful product?
That's all. A small list of useful links: