O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Symfony 4 Workshop - Limenius

392 visualizações

Publicada em

Theory bits for a workshop about Symfony 4. Workshop steps and exercises are in http://symfony4.limenius.com/

Publicada em: Software
  • Seja o primeiro a comentar

Symfony 4 Workshop - Limenius

  1. 1. Symfony 4 Workshop Victoria Quirante - @vicqr Nacho Martin - @nacmartin Notes: http://symfony4.limenius.com/
  2. 2. Nacho Martín @nacmartin nacho@limenius.com Victoria Quirante @vicqr victoria@limenius.com We build tailor-made projects with Symfony and React We have been working with Symfony since 2008 We provide development, consulting and training
  3. 3. Symfony 4
  4. 4. We are here
  5. 5. Symfony Evolution Symfony 12005 A monolith (very different) Symfony 22011 Components appear
  6. 6. Components used in popular projects
  7. 7. Doctrine
  8. 8. Silex
  9. 9. Laravel
  10. 10. Symfony Evolution Symfony 12005 A monolith (very different) Symfony 22011 Components appear Symfony 32015 Brief intermediate step Symfony 42017 Compose your apps
  11. 11. Symfony 2/3 SF 2 Web app SF 2 API SF 2 Microservice SF 4 Web app SF4 API SF4 Microservice Symfony 4
  12. 12. Symfony 4 SF4 SF 4 Web app SF4 API SF4 Microservice We need something to smooth out these transformations
  13. 13. Symfony Flex
  14. 14. Symfony Flex is a tool to implement Symfony 4 philosophy
  15. 15. It is a composer plugin that comes with Symfony 4 The idea is automation to the max when installing and configuring packages
  16. 16. Modifies the behaviour of the require and update commands Allows Symfony to perform tasks before or after the composer commands
  17. 17. Symfony 4 Your application with Symfony Flex composer req mailer Symfony Flex Server Recipe? No Regular install With composer
  18. 18. Symfony 4 Your application with Symfony Flex composer req mailer Symfony Flex Server Recipe? Yes Install them With composer Follow recipe instructions Decide which packages to install Run any task to configure them
  19. 19. http://symfony.sh
  20. 20. Directory Structure my-project/ ├── config/ │   ├── bundles.php │   ├── packages/ │   ├── routes.yaml │   └── services.yaml ├── public/ │   └── index.php ├── src/ │   ├── ... │   └── Kernel.php ├── templates/ └── vendor/
  21. 21. Doctrine
  22. 22. Entity
  23. 23. Entity namespace AppEntity; class Movie { private $id; private $name; private $director; private $year; private $picture; }
  24. 24. Entity /** * @ORMEntity() * @ORMTable(name="movie") */ class Movie { /** * @ORMColumn(type="integer") * @ORMId * @ORMGeneratedValue(strategy="AUTO") */ private $id; /** * @ORMColumn(type="string") */ private $name; /** * @ORMColumn(type="string", length=100) */ private $director; /** * @ORMColumn(type="smallint") */ private $year; /** * @ORMColumn(type="string") */ private $picture; }
  25. 25. Entity Code that has to do with the model itself. Doesn’t depend on services, or query the DB.
  26. 26. Accessors In this workshop we are going to use setters and getters: •getDirector() •setDirector() This is not the only way. See for instance: http://williamdurand.fr/2013/08/07/ddd-with-symfony2-folder-structure-and-code-first/
  27. 27. Entity Manager
  28. 28. Entity Manager $em = $this->getDoctrine()->getManager(); $em->getRepository(Movie::class)->find($movieId); $em = $this->getDoctrine()->getManager(); $em->persist($sale); $em->flush();
  29. 29. Doctrine Query Language (DQL) $em = $this->getDoctrine()->getManager(); $query = $em->createQuery( 'SELECT m, a FROM App:Movie m LEFT JOIN m.actors a WHERE m.id = :id' )->setParameter('id', $movieId); $movie = $query->getOneOrNullResult();
  30. 30. Query Builder $em = $this->getDoctrine()->getManager(); $query = $em->getRepository(Movie::class) ->createQueryBuilder('m') ->select('m, a') ->leftJoin('m.actors', 'a') ->where('m.id = :id') ->setParameter('id', $movieId) ->getQuery(); $movie = $query->getOneOrNullResult();
  31. 31. Entity Manager Code that deals with retrieving or persisting entities.
  32. 32. Fixtures With Alice
  33. 33. We could do this for ($i = 0; $i < 10; $i ++) { $movie = new Movie(); $movie->setYear(1994); $movie->setName('Pulp Fiction'.$i); //... $em->persist($movie); } $em->flush();
  34. 34. With Alice AppEntityMovie: movie{1..10}: name: '<sentence(4, true)>' director: '<name()>' year: '<numberBetween(1970, 2017)>' picture: '<image("./public/images", 500, 500, "cats", false)>' See all the generators in https://github.com/fzaninotto/Faker
  35. 35. Relationships appentitymovie: actor{1..10}: #… movie{1..10}: #... actors: ["@actor<numberBetween(1, 3)>", "@actor<numberBetween(4, 6)>"]
  36. 36. Twig
  37. 37. Twig {{ }} {% %} Display Data Define Structures
  38. 38. Access variables {{ foo }} {{ foo.bar }} {{ foo['bar'] }}
  39. 39. - Checks if foo is an array and bar an element - If not, checks if foo is an object and bar a property - If not, checks if foo is an object and bar a method - If not, checks if foo is an object and getBar a method - If not, checks if foo is an object and isBar a method {{ foo.bar }} Access variables
  40. 40. {% for user in users %} {{ user.username }} {% endfor %} For each
  41. 41. {% for i in 0..10 %} {{ i }} {% endfor %} For
  42. 42. For … else {% for item in items %} {{ item }} {% else %} No items. {% endfor %}
  43. 43. <html><head>...</head> <body> <h1> {% block title %}{% endblock %} </h1> {% block body %}{% endblock %} </body></html> Template inheritance templates/layout.html.twig
  44. 44. {% extends "layout.twig" %} {% block title %} Home {% endblock %} {% block body %} Lorem ipsum... {% endblock %} Template inheritance templates/home.html.twig
  45. 45. {% extends "layout.twig" %} {% block title %} Movie list {% endblock %} {% block body %} Duis aute irure dolor in.. {% endblock %} Template inheritance templates/movies.html.twig
  46. 46. Webpack Encore
  47. 47. Webpack
  48. 48. Webpack The standard nowadays. Very powerful. Steep learning curve. Cons Pros
  49. 49. Webpack Encore A wrapper around Webpack that makes it easier to set up. It solves a big % of cases. If you require something specific, you can still use Webpack without Encore.
  50. 50. Forms
  51. 51. Create a form $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Create Post']) ->getForm(); return $this->render('default/new.html.twig', [ 'form' => $form->createView(), ]);
  52. 52. Forms in Twig {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} You can customise a lot the rendering of every widget, If you need to.
  53. 53. Handle submission $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, ['label' => 'Create Task']) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($task); $em->flush(); return $this->redirectToRoute('task_success'); } return $this->render('default/new.html.twig', [ 'form' => $form->createView(), ]);
  54. 54. Forms in their own classes class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) ->add('save', SubmitType::class); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => Task::class, )); } }
  55. 55. Events
  56. 56. Event and EventListener Event EventListener dispatch
  57. 57. Event class SaleEvent extends Event { const NAME = 'sale.created'; protected $sale; protected $movie; protected $numTickets; public function __construct(Sale $sale, Movie $movie, int $numTickets) { $this->sale = $sale; $this->movie = $movie; $this->numTickets = $numTickets; } public function getSale() { return $this->sale; } public function getMovie() { return $this->movie; } public function getNumTickets() { return $this->numTickets; } } A bag of parameters that describe the event
  58. 58. Dispatch Events $this->get('event_dispatcher') ->dispatch( SaleEvent::NAME, new SaleEvent($sale, $movie, $numTickets) );
  59. 59. EventListener Processes the event class SaleListener { public function __construct(EntityManager $em) { $this->em = $em; } public function onSaleCreated(SaleEvent $event) { for ($i = 0; $i < $event->getNumTickets(); $i++) { $ticket = new Ticket(); $ticket->setMovie($event->getMovie()); $ticket->setSale($event->getSale()); $ticket->setRow(1); $ticket->setSeat(1); $this->em->persist($ticket); } } }
  60. 60. Services
  61. 61. Services A service is an object that does something: • Generate a thumbnail. • Generate a PDF. • A query to ElasticSearch. • Log something. • Send an email.
  62. 62. Example class SaleLogger { private $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function log(Sale $sale) { $this->logger->info('Sold ' . count($sale->getTickets()) . ' tickets to ' . $sale->getFullName()); } } Where does this come from?
  63. 63. Using services public function listAction(SaleLogger $saleLogger) { //… $saleLogger->log($sale); } Is injected with dependencies
  64. 64. Explicit configuration # app/config/services.yml services: # explicitly configure the service AppLoggerSaleLogger: arguments: $logger: '@monolog.logger.request'
  65. 65. Tagged services $taggedServices = $container->findTaggedServiceIds(‘app.my_tag’); foreach ($taggedServices as $id => $tags) { $definition->addMethodCall('callAMethod', array(new Reference($id))); } AppMyServices: resource: '../src/MyServices' tags: ['app.my_tag']
  66. 66. Validation
  67. 67. Built in assertions /** * @ORMColumn(type="string") * @AssertLength(min=2) */ private $fullName; /** * @ORMColumn(type="string") * @AssertEmail() */ private $email; /** * @ORMColumn(type="text") * @AssertLength(max=100) */ private $question;
  68. 68. Built in assertions There are 47 assertions From NotNull to Isbn And we can write our own assertions
  69. 69. Use validators if ($form->isValid()) {} $author = new Author(); $validator = $this->get('validator'); $errors = $validator->validate($author); Dependency container
  70. 70. Standalone use $email = ‘obama@usa.gov’; $emailConstraint = new AssertEmail(); $emailConstraint->message = 'Invalid email address'; $errorList = $this->get('validator')->validate( $email, $emailConstraint );
  71. 71. Creating new constraints Constraint Validator Validate
  72. 72. Constraint class HasAvailableSeats extends Constraint { public $message = 'No {{ number }} available seats’; protected $movie; public function __construct($options) { $this->movie = $options['movie']; } public function getMovie() { return $this->movie; } public function validatedBy() { return get_class($this).'Validator'; } } No logic, data that describes the constraint Who validates this?
  73. 73. class HasAvailableSeatsValidator extends ConstraintValidator { public function validate($value, Constraint $constraint) { $available = $constraint->getMovie()->getAvailableSeats(); if ($value > $available) { $this->context->buildViolation($constraint->message) ->setParameter('{{ number }}', $value) ->addViolation(); } } } Validator Validation logic
  74. 74. APIs
  75. 75. Up to level 2 Good use of HTTP verbs (GET, POST, PUT, DELETE, PATCH…) Structure based on resources (/movies, /movies/31). Use of HTTP Status codes (200, 201, 406, …). Use of representations (JSON, XML, …).
  76. 76. Serializer: the idea $recipe = new Recipe(); $recipe->setName($content['name']); $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); Request->Our object (manual deserialization) $responseData = [    'id' => $recipe->getId(),    'name' => $recipe->getName(),    'energy' => $recipe->getEnergy(),    'servings' => $recipe->getServings(),    ]; $response = new JsonResponse($responseData, 201); Our object->Request (manual serialization) Tedious!
  77. 77. $response = new Response($serializer->serialize($recipe, 'json'), 201); $responseData = [    'id' => $recipe->getId(),    'name' => $recipe->getName(),    'energy' => $recipe->getEnergy(),    'servings' => $recipe->getServings(),    ]; $response = new JsonResponse($responseData, 201); Our object->Request (manual serialization) Serialize
  78. 78. $recipe = $serializer->deserialize($content, Recipe::class, 'json'); $recipe = new Recipe(); $recipe->setName($content['name']); $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); Request->Our object (manual deserialization) Deserialize
  79. 79. Serializer
  80. 80. Representation in API != DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", avatar: "avatar.jpg", twitter_handler: "vicqr", profile: { id: 19, bio: "My bio." } } I want this to be “username” I don’t want to expose it! Only in profile, not in list We want to prepend “thumb_” Only in version 2 of the API I’d like this to be bio:”My bio” We do this in the normalizer
  81. 81. Annotations MaxDepth • Detect and limit the serialization depth • Especially useful when serializing large trees Groups • Sometimes, you want to serialize different sets of attributes from your entities • Groups are a handy way to achieve this need
  82. 82. API Platform makes it very easy
  83. 83. Admin Panel
  84. 84. Admin on REST with API Platform
  85. 85. Sonata Admin
  86. 86. Easy Admin
  87. 87. Thanks! @nacmartin nacho@limenius.com @vicqr victoria@limenius.com

×