(My) Best Practices in Symfony

1,275 views

Published on

"(My) Best Pratices in Symfony" è una parte delle slides utilizzate durante un Train to Symfony2 organizzato in una web agency nel Giugno 2014.

Non è un elenco di best practices in senso stretto, sono semplicemente spunti dai quali partire per affrontare alcune problematiche pratiche.

Scopri su http://traintosymfony.com cos'è Train to Symfony, e quanto possa essere utile alla tua azienda.

Published in: Engineering
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,275
On SlideShare
0
From Embeds
0
Number of Embeds
12
Actions
Shares
0
Downloads
24
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

(My) Best Practices in Symfony

  1. 1. Train to Symfony 1http://traintosymfony.com TRAIN TO SYMFONY (My) Best Practices in Symfony the frameworkshop http://traintosymfony.com @TrainToSymfony BUSINESS CLASS
  2. 2. Train to Symfony 2http://traintosymfony.com (MY) BEST PRACTICES IN SYMFONY
  3. 3. Train to Symfony 3http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Conoscere le best practices di Symfony non per sapere “come si fa” ma per capire “a cosa mi serve”
  4. 4. Train to Symfony 4http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Il codice nel posto giusto
  5. 5. Train to Symfony 5http://traintosymfony.comIl codice nel posto giusto Dove scrivo il codice che risolve questo problema? Quanti controller posso creare? È normale creare tanti servizi? Ho codice che si ripete in alcuni controller, è normale? Come chiamo i templates TWIG? Il progetto del collega è strutturato diversamente dal mio, come mai?
  6. 6. Train to Symfony 6http://traintosymfony.comIl codice nel posto giusto È utile conoscere ed adottare convenzioni per poi sapere dove trovare e scrivere codice
  7. 7. Train to Symfony 7http://traintosymfony.comIl codice nel posto giusto Symfony propone già delle convenzioni: ● struttura delle cartelle di un progetto ● struttura delle cartelle di un bundle ● configurazione dei bundles ● routing ● composer ● [...]
  8. 8. Train to Symfony 8http://traintosymfony.comIl codice nel posto giusto Ok, ma dove scrivo effettivamente il mio codice?
  9. 9. Train to Symfony 9http://traintosymfony.comIl codice nel posto giusto controller service listener repository form builder entity template
  10. 10. Train to Symfony 10http://traintosymfony.comIl codice nel posto giusto controller primo punto in cui verrebbe da mettere codice forse non è il posto giusto: ● se mi servirà in altri punti dell'applicazione ● se posso delegare della logica in un servizio (es. mandare un'email)
  11. 11. Train to Symfony 11http://traintosymfony.comIl codice nel posto giusto /** * @Route(“/{category_id}/{product_id}”) */ public function showAction($category_id, $product_id) { $em = $this->getDoctrine()->getManager(); $category = $em->getRepository('FooBarBundle:Category')->find($category_id); if (!$category) { throw $this->createNotFoundException('Unable to find Category.'); } $product = $em->getRepository('FooBarBundle:Product')->find($product_id); if (!$product) { throw $this->createNotFoundException('Unable to Product.'); } if (!$product->isInCategory($category)) { throw new HTTPException(500, “Product does not belong to Category {$category}”); } [...] } /** * @Route(“/{category_id}/{product_id}”) */ public function showAction($category_id, $product_id) { $em = $this->getDoctrine()->getManager(); $category = $em->getRepository('FooBarBundle:Category')->find($category_id); if (!$category) { throw $this->createNotFoundException('Unable to find Category.'); } $product = $em->getRepository('FooBarBundle:Product')->find($product_id); if (!$product) { throw $this->createNotFoundException('Unable to Product.'); } if (!$product->isInCategory($category)) { throw new HTTPException(500, “Product does not belong to Category {$category}”); } [...] } Il controller lancia eccezioni
  12. 12. Train to Symfony 12http://traintosymfony.comIl codice nel posto giusto /** * @Route(“/{category_id}/{product_id}”) */ public function showAction($category_id, $product_id) { // throws exceptions $this->container->get('url_checker')->checkProductUrl($product_id, $category_id); [...] } /** * @Route(“/{category_id}/{product_id}”) */ public function showAction($category_id, $product_id) { // throws exceptions $this->container->get('url_checker')->checkProductUrl($product_id, $category_id); [...] } Cerco di delegare più logica possibile ai servizi
  13. 13. Train to Symfony 13http://traintosymfony.comIl codice nel posto giusto Nel controller cerco di fare meno operazioni possibili Delego più logica possibile ad altri componenti
  14. 14. Train to Symfony 14http://traintosymfony.comIl codice nel posto giusto entity logica che opera su un singolo oggetto funzioni che operano su entity collegate funzioni che formattano uno o più campi class Product { public function getFullPrice() { } public function getSpecialPrice() { } } class Product { public function getFullPrice() { } public function getSpecialPrice() { } }
  15. 15. Train to Symfony 15http://traintosymfony.comIl codice nel posto giusto form builder definizione di un form specificandone i campi scelta dei campi da gestire in base a della logica
  16. 16. Train to Symfony 16http://traintosymfony.comIl codice nel posto giusto class ContactType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('email', 'email', array( 'label' => 'Email', 'attr' => array('placeholder' => 'Il tuo indirizzo email'), 'constraints' => array( new Email(array('message' => 'Inserisci un indirizzo email valido.')) ) )) ->add('message', 'textarea', array( 'label' => 'Messaggio', 'constraints' => array( new NotBlank(array('message' => 'Inserisci un messaggio.')), ) )); } public function getName() { return 'foo_barbundle_contact'; } } class ContactType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('email', 'email', array( 'label' => 'Email', 'attr' => array('placeholder' => 'Il tuo indirizzo email'), 'constraints' => array( new Email(array('message' => 'Inserisci un indirizzo email valido.')) ) )) ->add('message', 'textarea', array( 'label' => 'Messaggio', 'constraints' => array( new NotBlank(array('message' => 'Inserisci un messaggio.')), ) )); } public function getName() { return 'foo_barbundle_contact'; } } # src/Foo/BarBundle/Form/ContactType.php
  17. 17. Train to Symfony 17http://traintosymfony.comIl codice nel posto giusto listener catturare eventi lanciati da Symfony, da bundles di terze parti (FOSUserBundle), o custom sviluppare un'architettura orientata ad eventi difficoltà nel (ri)trovare il codice
  18. 18. Train to Symfony 18http://traintosymfony.comIl codice nel posto giusto http://symfony.com/it/doc/current/book/internals.html#evento-kernel-response class MyResponseListener { public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); [..] } } class MyResponseListener { public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); [..] } } # src/Foo/BarBundle/Listener/MyResponseListener.php
  19. 19. Train to Symfony 19http://traintosymfony.comIl codice nel posto giusto repository logica per estrarre informazioni dal database non eseguire operazioni su dati già disponibili all'interno dispongo solamente dell'entity manager
  20. 20. Train to Symfony 20http://traintosymfony.comIl codice nel posto giusto NO SI $em->getRepository('FooBarBundle:Product')->findSpecialOffers(); $em->getRepository('FooBarBundle:Product')->search(array( 'q' => $q, 'special_offer' => true, 'return_qb' => true )) $em->getRepository('FooBarBundle:Product')->findSpecialOffers(); $em->getRepository('FooBarBundle:Product')->search(array( 'q' => $q, 'special_offer' => true, 'return_qb' => true )) $em->getRepository('FooBarBundle:Product')->isInCategory($product, $category);$em->getRepository('FooBarBundle:Product')->isInCategory($product, $category);
  21. 21. Train to Symfony 21http://traintosymfony.comIl codice nel posto giusto template logica estremamente limitata (if) se un template mostra contenuto molto diverso in base a della logica, valutare se creare diversi templates (es. risultati di una ricerca, form contatti simili)
  22. 22. Train to Symfony 22http://traintosymfony.comIl codice nel posto giusto servizi accesso al container e agli altri servizi spesso fanno da “ponte” tra controller, custom repository e altri servizi acquistare molta familiarità con i servizi creare tutti quelli necessari nome e id del servizio molto importanti
  23. 23. Train to Symfony 23http://traintosymfony.comIl codice nel posto giusto /** * @Route(“/catalog/{productPath}”, requirements={“productPath” = “.+”}) */ public function showAction($productPath) { $catalogService = $this->container->get('catalog.service'); /* * check if $productPath is a valid url * throws exception */ $product = $catalogService->getProductFromPath($productPath); return array( 'product' => $product ); } /** * @Route(“/catalog/{productPath}”, requirements={“productPath” = “.+”}) */ public function showAction($productPath) { $catalogService = $this->container->get('catalog.service'); /* * check if $productPath is a valid url * throws exception */ $product = $catalogService->getProductFromPath($productPath); return array( 'product' => $product ); } # src/Foo/BarBundle/Controller/ProductController.php example.com/catalog/category1/subcategory1/product1example.com/catalog/category1/subcategory1/product1
  24. 24. Train to Symfony 24http://traintosymfony.comIl codice nel posto giusto services: # catalog service catalog.service: class: FooBarBundleServiceCatalogService arguments: [@doctrine.orm.entity_manager, @router] services: # catalog service catalog.service: class: FooBarBundleServiceCatalogService arguments: [@doctrine.orm.entity_manager, @router] # src/Foo/BarBundle/Resources/config/services.yml <service id="catalog.service" class="FooBarBundleServiceCatalogService"> <argument type="service" id="doctrine.orm.entity_manager" /> <argument type="service" id="router" /> </service> <service id="catalog.service" class="FooBarBundleServiceCatalogService"> <argument type="service" id="doctrine.orm.entity_manager" /> <argument type="service" id="router" /> </service> # src/Foo/BarBundle/Resources/config/services.xml
  25. 25. Train to Symfony 25http://traintosymfony.comIl codice nel posto giusto use [...] class CatalogService { public function __construct($em, $router) { [...] } public function getProductFromPath($productPath) { // divide $productPath in tokens // controlla che l'ultimo token sia lo slug di un prodotto, altrimenti lancia un'eccezione // controlla che gli altri token siano slug di categorie, altrimenti lancia un'eccezione // controlla che le categorie siano corrette, altrimenti lancia un'eccezione return $product; } } use [...] class CatalogService { public function __construct($em, $router) { [...] } public function getProductFromPath($productPath) { // divide $productPath in tokens // controlla che l'ultimo token sia lo slug di un prodotto, altrimenti lancia un'eccezione // controlla che gli altri token siano slug di categorie, altrimenti lancia un'eccezione // controlla che le categorie siano corrette, altrimenti lancia un'eccezione return $product; } } # src/Foo/BarBundle/Service/CatalogService.php
  26. 26. Train to Symfony 26http://traintosymfony.comIl codice nel posto giusto Quali sono le convenzioni che definiamo noi?
  27. 27. Train to Symfony 27http://traintosymfony.comIl codice nel posto giusto Devo (sempre) considerare che: qualcun altro metterà mano al nostro codice anch'io riprenderò in mano il mio codice se adotto delle soluzioni standard, il mio codice è più comprensibile
  28. 28. Train to Symfony 28http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Organizzazione dei bundles
  29. 29. Train to Symfony 29http://traintosymfony.comOrganizzazione dei bundles Quali bundles creo? SiteBundle (FrontendBundle) UserBundle (AdminBundle, BackendBundle) Creo un altro bundle: per una funzionalità particolare se devo estenderne uno dei vendor se mette a disposizione una funzionalità trasversale (es. RedirectBundle in SymfonyBricks)
  30. 30. Train to Symfony 30http://traintosymfony.comOrganizzazione dei bundles Organizzazione interna di un bundle
  31. 31. Train to Symfony 31http://traintosymfony.comOrganizzazione dei bundles /Controller nessun limite sul numero di controller meglio molti controller che pochi ma molto lunghi attenzione ai nomi dei controller, si riflettono in Resources/views
  32. 32. Train to Symfony 32http://traintosymfony.comOrganizzazione dei bundles /DependencyInjection configura il bundle classi chiamate al bootstrap del bundle
  33. 33. Train to Symfony 33http://traintosymfony.comOrganizzazione dei bundles class BricksSiteExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $loader = new LoaderYamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } } class BricksSiteExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $loader = new LoaderYamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } } # src/Bricks/SiteBundle/DependencyInjection/BricksSiteExtension.php *Extension.php si occupa di caricare i files di configurazione (servizi)
  34. 34. Train to Symfony 34http://traintosymfony.comOrganizzazione dei bundles class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('bricks_site'); // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for // more information on that topic. return $treeBuilder; } } class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('bricks_site'); // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for // more information on that topic. return $treeBuilder; } } # src/Bricks/SiteBundle/DependencyInjection/Configuration.php Configuration.php carica i parametri nel container
  35. 35. Train to Symfony 35http://traintosymfony.comOrganizzazione dei bundles /Entity meglio (?) definire tutte le entities in un solo posto solitamente nel frontend (problema generazione CRUD) problema inglese/italiano
  36. 36. Train to Symfony 36http://traintosymfony.comOrganizzazione dei bundles /Extension (/TWIG) contiene le estensioni custom di TWIG filtri e funzioni TWIG
  37. 37. Train to Symfony 37http://traintosymfony.comOrganizzazione dei bundles /Form usare i FormType anche per form semplici validazione possibile nel FormType usare i FormType anche come servizi
  38. 38. Train to Symfony 38http://traintosymfony.comOrganizzazione dei bundles /Listener contiene i miei custom Listener scegliere una convenzione per i nomi
  39. 39. Train to Symfony 39http://traintosymfony.comOrganizzazione dei bundles /Resources configurazione del bundle (config/services.xml) templates assets (sì js e css, no immagini)
  40. 40. Train to Symfony 40http://traintosymfony.comOrganizzazione dei bundles /Service contiene tutti i servizi creati nient'altro che semplici classi PHP con un namespace aspetto più critico di un servizio: il nome e l'id
  41. 41. Train to Symfony 41http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Design di controllers
  42. 42. Train to Symfony 42http://traintosymfony.comDesign di controllers Funzionalità del sito Sintassi degli url Caricamento delle routes in routing.yml Quanti controller creo? Come raggruppo le actions?
  43. 43. Train to Symfony 43http://traintosymfony.comDesign di controllers Buone pratiche nei controller: ● meno logica possibile ● evitare ripetizione di codice ● delegare il più possibile a servizi e repositories ● valutare se una pagina = una action (es. submit di un form) ● lanciare eccezioni
  44. 44. Train to Symfony 44http://traintosymfony.comDesign di controllers Utilizzo di @ParamConverter http://symfony.com/it/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
  45. 45. Train to Symfony 45http://traintosymfony.comDesign di controllers Al posto di: use FooBarBundleEntityProduct; /** * @Route("/product/{id}") */ public function showAction($id) { $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('FooBarBundle:Product')->find($id); if (!$product) { throw new NotFoundHttpException(“Unable to find entity”); } $price = $product->getPrice(); } use FooBarBundleEntityProduct; /** * @Route("/product/{id}") */ public function showAction($id) { $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('FooBarBundle:Product')->find($id); if (!$product) { throw new NotFoundHttpException(“Unable to find entity”); } $price = $product->getPrice(); } # src/Foo/BarBundle/Controller/DefaultController.php
  46. 46. Train to Symfony 46http://traintosymfony.comDesign di controllers use FooBarBundleEntityProduct; /** * @Route("/product/{id}") * @ParamConverter("product", class="FooBarBundle:Product") */ public function showAction(Product $product) { $price = $product->getPrice(); [...] } use FooBarBundleEntityProduct; /** * @Route("/product/{id}") * @ParamConverter("product", class="FooBarBundle:Product") */ public function showAction(Product $product) { $price = $product->getPrice(); [...] } # src/Foo/BarBundle/Controller/DefaultController.php utilizzo @ParamConverter:
  47. 47. Train to Symfony 47http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Files di configurazione custom
  48. 48. Train to Symfony 48http://traintosymfony.comFiles di configurazione custom È bene tenere in ordine i file di configurazione in app/config ● creare un file per ogni bundle da configurare ● inserire parametri nel container tramite files di configurazione custom
  49. 49. Train to Symfony 49http://traintosymfony.comFiles di configurazione custom La direttiva imports include un qualsiasi file yml: imports: - { resource: parameters.yml } - { resource: security.yml } # bundles config - { resource: bundle_assetic.yml } - { resource: bundle_fos_user.yml } - { resource: bundle_hwi_oauth.yml } - { resource: bundle_stof_doctrine_extensions.yml } - { resource: bundle_vich_uploads.yml } # project config - { resource: conf_custom_templates.yml } - { resource: conf_email_addresses.yml } - { resource: conf_locales.yml } - { resource: conf_special_product_categories.yml } imports: - { resource: parameters.yml } - { resource: security.yml } # bundles config - { resource: bundle_assetic.yml } - { resource: bundle_fos_user.yml } - { resource: bundle_hwi_oauth.yml } - { resource: bundle_stof_doctrine_extensions.yml } - { resource: bundle_vich_uploads.yml } # project config - { resource: conf_custom_templates.yml } - { resource: conf_email_addresses.yml } - { resource: conf_locales.yml } - { resource: conf_special_product_categories.yml } # app/config/config.yml
  50. 50. Train to Symfony 50http://traintosymfony.comFiles di configurazione custom In un file custom di configurazione definisco: ● parametri per un bundle, che voglio tenere in un file separato (bundle_*) ● parametri per il container (chiave “parameters”) ● parametri disponibili nei templates TWIG (chiave “twig.globals”)
  51. 51. Train to Symfony 51http://traintosymfony.comFiles di configurazione custom Per le configurazioni che cambiano in base alla macchina è consigliato tenere una versione .dist ● condivisione repository git ● ambiente locale/remoto ● opensource: informazioni sensibili ● es: parameters.yml e parameters.yml.dist
  52. 52. Train to Symfony 52http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Dal service container a TWIG
  53. 53. Train to Symfony 53http://traintosymfony.comDal service container a TWIG Come configuro variabili perché siano disponibili in TWIG? Con la chiave twig.globals una variabile è accessibile in tutti i templates
  54. 54. Train to Symfony 54http://traintosymfony.comDal service container a TWIG parameters: # array of available interface translations interface_translation_locales: en: code: en flag: gb.png it: code: it flag: it.png es: code: es flag: es.png twig: globals: # parameter accessible from twig templates interface_translation_locales: "%interface_translation_locales%" parameters: # array of available interface translations interface_translation_locales: en: code: en flag: gb.png it: code: it flag: it.png es: code: es flag: es.png twig: globals: # parameter accessible from twig templates interface_translation_locales: "%interface_translation_locales%" # SymfonyBricks/app/config/locales.yml
  55. 55. Train to Symfony 55http://traintosymfony.comDal service container a TWIG Posso rendere disponibile anche un servizio (da usare con discrezione)
  56. 56. Train to Symfony 56http://traintosymfony.comDal service container a TWIG services: # estensione twig per il catalogo catalog.twig.extension: class: FooBarBundleExtensionCatalogExtension arguments: [@catalog.service] tags: - { name: twig.extension } services: # estensione twig per il catalogo catalog.twig.extension: class: FooBarBundleExtensionCatalogExtension arguments: [@catalog.service] tags: - { name: twig.extension } # src/Foo/BarBundle/Resources/config/services.yml 1) definisco un'estensione TWIG custom
  57. 57. Train to Symfony 57http://traintosymfony.comDal service container a TWIG class CatalogExtension extends Twig_Extension { public function __construct($catalogService) { $this->catalogService = $catalogService; } public function getFunctions() { return array( 'getCatalogService' => new Twig_Function_Method($this, 'getCatalogService') ); } public function getCatalogService() { return $this->catalogService; } } class CatalogExtension extends Twig_Extension { public function __construct($catalogService) { $this->catalogService = $catalogService; } public function getFunctions() { return array( 'getCatalogService' => new Twig_Function_Method($this, 'getCatalogService') ); } public function getCatalogService() { return $this->catalogService; } } # src/Foo/BarBundle/Extension/CatalogExtension.php 2) implemento l'estensione TWIG CatalogExtension
  58. 58. Train to Symfony 58http://traintosymfony.comDal service container a TWIG {% set catalogService = getCatalogService() %} {% for category in rootCategory.children %} <a href="{{ catalogService.generatePath(category) }}"> {{ category.name }} </a> {% endfor %} {% set catalogService = getCatalogService() %} {% for category in rootCategory.children %} <a href="{{ catalogService.generatePath(category) }}"> {{ category.name }} </a> {% endfor %} 3) lo utilizzo nel template, senza che il servizio sia passato da un controller
  59. 59. Train to Symfony 59http://traintosymfony.com(MY) BEST PRACTICES IN SYMFONY Environments
  60. 60. Train to Symfony 60http://traintosymfony.comEnvironments prod, dev e test sono i 3 ambienti predefiniti ● app.php ● app_dev.php
  61. 61. Train to Symfony 61http://traintosymfony.comEnvironments Il front controller definisce l'ambiente corrente: $kernel = new AppKernel('prod', false);$kernel = new AppKernel('prod', false); # web/app.php
  62. 62. Train to Symfony 62http://traintosymfony.comEnvironments AppKernel.php carica la configurazione dell'environment class AppKernel extends Kernel { public function registerBundles() { [...] } public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } } class AppKernel extends Kernel { public function registerBundles() { [...] } public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } } # app/AppKernel.php
  63. 63. Train to Symfony 63http://traintosymfony.comEnvironments Creare un ambiente aggiuntivo è semplice: ● creo web/previewfeatures.php ● inizializzo l'environment “preview_features” tramite ● creo app/config/dev_preview_features.yml $kernel = new AppKernel('preview_features', false);$kernel = new AppKernel('preview_features', false);
  64. 64. Train to Symfony 64http://traintosymfony.com the frameworkshop http://traintosymfony.com @TrainToSymfony (My) Best Practices in Symfony TRAIN TO SYMFONYBUSINESS CLASS

×