Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Best Practices in Symfony2

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →

Best Practices in Symfony2

Going beyond the Official Best Practices in Symfony2

Andreas Hucks

November 12, 2014

Transcript

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

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

    PSR-1, PSR-2 • Use PHPCSFixer
 http://goo.gl/gQnc0N
  3. .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
  4. .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
  5. .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
  6. .gitignore /web/bundles/   /app/bootstrap.php.cache   /app/cache/*   /app/config/parameters.yml   /app/logs/*

      /build/   /vendor/   /bin/   /composer.phar   /data/uploads   .idea   this.
  7. FAIL a.k.a. „because I can“ • MyCompleteAppBundle (ok for small

    projects) • MyAppNeedingGlobalResourcesBundle • MyBundleInsideAnotherBundleBundle

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

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

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

    INI • PHP Routing, Bundle Config, Parameters Services Routing, Validators, ORM/ODM
  11. 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: /
  12. • Put Controllers on a Diet • Decouple them •

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

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


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

    ..
 
 return $this->render(
 'SensioConferenceBundle:Default:show.html.twig',
 array(
 'conference' => $conference
 )
 );
 }
  15. @Template([name]) /**
 * @Template()
 */
 public function listAction()
 {
 //

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


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

    ... persist data
 
 $this->get('event_dispatcher')->dispatch(
 ConferenceEvents::CREATED,
 new ConferenceEvent($conference)
 );
 
 // .. redirect
 }
  18. 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(
 // ...
 );
 }

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

    up /**
 * @return GeocoderInterface
 */
 private function getGeocoder()
 {
 return $this->get('geocoder');
 }
  20. ...\TwigBundle\Extension\AssetsExtension class AssetsExtension extends \Twig_Extension
 {
 private $container; 
 public

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

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

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

    function __construct( ContainerInterface $container ) {
 $this->container = $container;
 }
 
 public function getRequest()
 {
 $this->container->get('request');
 }
 }
  24. Split up Service Definitions public function load( array $configs, ContainerBuilder

    $container ) {
 // … 
 $loader->load('storage.yml');
 $loader->load('imageprocessing.yml');
 }
  25. 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');
 }
 }
  26. The Container as a Registry public function listAction()
 {
 $max

    = $this->container->getParameter( 'max_conferences' );
 $conferences = $someStorage->getConferences($max);
 
 // .. }
  27. Binding to the Environment public function listAction()
 {
 $env =

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

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

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

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

  30. 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
  31. Security • Make sure there are no leaks in the


    security.yml access_control section! • Better: Check Authorization in
 Controller, use the @Security 
 annotation
  32. PHPStorm • The Symfony Plugin is pretty stable, 
 use

    it! • Check out the PHP Annotations Plugin
  33. Forms in Controllers public function createAction(Request $request)
 {
 $form =

    $this->createFormBuilder()
 ->add('name')
 ->add('startDate', 'date')
 ->add('endDate', 'date')
 ->add('location', 'textarea')
 ->getForm()
 ;
 
 // ..
  34. 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')
 ;
 }
  35. Always set the data_class public function setDefaultOptions( OptionsResolverInterface $resolver )

    {
 $resolver->setDefaults(
 array(
 'data_class' => 'Acme\Conference'
 )
 );
 }
  36. 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');
 }
 }
  37. 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');
 }
 }
 );
  38. Don‘t disable CSRF public function setDefaultOptions( OptionsResolverInterface $resolver ) {


    $resolver->setDefaults(
 array(
 // ...
 'csrf_protection' => false
 )
 );
 }

More Decks by Andreas Hucks

Other Decks in Programming