Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The Naked Bundle - Symfony Live London 2014

3,316 views

Published on

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.

Published in: Technology
  • Be the first to comment

The Naked Bundle - Symfony Live London 2014

  1. 1. The Naked Bundle Matthias Noback @matthiasnoback
  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” is catchy and controversial
  6. 6. The official view on bundles
  7. 7. First-class citizens Documentation » The Quick Tour » The Architecture
  8. 8. Importance Your code is more important than the framework, which is an implementation detail
  9. 9. Reuse
  10. 10. Nice!
  11. 11. All your code lives in a bundle Documentation » The Book » Creating Pages in Symfony2
  12. 12. Reuse “All your code in a bundle” contradicts the promise of reuse
  13. 13. Everything lives inside a bundle Documentation » Glossary
  14. 14. Not really true Many things live inside libraries (the Symfony components are libraries too!)
  15. 15. Which is good!
  16. 16. But you probably know that already
  17. 17. “libraries first”
  18. 18. What about... ● Controllers ● Entities ● Templates ● ...
  19. 19. They just need to be in a bundle Or do they?
  20. 20. Don't get me wrong I love Symfony!
  21. 21. 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
  22. 22. A framework is there for you
  23. 23. Your code doesn't need a framework
  24. 24. Noback's Principle Code shouldn't rely on something it doesn't truly need
  25. 25. Bundle conventions Things in a bundle often rely on conventions to work
  26. 26. Conventions aren't necessary at all
  27. 27. So according to Noback's Principle, code shouldn't rely on bundle conventions too
  28. 28. Naming conventions Controllers: ● *Controller classes ● *action methods Templates: ● in /Resources/views ● name: Controller/Action.html.twig
  29. 29. Structural conventions Controller: ● Extends framework Controller class ● Is ContainerAware
  30. 30. Behavioral conventions Controller: ● Is allowed to return an array ● Actions can type-hint to objects which will be fetched based on route parameters (??)
  31. 31. 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) { }
  32. 32. These conventions are what makes an application a Symfony2 application
  33. 33. A Year With Symfony
  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 newAction(...) { ... throw new NotFoundHttpException(); ... } }
  60. 60. use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngineInterface; 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
  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 New { function __construct(...) { // only what's necessary for this “action” } public function __invoke(...) { ... } }
  66. 66. Nice! └─Controller └─Article ├─New.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="new_article_controller" class="MyBundleControllerArticleNew"> <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="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!
  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.2 { "require": { ..., "doctrine/doctrine­bundle": "~1.2@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. Get a $7,50 discount: http://leanpub.com/a-year-with-symfony/c/SymfonyLiveLondon2014
  108. 108. Get a $10,00 introduction discount: http://leanpub.com/principles-of-php-package-design/c/SymfonyLive London2014
  109. 109. Thank you Feedback: joind.in/11553 Talk to me: @matthiasnoback
  110. 110. Images Sally MacKenzie: www.amazon.com

×