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.

Symfony2 - The Unofficial "Best" Practices

6,830 views

Published on

We all read the best practices published a while ago, didn't we? In this talk, we will give some more insights into additional practices that you might consider in your next Symfony project.

Published in: Technology

Symfony2 - The Unofficial "Best" Practices

  1. 1. Symfony2 The Unofficial “Best” Practices Gerry Vandermaesen
  2. 2. AKA How Gerry Does Symfony2
  3. 3. About Me • Developer and trainer at King Foo • SensioLabs Certified Symfony Developer • We develop tailor-made PHP applications • @gerryvdm / gerry@king-foo.be
  4. 4. The Official Best Practices • Excellent guidelines for beginning Symfony developers • Use a single bundle for application specific code • Use the app/Resources/views/ directory for all application specific templates • Use YAML for application configuration
  5. 5. Keep your Business Logic Outside Bundles
  6. 6. • Your business logic hopefully outlives Symfony2 • Keep your model agnostic / decoupled from the framework (and ORM)
  7. 7. Keep Mapping and Validation Configuration
 Outside Entity Classes = Do Not Use Annotations
  8. 8. # config.yml 
 doctrine:
 orm:
 auto_mapping: false
 mappings:
 model:
 type: yml
 prefix: KingFooPresentationModel
 dir: %kernel.root_dir%/config/doctrine
 alias: Model
  9. 9. Tip:
 Defining Constraints in
 app/config/validation.yml
  10. 10. use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
 use SymfonyComponentDependencyInjectionContainerBuilder;
 
 class RegisterValidationMappingsPass implements CompilerPassInterface
 {
 public function process(ContainerBuilder $container)
 {
 if ($container->hasDefinition('validator.builder')) {
 $file = $container->getParameter('kernel.root_dir') .
 '/config/validation.yml';
 
 $container->getDefinition('validator.builder')
 ->addMethodCall('addYamlMappings', [$file]);
 }
 }
 }
  11. 11. Combo-Tip: Defining Constraints in
 app/config/validation/*.yml
  12. 12. use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
 use SymfonyComponentDependencyInjectionContainerBuilder;
 use SymfonyComponentFinderFinder;
 
 class RegisterValidationMappingsPass implements CompilerPassInterface
 {
 public function process(ContainerBuilder $container)
 {
 if ($container->hasDefinition('validator.builder')) {
 $dir = $container->getParameter('kernel.root_dir') .
 '/config/validation';
 
 $finder = new Finder();
 $finder->files()->name('*.yml')->in($dir);
 
 $files = [];
 
 foreach ($finder as $file) {
 $files[] = $file->getRealPath();
 }
 
 if (count($files)) {
 $container->getDefinition('validator.builder')
 ->addMethodCall('addYamlMappings', [$files]);
 }
 }
 }
 }
  13. 13. The Front Controller
  14. 14. $kernel = new AppKernel('prod', false);
  15. 15. use SymfonyComponentDebugDebug; $env = getenv('SYMFONY_ENV') ?: 'prod';
 $debug = getenv('SYMFONY_DEBUG') === '1' && $env !== 'prod'; if ($debug) {
 Debug::enable();
 } $kernel = new AppKernel($env, $debug);
  16. 16. <VirtualHost *:80>
 # ...
 SetEnv SYMFONY_ENV dev
 SetEnv SYMFONY_DEBUG 1
 </VirtualHost> server {
 location /app.php {
 # ...
 fastcgi_param SYMFONY_ENV dev;
 fastcgi_param SYMFONY_DEBUG 1;
 }
 } Apache Nginx
  17. 17. Taking It One Step Further
  18. 18. server {
 # ...
 
 location / {
 try_files $uri @app;
 }
 
 location @app {
 fastcgi_pass unix:/var/run/php5-fpm.sock;
 include fastcgi_params;
 
 fastcgi_param SCRIPT_FILENAME /path/to/project/app/app.php;
 fastcgi_param SCRIPT_NAME /path/to/project/app/app.php;
 
 fastcgi_param SYMFONY_ENV dev;
 fastcgi_param SYMFONY_DEBUG 1;
 }
 }

  19. 19. Use PSR-4 for the `src/` Directory
  20. 20. {
 "autoload": {
 "psr-4": { "KingFooPresentation": "src/" }
 }
 }
  21. 21. 💫
  22. 22. Prevent Autoloading of
 Tests in Production
  23. 23. {
 "autoload": {
 "psr-4": { "KingFooPresentation": "src/" }
 },
 "autoload-dev": {
 "psr-4": { “KingFooPresentationTests”: "tests/" }
 }
 }

  24. 24. Add Requirements to your Routes
  25. 25. # routing.yml
 homepage:
 path: /{_locale}
 
 ticket_index:
 path: /{_locale}/tickets
 
 tickets_detail:
 path: /{_locale}/tickets/{id}
 
 tickets_create:
 path: /{_locale}/tickets/create
 
 login:
 path: /login
  26. 26. # routing.yml
 homepage:
 path: /{_locale}
 requirements: { _locale: "(nl|fr|en)" }
 
 ticket_index:
 path: /{_locale}/tickets
 requirements: { _locale: "(nl|fr|en)" }
 
 tickets_detail:
 path: /{_locale}/tickets/{id}
 requirements: { _locale: "(nl|fr|en)", id: "[1-9][0-9]*" }
 
 tickets_create:
 path: /{_locale}/tickets/create
 requirements: { _locale: "(nl|fr|en)" }
 
 login:
 path: /login
  27. 27. Regex 😕
  28. 28. Global “Requirements”
  29. 29. use SymfonyComponentHttpKernelEventGetResponseEvent;
 use SymfonyComponentHttpKernelExceptionNotFoundHttpException;
 
 class CheckLocaleListener
 {
 private $availableLocales;
 
 public function __construct(array $availableLocales)
 {
 $this->availableLocales = $availableLocales;
 }
 
 public function checkLocale(GetResponseEvent $event)
 {
 $request = $event->getRequest();
 
 if ($locale = $request->attributes->get('_locale')) {
 if (!in_array($locale, $this->availableLocales)) {
 throw new NotFoundHttpException();
 }
 }
 }
 }
  30. 30. # services.yml
 parameters:
 available_locales: [nl, fr, en]
 
 services:
 check_locale_listener:
 class: CheckLocaleListener
 arguments: [%available_locales%]
 tags:
 - name: kernel.event_listener
 event: kernel.request
 method: checkLocale
 priority: 24
  31. 31. Bring Order in your Services Configuration
  32. 32. # services.yml
 
 # 500 lines of service declarations...
  33. 33. # services.yml
 imports:
 - { resource: services/controllers.yml }
 - { resource: services/event_listeners.yml }
 - { resource: services/repositories.yml }

  34. 34. Try Defining Your Services as Private
  35. 35. # services.yml
 services:
 my_fantastic_service:
 class: KingFooPresentationFantasticService
 public: false
  36. 36. class DependentService extends ContainerAware
 {
 public function doSomething()
 {
 $this->container->get('my_fantastic_service')
 ->doSomethingElse();
 }
 }
  37. 37. class DependentService
 {
 public function __construct(FantasticService $service)
 {
 $this->service = $service;
 }
 
 public function doSomething()
 {
 $this->service->doSomethingElse();
 }
 }
  38. 38. Define your Repositories as Services
  39. 39. # repositories.yml
 services:
 abstract_repository:
 abstract: true
 factory_service: doctrine.orm.entity_manager
 factory_method: getRepository
 public: false
 blog_post_repository:
 parent: abstract_repository
 class: KingFooPresentationRepositoryBlogPostRepository
 arguments: [Model:BlogPost]
 comment_repository:
 parent: abstract_repository
 class: KingFooPresentationRepositoryCommentRepository
 arguments: [Model:Comment]
  40. 40. Define your Controllers as Services
  41. 41. # controllers.yml
 services:
 blog_post_controller:
 class: KingFooPresentationControllerBlogPostController
 arguments:
 - @blog_post_repository
 - @doctrine.orm.entity_manager
  42. 42. One Controller One Action
  43. 43. # controllers.yml
 services:
 blog_post_list_controller:
 class: KingFooPresentationControllerBlogPostListController
 arguments: [@blog_post_repository]
 blog_post_create_controller:
 class: KingFooPresentationControllerBlogPostCreateController
 arguments: [@doctrine.orm.entity_manager]
  44. 44. <?php
 
 namespace KingFooPresentationControllerBlogPost;
 
 use DoctrineCommonPersistenceObjectManager;
 use SymfonyComponentHttpFoundationRequest;
 
 class CreateController
 {
 public function __construct(ObjectManager $manager)
 {
 // ...
 }
 
 public function __invoke(Request $request)
 {
 // ...
 }
 }
  45. 45. Annotate your Controllers
  46. 46. public function __invoke(Request $request)
 {
 if (!$this->authorizationChecker->isGranted('ROLE_EDITOR')) {
 throw new AccessDeniedHttpException();
 }
 
 $response = new Response();
 $response->setMaxAge(3600);
 
 return $this->templating->renderResponse(
 'blog_post/create.html.twig',
 array(
 // view parameters
 ),
 $response
 );
 }
  47. 47. use SensioBundleFrameworkExtraBundleConfigurationCache;
 use SensioBundleFrameworkExtraBundleConfigurationRoute;
 use SensioBundleFrameworkExtraBundleConfigurationSecurity;
 use SensioBundleFrameworkExtraBundleConfigurationTemplate;
 
 /**
 * @Route(service="blog_post_create_controller")
 */
 class CreateController
 {
 /**
 * @Route("/blog/create", name="foo")
 * @Cache(maxage=3600)
 * @Security("has_role('ROLE_EDITOR')")
 * @Template("blog_post/create.html.twig")
 */
 public function __invoke(Request $request)
 {
 return array(
 // view parameters
 );
 }
 }
  48. 48. Never Circumvent the Framework
  49. 49. class BadController
 {
 public function __invoke()
 {
 $template = $_GET['template'];
 setcookie('lastGeneration', time());
 
 $this->archaicPdfLib->output($template);
 exit;
 }
 }
  50. 50. use SymfonyComponentHttpFoundationCookie;
 use SymfonyComponentHttpFoundationRequest;
 use SymfonyComponentHttpFoundationStreamedResponse;
 
 class BetterController
 {
 public function __invoke(Request $request)
 {
 $template = $request->query->get('template');
 
 $response = new StreamedResponse(
 function() use ($template) {
 $this->archaicPdfLib->output($template);
 }
 );
 
 $cookie = new Cookie('lastGeneration', time());
 $response->headers->setCookie($cookie);
 
 return $response;
 }
 }
  51. 51. Securing your Application
  52. 52. Avoid usage of `access_control` in security.yml access_control:
 - { path: ^/(nl|fr|en)/admin, roles: ROLE_ADMIN }
  53. 53. Regex 😕
  54. 54. class AdminController
 {
 /**
 * @Route("/{_locale}/admin")
 * @Security("has_role('ROLE_ADMIN')")
 */
 public function __invoke(Request $request)
 {
 // if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
 // throw new AccessDeniedHttpException();
 // }
 
 // ..
 }
 }
  55. 55. Use Bcrypt
  56. 56. # security.yml
 security:
 encoders:
 KingFooPresentationSecurityUser:
 algorithm: bcrypt
 cost: 13
  57. 57. # services.yml
 services:
 password_encoder:
 class: SymfonyComponentSecurity...BCryptPasswordEncoder
 arguments: [13]
 
 # security.yml
 security:
 encoders:
 KingFooPresentationSecurityUser:
 id: password_encoder
  58. 58. Create Your Own Symfony Framework Edition
  59. 59. • Remove clutter • Add/remove default dependencies • Customize default configuration
  60. 60. • Fork symfony/symfony-standard • Make modifications • Modify composer.json package name • Publish on GitHub and Packagist
  61. 61. {
 "name": "kingfoo/symfony-project",
 "autoload": {
 "psr-4": { "": "src/" }
 },
 "require": {
 "php": ">=5.5.0",
 "symfony/symfony": "~2.6",
 "doctrine/mongodb-odm": "~1.0@dev",
 "doctrine/mongodb-odm-bundle": "~3.0@dev",
 "symfony/monolog-bundle": "~2.4",
 "sensio/distribution-bundle": "~3.0,>=3.0.12",
 "sensio/framework-extra-bundle": "~3.0",
 "incenteev/composer-parameter-handler": "~2.0"
 },
 "require-dev": {
 "phpunit/phpunit": "~4.4"
 }
 }
  62. 62. Miscellaneous / Flamebait • Bundles are for extending the container and configuring (third-party) libraries that are reusable across projects in the container. • Try doing without bundles for project- specific configuration. • YAML is the preferred configuration file format for application-specific config, XML for configuration of bundles
  63. 63. Develop Your Own
 Best Practices • Do it as a team • Write them down • Be consistent • Learn regex 😉
  64. 64. Next Symfony Training • Getting Started with Symfony2 • 12-13th February 2015 • Aarschot • Leave your name + email for a €50 reduction
  65. 65. Questions? 🙉

×