111
The Naked Bundle Matthias Noback @matthiasnoback Symfony Usergroup Belgium October 13 th , 2014

The Naked Bundle - Symfony Usergroup Belgium

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: The Naked Bundle - Symfony Usergroup Belgium

The Naked Bundle

Matthias Noback@matthiasnoback

Symfony Usergroup BelgiumOctober 13th, 2014

Page 2: The Naked Bundle - Symfony Usergroup Belgium

What's it all about?

Page 3: The Naked Bundle - Symfony Usergroup Belgium

An actual naked bundle

Page 4: The Naked Bundle - Symfony Usergroup Belgium

I could've called it

BundleLitetm

The No Code Bundle

The Clean Bundle

Page 5: The Naked Bundle - Symfony Usergroup Belgium

But “naked” was catchy and controversial

Page 6: The Naked Bundle - Symfony Usergroup Belgium

Until Pierre came along

Page 7: The Naked Bundle - Symfony Usergroup Belgium

The official view on bundles

Page 8: The Naked Bundle - Symfony Usergroup Belgium

First-class citizensDocumentation » The Quick Tour » The Architecture

Page 9: The Naked Bundle - Symfony Usergroup Belgium

Importance

Your code is more important than the framework,

which is an implementation detail

Page 10: The Naked Bundle - Symfony Usergroup Belgium

Reuse

Page 11: The Naked Bundle - Symfony Usergroup Belgium

Nice!

Page 12: The Naked Bundle - Symfony Usergroup Belgium

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

Page 13: The Naked Bundle - Symfony Usergroup Belgium

Reuse

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

Page 14: The Naked Bundle - Symfony Usergroup Belgium

Everything lives inside a bundleDocumentation » Glossary

Page 15: The Naked Bundle - Symfony Usergroup Belgium

Not really true

Many things live inside libraries

(the Symfony components are libraries too!)

Page 16: The Naked Bundle - Symfony Usergroup Belgium

Which is good!

Page 17: The Naked Bundle - Symfony Usergroup Belgium

But you probably know that already

Page 18: The Naked Bundle - Symfony Usergroup Belgium

“libraries first”

Page 19: The Naked Bundle - Symfony Usergroup Belgium

What about...● Controllers

● Entities

● Templates

● ...

Page 20: The Naked Bundle - Symfony Usergroup Belgium

They just need to be in a bundle

Or do they?

Page 21: The Naked Bundle - Symfony Usergroup Belgium

Don't get me wrong

I love Symfony!

Page 22: The Naked Bundle - Symfony Usergroup Belgium

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 23: The Naked Bundle - Symfony Usergroup Belgium

A framework is there for you

Page 24: The Naked Bundle - Symfony Usergroup Belgium

Your code doesn't need a framework

Page 25: The Naked Bundle - Symfony Usergroup Belgium

Noback's Principle

Code shouldn't rely on something

it doesn't truly need

Page 26: The Naked Bundle - Symfony Usergroup Belgium

Bundle conventions

Things in a bundle often rely on conventions to work

Page 27: The Naked Bundle - Symfony Usergroup Belgium

Conventions aren't necessary at all

Page 28: The Naked Bundle - Symfony Usergroup Belgium

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

Page 29: The Naked Bundle - Symfony Usergroup Belgium

Naming conventionsControllers:

● *Controller classes

● *action methods

Templates:

● in /Resources/views

● name: Controller/Action.html.twig

Page 30: The Naked Bundle - Symfony Usergroup Belgium

Structural conventionsController:

● Extends framework Controller class

● Is ContainerAware

Page 31: The Naked Bundle - Symfony Usergroup Belgium

Behavioral conventionsController:

● Is allowed to return an array

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

Page 32: The Naked Bundle - Symfony Usergroup Belgium

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 33: The Naked Bundle - Symfony Usergroup Belgium

These conventions are what makes an application a Symfony2 application

Page 34: The Naked Bundle - Symfony Usergroup Belgium

A Year With Symfony

Page 35: The Naked Bundle - Symfony Usergroup Belgium

About bundles

Page 36: The Naked Bundle - Symfony Usergroup Belgium

A bundle exposes resources

Page 37: The Naked Bundle - Symfony Usergroup Belgium

Resources● Service definitions

● Controllers

● Routes

● Templates

● Entities

● Form types

● Event listeners

● Translations

● ...

Page 38: The Naked Bundle - Symfony Usergroup Belgium

No need for them to be inside a bundle

Page 39: The Naked Bundle - Symfony Usergroup Belgium

When placed outside the bundlethe resources could be reused separately

Page 40: The Naked Bundle - Symfony Usergroup Belgium

The bundle would be really small

Page 41: The Naked Bundle - Symfony Usergroup Belgium

And could just as well be a:Laravel package,

Zend or Drupal module,CakePHP plugin,

...

Page 42: The Naked Bundle - Symfony Usergroup Belgium

So the challenge is to

Make the bundle as clean as possible

Page 43: The Naked Bundle - Symfony Usergroup Belgium

Move the “misplaced” things to● a library

● with dependencies

● but not symfony/framework­bundle ;)

Page 44: The Naked Bundle - Symfony Usergroup Belgium

Being realistic

Practical reusability

Page 45: The Naked Bundle - Symfony Usergroup Belgium

Reuse within the Symfony family

Think: Silex, Laravel, etc.

Page 46: The Naked Bundle - Symfony Usergroup Belgium

Allowed dependency

HttpFoundation● Request● Response

● Exceptions● etc.

Page 47: The Naked Bundle - Symfony Usergroup Belgium

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 48: The Naked Bundle - Symfony Usergroup Belgium

Why? My secret missions

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

Page 49: The Naked Bundle - Symfony Usergroup Belgium

And of course

Education

Page 50: The Naked Bundle - Symfony Usergroup Belgium

You need a strong coupling radar

Page 51: The Naked Bundle - Symfony Usergroup Belgium

Explicit dependencies● Function calls

● Imported classes (“use”)

● Included files

● ...

Page 52: The Naked Bundle - Symfony Usergroup Belgium

Implicit dependencies● File locations

● File, class, method names

● Structure of return values

● ...

Page 53: The Naked Bundle - Symfony Usergroup Belgium

There we go!

Page 54: The Naked Bundle - Symfony Usergroup Belgium

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 55: The Naked Bundle - Symfony Usergroup Belgium

TODO✔ Don't rely on things that may not

be there in another context:✔ Parent Controller class

✔ Routing, template, annotations, etc.

Page 56: The Naked Bundle - Symfony Usergroup Belgium

class ArticleController{

function editAction(...){

...}

}

Nice and clean ;)

Page 57: The Naked Bundle - Symfony Usergroup Belgium

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 58: The Naked Bundle - Symfony Usergroup Belgium

TODO✔ Inject dependencies

✔ Don't use helper methods

✔ Render the template manually

✔ Keep using Request (not really a TODO)

Page 59: The Naked Bundle - Symfony Usergroup Belgium

use Doctrine\ORM\EntityManager;

class ArticleController{

function __construct(EntityManager $em, 

) {$this­>em = $em;

}

...}

Inject dependencies

Page 60: The Naked Bundle - Symfony Usergroup Belgium

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

class ArticleController{    ...

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

Page 61: The Naked Bundle - Symfony Usergroup Belgium

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 62: The Naked Bundle - Symfony Usergroup Belgium

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 63: The Naked Bundle - Symfony Usergroup Belgium

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 64: The Naked Bundle - Symfony Usergroup Belgium

The cause?

Convention

Page 65: The Naked Bundle - Symfony Usergroup Belgium

One controller, many actions

one action!

Page 66: The Naked Bundle - Symfony Usergroup Belgium

__invoke()

namespace MyLibrary\Controller\Article;

class New{

function __construct(...) {

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

public function __invoke(...){

...}

}

Page 67: The Naked Bundle - Symfony Usergroup Belgium

Nice!

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

Page 68: The Naked Bundle - Symfony Usergroup Belgium

TODO✔ Set up routing

✔ Create a service and provide the right arguments

Page 69: The Naked Bundle - Symfony Usergroup Belgium

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 70: The Naked Bundle - Symfony Usergroup Belgium

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 71: The Naked Bundle - Symfony Usergroup Belgium

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 72: The Naked Bundle - Symfony Usergroup Belgium

Next up: Entities

Page 73: The Naked Bundle - Symfony Usergroup Belgium

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

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

Entity conventions

Page 74: The Naked Bundle - Symfony Usergroup Belgium

What's wrong with annotations?

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

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

Page 75: The Naked Bundle - Symfony Usergroup Belgium

Annotations are classes

Page 76: The Naked Bundle - Symfony Usergroup Belgium

use Doctrine\Common\Annotations\AnnotationReader;

$reader = new AnnotationReader();

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

$reader­>getClassAnnotations($class);

BANG

Class Doctrine\ORM\Mapping\Column 

not found

Page 77: The Naked Bundle - Symfony Usergroup Belgium

Well, uhm, yes, but...

Page 78: The Naked Bundle - Symfony Usergroup Belgium

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

Page 79: The Naked Bundle - Symfony Usergroup Belgium

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 80: The Naked Bundle - Symfony Usergroup Belgium

TODO✔ Remove annotations

✔ Find another way to map the data

Page 81: The Naked Bundle - Symfony Usergroup Belgium

namespace My\Bundle\Entity;

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

Nice and clean

A true POPO, the ideal of the data mapper pattern

Page 82: The Naked Bundle - Symfony Usergroup Belgium

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 83: The Naked Bundle - Symfony Usergroup Belgium

Conventions for XML metadata● For MyBundle\Entity\Article

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

Page 84: The Naked Bundle - Symfony Usergroup Belgium

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

Page 85: The Naked Bundle - Symfony Usergroup Belgium

You need DoctrineBundle >=1.2

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

Page 86: The Naked Bundle - Symfony Usergroup Belgium

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 87: The Naked Bundle - Symfony Usergroup Belgium

Now:● For MyLibrary\Model\Article

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

Page 88: The Naked Bundle - Symfony Usergroup Belgium

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

anywhere and in different formats● Entities are true POPOs

Page 89: The Naked Bundle - Symfony Usergroup Belgium

Finally: Templates

Page 90: The Naked Bundle - Symfony Usergroup Belgium

Conventions● In /Resources/views/[Controller]

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

Page 91: The Naked Bundle - Symfony Usergroup Belgium

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 92: The Naked Bundle - Symfony Usergroup Belgium

Still, we want them out!

And it's possible

Page 93: The Naked Bundle - Symfony Usergroup Belgium

# 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 94: The Naked Bundle - Symfony Usergroup Belgium

Get rid of absolute paths

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

Page 95: The Naked Bundle - Symfony Usergroup Belgium

What Puli does

Find the absolute paths of resources in a project

Page 96: The Naked Bundle - Symfony Usergroup Belgium

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 97: The Naked Bundle - Symfony Usergroup Belgium

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 98: The Naked Bundle - Symfony Usergroup Belgium

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 99: The Naked Bundle - Symfony Usergroup Belgium

Many possibilities● Templates

● Translation files

● Mapping metadata

● Service definitions

● And so on!

Page 100: The Naked Bundle - Symfony Usergroup Belgium

The future is bright● Puli is not stable yet

● But I expect much from it:

Page 101: The Naked Bundle - Symfony Usergroup Belgium

Puli will be the ultimate tool

Page 102: The Naked Bundle - Symfony Usergroup Belgium

to create NAKED BUNDLES

Page 103: The Naked Bundle - Symfony Usergroup Belgium

and to enable reuse of many kinds of resources

Page 104: The Naked Bundle - Symfony Usergroup Belgium

not limited byproject,

framework,even language

boundaries!

Page 105: The Naked Bundle - Symfony Usergroup Belgium

But even without Puli

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

Page 106: The Naked Bundle - Symfony Usergroup Belgium

Remember

The framework is for youYour code doesn't need it

Page 107: The Naked Bundle - Symfony Usergroup Belgium

Questions?

Page 108: The Naked Bundle - Symfony Usergroup Belgium

Buy it for $ 17,50http://leanpub.com/a-year-with-symfony/c/symfony-usergroup-belgium

Page 109: The Naked Bundle - Symfony Usergroup Belgium

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

Page 110: The Naked Bundle - Symfony Usergroup Belgium

Thank you

Feedback: meetup.com

Talk to me: @matthiasnoback