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

Как превратить проект на symfony в боль. How to turn Symfony2 project into pain.

Roma
October 12, 2013

Как превратить проект на symfony в боль. How to turn Symfony2 project into pain.

Набор практических советов на основе реальных проектов.

Поговорим о том, какие ошибки можно совершить при использовании фреймворка Symfony2. Как это портит жизнь вам и вашим коллегам, и как этого можно избежать.

Главные герои: composer, окружения, структура и организация проекта, кэш, логи, конфиги, doctrine, DI контейнер и другие.

Roma

October 12, 2013
Tweet

More Decks by Roma

Other Decks in Programming

Transcript

  1. Who is this guy? • Web developer since 2005 •

    Evercode Lab – Co-founder and CEO • Using Symfony2 since 2011 • Love foosball and comics
  2. Why use frameworks? • Code Organization • Existing wheels •

    Faster development • Community • Focus on your business logic, not core features
  3. Project Structure !"" app # !"" AppKernel.php # !"" cache

    # !"" config # !"" logs | $"" ... !"" bin !"" composer.json !"" composer.lock !"" src # $"" Acme !"" vendor $"" web !"" app.php !"" app_dev.php $"" ...
  4. Project Structure !"" app # !"" autoload.php # !"" AppCache.php

    # !"" AppKernel.php # !"" config # $"" Resources !"" bin # !"" console # $"" symfony_requirements !"" phpunit.xml.dist !"" src !"" var # !"" bootstrap.php.cache # !"" cache # !"" logs # $"" SymfonyRequirements.php $"" web
  5. Project Structure !"" SomeNamespace # !"" Bundles # !"" Themes

    # $"" Utilities !"" CompanyName # !"" CampaignBundle # $"" RedactorBundle !"" Vagrant !"" build.xml !"" default.build.properties $"" symfony !"" app !"" composer.json !"" schema.xml $"" web
  6. Environments !"" app # !"" config # # !"" config.yml

    # # !"" config_alex.yml # # !"" config_artur.yml # # !"" config_dev.yml # # !"" config_john.yml # # !"" config_prod.yml # # !"" config_test.yml # # !"" parameters.yml # # !"" ... $"" web !"" app.php !"" app_alex.php !"" app_artur.php !"" app_dev.php !"" app_john.php
  7. Environments • Environment is not each machine you running your

    project on • Environment is each project configuration you use
  8. Environments • You should be able to run your app

    using each environment on your local machine • So, please, be nice and keep env’s configs in sync • And put sensitive params to parameters.yml or ignored local config
  9. Coupling app to env namespace App\MainBundle\Service; class Mailer { private

    $env public function __construct($env) { $this->env = $env; } public function mail($to, $subject, $body) { if($this->env != 'prod') { return; } // ... } }
  10. Decoupling app from env namespace App\MainBundle\Service; class Mailer { private

    $disableDelivery public function __construct($disableDelivery) { $this->disableDelivery = $disableDelivery; } public function mail($to, $subject, $body) { if($this->disableDelivery) { return; } // ... } }
  11. The application should not be aware of what environment it

    is running under, it should just be configured a particular way based on its configuration files. http://richardmiller.co.uk/2013/05/28/symfony2-avoiding-coupling-applications-to-the-environment/ http://www.flickr.com/photos/ezu/73784031/
  12. Cache and logs /** * change caching directory for the

    system's - * optimization for local/test/dev * * @return string */ public function getCacheDir() { if (in_array( $this->environment, array('dev','test','local')) ) { return sys_get_temp_dir() . '/cache/' . $this->environment; } return parent::getCacheDir(); }
  13. Cache and logs /** * change caching directory for the

    system's - * optimization for local/test/dev * * @return string */ public function getCacheDir() { if (in_array( $this->environment, array('dev','test','local')) ) { return sys_get_temp_dir() . '/cache/' . $this->environment; } return parent::getCacheDir(); }
  14. Cache and logs How many times you just set umask(0000)?

    http://symfony.com/doc/current/book/ installation.html#configuration-and-setup
  15. Annoying Composer? • Add your composer.lock to .gitignore • ...

    • Profit! • Wait, no. Have fun, bitches!
  16. The vendor directory is a black box. The public API

    is vendor/autoload.php. https://igor.io/2013/09/04/composer-vendor-directory.html http://www.flickr.com/photos/ezu/73784031/
  17. The upside of OSS is that you are in charge,

    and you actually can fix things when they break. Also, you can prevent them from breaking in the first place. https://igor.io/2013/09/24/dependency-responsibility.html http://www.flickr.com/photos/ezu/73784031/
  18. Git

  19. .gitignore *.iml *.DS_Store .idea Acme/DemoBundle/AcmeDemoBundle.php Acme/DemoBundle/Controller/DemoController.php Acme/DemoBundle/Controller/SecuredController.php Acme/DemoBundle/Controller/WelcomeController.php Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php Acme/DemoBundle/EventListener/ControllerListener.php

    Acme/DemoBundle/Form/ContactType.php Acme/DemoBundle/Resources/config/services.xml Acme/DemoBundle/Resources/public/css/demo.css Acme/DemoBundle/Resources/public/images/blue-arrow.png Acme/DemoBundle/Resources/public/images/field-background.gif Acme/DemoBundle/Resources/public/images/logo.gif Acme/DemoBundle/Resources/public/images/search.png Acme/DemoBundle/Twig/Extension/DemoExtension.php ...
  20. ACL

  21. ACL

  22. ACL • Don’t use ACL • Common, just never don’t

    • Even if somebody asks you to • Just. Don’t. Use. ACL
  23. ACL • Don’t use ACL • Common, just never don’t

    • Even if somebody asks you to • Just. Don’t. Use. ACL • Another bullet about not using ACL
  24. Voters • Can user edit the record? • Is he

    the author of it? • Does he have Roles:ROLE_SUPER_ADMIN ? • No? Then screw you! Access denied!
  25. Voters public function editAction($shopId) { $shop = // get the

    Shop entity ... if (!$this->sc->isGranted("EDIT", $shop)) { throw new AccessDeniedExceptions); } }
  26. Voters use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class ShopVoter implements VoterInterface {

    public function supportsAttribute($attribute) { return in_array($attribute, array( "EDIT", "MANAGE_ADMINS", "DELETE") ); } public function supportsClass($class) { return $class == "App\AppBundle\Entity\Shop"; } }
  27. Voters public function vote(TokenInterface $token, $object, array $attributes) { if

    (!$this->supportObject($object)) { return VoterInterface::ACCESS_ABSTAIN; } $accessToken = $this->container->get('request')->getUser(); try { $user = $this->getRepository('AppBundle:User') ->findUserByAccessToken($accessToken); if (!$user->getToken()->isActive()) { return VoterInterface::ACCESS_DENIED; } } catch (\Doctrine\ORM\NoResultException $e) { return VoterInterface::ACCESS_DENIED; } ... }
  28. Voters public function vote(TokenInterface $token, $object, array $attributes) { ...

    if ($user->hasRole(UserRole::ROLE_ADMIN)) { return VoterInterface::ACCESS_GRANTED; } if ($object->getAdmins()->contains($user)) { return VoterInterface::ACCESS_GRANTED; } return VoterInterface::ACCESS_DENIED; }
  29. Naming <?php namespace Sto\ContentBundle\Form; class CompanyType extends AbstractType { ...

    } <?php namespace Sto\AdminBundle\Form; class CompanyType extends AbstractType { ... }
  30. Naming • instead of NewUserEventListener use SendConfirmationListener • {name of

    the bundle without “Bundle”}. {name of the controller without “Controller”}.{name of the action without “Action”} • Stay consistent
  31. Code generation inside generate generate:bundle Generates a bundle generate:controller Generates

    a controller generate:doctrine:crud Generates a CRUD based on a ... generate:doctrine:entities Generates entity classes and ... generate:doctrine:entity Generates a new Doctrine ... generate:doctrine:form
  32. No $request->get() public function get($key, $default = null, $deep =

    false) { return $this->query->get( $key, $this->attributes->get( $key, $this->request->get( $key, $default, $deep) , $deep) , $deep ); }
  33. Controller on a diet • get Request • init service

    • call service’s method with params • return rendered Response