Top Banner
The Naked Bundle Matthias Noback @matthiasnoback
110

The Naked Bundle - Symfony Live London 2014

Nov 27, 2014

Download

Technology

matthiasnoback

The Bundle system is one of the greatest and most powerful features of Symfony2. Bundles contain all the files related to a single feature of your application: controllers, entities, event listeners, form types, Twig templates, etc. But how much of that actually needs to be inside a bundle?

In this talk we’ll take a bundle, containing all those different types of classes, configuration files and templates, and strip it down to the bare necessities. And I promise that after moving many files out of the bundle, everything still works.

While looking for ways to move things out of the bundle, I will discuss some of the more advanced features of bundle design, like prepending configuration, compiler passes and Doctrine mapping drivers. We will end with a very lean bundle, surrounded by a few highly reusable, maximally decoupled libraries.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: The Naked Bundle - Symfony Live London 2014

The Naked BundleMatthias Noback@matthiasnoback

Page 2: The Naked Bundle - Symfony Live London 2014

What's it all about?

Page 3: The Naked Bundle - Symfony Live London 2014

An actual naked bundle

Page 4: The Naked Bundle - Symfony Live London 2014

I could've called it

BundleLitetm

The No Code Bundle

The Clean Bundle

Page 5: The Naked Bundle - Symfony Live London 2014

But “naked” is catchy and controversial

Page 6: The Naked Bundle - Symfony Live London 2014

The official view on bundles

Page 7: The Naked Bundle - Symfony Live London 2014

First-class citizensDocumentation » The Quick Tour » The Architecture

Page 8: The Naked Bundle - Symfony Live London 2014

Importance

Your code is more important than the framework,

which is an implementation detail

Page 9: The Naked Bundle - Symfony Live London 2014

Reuse

Page 10: The Naked Bundle - Symfony Live London 2014

Nice!

Page 11: The Naked Bundle - Symfony Live London 2014

All your code lives in a bundleDocumentation » The Book » Creating Pages in Symfony2

Page 12: The Naked Bundle - Symfony Live London 2014

Reuse

“All your code in a bundle” contradicts the promise of reuse

Page 13: The Naked Bundle - Symfony Live London 2014

Everything lives inside a bundleDocumentation » Glossary

Page 14: The Naked Bundle - Symfony Live London 2014

Not really true

Many things live inside libraries

(the Symfony components are libraries too!)

Page 15: The Naked Bundle - Symfony Live London 2014

Which is good!

Page 16: The Naked Bundle - Symfony Live London 2014

But you probably know that already

Page 17: The Naked Bundle - Symfony Live London 2014

“libraries first”

Page 18: The Naked Bundle - Symfony Live London 2014

What about...● Controllers

● Entities

● Templates

● ...

Page 19: The Naked Bundle - Symfony Live London 2014

They just need to be in a bundle

Or do they?

Page 20: The Naked Bundle - Symfony Live London 2014

Don't get me wrong

I love Symfony!

Page 21: The Naked Bundle - Symfony Live London 2014

But a framework is just a framework● Quickstarter for your projects

● Prevents and solves big security issues for you

● Has a community you can rely on

Page 22: The Naked Bundle - Symfony Live London 2014

A framework is there for you

Page 23: The Naked Bundle - Symfony Live London 2014

Your code doesn't need a framework

Page 24: The Naked Bundle - Symfony Live London 2014

Noback's Principle

Code shouldn't rely on something

it doesn't truly need

Page 25: The Naked Bundle - Symfony Live London 2014

Bundle conventions

Things in a bundle often rely on conventions to work

Page 26: The Naked Bundle - Symfony Live London 2014

Conventions aren't necessary at all

Page 27: The Naked Bundle - Symfony Live London 2014

So according to Noback's Principle,code shouldn't rely on bundle conventions too

Page 28: The Naked Bundle - Symfony Live London 2014

Naming conventionsControllers:

● *Controller classes

● *action methods

Templates:

● in /Resources/views

● name: Controller/Action.html.twig

Page 29: The Naked Bundle - Symfony Live London 2014

Structural conventionsController:

● Extends framework Controller class

● Is ContainerAware

Page 30: The Naked Bundle - Symfony Live London 2014

Behavioral conventionsController:

● Is allowed to return an array

● Actions can type-hint to objects which will be fetched based on route parameters (??)

Page 31: The Naked Bundle - Symfony Live London 2014

Configuration conventions

Use lots of annotations!

/** * @Route("/{id}") * @Method("GET") * @ParamConverter("post", class="SensioBlogBundle:Post") * @Template("SensioBlogBundle:Annot:show.html.twig") * @Cache(smaxage="15", lastmodified="post.getUpdatedAt()") * @Security("has_role('ROLE_ADMIN')") */public function showAction(Post $post){}

Page 32: The Naked Bundle - Symfony Live London 2014

These conventions are what makes an application a Symfony2 application

Page 33: The Naked Bundle - Symfony Live London 2014

A Year With Symfony

Page 34: The Naked Bundle - Symfony Live London 2014

About bundles

Page 35: The Naked Bundle - Symfony Live London 2014

A bundle exposes resources

Page 36: The Naked Bundle - Symfony Live London 2014

Resources● Service definitions

● Controllers

● Routes

● Templates

● Entities

● Form types

● Event listeners

● Translations

● ...

Page 37: The Naked Bundle - Symfony Live London 2014

No need for them to be inside a bundle

Page 38: The Naked Bundle - Symfony Live London 2014

When placed outside the bundlethe resources could be reused separately

Page 39: The Naked Bundle - Symfony Live London 2014

The bundle would be really small

Page 40: The Naked Bundle - Symfony Live London 2014

And could just as well be a:Laravel package,

Zend or Drupal module,CakePHP plugin,

...

Page 41: The Naked Bundle - Symfony Live London 2014

So the challenge is to

Make the bundle as clean as possible

Page 42: The Naked Bundle - Symfony Live London 2014

Move the “misplaced” things to● a library

● with dependencies

● but not symfony/framework­bundle ;)

Page 43: The Naked Bundle - Symfony Live London 2014

Being realistic

Practical reusability

Page 44: The Naked Bundle - Symfony Live London 2014

Reuse within the Symfony family

Think: Silex, Laravel, etc.

Page 45: The Naked Bundle - Symfony Live London 2014

Allowed dependency

HttpFoundation● Request● Response

● Exceptions● etc.

Page 46: The Naked Bundle - Symfony Live London 2014

What do we rely on

HttpKernelnamespace Symfony\Component\HttpKernel;

use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;

interface HttpKernelInterface{    /**     * Handles a Request to convert it to a Response.     */    public function handle(Request $request, ...);}

Page 47: The Naked Bundle - Symfony Live London 2014

Why? My secret missions

“Let's rebuild the application, but this time we use Zend4 instead of Symfony2”

Page 48: The Naked Bundle - Symfony Live London 2014

And of course

Education

Page 49: The Naked Bundle - Symfony Live London 2014

You need a strong coupling radar

Page 50: The Naked Bundle - Symfony Live London 2014

Explicit dependencies● Function calls

● Imported classes (“use”)

● Included files

● ...

Page 51: The Naked Bundle - Symfony Live London 2014

Implicit dependencies● File locations

● File, class, method names

● Structure of return values

● ...

Page 52: The Naked Bundle - Symfony Live London 2014

There we go!

Page 53: The Naked Bundle - Symfony Live London 2014

use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

/** * @Route(“/article”) */class ArticleController extends Controller{

/** * @Route(“/edit”) * @Template() */function editAction(...){

...}

}

Controller

Page 54: The Naked Bundle - Symfony Live London 2014

TODO✔ Don't rely on things that may not

be there in another context:✔ Parent Controller class

✔ Routing, template, annotations, etc.

Page 55: The Naked Bundle - Symfony Live London 2014

class ArticleController{

function editAction(...){

...}

}

Nice and clean ;)

Page 56: The Naked Bundle - Symfony Live London 2014

use Symfony\Component\HttpFoundation\Request;

class ArticleController{    public function editAction(Request $request)    {        $em = $this­>get('doctrine')­>getManager();        ...

        if (...) {            throw $this­>createNotFoundException();        }

        ...

        return array(            'form' => $form­>createView()        );    }}

Zooming in a bit

Page 57: The Naked Bundle - Symfony Live London 2014

TODO✔ Inject dependencies

✔ Don't use helper methods

✔ Render the template manually

✔ Keep using Request (not really a TODO)

Page 58: The Naked Bundle - Symfony Live London 2014

use Doctrine\ORM\EntityManager;

class ArticleController{

function __construct(EntityManager $em, 

) {$this­>em = $em;

}

...}

Inject dependencies

Page 59: The Naked Bundle - Symfony Live London 2014

Inline helper methodsuse Symfony\Component\HttpKernel\Exception\NotFoundHttpException

class ArticleController{    ...

    public function newAction(...)    {        ...        throw new NotFoundHttpException();        ...    }}

Page 60: The Naked Bundle - Symfony Live London 2014

use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Templating\EngineInterface;

class ArticleController{

function __construct(..., EngineInterface $templating) {}

public function newAction(...){

...return new Response(

$this­>templating­>render('@MyBundle:Article:new.html.twig',array(

'form' => $form­>createView())

));

}}

Render the template manually

Page 61: The Naked Bundle - Symfony Live London 2014

Dependencies are explicit now

use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Templating\EngineInterface;use Doctrine\ORM\EntityManager;

Also: no mention of a “bundle” anywhere!

Page 62: The Naked Bundle - Symfony Live London 2014

Dependency overflowuse Symfony\Component\HttpFoundation\Response;use Symfony\Component\Templating\EngineInterface;

class ArticleController{

function __construct(EntityManager $entityManager,EngineInterface $templating,TranslatorInterface $translator,ValidatorInterface $validator,Swift_Mailer $mailer,RouterInterface $router

) {...

}

...}

Page 63: The Naked Bundle - Symfony Live London 2014

The cause?

Convention

Page 64: The Naked Bundle - Symfony Live London 2014

One controller, many actions

one action!

Page 65: The Naked Bundle - Symfony Live London 2014

__invoke()

namespace MyLibrary\Controller\Article;

class New{

function __construct(...) {

// only what's necessary for this “action”}

public function __invoke(...){

...}

}

Page 66: The Naked Bundle - Symfony Live London 2014

Nice!

└─Controller  └─Article    ├─New.php    ├─Edit.php    ├─Archive.php    └─Delete.php

Page 67: The Naked Bundle - Symfony Live London 2014

TODO✔ Set up routing

✔ Create a service and provide the right arguments

Page 68: The Naked Bundle - Symfony Live London 2014

Bundle stuff: services.xml<!­­ in MyBundle/Resources/config/services.xml →

<?xml version="1.0" ?><container><services>

<service id="new_article_controller"           class="MyBundle\Controller\Article\New">    <argument type="service"              id="doctrine.orm.default_entity_manager" />    <argument type="service" id="templating" /></service>

</services></container>

Page 69: The Naked Bundle - Symfony Live London 2014

Bundle stuff: routing.xml<!­­ in MyBundle/Resources/config/routing.xml →

<?xml version="1.0" encoding="UTF­8" ?><routes>

<route id="new_article"       path="/article/new">    <default key="_controller">        new_article_controller:__invoke    </default></route>

</routes>

Pull request by Kevin Bond allows you to leave out the “:__invoke” part!

Page 70: The Naked Bundle - Symfony Live London 2014

Controller – Achievements● Can be anywhere

● No need to follow naming conventions (“*Controller”, “*action”)

● Dependency injection, no service location

● Reusable in any application using HttpFoundation

Page 71: The Naked Bundle - Symfony Live London 2014

Next up: Entities

Page 72: The Naked Bundle - Symfony Live London 2014

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

    /**     * @ORM\Column(type=”string”)     */    private $title;}

Entity conventions

Page 73: The Naked Bundle - Symfony Live London 2014

What's wrong with annotations?

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

    /**     * @ORM\Column(type=”string”)     */    private $title;}

Page 74: The Naked Bundle - Symfony Live London 2014

Annotations are classes

Page 75: The Naked Bundle - Symfony Live London 2014

use Doctrine\Common\Annotations\AnnotationReader;

$reader = new AnnotationReader();

$class = new \ReflectionClass('Article');

$reader­>getClassAnnotations($class);

BANG

Class Doctrine\ORM\Mapping\Column 

not found

Page 76: The Naked Bundle - Symfony Live London 2014

Well, uhm, yes, but...

Page 77: The Naked Bundle - Symfony Live London 2014

Are you ever going to use anything else than Doctrine ORM?

Page 78: The Naked Bundle - Symfony Live London 2014

Well...Think about Doctrine MongoDB ODM, Doctrine CouchDB ODM, etc.namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;use Doctrine\ODM\CouchDB\Mapping\Annotations as CoucheDB;

class Article{    /**     * @ORM\Column     * @MognoDB\Field     * @CoucheDB\Field     */    private $title;}

Page 79: The Naked Bundle - Symfony Live London 2014

TODO✔ Remove annotations

✔ Find another way to map the data

Page 80: The Naked Bundle - Symfony Live London 2014

namespace My\Bundle\Entity;

class Article{    private $id;    private $title;}

Nice and clean

A true POPO, the ideal of the data mapper pattern

Page 81: The Naked Bundle - Symfony Live London 2014

Use XML for mapping metadata<doctrine­mapping>

<entity name=”My\Bundle\Entity\Article”>    <id name="id" type="integer" column="id">        <generator strategy="AUTO"/>    </id>    <field name=”title” type=”string”></entity>    </doctrine­mapping>

Page 82: The Naked Bundle - Symfony Live London 2014

Conventions for XML metadata● For MyBundle\Entity\Article

● Put XML here: @MyBundle/Resources/config/doctrine/ Article.orm.xml

Page 83: The Naked Bundle - Symfony Live London 2014

We don't want it in the bundle!There's a nice little trick

Page 84: The Naked Bundle - Symfony Live London 2014

You need DoctrineBundle >=1.2

{    "require": {        ...,        "doctrine/doctrine­bundle": "~1.2@dev"    }}

Page 85: The Naked Bundle - Symfony Live London 2014

use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\        DoctrineOrmMappingsPass;

class MyBundle extends Bundle{    public function build(ContainerBuilder $container)    {        $container­>addCompilerPass(            $this­>buildMappingCompilerPass()        );    }

    private function buildMappingCompilerPass()    {        $xmlPath = '%kernel.root_dir%/../src/MyLibrary/Doctrine';        $namespacePrefix = 'MyLibrary\Model';

        return DoctrineOrmMappingsPass::createXmlMappingDriver(            array($xmlPath => $namespacePrefix)        );    }}

Page 86: The Naked Bundle - Symfony Live London 2014

Now:● For MyLibrary\Model\Article

● Put XML here: src/MyLibrary/Doctrine/Article.orm.xml

Page 87: The Naked Bundle - Symfony Live London 2014

Entities - Achievements● Entity classes can be anywhere● Mapping metadata can be

anywhere and in different formats● Entities are true POPOs

Page 88: The Naked Bundle - Symfony Live London 2014

Finally: Templates

Page 89: The Naked Bundle - Symfony Live London 2014

Conventions● In /Resources/views/[Controller]

● Filename: [Action].[format].[engine]

Page 90: The Naked Bundle - Symfony Live London 2014

The difficulty with templatesThey can have all kinds of implicit dependencies:

● global variables, e.g. {{ app.request }}

● functions, e.g. {{ path(...) }}

● parent templates, e.g. {% extends “::base.html.twig” %}

Page 91: The Naked Bundle - Symfony Live London 2014

Still, we want them out!

And it's possible

Page 92: The Naked Bundle - Symfony Live London 2014

# in config.ymltwig:    ...    paths:        "%kernel.root_dir%/../src/MyLibrary/Views": MyLibrary

Twig namespacesDocumentation » The Cookbook » Templating » How to use and Register namespaced Twig Paths

// in the controllerreturn $this­>templating­>render('@MyLibrary/Template.html.twig');

Page 93: The Naked Bundle - Symfony Live London 2014

Get rid of absolute paths

Using Puli, created by Bernhard Schüssek (Symfony Forms, Validation)

Page 94: The Naked Bundle - Symfony Live London 2014

What Puli does

Find the absolute paths of resources in a project

Page 95: The Naked Bundle - Symfony Live London 2014

use Webmozart\Puli\Repository\ResourceRepository;

$repo = new ResourceRepository();$repo­>add('/my­library/views', '/absolute/path/to/views/*');

/my-library/views /index.html.twig

/absolute/path/to/views /index.html.twig

echo $repo­>get('/my­library/views/index.html.twig')­>getRealPath();

// => /absolute/path/to/views/index.html.twig

Page 96: The Naked Bundle - Symfony Live London 2014

Register “prefixes”

Manually, or using the Puli Composer plugin

// in the composer.json file of a package or project{    "extra": {        "resources": {            "/my­library/views": "src/MyLibrary/Views"        }    }}

Page 97: The Naked Bundle - Symfony Live London 2014

Twig templates// in composer.json{    "extra": {        "resources": {            "/my­library/views": "src/MyLibrary/Views"        }    }}

// in the controllerreturn $this­>templating    ­>render('/my­library/views/index.html.twig');

Puli Twig extension

Page 98: The Naked Bundle - Symfony Live London 2014

Many possibilities● Templates

● Translation files

● Mapping metadata

● Service definitions

● And so on!

Page 99: The Naked Bundle - Symfony Live London 2014

The future is bright● Puli is not stable yet

● But I expect much from it:

Page 100: The Naked Bundle - Symfony Live London 2014

Puli will be the ultimate tool

Page 101: The Naked Bundle - Symfony Live London 2014

to create NAKED BUNDLES

Page 102: The Naked Bundle - Symfony Live London 2014

and to enable reuse of many kinds of resources

Page 103: The Naked Bundle - Symfony Live London 2014

not limited byproject,

framework,even language

boundaries!

Page 104: The Naked Bundle - Symfony Live London 2014

But even without Puli

There's a whole lot you can do to make your code not rely on the framework

Page 105: The Naked Bundle - Symfony Live London 2014

Remember

The framework is for youYour code doesn't need it

Page 106: The Naked Bundle - Symfony Live London 2014

Questions?

Page 107: The Naked Bundle - Symfony Live London 2014

Get a $7,50 discount:http://leanpub.com/a-year-with-symfony/c/SymfonyLiveLondon2014

Page 108: The Naked Bundle - Symfony Live London 2014

Get a $10,00 introduction discount:http://leanpub.com/principles-of-php-package-design/c/SymfonyLiveLondon2014

Page 109: The Naked Bundle - Symfony Live London 2014

Thank you

Feedback: joind.in/11553

Talk to me: @matthiasnoback