El objetivo de esta guía es demostrar cómo puedes utilizar el CMF BlockBundle y ContentBundle como componentes independientes, y mostrarte cómo complementan al PHPCR.
Esta guía demuestra el uso más sencillo posible, para instalarlo y ejecutarlo rápidamente. Una vez que estés familiarizado con lo básico, la documentación en profundidad de ambos paquetes te ayudará a adaptar estos ejemplos básicos para servir casos de uso más avanzados.
Empezarás utilizando únicamente el BlockBundle, con bloques de contenido enlazados directamente al PHPCR. Luego, verás una introducción al ContentBundle para mostrarte cómo puedes representar contenido en las páginas con bloques.
Nota
A pesar de que no es un requisito utilizar BlockBundle o ContentBundle, esta guía también usa DoctrineFixturesBundle. Esto es porque este proporciona una manera fácil de cargar algún contenido de prueba.
Nota
Esta guía está basada en la utilización de PHPCR-ODM instalado con Jackalope, DBAL de Doctrine y una base de datos MySQL. Debería ser fácil adaptarlo para trabajar con una de las otras opciones PHPCR documentadas en Instalando y configurando Doctrine y PHPCR-ODM.
Puedes utilizar una base de datos existente, o crear una ahora para ayudarte a seguir esta guía. Para una nueva base de datos, ejecuta estas órdenes en MySQL:
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';
Tu archivo parameters.yml necesita emparejar lo anterior, por ejemplo:
# app/config/parameters.yml
parameters:
database_driver: pdo_mysql
database_host: localhost
database_port: ~
database_name: symfony
database_user: symfony
database_password: UseABetterPassword
Nota
Si seguiste la guía Instalando y configurando Doctrine y PHPCR-ODM, puedes saltarte esta sección.
Necesitas instalar los componentes del PHPCR-ODM. Añade los siguiente a tu archivo composer.json:
"require": {
...
"jackalope/jackalope-jackrabbit": "1.0.*",
"jackalope/jackalope-doctrine-dbal": "dev-master",
"doctrine/phpcr-bundle": "1.0.*",
"doctrine/phpcr-odm": "1.0.*"
}
Para instalar lo anterior, ejecuta:
php composer.phar update
En tu archivo config.yml, añade la siguiente configuración para doctrine_phpcr:
# app/config/config.yml
doctrine_phpcr:
session:
backend:
type: doctrinedbal
connection: doctrine.dbal.default_connection
workspace: default
odm:
auto_mapping: true
Añade la siguiente línea al método registerBundles() en el archivo AppKernel.php:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(),
);
// ...
}
Añade la siguiente línea a tu archivo autoload.php, inmediatamente después de la última línea AnnotationRegistry::registerFile:
// app/autoload.php
// ...
AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');
// ...
Crea el esquema de la base de datos y registra el tipo de nodo PHPCR usando las siguientes órdenes de consola:
php app/console doctrine:phpcr:init:dbal
php app/console doctrine:phpcr:repository:init
Ahora deberías tener una serie de tablas en tu base de datos MySQL con el prefijo phpcr_.
Añade lo siguiente al archivo composer.json:
"require": {
...
"symfony-cmf/block-bundle": "dev-master"
}
Para instalar las dependencias anteriores, ejecuta:
php composer.phar update
Añade las siguientes líneas al archivo AppKernel.php:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Sonata\BlockBundle\SonataBlockBundle(),
new Symfony\Cmf\Bundle\BlockBundle\SymfonyCmfBlockBundle(),
);
// ...
}
SonataBlockBundle es una dependencia del BlockBundle de CMF y necesitas configurarlas. Añade lo siguiente a tu archivo config.yml:
# app/config/config.yml
sonata_block:
default_contexts: [cms]
Nota
Como mencioné al inicio, este no es un requisito para BlockBundle o ContentBundle; No obstante, es una buena manera de manejar contenido predefinido o de ejemplo.
Añade lo siguiente al archivo composer.json:
"require": {
...
"doctrine/doctrine-fixtures-bundle": "dev-master"
}
Para instalar las dependencias anteriores, ejecuta:
php composer.phar update
Añade la siguiente línea al método registerBundles() en el archivo AppKernel.php:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
);
// ...
}
Basándote en la documentación de DoctrineFixturesBundle, necesitarás crear una clase fixtures.
Para empezar, crea un directorio DataFixtures dentro de tu propio paquete (p. ej. «MainBundle»), y dentro, crea un directorio llamado PHPCR. Si quieres seguir los ejemplos de más adelante, el DoctrineFixturesBundle cargará automáticamente las clases fixtures colocadas allí.
En el cargador de accesorios, un ejemplo de la creación de un bloque de contenido podría tener esta apariencia:
$myBlock = new SimpleBlock();
$myBlock->setParentDocument($parentPage);
$myBlock->setName('sidebarBlock');
$myBlock->setTitle('My first block');
$myBlock->setContent('Hello block world!');
$documentManager->persist($myBlock);
Aún así, lo anterior en sí mismo no será suficiente, porque no hay ninguna página padre ($parentPage) con la cual enlazar los bloques. Hay varias posibles opciones que puedes utilizar como el padre:
Para almacenar un bloque directamente en el PHPCR del CMF, crea la siguiente clase dentro de tu directorio DataFixtures/PHPCR:
<?php
// src/Acme/MainBundle/DataFixtures/PHPCR/LoadBlockWithPhpcrParent.php
namespace 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)
{
// obtiene el documento raíz desde el PHPCR
$rootDocument = $manager->find(null, '/');
// Crea un documento PHPCR genérico bajo la raíz, para usarlo
// como el tipo de categoría para los bloques
$document = new Generic();
$document->setParent($rootDocument);
$document->setNodename('blocks');
$manager->persist($document);
// Crea un nuevo SimpleBlock (ve
// http://gitnacho.github.com/symfony-doc-es/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);
// Confirma los cambios de $document y $block a la base de datos
$manager->flush();
}
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
Esta clase carga un bloque de contenido de ejemplo que utiliza el BlockBundle de CMF (sin necesitar ningún otro paquete del CMF). Para asegurar el bloque tienes un padre en el repositorio, el cargador también crea un documento Genérico llamado 'blocks' dentro del PHPCR.
Ahora, carga los accesorios usando la consola:
php app/console doctrine:phpcr:fixtures:load
El contenido en tu base de datos ahora se tendría que ver algo así:
SELECT path, parent, local_name FROM phpcr_nodes;
path | parent | local_name |
---|---|---|
/ | ||
/blocks | / | blocks |
/blocks/testBlock | / blocks | testBlock |
Sigue este ejemplo para utilizar ambos componentes Block y Content.
The ContentBundle is best used together with the RoutingExtraBundle. Add the following to composer.json:
"require": {
...
"symfony-cmf/content-bundle": "dev-master",
"symfony-cmf/routing-extra-bundle": "dev-master"
}
Instala todo de la manera habitual:
php composer.phar update
Añade la siguiente línea al archivo AppKernel.php:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Cmf\Bundle\ContentBundle\SymfonyCmfContentBundle(),
);
// ...
}
Ahora deberías tener todo lo necesario para cargar una página de contenido con un bloque de ejemplo, así que crea la clase LoadBlockWithCmfParent.php:
<?php
// src/Acme/Bundle/MainBundle/DataFixtures/PHPCR/LoadBlockWithCmfParent.php
namespace 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)
{
// Obtiene el nombre de la ruta base a utilizar en la configuración
$session = $manager->getPhpcrSession();
$basepath = $this->container->getParameter('symfony_cmf_content.static_basepath');
// Crea la ruta en el repositorio
NodeHelper::createPath($session, $basepath);
// Crea un nuevo documento que utiliza StaticContent de ContentBundle del CMF
$document = new StaticContent();
$document->setPath($basepath . '/blocks');
$manager->persist($document);
// Crea un nuevo SimpleBlock (ve
// http://gitnacho.github.com/symfony-doc-es/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);
// Confirma los cambios de $document y $block a la base de datos
$manager->flush();
}
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
Esta clase crea una página de contenido de ejemplo que utiliza el ContentBundle del CMF. Luego, carga el bloque de ejemplo como antes, usando la nueva página de contenido como su padre.
De manera predeterminada, la ruta base para el contenido es /cms/content/static. Para mostrar cómo lo puedes configurar a cualquier ruta, añade la siguiente entrada opcional a tu archivo config.yml:
# app/config/config.yml
symfony_cmf_content:
static_basepath: /content
Ahora deberías poder cargar los accesorios anteriores:
php app/console doctrine:phpcr:fixtures:load
Todo va sobre ruedas, el contenido en tu base de datos se tendría que ver algo así (si también seguiste el ejemplo LoadBlockWithPhpcrParent, todavía deberías tener dos entradas /blocks también):
SELECT path, parent, local_name FROM phpcr_nodes;
path | parent | local_name |
---|---|---|
/ | ||
/content | / | content |
/content/blocks | /content | blocks |
/content/blocks/testBlock | /content/blocks | testBlock |
Esto lo maneja el BlockBundle de Sonata. sonata_block_render ya está registrado como extensión de Twig al incluir SonataBlockBundle en el archivo AppKernel.php. Por lo tanto, puedes dibujar cualquier bloque dentro de cualquier plantilla simplemente refiriéndote a su ruta.
El siguiente código muestra el resultado de ambas instancias de los bloques testBlock de los ejemplos anteriores. Si sólo seguiste uno de los ejemplos, asegúrate de incluir únicamente ese bloque:
{# src/Acme/Bundle/MainBundle/resources/views/Default/index.html.twig #}
{# Incluye esto si seguiste el ejemplo BlockBundle con PHPCR #}
{{ sonata_block_render({
'name': '/blocks/testBlock'
}) }}
{# incluye esto si seguiste el ejemplo BlockBundle con ContentBundle #}
{{ sonata_block_render({
'name': '/content/blocks/testBlock'
}) }}
Ahora tu página index debería mostrar lo siguiente (suponiendo que seguiste ambos ejemplos):
CMF BlockBundle only
Block from CMF BlockBundle, parent from the PHPCR (Generic document).
CMF BlockBundle and ContentBundle
Block from CMF BlockBundle, parent from CMF ContentBundle (StaticContent).
Esto sucede al dibujar un bloque, ve el... index:: BlockBundle para más detalles:
Nota
Un bloque también se puede configurar usando opciones, estas te permiten crear bloques más avanzados y reutilizables. Las opciones predefinidas se configuran en el bloque del servicio y se pueden alterar en el ayudante de Twig y el bloque del documento. Un ejemplo es un bloque lector de RSS, la URL y el título se almacenan en las opciones del bloque del documento, la cantidad
máxima de elementos a mostrar se especifica al llamar a sonata_block_render.
The SymfonyCmfBlockBundle provides a twig filter cmf_embed_blocks that looks through the content and looks for special tags to render blocks. To use the tag, you need to apply the cmf_embed_blocks filter to your output. If you can, render your blocks directly in the template. This feature is only a cheap solution for web editors to place blocks anywhere in their HTML content. A better solution to build composed pages is to build it from blocks. (There might be a CMF bundle at some point for this.)
{# 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:
<span>%block:"/absolute/path/to/block"%</span>
You can change the prefix and postfix (the parts <span>%block: and %</span> to have a different tag for your users. Say you want to write %%%block:"/absolute/path"%%% then you do:
# app/config/config.yml
symfony_cmf_block:
twig:
cmf_embed_blocks:
prefix: %%%block:
postfix: %%%
Advertencia
Currently there is no limitation built into this feature. Only enable it on content for which you are sure only trusted users may edit it. Restrictions about what block can be where that are built into an admin interface are not respected here.
Ahora deberías estar listo para usar en tu aplicación el BlockBundle y/o el ContentBundle, o para explorar los otros paquetes disponibles en el CMF.
Si tienes problemas, posiblemente sea más fácil empezar con una instalación de Symfony2 fresca. También puedes probar ejecutando y modificando el código en el ejemplo externo del recinto de seguridad del bloque del CMF.
Configuración de Doctrine
Si empezaste con la distribución estándar de Symfony2 (versión 2.1.x), ya deberías tener correctamente configurado tu archivo config.yml. Si no, inténtalo usando la siguiente sección:
# app/config/config.yml
doctrine:
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
“Ninguna orden definida” al cargar accesorios
[InvalidArgumentException]
No hay ninguna orden definida en el espacio de nombres ``doctrine:phpcr:fixtures``.
Asegúrate de que tu archivo AppKernel.php contiene las siguientes líneas:
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(),
“No configuraste una sesión”
[InvalidArgumentException]
No configuraste una sesión para los gestores de documento
Asegúrate de que tienes lo siguiente en tu archivo app/config.yml:
doctrine_phpcr:
session:
backend:
type: doctrinedbal
connection: doctrine.dbal.default_connection
workspace: default
odm:
auto_mapping: true
“La anotación no existe”
[Doctrine\Common\Annotations\AnnotationException]
[Error semántico] La anotación «@Doctrine\ODM\PHPCR\Mapping\Annotations\Document» en la clase «Doctrine\ODM\PHPCR\Document\Generic» no existe, o no se puede cargar automáticamente.
Asegúrate de añadir esta línea a tu archivo app/autoload.php (inmediatamente después de la línea AnnotationRegistry::registerLoader):
AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');
no se encuentra la clase SimpleBlock
[Doctrine\Common\Persistence\Mapping\MappingException]
La clase 'Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock' no se encuentra en la cadena de espacios de nombres configurada Doctrine\ODM\PHPCR\Document, Sonata\UserBundle\Document, FOS\UserBundle\Document
Asegúrate de que está instalado el BlockBundle del CMF y de que se carga en el archivo app/AppKernel.php:
new Symfony\Cmf\Bundle\BlockBundle\SymfonyCmfBlockBundle(),
No se encuentra la RouteAwareInterface
Error fatal: No se encuentra la interfaz 'Symfony\Cmf\Component\Routing\RouteAwareInterface' en /var/www/your-site/vendor/symfony-cmf/content-bundle/Symfony/Cmf/Bundle/ContentBundle/Document/StaticContent.php en la línea 15
Si estás utilizando el ContentBundle, asegúrate de instalar el RoutingExtraBundle, también:
// composer.json
"symfony-cmf/routing-extra-bundle": "dev-master"
...e instala:
php composer.phar update