Sulu - ein CMS auf Basis von Symfony

Sulu - ein CMS auf Basis von Symfony

Es gibt immer mehr CMS, die Symfony Komponenten einbauen oder gleich auf dem kompletten Stack aufbauen. Sulu gehört zu den letzteren und erfreut sich immer grösserer Beliebtheit. Soweit so gut, nur was sind die Vorteile für die Entwickler? Wie können sie das Potential wirklich nutzen? Genau diese Fragen sollten nach diesem Vortrag beantwortet sein. Anhand von Sulu werden die unterschiedlichen Schnittstellen in das Symfony Ökosystem und deren Verwendung erklärt.

Im Detail werden wir uns dem Thema Routing, Controllers und Events im Umfeld Symfony und Sulu widmen. Des Weiteren spielt der Service Container inklusive Compiler Pass, welches wohl eines der Hidden Features in Symfony ist, eine grosse Rolle, um das Zusammenspiel reibungslos zu ermöglichen.

D6bc02afa30223e3acd9fdf512d82e2c?s=128

Thomas Schedler

October 27, 2017
Tweet

Transcript

  1. Sulu CMS Batteries included! Thanks to Symfony.

  2. I'm Thomas Schedler @chirimoya | https://github.com/chirimoya ... head of development

    and technical consultant. Young father trying to master Heston Blumenthal recipes.
  3. Who knows Sulu?

  4. Sulu – Content Management Platform – Full-Stack Symfony – Made

    for businesses – Simple UI – High Performance – Open Source
  5. For business – Built with the needs of business and

    industry in mind – Enterprise features without ridiculous license fees – Supports multi-language, multi-portal and multi-channel – Easy to integrate data from external resources
  6. For editors – Really simple and very fast user interface

    – Web-based, no installation required – Edit forms that validate content & ensure correct semantics – Live preview content as you type it – Switch between different devices (Smartphone, Tablet or Desktop)
  7. For developers – Full-Stack Symfony environment – Semantic configuration of

    templates – Easy transition from data to HTML – Build applications around content management – Add/Remove functionality with Symfony Bundles
  8. Where we see us Bicycles Everyone can ride them, many

    can repair it
 (WordPress etc.) Cars Many can ride them, some can repair it
 (TYPO3 etc.) Supertanker Need highly specialized staff, expensive and very complex
 (Hybris, OpenText, AEM etc.) – Trucks Need a special license, must be configured to your needs
 (eZ Publish, Pimcore etc.)
  9. When to use Sulu? – Complex website scenarios – News-

    and media platforms – Brand and corporate presences – Social and collaborative sites – E-Business projects – Handling external data resources – Speed is a critical success factor – Skilled PHP/Symfony developers – Web standards
  10. When not to use Sulu? – Sulu is not Wordpress

    – We don’t recommend to use Sulu for: – Low budget marketing sites – Simple blogs – Hobby websites – (Very) small business websites
  11. Getting Started The basic architecture and concepts of Sulu.

  12. PHPCR DOCTRINE MySQL, PostgreSQL, Jackrabbit, ... Framework Symfony Symfony CMF

    Contact Media Content ... Sulu
  13. REST API Single-Page Application Your Application Website Admin Symfony Symfony

    CMF Contact Media Content ... Sulu
  14. Webspaces – One single content-structure / page tree – The

    structure represents one or more websites – Multiple languages implemented as dimensions – Multiple webspaces support Webspace ...
  15. Content Tree …

  16. We XML

  17. <webspace> <name>example.com</name> <key>example</key> <localizations> <localization language="en" default="true"/> <localization language="de" country="at"/>

    </localizations> <portals> <portal> <name>example.com</name> <key>example</key> <environments> <environment type="prod"> <urls> <url>example.com/{localization}</url> </urls> </environment> <environment type="dev"> <urls> <url>{host}/{localization}</url> </urls> </environment> </environments> </portal>
  18. Templates & Content Types – The structure of the page

    – How that structure is rendered – Each page template is defined by two files: – an XML file that contains the page structure – a Twig file that contains the HTML code – A page structure consists of properties, each of which has a content type
  19. Content Types ... Live Preview ...

  20. <?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>templates/default</view> <controller>SuluWebsiteBundle:Default:index</controller> <cacheLifetime>2400</cacheLifetime> <properties> <section name="highlight"> <properties> <property name="title" type="text_line" mandatory="true"> <tag name="sulu.rlp.part"/> </property> <property name="url" type="resource_locator" mandatory="true"> <tag name="sulu.rlp"/> </property> </properties> </section> <property name="article" type="text_editor"/> </properties> </template>
  21. {% extends "master.html.twig" %} {% block content %} <h1 property="title">{{

    content.title }}</h1> <div property="article"> {{ content.article|raw }} </div> {% endblock %}
  22. None
  23. Router The main entry point to your Symfony application.

  24. / /services /blog Request Response Kernel Controller Router Request URI

    Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services
  25. Chain Router Router / /services /blog Request Response Kernel Controller

    Request URI Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services Dynamic Router
  26. 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, …)
  27. // 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();
  28. // 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!
  29. None
  30. Controller & View Add your custom logic within your own

    content Controller.
  31. 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 Default Controller
  32. // 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>templates/default</view> 
 <cacheLifetime>2400</cacheLifetime>
 
 <meta>
 <title lang="en">Default</title>
 <title lang="de">Standard</title>
 </meta>
 
 ... </template>
 <controller>AppBundle:Custom:index</controller>
  33. // 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;
 }
 }
  34. Response Format HTML, XML or JSON

  35. // 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
  36. None
  37. HTTP Cache & ESI HTTP Standards FTW!

  38. 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
  39. “ Caching entire responses isn't always possible for highly dynamic

    sites, or is it?
  40. 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
  41. // 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 })) }}
  42. Model Customize what you need.

  43. 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
  44. // 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
  45. Event Dispatcher Handle additional business logic within your Event Subscriber.

  46. 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 …
  47. 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
  48. // 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>
  49. Service Container The control center for all you application.

  50. Service Container – Foundation for extensibility & customizability – Sulu

    heavily uses service definitions – Add new functionality (Modulnavigation, Content-Type, ...) – Extend existing (Smart-Content, Teaser, ...) – Overwrite services
  51. // 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>
  52. // 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());
 }
 }
  53. Thanks for watching! www.sulu.io