Share
Embed
Sulu - Leverage the power of a framework based CMS
Batteries included! Thanks to Symfony.
Thomas Schedler
September 07, 2017
Transcript
91 slides
-
I installed this free plugin and now the whole website
crashed! It can´t take that long to add this simple feature! Why is our website so slow?! Can we add a new language to the site by tomorrow? Sulu CMS Batteries included! Thanks to Symfony. -
I'm Thomas Schedler @chirimoya | https://github.com/chirimoya ... head of development
and technical consultant. Young father trying to master Heston Blumenthal recipes. -
/ /services /blog Request Response Kernel Router Request URI Controller
& Action Front Controller -
/ /services /blog Request Response Kernel Controller Router Request URI
Controller & Action Front Controller -
/ /services /blog Request Response Kernel Controller Router Request URI
Controller & Action Front Controller indexAction() servicesAction() blogAction() -
/ /services /blog Request Response Kernel Controller Router Request URI
Controller & Action Front Controller indexAction() servicesAction() blogAction() Model View Services -
/ /services /blog Request Response Kernel Controller Router Request URI
Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services -
Router / /services /blog Request Response Kernel Controller Request URI
Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services -
Router / /services /blog Request Response Kernel Controller Request URI
Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services -
Router Chain Router / /services /blog Request Response Kernel Controller
Request URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services -
Router Chain Router / /services /blog Request Response Kernel Controller
Request URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services -
Router Chain Router / /services /blog Request Response Kernel Controller
Request URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services Dynamic Router -
Sulu routing summarized – CMF ChainRouter replaces the default routing
system – and works by accepting a set of prioritized Routers -
Sulu routing summarized – CMF ChainRouter replaces the default routing
system – and works by accepting a set of prioritized Routers – The Symfony default Router is registered with the highest priority -
Sulu routing summarized – CMF ChainRouter replaces the default routing
system – and works by accepting a set of prioritized Routers – The Symfony default Router is registered with the highest priority – DynamicRouters handle all the dynamically defined routes (pages, redirects, …) -
// app/WebsiteKernel.php class WebsiteKernel extends AbstractKernel { /** *
{@inheritdoc} */ protected $name = 'website'; /** * @param string $environment * @param bool $debug */ public function __construct($environment, $debug) { parent::__construct($environment, $debug); $this->setContext(self::CONTEXT_WEBSITE); } /** * {@inheritdoc} */ public function registerBundles() { $bundles = parent::registerBundles(); $bundles[] = new Symfony\Cmf\Bundle\RoutingBundle\CmfRoutingBundle(); return $bundles; } } -
// app/WebsiteKernel.php class WebsiteKernel extends AbstractKernel { /** *
{@inheritdoc} */ protected $name = 'website'; /** * @param string $environment * @param bool $debug */ public function __construct($environment, $debug) { parent::__construct($environment, $debug); $this->setContext(self::CONTEXT_WEBSITE); } /** * {@inheritdoc} */ public function registerBundles() { $bundles = parent::registerBundles(); $bundles[] = new Symfony\Cmf\Bundle\RoutingBundle\CmfRoutingBundle(); return $bundles; } } $bundles[] = new AppBundle\AppBundle(); -
// app/config/website/routing.yml app: resource: "@AppBundle/Controller/" type: annotation prefix: /app //
src/AppBundle/Controller/DefaultController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class DefaultController extends Controller { /** * @Route("/") */ public function indexAction() { return $this->render('AppBundle:Default:index.html.twig'); } } -
// app/config/website/routing.yml app: resource: "@AppBundle/Controller/" type: annotation prefix: /app //
src/AppBundle/Controller/DefaultController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class DefaultController extends Controller { /** * @Route("/") */ public function indexAction() { return $this->render('AppBundle:Default:index.html.twig'); } } // src/AppBundle/Resources/views/Default/index.html.twig Hallo World! -
Controller & View Add your custom logic within your own
content Controller. -
Controller Chain Router Router / /services /blog Request Response Kernel
Request URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services Dynamic Router -
Chain Router Router / /services /blog Request Response Kernel Request
URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services Dynamic Router DefaultController -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://
schemas.sulu.io/template/template-1.0.xsd"> <key>default</key> <cacheLifetime>2400</cacheLifetime> <meta> <title lang="en">Default</title> <title lang="de">Standard</title> </meta> ... </template> <controller>SuluWebsiteBundle:Default:index</controller> <view>templates/default</view> -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://
schemas.sulu.io/template/template-1.0.xsd"> <key>default</key> <cacheLifetime>2400</cacheLifetime> <meta> <title lang="en">Default</title> <title lang="de">Standard</title> </meta> ... </template> <controller>AppBundle:Custom:index</controller> <view>templates/default</view> -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://
schemas.sulu.io/template/template-1.0.xsd"> <key>default</key> <cacheLifetime>2400</cacheLifetime> <meta> <title lang="en">Default</title> <title lang="de">Standard</title> </meta> ... </template> <view>AppBundle:Custom:index</view> <controller>AppBundle:Custom:index</controller> -
// src/AppBundle/Controller/CustomController.php namespace AppBundle\Controller; use Sulu\Bundle\WebsiteBundle\Controller\WebsiteController; use Sulu\Component\Content\Compat\StructureInterface;
class CustomController extends WebsiteController { /** * My custom controller action. * * @param StructureInterface $structure * @param bool $preview * @param bool $partial * * @return Response */ public function indexAction(StructureInterface $structure, $preview = false, $partial = false) { $response = $this->renderStructure( $structure, [ // here you can add some custom data for your template 'myData' => $this->get('my_custom_service')->getMyData(), ], $preview, $partial ); return $response; } } -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
<key>default</key> <view>AppBundle:Custom:index</view> <controller>AppBundle:Custom:index</controller> <cacheLifetime>2400</cacheLifetime> ... </template> -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
<key>default</key> <view>AppBundle:Custom:index</view> <controller>AppBundle:Custom:index</controller> <cacheLifetime>2400</cacheLifetime> ... </template> .html.twig -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
<key>default</key> <view>AppBundle:Custom:index</view> <controller>AppBundle:Custom:index</controller> <cacheLifetime>2400</cacheLifetime> ... </template> // src/AppBundle/Resources/views/Custom/index.html.twig {% extends "master.html.twig" %} {% block content %} <h1 property="title">{{ content.title }}</h1> <div property="article"> {{ content.article|raw }} </div> {% endblock %} .html.twig -
// app/Resources/templates/pages/default.xml <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
<key>default</key> <view>AppBundle:Custom:index</view> <controller>AppBundle:Custom:index</controller> <cacheLifetime>2400</cacheLifetime> ... </template> // src/AppBundle/Resources/views/Custom/index.html.twig {% extends "master.html.twig" %} {% block content %} <h1 property="title">{{ content.title }}</h1> <div property="article"> {{ content.article|raw }} </div> {% endblock %} // src/AppBundle/Resources/views/Custom/index.json.twig {{ content|json_encode|raw }} .html.twig -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid – HTTP cache headers are used to mark a response cacheable and for how long -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid – HTTP cache headers are used to mark a response cacheable and for how long – Symfony comes with a reverse proxy written in PHP -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid – HTTP cache headers are used to mark a response cacheable and for how long – Symfony comes with a reverse proxy written in PHP – Switch to something more robust like Varnish without any problem -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid – HTTP cache headers are used to mark a response cacheable and for how long – Symfony comes with a reverse proxy written in PHP – Switch to something more robust like Varnish without any problem https://tomayko.com/blog/2008/things-caches-do -
Reverse Proxy Caches – A HTTP Cache is a full
page cache – It bypasses your application entirely, if the cache entry is valid – HTTP cache headers are used to mark a response cacheable and for how long – Symfony comes with a reverse proxy written in PHP – Switch to something more robust like Varnish without any problem https://tomayko.com/blog/2008/things-caches-do -
“ Caching entire responses isn't always possible for highly dynamic
sites, or is it? -
ESI - Edge Side Includes – The ESI specification describes
tags to communicate with the gateway cache -
ESI - Edge Side Includes – The ESI specification describes
tags to communicate with the gateway cache – In Symfony the <esi:include/> is implemented -
ESI - Edge Side Includes – The ESI specification describes
tags to communicate with the gateway cache – In Symfony the <esi:include/> is implemented – If the response contains ESI tags, the cache either requests the page fragment from the backend or embeds the fresh cache entry -
// app/config/config.yml framework: ... esi: { enabled: true } //
app/Resources/views/Default/index.html.twig {# you can use a controller reference #} {{ render_esi(controller('AppBundle:News:latest', { 'limit': 5 })) }} {# ... or a URL #} {{ render_esi(url('latest_news', { 'limit': 5 })) }} -
Customizing Models – Doctrine doesn't support model customization – Inheritance
leads to multiple tables for the same data structure -
Customizing Models – Doctrine doesn't support model customization – Inheritance
leads to multiple tables for the same data structure – Sulu’s PersistenceBundle allows to replace models via configuration -
Customizing Models – Doctrine doesn't support model customization – Inheritance
leads to multiple tables for the same data structure – Sulu’s PersistenceBundle allows to replace models via configuration – Inspired by Sylius ResourceBundle -
// src/AppBundle/EntityTag.php <?php namespace AppBundle\Entity; use Sulu\Bundle\TagBundle\Entity\Tag as SuluTag; class
Tag extends SuluTag { public $description; } -
// src/AppBundle/EntityTag.php <?php namespace AppBundle\Entity; use Sulu\Bundle\TagBundle\Entity\Tag as SuluTag; class
Tag extends SuluTag { public $description; } // src/AppBundle/Resources/config/doctrine/Tag.orm.xml <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas orm/doctrine-mapping.xsd"> <entity name="AppBundle\Entity\Tag" table="ta_tags"> <field name="description" type="text" column="description" nullable="true"/> </entity> </doctrine-mapping> -
// src/AppBundle/EntityTag.php <?php namespace AppBundle\Entity; use Sulu\Bundle\TagBundle\Entity\Tag as SuluTag; class
Tag extends SuluTag { public $description; } // src/AppBundle/Resources/config/doctrine/Tag.orm.xml <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas orm/doctrine-mapping.xsd"> <entity name="AppBundle\Entity\Tag" table="ta_tags"> <field name="description" type="text" column="description" nullable="true"/> </entity> </doctrine-mapping> // app/config/config.yml sulu_tag: objects: tag: model: AppBundle\Entity\Tag -
Symfony Events Kernel Events – kernel.request – kernel.response – kernel.controller
– kernel.view – kernel.terminate … -
Symfony Events Kernel Events – kernel.request – kernel.response – kernel.controller
– kernel.view – kernel.terminate … Doctrine Events -
Symfony Events Kernel Events – kernel.request – kernel.response – kernel.controller
– kernel.view – kernel.terminate … Doctrine Events Lifecycle Events – [pre|post]Remove – [pre|post]Persist – [pre|post]Update – [pre|on|post]Flush – onClear … -
Sulu Document Manager Events +----------------------+ | Events | +----------------------+ |
persist | | hydrate | | remove | | refresh | | copy | | move | | create | | clear | | find | | reorder | | publish | | unpublish | | remove_draft | | flush | | query.create | | query.create_builder | | query.execute | | configure_options | | metadata_load | | restore | +----------------------+ bin/adminconsole sulu:document:subscriber:debug -
// src/AppBundle/Document/Subscriber/MailSubscriber.php namespace AppBundle\Document\Subscriber; use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class MailSubscriber implements EventSubscriberInterface { ... /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ Events::PUBLISH => ['sendNotification', -1000], ]; } public function sendNotification(PublishEvent $event) { $message = new \Swift_Message('Page Published', 'URL: ' . $event->getDocument()->getResourceSegment()); $this->mailer->send($message); } } -
// src/AppBundle/Document/Subscriber/MailSubscriber.php namespace AppBundle\Document\Subscriber; use Sulu\Component\DocumentManager\Event\PublishEvent; use Sulu\Component\DocumentManager\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class MailSubscriber implements EventSubscriberInterface { ... /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ Events::PUBLISH => ['sendNotification', -1000], ]; } public function sendNotification(PublishEvent $event) { $message = new \Swift_Message('Page Published', 'URL: ' . $event->getDocument()->getResourceSegment()); $this->mailer->send($message); } } <service id="app.document_subscriber.email" class="AppBundle\Document\Subscriber\MailSubscriber"> <argument type="service" id="mailer" /> <tag name="sulu_document_manager.event_subscriber"/> </service> -
Service Container – Foundation for extensibility & customizability – Sulu
heavily uses service definitions -
Service Container – Foundation for extensibility & customizability – Sulu
heavily uses service definitions – Add new functionality (Modulnavigation, Content-Type, ...) -
Service Container – Foundation for extensibility & customizability – Sulu
heavily uses service definitions – Add new functionality (Modulnavigation, Content-Type, ...) – Extend existing (Smart-Content, Teaser, ...) -
Service Container – Foundation for extensibility & customizability – Sulu
heavily uses service definitions – Add new functionality (Modulnavigation, Content-Type, ...) – Extend existing (Smart-Content, Teaser, ...) – Overwrite services -
// src/AppBundle/Admin/AppAdmin.php namespace AppBundle\Admin; use Sulu\Bundle\AdminBundle\Admin\Admin; use Sulu\Bundle\AdminBundle\Navigation\Navigation;
use Sulu\Bundle\AdminBundle\Navigation\NavigationItem; class AppAdmin extends Admin { public function __construct($title) { $rootNavigationItem = new NavigationItem($title); $section = new NavigationItem('navigation.modules'); $myModule = new NavigationItem('app.my_module'); $myModule->setIcon('custom'); $item = new NavigationItem('app.my_module.title'); $item->setAction('my_module/custom'); $myModule->addChild($item); $rootNavigationItem->addChild($section); $section->addChild($myModule); $this->setNavigation(new Navigation($rootNavigationItem)); } } -
// src/AppBundle/Admin/AppAdmin.php namespace AppBundle\Admin; use Sulu\Bundle\AdminBundle\Admin\Admin; use Sulu\Bundle\AdminBundle\Navigation\Navigation;
use Sulu\Bundle\AdminBundle\Navigation\NavigationItem; class AppAdmin extends Admin { public function __construct($title) { $rootNavigationItem = new NavigationItem($title); $section = new NavigationItem('navigation.modules'); $myModule = new NavigationItem('app.my_module'); $myModule->setIcon('custom'); $item = new NavigationItem('app.my_module.title'); $item->setAction('my_module/custom'); $myModule->addChild($item); $rootNavigationItem->addChild($section); $section->addChild($myModule); $this->setNavigation(new Navigation($rootNavigationItem)); } } <service id="app.admin" class="AppBundle\Admin\AppAdmin"> <argument>%sulu_admin.name%</argument> <tag name="sulu.admin"/> <tag name="sulu.context" context="admin"/> </service> -
// src/AppBundle/DependencyInjection/AppCompilerPass.php namespace AppBundle\DependencyInjection; use AppBundle\Contact\CustomContactManager; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; class AppCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $definition = $container->getDefinition('sulu_contact.contact_manager'); $definition->setClass(CustomContactManager::class); $definition->addArgument(new Reference('app.my_custom_service')); } } -
// src/AppBundle/DependencyInjection/AppCompilerPass.php namespace AppBundle\DependencyInjection; use AppBundle\Contact\CustomContactManager; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; class AppCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $definition = $container->getDefinition('sulu_contact.contact_manager'); $definition->setClass(CustomContactManager::class); $definition->addArgument(new Reference('app.my_custom_service')); } } // src/AppBundle/AppBundle.php namespace AppBundle; use AppBundle\DependencyInjection\AppCompilerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class AppBundle extends Bundle { public function build(ContainerBuilder $container) { $container->addCompilerPass(new AppCompilerPass()); } }