Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
The Naked Bundle 
Matthias Noback 
@matthiasnoback 
Symfony Barcelona 
October 24th, 2014
What's it all about?
An actual naked bundle
I could've called it 
BundleLitetm 
The No Code Bundle 
The Clean Bundle
But “naked” was catchy and controversial
Until Pierre came along
The official view on bundles
First-class citizens 
Documentation » 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 bundle 
Documentation » The Book » Creating Pages in Symfony2
Reuse 
“All your code in a bundle” 
contradicts the promise of reuse
Everything lives inside a bundle 
Documentation » Glossary
Not really true 
Many things live inside libraries 
(the Symfony components are 
libraries too!)
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 ...
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
So according to Noback's Principle, 
code shouldn't rely on bundle conventions too
Naming conventions 
Controllers: 
● *Controller classes 
● *action methods 
Templates: 
● in /Resources/views 
● name: Con...
Structural conventions 
Controller: 
● Extends framework Controller class 
● Is ContainerAware
Behavioral conventions 
Controller: 
● Is allowed to return an array 
● Actions can type-hint to objects which will be 
fe...
Configuration conventions 
Use lots of annotations! 
/** 
* @Route("/{id}") 
* @Method("GET") 
* @ParamConverter("post", c...
These conventions are what makes an application 
a Symfony2 application
About bundles
A bundle exposes resources
Resources 
● Service definitions 
● Controllers 
● Routes 
● Templates 
● Entities 
● Form types 
● Event listeners 
● Tra...
No need for them to be inside a bundle
When placed outside the bundle 
the resources could be reused separately
The bundle would be really small
And could just as well be a: 
Laravel 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 
HttpKernel 
namespace SymfonyComponentHttpKernel; 
use SymfonyComponentHttpFoundationRequest; 
use Sym...
Why? My secret missions 
“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”) 
● Included files 
● ...
Implicit dependencies 
● File locations 
● File, class, method names 
● Structure of return values 
● ...
There we go!
use SymfonyBundleFrameworkBundleControllerController; 
use SensioBundleFrameworkExtraBundleConfigurationRoute; 
use Sensio...
TODO 
✔ Don't rely on things that may not 
be there in another context: 
✔ Parent Controller class 
✔ Routing, template, a...
class ArticleController 
{ 
function editAction(...) 
{ 
... 
} 
} 
Nice and clean ;)
use SymfonyComponentHttpFoundationRequest; 
class ArticleController 
{ 
public function editAction(Request $request) 
{ 
$...
TODO 
✔ Inject dependencies 
✔ Don't use helper methods 
✔ Render the template manually 
✔ Keep using Request 
(not really...
use DoctrineORMEntityManager; 
class ArticleController 
{ 
function __construct( 
EntityManager $em, 
) { 
$this->em = $em...
Inline helper methods 
use SymfonyComponentHttpKernelExceptionNotFoundHttpException 
class ArticleController 
{ 
... 
publ...
use SymfonyComponentHttpFoundationResponse; 
use SymfonyComponentTemplatingEngineInterface; 
class ArticleController 
{ 
f...
Dependencies are explicit now 
Also: no mention of a “bundle” 
anywhere! 
use SymfonyComponentHttpFoundationRequest; 
use ...
Dependency overflow 
use SymfonyComponentHttpFoundationResponse; 
use SymfonyComponentTemplatingEngineInterface; 
class Ar...
The cause? 
Convention
One controller, many actions 
one action!
__invoke() 
namespace MyLibraryControllerArticle; 
class Edit 
{ 
function __construct(...) 
{ 
// only what's necessary f...
Nice! 
└─Controller 
└─Article 
├─Create.php 
├─Edit.php 
├─Archive.php 
└─Delete.php
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> 
<servic...
Bundle stuff: routing.xml 
<!-- in MyBundle/Resources/config/routing.xml → 
<?xml version="1.0" encoding="UTF-8" ?> 
<rout...
Controller – Achievements 
● Can be anywhere 
● No need to follow naming conventions 
(“*Controller”, “*action”) 
● Depend...
Next up: Entities
namespace MyBundleEntity; 
use DoctrineORMMapping as ORM; 
class Article 
{ 
... 
/** 
* @ORMColumn(type=”string”) 
*/ 
pr...
What's wrong with annotations? 
namespace MyBundleEntity; 
use DoctrineORMMapping as ORM; 
class Article 
{ 
... 
/** 
* @...
Annotations are classes
use DoctrineCommonAnnotationsAnnotationReader; 
$reader = new AnnotationReader(); 
$class = new ReflectionClass('Article')...
“Well, uhm, yes, but...
… are you ever going to use 
anything else than Doctrine ORM?”
Well... 
Think about Doctrine MongoDB ODM, 
Doctrine CouchDB ODM, etc. 
namespace MyBundleEntity; 
use DoctrineORMMapping ...
TODO 
✔ Remove annotations 
✔ Find another way to map the data
namespace MyBundleEntity; 
class Article 
{ 
private $id; 
private $title; 
} 
Nice and clean 
A true POPO, the ideal of t...
Use XML for mapping metadata 
<doctrine-mapping> 
<entity name=”MyBundleEntityArticle”> 
<id name="id" type="integer" colu...
Conventions for XML metadata 
● For MyBundleEntityArticle 
● Put XML here: 
@MyBundle/Resources/config/doctrine/ 
Article....
We don't want it in the bundle! 
There's a nice little trick
You need DoctrineBundle >=1.3 
{ 
"require": { 
..., 
"doctrine/doctrine-bundle": "~1.3@dev" 
} 
}
use DoctrineBundleDoctrineBundleDependencyInjectionCompiler 
DoctrineOrmMappingsPass; 
class MyBundle extends Bundle 
{ 
p...
Now: 
● For MyLibraryModelArticle 
● Put XML here: 
src/MyLibrary/Doctrine/Article.orm.xml
Entities - Achievements 
● Entity classes can be anywhere 
●Mapping metadata can be 
anywhere and in different formats 
● ...
Finally: Templates
Conventions 
● In /Resources/views/[Controller] 
● Filename: [Action].[format].[engine]
The difficulty with templates 
They can have all kinds of implicit 
dependencies: 
● global variables, e.g. {{ app.request...
Still, we want them out! 
And it's possible
Documentation » The Cookbook » Templating » 
How to use and Register namespaced Twig Paths 
# in config.yml 
twig: 
... 
p...
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 WebmozartPuliRepositoryResourceRepository; 
$repo = new ResourceRepository(); 
$repo->add('/my-library/views', '/absol...
Register “prefixes” 
Manually, or using the 
Puli Composer plugin 
// in the composer.json file of a package or project 
{...
Twig templates 
// in composer.json 
{ 
"extra": { 
"resources": { 
"/my-library/views": "src/MyLibrary/Views" 
} 
} 
} 
P...
Many possibilities 
● Templates 
● Translation files 
●Mapping metadata 
● Service definitions 
● And so on!
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
not limited by 
project, 
framework, 
even language 
boundaries!
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 you 
Your code doesn't need it
Questions?
Buy it for $ 17,50 
http://leanpub.com/a-year-with-symfony/c/symfony-barcelona
Get a $10,00 introduction discount: 
http://leanpub.com/principles-of-php-package-design/c/symfony-barcelona
Thank you 
Talk to me: @matthiasnoback
Images 
Sally MacKenzie: 
www.amazon.com
The Naked Bundle - Symfony Barcelona
Nächste SlideShare
Wird geladen in …5
×

The Naked Bundle - Symfony Barcelona

3.116 Aufrufe

Veröffentlicht am

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.

Veröffentlicht in: Technologie

The Naked Bundle - Symfony Barcelona

  1. 1. The Naked Bundle Matthias Noback @matthiasnoback Symfony Barcelona October 24th, 2014
  2. 2. What's it all about?
  3. 3. An actual naked bundle
  4. 4. I could've called it BundleLitetm The No Code Bundle The Clean Bundle
  5. 5. But “naked” was catchy and controversial
  6. 6. Until Pierre came along
  7. 7. The official view on bundles
  8. 8. First-class citizens Documentation » The Quick Tour » The Architecture
  9. 9. Importance Your code is more important than the framework, which is an implementation detail
  10. 10. Reuse
  11. 11. Nice!
  12. 12. All your code lives in a bundle Documentation » The Book » Creating Pages in Symfony2
  13. 13. Reuse “All your code in a bundle” contradicts the promise of reuse
  14. 14. Everything lives inside a bundle Documentation » Glossary
  15. 15. Not really true Many things live inside libraries (the Symfony components are libraries too!)
  16. 16. Which is good!
  17. 17. But you probably know that already
  18. 18. “libraries first”
  19. 19. What about... ● Controllers ● Entities ● Templates ● ...
  20. 20. They just need to be in a bundle Or do they?
  21. 21. Don't get me wrong I love Symfony!
  22. 22. 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
  23. 23. A framework is there for you
  24. 24. Your code doesn't need a framework
  25. 25. Noback's Principle Code shouldn't rely on something it doesn't truly need
  26. 26. Bundle conventions Things in a bundle often rely on conventions to work
  27. 27. Conventions aren't necessary at all
  28. 28. So according to Noback's Principle, code shouldn't rely on bundle conventions too
  29. 29. Naming conventions Controllers: ● *Controller classes ● *action methods Templates: ● in /Resources/views ● name: Controller/Action.html.twig
  30. 30. Structural conventions Controller: ● Extends framework Controller class ● Is ContainerAware
  31. 31. Behavioral conventions Controller: ● Is allowed to return an array ● Actions can type-hint to objects which will be fetched based on route parameters (??)
  32. 32. 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) {}
  33. 33. These conventions are what makes an application a Symfony2 application
  34. 34. About bundles
  35. 35. A bundle exposes resources
  36. 36. Resources ● Service definitions ● Controllers ● Routes ● Templates ● Entities ● Form types ● Event listeners ● Translations ● ...
  37. 37. No need for them to be inside a bundle
  38. 38. When placed outside the bundle the resources could be reused separately
  39. 39. The bundle would be really small
  40. 40. And could just as well be a: Laravel package, Zend or Drupal module, CakePHP plugin, ...
  41. 41. So the challenge is to Make the bundle as clean as possible
  42. 42. Move the “misplaced” things to ● a library ● with dependencies ● but not symfony/framework-bundle ;)
  43. 43. Being realistic Practical reusability
  44. 44. Reuse within the Symfony family Think: Silex, Laravel, etc.
  45. 45. Allowed dependency HttpFoundation ● Request ● Response ● Exceptions ● etc.
  46. 46. What do we rely on HttpKernel namespace SymfonyComponentHttpKernel; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; interface HttpKernelInterface { /** * Handles a Request to convert it to a Response. */ public function handle(Request $request, ...); }
  47. 47. Why? My secret missions “Let's rebuild the application, but this time we use Zend4 instead of Symfony2”
  48. 48. And of course Education
  49. 49. You need a strong coupling radar
  50. 50. Explicit dependencies ● Function calls ● Imported classes (“use”) ● Included files ● ...
  51. 51. Implicit dependencies ● File locations ● File, class, method names ● Structure of return values ● ...
  52. 52. There we go!
  53. 53. use SymfonyBundleFrameworkBundleControllerController; use SensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; /** * @Route(“/article”) */ class ArticleController extends Controller { /** * @Route(“/edit”) * @Template() */ function editAction(...) { ... } } Controller
  54. 54. TODO ✔ Don't rely on things that may not be there in another context: ✔ Parent Controller class ✔ Routing, template, annotations, etc.
  55. 55. class ArticleController { function editAction(...) { ... } } Nice and clean ;)
  56. 56. use SymfonyComponentHttpFoundationRequest; 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
  57. 57. TODO ✔ Inject dependencies ✔ Don't use helper methods ✔ Render the template manually ✔ Keep using Request (not really a TODO)
  58. 58. use DoctrineORMEntityManager; class ArticleController { function __construct( EntityManager $em, ) { $this->em = $em; } ... } Inject dependencies
  59. 59. Inline helper methods use SymfonyComponentHttpKernelExceptionNotFoundHttpException class ArticleController { ... public function editAction(...) { ... throw new NotFoundHttpException(); ... } }
  60. 60. use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; class ArticleController { function __construct(..., EngineInterface $templating) { ... } public function editAction(...) { ... return new Response( $this->templating->render( '@MyBundle:Article:edit.html.twig', array( 'form' => $form->createView() ) ) ); } } Render the template manually
  61. 61. Dependencies are explicit now Also: no mention of a “bundle” anywhere! use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; use DoctrineORMEntityManager;
  62. 62. Dependency overflow use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; class ArticleController { function __construct( EntityManager $entityManager, EngineInterface $templating, TranslatorInterface $translator, ValidatorInterface $validator, Swift_Mailer $mailer, RouterInterface $router ) { ... } ... }
  63. 63. The cause? Convention
  64. 64. One controller, many actions one action!
  65. 65. __invoke() namespace MyLibraryControllerArticle; class Edit { function __construct(...) { // only what's necessary for this “action” } public function __invoke(...) { ... } }
  66. 66. Nice! └─Controller └─Article ├─Create.php ├─Edit.php ├─Archive.php └─Delete.php
  67. 67. TODO ✔ Set up routing ✔ Create a service and provide the right arguments
  68. 68. Bundle stuff: services.xml <!-- in MyBundle/Resources/config/services.xml → <?xml version="1.0" ?> <container> <services> <service id="edit_article_controller" class="MyBundleControllerArticleEdit"> <argument type="service" id="doctrine.orm.default_entity_manager" /> <argument type="service" id="templating" /> </service> </services> </container>
  69. 69. Bundle stuff: routing.xml <!-- in MyBundle/Resources/config/routing.xml → <?xml version="1.0" encoding="UTF-8" ?> <routes> <route id="edit_article" path="/article/edit/{id}"> <default key="_controller"> edit_article_controller:__invoke </default> </route> </routes> Pull request by Kevin Bond allows you to leave out the “:__invoke” part!
  70. 70. Controller – Achievements ● Can be anywhere ● No need to follow naming conventions (“*Controller”, “*action”) ● Dependency injection, no service location ● Reusable in any application using HttpFoundation
  71. 71. Next up: Entities
  72. 72. namespace MyBundleEntity; use DoctrineORMMapping as ORM; class Article { ... /** * @ORMColumn(type=”string”) */ private $title; } Entity conventions
  73. 73. What's wrong with annotations? namespace MyBundleEntity; use DoctrineORMMapping as ORM; class Article { ... /** * @ORMColumn(type=”string”) */ private $title; }
  74. 74. Annotations are classes
  75. 75. use DoctrineCommonAnnotationsAnnotationReader; $reader = new AnnotationReader(); $class = new ReflectionClass('Article'); $reader->getClassAnnotations($class); BANG Class DoctrineORMMappingColumn not found
  76. 76. “Well, uhm, yes, but...
  77. 77. … are you ever going to use anything else than Doctrine ORM?”
  78. 78. Well... Think about Doctrine MongoDB ODM, Doctrine CouchDB ODM, etc. namespace MyBundleEntity; use DoctrineORMMapping as ORM; use DoctrineODMMongoDBMappingAnnotations as MongoDB; use DoctrineODMCouchDBMappingAnnotations as CoucheDB; class Article { /** * @ORMColumn * @MognoDBField * @CoucheDBField */ private $title; }
  79. 79. TODO ✔ Remove annotations ✔ Find another way to map the data
  80. 80. namespace MyBundleEntity; class Article { private $id; private $title; } Nice and clean A true POPO, the ideal of the data mapper pattern
  81. 81. Use XML for mapping metadata <doctrine-mapping> <entity name=”MyBundleEntityArticle”> <id name="id" type="integer" column="id"> <generator strategy="AUTO"/> </id> <field name=”title” type=”string”> </entity> </doctrine-mapping>
  82. 82. Conventions for XML metadata ● For MyBundleEntityArticle ● Put XML here: @MyBundle/Resources/config/doctrine/ Article.orm.xml
  83. 83. We don't want it in the bundle! There's a nice little trick
  84. 84. You need DoctrineBundle >=1.3 { "require": { ..., "doctrine/doctrine-bundle": "~1.3@dev" } }
  85. 85. use DoctrineBundleDoctrineBundleDependencyInjectionCompiler 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 = 'MyLibraryModel'; return DoctrineOrmMappingsPass::createXmlMappingDriver( array($xmlPath => $namespacePrefix) ); } }
  86. 86. Now: ● For MyLibraryModelArticle ● Put XML here: src/MyLibrary/Doctrine/Article.orm.xml
  87. 87. Entities - Achievements ● Entity classes can be anywhere ●Mapping metadata can be anywhere and in different formats ● Entities are true POPOs
  88. 88. Finally: Templates
  89. 89. Conventions ● In /Resources/views/[Controller] ● Filename: [Action].[format].[engine]
  90. 90. The difficulty with templates They 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” %}
  91. 91. Still, we want them out! And it's possible
  92. 92. Documentation » The Cookbook » Templating » How to use and Register namespaced Twig Paths # in config.yml twig: ... paths: Twig namespaces "%kernel.root_dir%/../src/MyLibrary/Views": MyLibrary // in the controller return $this->templating->render('@MyLibrary/Template.html.twig');
  93. 93. Get rid of absolute paths Using Puli, created by Bernhard Schüssek (Symfony Forms, Validation)
  94. 94. What Puli does Find the absolute paths of resources in a project
  95. 95. use WebmozartPuliRepositoryResourceRepository; $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
  96. 96. 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" } } }
  97. 97. Twig templates // in composer.json { "extra": { "resources": { "/my-library/views": "src/MyLibrary/Views" } } } Puli Twig extension // in the controller return $this->templating ->render('/my-library/views/index.html.twig');
  98. 98. Many possibilities ● Templates ● Translation files ●Mapping metadata ● Service definitions ● And so on!
  99. 99. The future is bright ● Puli is not stable yet ● But I expect much from it:
  100. 100. Puli will be the ultimate tool
  101. 101. to create NAKED BUNDLES
  102. 102. and to enable reuse of many kinds of resources
  103. 103. not limited by project, framework, even language boundaries!
  104. 104. But even without Puli There's a whole lot you can do to make your code not rely on the framework
  105. 105. Remember The framework is for you Your code doesn't need it
  106. 106. Questions?
  107. 107. Buy it for $ 17,50 http://leanpub.com/a-year-with-symfony/c/symfony-barcelona
  108. 108. Get a $10,00 introduction discount: http://leanpub.com/principles-of-php-package-design/c/symfony-barcelona
  109. 109. Thank you Talk to me: @matthiasnoback
  110. 110. Images Sally MacKenzie: www.amazon.com

×