Save 37% off PRO during our Black Friday Sale! »

Best Practices in Symfony2

Best Practices in Symfony2

Going beyond the Official Best Practices in Symfony2

E9612cd342dbddff6640b99db21deee7?s=128

Andreas Hucks

November 12, 2014
Tweet

Transcript

  1. Best Practices in Symfony2 php[world] 2014 Washington D.C. Nov. 12th

    Andreas Hucks
  2. @meandmymonkey Andreas Hucks Software Architect & Trainer at SensioLabs

  3. Best Practices

  4. symfony.com/doc/current/best_practices/index.html

  5. None
  6. None
  7. Bad Practices


  8. symfony Day 2009

  9. Chapter One A Clean Project

  10. Naming Things in Symphony • Follow PSR-0, PSR-1, PSR-2 •

    Find a common scheme for your team • Be explicit • Be consistent
  11. Care about your coding style • Again - follow PSR-0,

    PSR-1, PSR-2 • Use PHPCSFixer
 http://goo.gl/gQnc0N
  12. .gitignore /web/bundles/   /app/bootstrap.php.cache   /app/cache/*   /app/config/parameters.yml   /app/logs/*

      /build/   /vendor/   /bin/   /composer.phar   /data/uploads   .idea   add your own
  13. .gitignore /web/bundles/   /app/bootstrap.php.cache   /app/cache/*   /app/config/parameters.yml   /app/logs/*

      /build/   /vendor/   /bin/   /composer.phar   /data/uploads   .idea   should be in your global .gitignore
  14. .gitignore /web/bundles/   /app/bootstrap.php.cache   /app/cache/*   /app/config/parameters.yml   /app/logs/*

      /build/   /vendor/   /bin/   /composer.phar   /data/uploads   .idea   this too
  15. .gitignore /web/bundles/   /app/bootstrap.php.cache   /app/cache/*   /app/config/parameters.yml   /app/logs/*

      /build/   /vendor/   /bin/   /composer.phar   /data/uploads   .idea   this.
  16. Committing parameters.yml is a
 Three Kitten Offense

  17. Remove Acme\* (if present)

  18. To Bundle or not to Bundle

  19. If you DO need separate Bundles... Vendor\AwesomeBundle vs. Vendor\Bundle\AwesomeBundle

  20. If you need separate Bundles... Vendor\AwesomeBundle vs. Vendor\Bundle\AwesomeBundle

  21. FAIL a.k.a. „because I can“ • MyCompleteAppBundle (ok for small

    projects) • MyAppNeedingGlobalResourcesBundle • MyBundleInsideAnotherBundleBundle

  22. FAIL a.k.a. „because I can“ • MyCompleteAppBundle (ok for small

    projects) • MyAppNeedingGlobalResourcesBundle • MyBundleInsideAnotherBundleBundle
 [sic]
  23. What should go into a Bundle • Bundles should be

    self-contained • Sets of Features • Examples: API, Worker Commands,
 Admin Panel… • Configured in /app/config
  24. Your config options • YAML • XML • Annotations •

    INI • PHP
  25. Your config options • YAML • XML • Annotations •

    INI • PHP meh.
  26. Your config options • YAML • XML • Annotations •

    INI • PHP special use cases
  27. Your config options • YAML • XML • Annotations •

    INI • PHP mostly good
  28. Your config options • YAML • XML • Annotations •

    INI • PHP Routing, Bundle Config, Parameters Services Routing, Validators, ORM/ODM
  29. If not using Annotations… Nest your routing files # /app/config/routing.yml


    acme_conference:
 resource: @AcmeConferenceBundle/[…]/routing.yml
 prefix: /conference
 
 
 # /src/Acme/Bundle/ConferenceBundle/[…]/routing.yml
 acme_conference_admin:
 resource: routing_admin.yml
 prefix: /
 
 acme_conference_frontend:
 resource: routing_frontend.yml
 prefix: /
  30. Chapter Two Controllers

  31. • Put Controllers on a Diet • Decouple them •

    But don't go overboard (this is the 
 application layer)

  32. Losing the Boilerplate

  33. Inject the Request into Controllers public function createAction(Request $request)
 {


    // not: $request = $this->getRequest();
 
 $session = $request->getSession();
 
 // ..
 }
  34. @Cache(...) /**
 * @Cache(smaxage=3600)
 */
 public function listAction()
 {
 //

    ..
 
 return $this->render(
 'SensioConferenceBundle:Default:show.html.twig',
 array(
 'conference' => $conference
 )
 );
 }
  35. ParameterConverters public function showAction(Conference $conference)
 {
 return $this->render(
 'SensioConferenceBundle:Default:show.html.twig',
 array(


    'conference' => $conference
 )
 );
 }
  36. @Template([name]) /**
 * @Template()
 */
 public function listAction()
 {
 //

    ..
 
 return array(
 'conferences' => $conferences
 );
 } compare to official best practices
  37. Use handleRequest() public function createAction(Request $request)
 {
 // .. 


    
 $form->handleRequest($request);
 
 if ($form->isSubmitted() && $form->isValid())
 {
 // ... persist data, redirect
 }
 
 // ..
 }
  38. Events for add-on actions if ($form->isSubmitted() && $form->isValid())
 {
 //

    ... persist data
 
 $this->get('event_dispatcher')->dispatch(
 ConferenceEvents::CREATED,
 new ConferenceEvent($conference)
 );
 
 // .. redirect
 }
  39. Service Layers – where sensible class ConferenceHandler
 {
 public function

    __construct(
 ObjectManager $manager,
 EventDispatcherInterface $dispatcher
 ) {
 $this->manager = $manager;
 $this->dispatcher = $dispatcher;
 }
 
 public function save(Conference $conference)
 {
 $this->manager->persist($conference);
 $this->manager->flush();
 
 $this->dispatcher->dispatch(
 // ...
 );
 }

  40. this->get('conference_handler')->save(
 $conference
 );
 Service Layers – where sensible

  41. Using the BaseController? Wrap calls to the Container to clean

    up /**
 * @return GeocoderInterface
 */
 private function getGeocoder()
 {
 return $this->get('geocoder');
 }
  42. Chapter Three Dependency Injection

  43. Injecting the Container class MyWebserviceConnector
 {
 public function __construct( ContainerInterface

    $container ) {
 // here be dragons
 }
 }
  44. But... but Symfony is doing it!

  45. ...\TwigBundle\Extension\AssetsExtension class AssetsExtension extends \Twig_Extension
 {
 private $container; 
 public

    function __construct( ContainerInterface $container ) {
 $this->container = $container;
 }
  46. So why not… Because Scopes! class MyMailer
 {
 public function

    __construct(Request $request)
 {
 // here be dragons
 }
 }
  47. Solution: RequestStack (>= 2.4) class MyMailer
 {
 public function __construct(RequestStack

    $stack)
 {
 $this->stack = $stack;
 }
 
 public function doSomething()
 {
 $request = $this->stack->getCurrentRequest();
 }
 }
  48. Alternative: Providing the Request (< 2.4) class RequestProvider
 {
 public

    function __construct( ContainerInterface $container ) {
 $this->container = $container;
 }
 
 public function getRequest()
 {
 $this->container->get('request');
 }
 }
  49. Service Organization

  50. Split up Service Definitions public function load( array $configs, ContainerBuilder

    $container ) {
 // … 
 $loader->load('storage.yml');
 $loader->load('imageprocessing.yml');
 }
  51. Dynamic Loading of Service Definitions public function load( array $configs,

    ContainerBuilder $container ) {
 // …
 
 $loader->load('storage.yml');
 $loader->load('imageprocessing.yml');
 
 if (isset($config['logging'])) {
 $loader->load('logging.yml');
 }
 }
  52. Semantic Configuration (for reusable Bundles) acme_conference:
 logging: ~
 thumbnails:
 width:

    300
 height: 200
  53. The Container as a Registry public function listAction()
 {
 $max

    = $this->container->getParameter( 'max_conferences' );
 $conferences = $someStorage->getConferences($max);
 
 // .. }
  54. Instead: Proper Service Configuration services:
 my_storage:
 class: "Sensio\[…]\Storage\MyStorage"
 arguments: ["%max_conferences%"]

  55. Binding to the Environment public function listAction()
 {
 $env =

    $this->container->getParameter( 'kernel.environment' );
 
 if ('dev' === $env) {
 // ...
 } // ...
 }
  56. Instead: Use Your config files # /app/config/config.yml acme_conference:
 thumbnails:
 width:

    300
 height: 200 # /app/config/config_dev.yml acme_conference:
 logging: ~

  57. Miscellaneous • Use YAML for Service Definitions • Remember you

    can (and should) use 
 Environment Variables • Use %kernel.root_dir% as a reference

  58. Intermezzo Random Tips out of Context

  59. PHP • Please drop 5.3 (it‘s also faster) • Use

    an Opcode Cache
  60. Composer • Install globally • Use the --optimize-autoloader option
 for

    production
  61. Doctrine • Activate Metadata Cache • Activate Query Cache •

    Pro Level only: Use factory-service to 
 register Repos & Managers as Services • Do NOT inject the EntityManager into 
 your entities
  62. Security • Make sure there are no leaks in the


    security.yml access_control section! • Better: Check Authorization in
 Controller, use the @Security 
 annotation
  63. Translation • Work with translation keys instead of
 full text

    to avoid breaking translations
  64. Searching • Look for „Symfony2“
 (without the space)

  65. PHPStorm • The Symfony Plugin is pretty stable, 
 use

    it! • Check out the PHP Annotations Plugin
  66. Read the Documentation (and the Changelogs)


  67. Chapter Four Forms

  68. Forms in Controllers public function createAction(Request $request)
 {
 $form =

    $this->createFormBuilder()
 ->add('name')
 ->add('startDate', 'date')
 ->add('endDate', 'date')
 ->add('location', 'textarea')
 ->getForm()
 ;
 
 // ..
  69. • Couples Form setup to Controller • No reusability


  70. Better: Use Type Classes class ConferenceType extends AbstractType
 {
 public

    function buildForm( FormBuilderInterface $builder, array $options ) {
 $builder
 ->add('name')
 ->add('startDate', 'date')
 ->add('endDate', 'date')
 ->add('location', 'textarea')
 ;
 }
  71. Always set the data_class public function setDefaultOptions( OptionsResolverInterface $resolver )

    {
 $resolver->setDefaults(
 array(
 'data_class' => 'Acme\Conference'
 )
 );
 }
  72. Using Data in Constructors class ConferenceType extends AbstractType
 {
 public

    function __construct(Venue $venue)
 {
 $this->venue = $venue;
 }
 
 public function buildForm( FormBuilderInterface $builder, array $options ) {
 // ...
 
 if ($this->venue->hasWifi()) {
 $builder->add('ssid');
 }
 }
  73. Use Form Events for Related Data public function buildForm(FormBuilderInterface $builder,

    array $options)
 {
 // ...
 
 $builder->addEventListener(
 FormEvents::PRE_SET_DATA,
 function (FormEvent $e) {
 if ($e->getData()->hasWifi()) {
 $e->getForm()->add('ssid');
 }
 }
 );
  74. Define Types as Services (for reusable Bundles) form_conference:
 class: "Sensio\[…]\Form\ConferenceType"


    tags:
 - { name: twig.extension, alias: conference }
  75. public function createAction(Request $request)
 {
 $form = $this->createForm('conference');
 $form ->handleRequest();


    
 // ...
 Define Types as Services (for reusable Bundles)
  76. Don‘t disable CSRF public function setDefaultOptions( OptionsResolverInterface $resolver ) {


    $resolver->setDefaults(
 array(
 // ...
 'csrf_protection' => false
 )
 );
 }
  77. Thanks! Questions? Please give feedback: https://joind.in/11884