Achieving wise architecture
             with Symfony2


             Wojciech Sznapka
                       ALEJE.IT
Cześć!
Wojciech Sznapka
                              Technical Manager @ XSolve   
                         Zend Certified Engineer since 2010   
                            Symfony Framework since 2008   
                                             PHP since 2004   
                              Web development since 2001   




 Besides: ice hockey, windsurfing, skiing, crime stories
How can 
architecture
   be bad?
Classes with 
     hundreds 
lines of code?
Model mixed with 
 View located in 
      Controler ?
Changes and 
  maintanance 
costing fortune ?
Crashing due to 
enormous load ?
Unable to migrate 
        to cloud ?
Yes, that's bad!
Make it
        wisely
with Symfony2
Most popular 
  framework in
PHP ecosystem
2078 Forks
6245 Favs
1910 Bundles
Built on top of
 best patterns
So... is
      Symfony2
a silver bullet?
Symfony2 is only
   good tool ...
… but even good tools
  can be used poorly
So let's keep in mind
  three good rules...
Rule #1:
 SOLID
S ingle responsibility principle
O pen/closed principle
L iskov substitution principle
I nterface segregation principle
D ependency inversion principle
Each service should have
              exactly one
                 purpose
Other responsibilities
                should be
    aggregated in  services
injected as dependencies
<?php

namespace WowoNewsletterBundleNewsletterModel;

class MailingManager implements MailingManagerInterface
{
    protected $templateManager;

    public function __construct(TemplateManagerInterface $templateManager)
    {
        $this->templateManager = $templateManager;
    }

    public function createMailing($data, Mailing $mailing)
    {
        $body = $this->templateManager->applyTemplate($mailing->getBody(), $mailing->getTitle());
        $mailing->setBody($body);
            $mailing->setSubject($data['subject']);

        return $mailing;
    }
}
Services should be
  closed for modification
But opened for extension
Abstract classes
           helps with
Open/Closed principle
<?php

abstract class Animal
{
    public function makeHappySound()
    {
        return sprintf("Happilly %sn", $this->speak());
    }

    abstract public function speak();
}

class Dog extends Animal
{
    function speak()
    {
        return "Woof, woof!";
    }
}

class Cat extends Animal
{
    function speak()
    {
        return "Meow...";
    }
}

foreach (array(new Dog, new Cat) as $creature) {
    printf($creature->makeHappySound());
}
It should be possible to 
replace class with other 
         implementation 
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        <parameter
key="mailing_manager.class">WowoNewsletterBundleNewsletterModelMailingManager</parameter>
    </parameters>

    <services>
        <service id="wowo_newsletter.mailing_manager" class="%mailing_manager.class%">
            <argument type="service" id="wowo_newsletter.entity_manager" />
            <argument>%wowo_newsletter.model.contact.class%</argument>
            <call method="setTemplateManager">
                <argument type="service" id="wowo_newsletter.template_manager"/>
            </call>
        </service>
    </services>
</container>



#config_dev.yml

parameters:
    mailing_manager.class: WowoNewsletterBundleMocksMailingManagerFancyMock
Interfaces should be 
        fine grained 
Many specific interfaces
   Are better than one
       general­purpose
<?php


interface PlaceholderProcessorInterface
{
    public function process($object, $body);
}

interface TemplateManagerInterface
{
    public function setAvailableTemplates(array $templates);
    public function getAvailableTemplates();
    public function applyTemplate($body, $title);
}

interface MediaManagerInterface
{
    public function embed($body, Swift_Message $message);
    public function getRegex($name);
}

interface BuilderInterface
{
    public function buildMessage($mailingId, $contactId, $contactClass);
}

interface SenderInterface
{
    public function send($mailingId, $contactId, $contactClass);
}
Application should be 
      decoupled into 
    dependant upon 
 abstractions modules
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="twig.extension.acme.demo"
class="AcmeDemoBundleTwigExtensionDemoExtension" public="false">
            <tag name="twig.extension" />
            <argument type="service" id="twig.loader" />
        </service>

        <service id="acme.demo.listener" class="AcmeDemoBundleEventListenerControllerListener">
            <tag name="kernel.event_listener" event="kernel.controller"
method="onKernelController" />
            <argument type="service" id="twig.extension.acme.demo" />
        </service>
    </services>
</container>
Rule #2:
Maintainability
Live application needs
           to be tested
Behat and/or unit tests
Live application needs
               logging
<?php

class MailingPersistService implements PersistService, LoggerAware
{
    // [...]

    protected $logger;

    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function Mailing(EntityMailing $mailing)
    {
        $user = $this->security->getUser();
        try {
            $this->entityManager->persist($mailing);
            $this->logger->info(sprintf('Mailing #%d %s has been saved by %s',
                $mailing->getId(), $mailing, $user),
                array('mailing' => $mailing, 'user' => $user));
        } catch (Exception $e) {
            $this->logger->err(sprintf('Problem while saving mailing by user %s: %s',
                $user, $e->getMessage()),
                array('exception' => $e, 'user' => $user));
        }
    }
Live application needs
            monitoring
Nagios or New Relic
Rule #3:
Scalability
Forget about local disk!
Keep sessions in 
         global storage
like database or NoSQL
Store user files
     global storage
like S3 or MongoFS
Write logs to
          central logger
like syslogd or logstash
Scale your DB with
master/slave connections
           using Doctrine
Wise
architecture
  ingridients
Design based on
 SOLID principles
Problems solved with
     existing bundles
made by community
Code covered with
   automatic tests
Logging and monitoring
Ready to
scale horizontally
Dziękuję!
Wojciech Sznapka
         wojciech@sznapka.pl
         blog.sznapka.pl
         @sznapka
         @wowo

Osiąganie mądrej architektury z Symfony2