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

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

Dd3f18c87b851137000c7427d7bd5d32?s=47 fwdays
October 18, 2013

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

Dd3f18c87b851137000c7427d7bd5d32?s=128

fwdays

October 18, 2013
Tweet

More Decks by fwdays

Other Decks in Programming

Transcript

  1. Как превратить проект на Symfony в боль Roman Lapin Evercode

    Lab @memphys roma@evercodelab.com
  2. Who is this guy? • Web developer since 2005 •

    Evercode Lab – Co-founder and CEO • Using Symfony2 since 2011 • Love foosball and comics
  3. None
  4. Viewers Discretion is Advised

  5. Where does the pain come from?

  6. Why use frameworks? http://www.flickr.com/photos/doberagi/1404539812/

  7. Why use frameworks? • Code Organization • Existing wheels •

    Faster development • Community • Focus on your business logic, not core features
  8. Why use Symfony2?

  9. Why use Symfony2?

  10. Project Structure http://www.flickr.com/photos/kwl/8642451160/

  11. Project Structure !"" app # !"" AppKernel.php # !"" cache

    # !"" config # !"" logs | $"" ... !"" bin !"" composer.json !"" composer.lock !"" src # $"" Acme !"" vendor $"" web !"" app.php !"" app_dev.php $"" ...
  12. 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
  13. Project Structure !"" SomeNamespace # !"" Bundles # !"" Themes

    # $"" Utilities !"" CompanyName # !"" CampaignBundle # $"" RedactorBundle !"" Vagrant !"" build.xml !"" default.build.properties $"" symfony !"" app !"" composer.json !"" schema.xml $"" web
  14. Project Structure • http://symfony.com/doc/current/cookbook/ configuration/override_dir_structure.html • http://rad.knplabs.com/ • https://github.com/symfony/symfony- standard/issues/

    584#issuecomment-23148928
  15. Environments http://www.flickr.com/photos/minustah/5809683503/

  16. Environments • dev • prod • test

  17. 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
  18. Environments • Environment is not each machine you running your

    project on • Environment is each project configuration you use
  19. 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
  20. Coupling app to env if ($this->container->getParameter('kernel.environment') == 'prod') { $this->doSomethingCool();

    }
  21. 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; } // ... } }
  22. 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; } // ... } }
  23. 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/
  24. Cache and Logs

  25. 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(); }
  26. 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(); }
  27. Cache and logs

  28. Cache and logs How many times you just set umask(0000)?

    http://symfony.com/doc/current/book/ installation.html#configuration-and-setup
  29. Composer

  30. Composer. The bad way.

  31. Composer

  32. Annoying Composer? {"scripts": {"post-install-cmd": "rm -rf /"}}

  33. Annoying Composer? {"scripts": {"post-install-cmd": “apt-get install ruby“}}

  34. Annoying Composer? {"scripts": {"post-install-cmd": “apt-get uninstall php“}}

  35. Annoying Composer? In a SF2 bundle: "require": { "zendframework/zendframework": "dev-master"

    }
  36. Annoying Composer?

  37. Annoying Composer? • Add your composer.lock to .gitignore

  38. Annoying Composer? • Add your composer.lock to .gitignore • ...

  39. Annoying Composer? • Add your composer.lock to .gitignore • ...

    • Profit!
  40. Annoying Composer? • Add your composer.lock to .gitignore • ...

    • Profit! • Wait, no. Have fun, bitches!
  41. Let’s change vendors! Edit vendor/sonata-project/user-bundle/Sonata/UserBundle/ Model/User.php. Change line: use FOS\UserBundle\Entity\User

    as AbstractedUser; to use FOS\UserBundle\Document\User as AbstractedUser;
  42. Let’s change vendors!

  43. 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/
  44. Update blindly $ composer update

  45. Update blindly $ composer update Now pray.

  46. 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/
  47. Composer "minimum-stability": "dev", "prefer-stable": false,

  48. Git

  49. .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 ...
  50. .gitignore /web/bundles/ /app/bootstrap* /app/cache/* /app/logs/* /vendor/ /app/config/parameters.yml

  51. Wut .gitignore?! • vendors • compiled assets • Acme •

    parameters.yml • composer.lock
  52. Wut .gitignore?! https://help.github.com/articles/ignoring-files

  53. DI http://www.flickr.com/photos/blueship/137771066/

  54. Classic some_name.cluster_access.switcher.twig.extension: class: App\Bundles\SomeBundle\Twig\SomeExtension arguments: [@service_container] tags: - { name:

    twig.extension }
  55. Classic class SomeExtension extends \Twig_Extension { ... $cluster = $this->container

    ->get('some.manager') ->getCurrentSomething(); ... }
  56. Extended app.rubric.service: class: App\DefaultBundle\Service\RubricService arguments: [@security.context, ...]

  57. Extended class RubricService { public function __construct($security, ...) { $this->user

    = $security->getToken()->getUser(); ... } }
  58. ACL

  59. ACL

  60. ACL • Don’t use ACL

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

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

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

    • Even if somebody asks you to • Just. Don’t. Use. ACL
  64. 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
  65. Why?

  66. Why? Because Voters!

  67. 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!
  68. Voters public function editAction($shopId) { $shop = // get the

    Shop entity ... if (!$this->sc->isGranted("EDIT", $shop)) { throw new AccessDeniedExceptions); } }
  69. 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"; } }
  70. 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; } ... }
  71. 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; }
  72. General stuff http://www.flickr.com/photos/anitacanita/756830136/

  73. Naming <?php namespace Sto\ContentBundle\Form; class CompanyType extends AbstractType { ...

    } <?php namespace Sto\AdminBundle\Form; class CompanyType extends AbstractType { ... }
  74. 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
  75. 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
  76. 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 ); }
  77. Be polite bin/php-cs-fixer fix src

  78. You probably heard • Fat controllers • Tight coupling •

    Code immobility
  79. Controller on a diet • get Request • init service

    • call service’s method with params • return rendered Response
  80. README

  81. Don’t hurt your self and be cool.

  82. Final bullet

  83. Thank you! Roma Lapin Evercode Lab roma@evercodelab.com @memphys @evercodelab