The Naked Bundle - Tryout

Post on 02-Jul-2015

203 views 0 download

description

Tryout of The Naked Bundle at e-Active in Zwolle (NL).

Transcript of The Naked Bundle - Tryout

The Naked BundleMatthias Noback@matthiasnoback

What is it, really?

An actual naked bundle

I could've called it

BundleLitetm

The No Code Bundle

The Clean Bundle

But “naked” is catchy and controversial

The official view on bundles

First-class citizensDocumentation » The Quick Tour » The Architecture

Importance

Your code is more important than the framework,

which is an implementation detail

Reuse

Nice!

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

Reuse

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

Everything lives inside a bundleDocumentation » Glossary

Not really true

Many things live inside libraries (including the Symfony components)

Which is good!

But you probably know that already

“libraries first”

What about...● Controllers

● Entities

● Templates

● ...

They just need to be in a bundle

Or do they?

Don't get me wrong

I love Symfony!

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

A framework is there for you

Your code doesn't need a framework

Noback's Principle

Code shouldn't rely on something

it doesn't truly need

Bundle conventions

Things in a bundle often rely on conventions to work

Conventions aren't necessary at all

Naming conventionsControllers:

● *Controller classes

● *action methods

Templates:

● in /Resources/views

● name: Controller/Action.html.twig

Structural conventionsController:

● Extends framework Controller class

● Is ContainerAware

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){}

These conventions are what makes an application a Symfony2 application

A Year With Symfony

About bundles

A bundle exposes resources

Resources● Service definitions

● Controllers

● Routes

● Templates

● Entities

● Form types

● Event listeners

● Translations

● ...

No need for them to be inside a bundle

When placed outside the bundlethe resources could be reused separately

The bundle would be really small

And could just as well be a:Laravel or CodeIgniter package,

Zend or Drupal module,CakePHP plugin,

...

So the challenge is to

Make the bundle as clean as possible

Move the “misplaced” things to● a library

● with dependencies

● but not symfony/framework­bundle ;)

Being realistic

Practical reusability

Reuse within the Symfony family

Think: Silex, Laravel, etc.

Allowed dependency

HttpFoundation● Request● Response

● Exceptions● etc.

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, ...);}

Why? My secret missions● Prevent the need for a complete

rework

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

And of course

Education

You need a strong coupling radar

Explicit dependencies● Function calls

● Imported classes (“use”)

● ...

Implicit dependencies● File locations

● File names

● Method names

● ...

There we go!

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

TODO✔ Don't rely on things that may not

be there in another context:✔ Parent Controller class

✔ Routing, template, annotations, etc.

class ArticleController{

function editAction(...){

...}

}

Nice and clean ;)

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

TODO✔ Inject dependencies

✔ Don't use helper methods

✔ Render the template manually

✔ Keep using Request (not really a TODO)

use Doctrine\ORM\EntityManager;

class ArticleController{

function __construct(EntityManager $em, 

) {$this­>em = $em;

}

...}

Inject dependencies

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

class ArticleController{    ...

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

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

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!

TODO✔ Set up routing

✔ Create a service and provide the right arguments

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

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

<service id="article_controller"           class="MyBundle\Controller\ArticleController">    <argument type="service"              id="doctrine.orm.default_entity_manager" />    <argument type="service" id="mailer" />    <argument type="service" id="templating" /></service>

</services></container>

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">        article_controller:newAction    </default></route>

</routes>

Controller – Achievements● Can be anywhere

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

● Dependency injection, no service location

● Reusable in any application using HttpFoundation

Next up: Entities

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

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

Convention● They have to be in the /Entity

directory

● You define mapping metadata using annotations

What's wrong with annotations?

Annotations are classes which need to be there

namespace My\Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Article{    ...

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

Well, uhm, yes, but...

Are you ever going to replace your persistence library?

There is no real alternative to Doctrine, right?

Well...

Think of Doctrine MongoDB ODM, Doctrine CouchDB ODM, etc.

TODO✔ Remove annotations

✔ Find another way to map the data

namespace My\Bundle\Entity;

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

Nice and clean

A true POPO, the ideal of the data mapper pattern

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>

Conventions for XML metadata● For MyBundle\Entity\Article

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

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

You need DoctrineBundle >=1.2

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

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

class MyBundle{    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)        );    }}

Now:● For MyLibrary\Model\Article

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

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

anywhere and in different formats● Entities are true POPOs

Finally: Templates

Conventions● In /Resources/views/[Controller]

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

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” %}

● ...

Still, we want them out!

And it's possible

# 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');

Get rid of absolute paths

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

What Puli does

Find the absolute paths of resources in a project

use Webmozart\Puli\Repository\ResourceRepository;

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

/my-library/views /css/style.css

/absolute/path/to/views /css/style.css

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

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

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"        }    }}

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

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

Many possibilities● Templates

● Translation files

● Mapping metadata

● Service definitions

● ...

The future is bright● Puli is not stable yet

● But I expect much from it:

Puli will be the ultimate tool

to create NAKED BUNDLES

and to enable reuse of many kinds of resources

cross package!cross application!cross framework!

But even without Puli

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

Remember

The framework is for youYour code doesn't need it

Questions?

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

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

Thank you

Feedback: joind.in/11553

Talk to me: @matthiasnoback

Images● Sally MacKenzie:

www.amazon.com