Symfony 4
Workshop
Victoria Quirante - @vicqr
Nacho Martin - @nacmartin
Notes: http://symfony4.limenius.com/
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
Symfony 4
We are here
Symfony Evolution
Symfony 12005 A monolith (very different)
Symfony 22011 Components appear
Components used in popular projects
Doctrine
Silex
Laravel
Symfony Evolution
Symfony 12005 A monolith (very different)
Symfony 22011 Components appear
Symfony 32015 Brief intermediate step
Symfony 42017 Compose your apps
Symfony 2/3
SF 2
Web app
SF 2
API
SF 2
Microservice
SF 4
Web app
SF4
API
SF4
Microservice
Symfony 4
Symfony 4
SF4 SF 4
Web app
SF4
API
SF4
Microservice
We need something to smooth
out these transformations
Symfony Flex
Symfony Flex is a tool to implement
Symfony 4 philosophy
It is a composer plugin that comes with
Symfony 4
The idea is automation to the max
when installing and configuring packages
Modifies the behaviour of the
require and update commands
Allows Symfony to perform tasks before
or after the composer commands
Symfony 4
Your application
with Symfony Flex
composer req mailer
Symfony Flex
Server
Recipe?
No
Regular install
With composer
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
http://symfony.sh
Directory Structure
my-project/
├── config/
│   ├── bundles.php
│   ├── packages/
│   ├── routes.yaml
│   └── services.yaml
├── public/
│   └── index.php
├── src/
│   ├── ...
│   └── Kernel.php
├── templates/
└── vendor/
Doctrine
Entity
Entity
namespace AppEntity;
class Movie
{
private $id;
private $name;
private $director;
private $year;
private $picture;
}
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;
}
Entity
Code that has to do with the model itself.
Doesn’t depend on services, or query the DB.
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/
Entity Manager
Entity Manager
$em = $this->getDoctrine()->getManager();
$em->getRepository(Movie::class)->find($movieId);
$em = $this->getDoctrine()->getManager();
$em->persist($sale);
$em->flush();
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();
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();
Entity Manager
Code that deals with retrieving or persisting
entities.
Fixtures

With Alice
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();
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
Relationships
appentitymovie:
actor{1..10}:
#…
movie{1..10}:
#...
actors: ["@actor<numberBetween(1, 3)>", "@actor<numberBetween(4, 6)>"]
Twig
Twig
{{ }}
{% %}
Display Data
Define Structures
Access variables
{{ foo }}
{{ foo.bar }}
{{ foo['bar'] }}
- 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
{% for user in users %}
{{ user.username }}
{% endfor %}
For each
{% for i in 0..10 %}
{{ i }}
{% endfor %}
For
For … else
{% for item in items %}
{{ item }}
{% else %}
No items.
{% endfor %}
<html><head>...</head>
<body>
<h1>
{% block title %}{% endblock %}
</h1>
{% block body %}{% endblock %}
</body></html>
Template inheritance
templates/layout.html.twig
{% extends "layout.twig" %}
{% block title %}
Home
{% endblock %}
{% block body %}
Lorem ipsum...
{% endblock %}
Template inheritance
templates/home.html.twig
{% extends "layout.twig" %}
{% block title %}
Movie list
{% endblock %}
{% block body %}
Duis aute irure dolor in..
{% endblock %}
Template inheritance
templates/movies.html.twig
Webpack

Encore
Webpack
Webpack
The standard nowadays.
Very powerful.
Steep learning curve.
Cons
Pros
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.
Forms
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(),
]);
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.
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(),
]);
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,
));
}
}
Events
Event and EventListener
Event EventListener
dispatch
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
Dispatch Events
$this->get('event_dispatcher')
->dispatch(
SaleEvent::NAME,
new SaleEvent($sale, $movie, $numTickets)
);
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);
}
}
}
Services
Services
A service is an object that does something:
• Generate a thumbnail.
• Generate a PDF.
• A query to ElasticSearch.
• Log something.
• Send an email.
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?
Using services
public function listAction(SaleLogger $saleLogger)
{
//…
$saleLogger->log($sale);
}
Is injected with dependencies
Explicit configuration
# app/config/services.yml
services:
# explicitly configure the service
AppLoggerSaleLogger:
arguments:
$logger: '@monolog.logger.request'
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']
Validation
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;
Built in assertions
There are 47 assertions
From NotNull to Isbn
And we can write our
own assertions
Use validators
if ($form->isValid()) {}
$author = new Author();
$validator = $this->get('validator');
$errors = $validator->validate($author);
Dependency container
Standalone use
$email = ‘obama@usa.gov’;
$emailConstraint = new AssertEmail();
$emailConstraint->message = 'Invalid email address';
$errorList = $this->get('validator')->validate(
$email,
$emailConstraint
);
Creating new constraints
Constraint Validator
Validate
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?
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
APIs
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, …).
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!
$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
$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
Serializer
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
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
API Platform makes it very easy
Admin Panel
Admin on REST with API Platform
Sonata Admin
Easy Admin
Thanks! @nacmartin
nacho@limenius.com
@vicqr
victoria@limenius.com

Symfony 4 Workshop - Limenius

  • 1.
    Symfony 4 Workshop Victoria Quirante- @vicqr Nacho Martin - @nacmartin Notes: http://symfony4.limenius.com/
  • 2.
    Nacho Martín @nacmartin [email protected] Victoria Quirante @vicqr [email protected] Webuild tailor-made projects with Symfony and React We have been working with Symfony since 2008 We provide development, consulting and training
  • 3.
  • 4.
  • 5.
    Symfony Evolution Symfony 12005A monolith (very different) Symfony 22011 Components appear
  • 6.
    Components used inpopular projects
  • 7.
  • 8.
  • 9.
  • 10.
    Symfony Evolution Symfony 12005A monolith (very different) Symfony 22011 Components appear Symfony 32015 Brief intermediate step Symfony 42017 Compose your apps
  • 11.
    Symfony 2/3 SF 2 Webapp SF 2 API SF 2 Microservice SF 4 Web app SF4 API SF4 Microservice Symfony 4
  • 12.
    Symfony 4 SF4 SF4 Web app SF4 API SF4 Microservice We need something to smooth out these transformations
  • 13.
  • 14.
    Symfony Flex isa tool to implement Symfony 4 philosophy
  • 15.
    It is acomposer plugin that comes with Symfony 4 The idea is automation to the max when installing and configuring packages
  • 16.
    Modifies the behaviourof the require and update commands Allows Symfony to perform tasks before or after the composer commands
  • 17.
    Symfony 4 Your application withSymfony Flex composer req mailer Symfony Flex Server Recipe? No Regular install With composer
  • 18.
    Symfony 4 Your application withSymfony 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.
  • 20.
    Directory Structure my-project/ ├── config/ │  ├── bundles.php │   ├── packages/ │   ├── routes.yaml │   └── services.yaml ├── public/ │   └── index.php ├── src/ │   ├── ... │   └── Kernel.php ├── templates/ └── vendor/
  • 21.
  • 22.
  • 23.
    Entity namespace AppEntity; class Movie { private$id; private $name; private $director; private $year; private $picture; }
  • 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.
    Entity Code that hasto do with the model itself. Doesn’t depend on services, or query the DB.
  • 26.
    Accessors In this workshopwe 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.
  • 28.
    Entity Manager $em =$this->getDoctrine()->getManager(); $em->getRepository(Movie::class)->find($movieId); $em = $this->getDoctrine()->getManager(); $em->persist($sale); $em->flush();
  • 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.
    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.
    Entity Manager Code thatdeals with retrieving or persisting entities.
  • 32.
  • 33.
    We could dothis for ($i = 0; $i < 10; $i ++) { $movie = new Movie(); $movie->setYear(1994); $movie->setName('Pulp Fiction'.$i); //... $em->persist($movie); } $em->flush();
  • 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.
  • 36.
  • 37.
    Twig {{ }} {% %} DisplayData Define Structures
  • 38.
    Access variables {{ foo}} {{ foo.bar }} {{ foo['bar'] }}
  • 39.
    - Checks iffoo 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.
    {% for userin users %} {{ user.username }} {% endfor %} For each
  • 41.
    {% for iin 0..10 %} {{ i }} {% endfor %} For
  • 42.
    For … else {%for item in items %} {{ item }} {% else %} No items. {% endfor %}
  • 43.
    <html><head>...</head> <body> <h1> {% block title%}{% endblock %} </h1> {% block body %}{% endblock %} </body></html> Template inheritance templates/layout.html.twig
  • 44.
    {% extends "layout.twig"%} {% block title %} Home {% endblock %} {% block body %} Lorem ipsum... {% endblock %} Template inheritance templates/home.html.twig
  • 45.
    {% extends "layout.twig"%} {% block title %} Movie list {% endblock %} {% block body %} Duis aute irure dolor in.. {% endblock %} Template inheritance templates/movies.html.twig
  • 46.
  • 47.
  • 48.
    Webpack The standard nowadays. Verypowerful. Steep learning curve. Cons Pros
  • 49.
    Webpack Encore A wrapperaround 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.
  • 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.
    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.
    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.
    Forms in theirown 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.
  • 56.
    Event and EventListener EventEventListener dispatch
  • 57.
    Event class SaleEvent extendsEvent { 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.
  • 59.
    EventListener Processes the event classSaleListener { 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.
  • 61.
    Services A service isan object that does something: • Generate a thumbnail. • Generate a PDF. • A query to ElasticSearch. • Log something. • Send an email.
  • 62.
    Example class SaleLogger { private $logger; publicfunction __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.
    Using services public functionlistAction(SaleLogger $saleLogger) { //… $saleLogger->log($sale); } Is injected with dependencies
  • 64.
    Explicit configuration # app/config/services.yml services: #explicitly configure the service AppLoggerSaleLogger: arguments: $logger: '@monolog.logger.request'
  • 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.
  • 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.
    Built in assertions Thereare 47 assertions From NotNull to Isbn And we can write our own assertions
  • 69.
    Use validators if ($form->isValid()){} $author = new Author(); $validator = $this->get('validator'); $errors = $validator->validate($author); Dependency container
  • 70.
    Standalone use $email =[email protected]’; $emailConstraint = new AssertEmail(); $emailConstraint->message = 'Invalid email address'; $errorList = $this->get('validator')->validate( $email, $emailConstraint );
  • 71.
  • 72.
    Constraint class HasAvailableSeats extendsConstraint { 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.
    class HasAvailableSeatsValidator extendsConstraintValidator { 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.
  • 76.
    Up to level2 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, …).
  • 77.
    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!
  • 78.
    $response = newResponse($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
  • 79.
    $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
  • 80.
  • 81.
    Representation in API!= DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "[email protected]", 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
  • 82.
    Annotations MaxDepth • Detect andlimit 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
  • 83.
    API Platform makesit very easy
  • 84.
  • 85.
    Admin on RESTwith API Platform
  • 86.
  • 87.
  • 88.