Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Thomas Schedler

October 27, 2017
Tweet

More Decks by Thomas Schedler

Other Decks in Programming

Transcript

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

    and technical consultant. Young father trying to master Heston Blumenthal recipes.
  2. Sulu – Content Management Platform – Full-Stack Symfony – Made

    for businesses – Simple UI – High Performance – Open Source
  3. 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
  4. 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)
  5. 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
  6. 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.)
  7. 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
  8. 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
  9. Webspaces – One single content-structure / page tree – The

    structure represents one or more websites – Multiple languages implemented as dimensions – Multiple webspaces support Webspace ...
  10. <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>
  11. 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
  12. <?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>
  13. {% extends "master.html.twig" %} {% block content %} <h1 property="title">{{

    content.title }}</h1> <div property="article"> {{ content.article|raw }} </div> {% endblock %}
  14. / /services /blog Request Response Kernel Controller Router Request URI

    Controller & Action Front Controller indexAction() servicesAction() blogAction() Response Response Response Model View Services
  15. 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
  16. 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, …)
  17. // 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();
  18. // 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!
  19. 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
  20. // 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>
  21. // 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;
 }
 }
  22. // 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
  23. 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
  24. 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
  25. // 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 })) }}
  26. 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
  27. // 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
  28. 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 …
  29. 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
  30. // 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>
  31. Service Container – Foundation for extensibility & customizability – Sulu

    heavily uses service definitions – Add new functionality (Modulnavigation, Content-Type, ...) – Extend existing (Smart-Content, Teaser, ...) – Overwrite services
  32. // 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>
  33. // 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());
 }
 }