Mathias Arlaud
Co-Founder & COO @Bakslash - Co-Founder & CTO @Synegram
@matarld
@matarldmtarld@bakslahHQ
@matarld
@matarld
Representing data structures in a format that can be sent or persisted in order to be reconstructed later
Binary, textual
Construction pattern
Databases, flat files, APIs
Anywhere, interoperable
@matarld
GET /api/cats/1Accept: application/json {
  "name": "Undercover",
  "aggressivity": "high"
}
serialization
@matarldPOST /api/catsContent-Type: application/json {
  "name": "Black mustache",
  "aggressivity": "low"
}deserialization
@matarld
SomeProvider
AnotherProvider
...
GET /api/cats/1Accept: application/json Response
@matarld
SomeProvider
DeserializeProvider
...
POST /api/catsContent-Type: application/json {
  "name": "Black mustache",
  "aggressivity": "low"
}@matarld
@matarld
Just count mustachesCapitalize the nameEvaluate aggressivityDon't keep the id@matarld
Create "n" default mustachesSet claws size by aggressivityInstantiate a new cat@matarld
class Cat
{
  #[Ignore]
  public int $id;
  #[SerializedName('name')]
  private string $nickname;
  
  /** @var Mustache[] */
  public array $mustaches;
  
  public function getNickname(): string
  {
    return $this->name;
  }
}@matarld
class Cat
{
  #[Ignore]
  public int $id;
  #[SerializedName('name')]
  private string $nickname;
  
  /** @var Mustache[] */
  public array $mustaches;
  
  public function getNickname(): string
  {
    return $this->nickname;
  }
}Can I read $id?
No, you can ignore it.
@matarld
class Cat
{
  #[Ignore]
  public int $id;
  #[SerializedName('name')]
  private string $nickname;
  
  /** @var Mustache[] */
  public array $mustaches;
  
  public function getNickname(): string
  {
    return $this->nickname;
  }
}Can I read $nickname?
Yep, but use the getter to read it.
Change "nickname" to "name".
Do anything with the key?
Nothing in particular.
Do anything with the value?
@matarld
class Cat
{
  #[Ignore]
  public int $id;
  #[SerializedName('name')]
  private string $nickname;
  
  /** @var Mustache[] */
  public array $mustaches;
  
  public function getNickname(): string
  {
    return $this->nickname;
  }
}Can I read $mustaches?
Yes, you can.
Nothing in particular.
Do anything with the key?
Check if it's an object collection. If so, serialize objects of that collection as well.
Do anything with the value?
@matarld
class CatMustacheNormalizer implements NormalizerInterface
{
  public function normalize($object, $format, $context): array
  {
    $normalized = $this->normalizer->normalize($object, $format, $context);
    $normalized['mustaches'] = count($object->mustaches);
    
    return $normalized;
  }
  // ...
}@matarld
Cache
@matarld
( )
@matarld
Metadata is data
data, but about data
@matarld
@matarld
@matarld
@matarld
@matarld
data
/** @temlate T of string */
final class Rain {
  /** @return list<T> */
  public function content(): array {...}
}
/** @var Rain<'cat'|'dog'> $*/
$englishRain = new Rain();
$fallingStuff = $englishRain->content();type
no idea
array of something
list of something
list of cats and dogs
Possible JSON
[
  [0.7]
]
{
  "c": 1
}
true
{
  "a": 1
}[
  3.14
][
  "pi"
][ true ]
[
  3.14
][
  "dog"
][ "cat" ]
[
  "dog"
][
  "dog"
]@matarld
interface PropertyTypeExtractorInterface
{
    /**
     * @return Type[]|null
     */
    public function getTypes(...): ?array;
}@matarld
interface PropertyTypeExtractorInterface
{
    /**
     * @return Type[]|null
     */
    public function getTypes(...): ?array;
}only properties
no union/intersection difference
no list/dict difference
no generics
...
@matarld
/**
 * @template T of Animal
 */
final class FlyingAnimal
{
  public function __construct(
    public object $type,
    private int $speed,
  ) {
  }
  
  /**
   * @return class-string<T>
   */
  public function getTypeClass(): string
  {
    return $this->type::class;
  }
  
  public function setSpeed(int $speed): void
  {
  	$this->speed = $speed;
  }
}
PropertyInfo
@matarld
ft. @Korbeil_
@matarld
/**
 * @template T of Animal
 */
final class FlyingAnimal
{
  public function __construct(
    public object $type,
    private int $speed,
  ) {
  }
  
  /**
   * @return class-string<T>
   */
  public function getTypeClass(): string
  {
    return $this->type::class;
  }
  
  public function setSpeed(int $speed): void
  {
  	$this->speed = $speed;
  }
}
TypeInfo
@matarld
PropertyInfo
TypeInfo
@matarld
object
array
format
huge collection of objects
super big array
@matarld
format
huge collection of objects
Streaming!
@matarld
@matarld
class Cat
{
  public string $name;
  public bool $flying;
}PHP
Possible JSON
{
  "name": "any_string",
  "flying": true|false
}any string
true/false
@matarld
[
  {
    "name": "[
  {
    "name": "Undercover[
  {
    "name": "Undercover",
    "flying": [
  {
    "name": "Undercover",
    "flying": true[
  {
    "name": "Undercover",
    "flying": true
  },
  {
    "name": "[
  {
    "name": "Undercover",
    "flying": true
  },
  {
    "name": "Black mustache[
  {
    "name": "Undercover",
    "flying": true
  },
  {
    "name": "Black mustache",
    "flying": [
  {
    "name": "Undercover",
    "flying": true
  },
  {
    "name": "Black mustache",
    "flying": false[
  {
    "name": "Undercover",
    "flying": true
  },
  {
    "name": "Black mustache",
    "flying": false
  }
]in memory
in memory
@matarld
Encoder
return static function ($cats, $stream, $config) {
  $stream->write('[');
  $prefix_0 = '';
  foreach ($cats as $cat) {
    $stream->write($prefix_0);
    $stream->write('{"name":');
    $stream->write(json_encode($cat->name));
    $stream->write(',"flying":');
    $stream->write($cat->flying ? 'true' : 'false');
    $stream->write('}');
    $prefix_0 = ',';
  }
  $stream->write(']');
};@matarld
Does an encoder exist?
Yep 👌
Encode the cat
    /\_____/\
   /  o   o  \
  ( ==  ^  == )
   )         (
  (           )
 ( (  )   (  ) )
(__(__)___(__)__)Compute the cat JSON shape
Not yet 😬
Generate and store the encoder
@matarld
GET /api/cats/1Accept: application/json Response
@matarld
GET /api/cats/1Accept: application/json StreamedResponse
SerializeAndRespondProcessor
@matarld
Read only needed
JSON part lazily
Blazing fast!
Flat memory usage
Stream ready
Generics ready
Simple API
Edit data on-the-fly
@matarld
   __
o-''|\_____/)
 \_/|_)     )
    \  __  /
    (_/ (_/  @matarld
class Cat
{
  #[Groups('read')]
  public int $id;
  #[Groups(['read', 'write'])]
  public string $nickname;
  
  #[Groups(['admin'])]
  public bool $isAlive;
}-
read
write
admin
read + write
read + admin
write + admin
read + write + admin
@matarld
#[ORM\Entity]
#[ApiResource(
  normalizationContext: ['groups' => 'read'],
  denormalizationContext: ['groups' => 'write'],
)]
class Cat
{
  #[ORM\Id]
  #[ORM\GeneratedValue]
  #[ORM\Column]
  #[Groups('read')]
  public int $id;
  #[ORM\Column]
  #[SerializedName('name')]
  #[Groups(['read', 'write'])]
  public string $nickname;
}@matarld
#[ORM\Entity]
#[ApiResource(
  normalizationContext: ['groups' => 'read'],
  denormalizationContext: ['groups' => 'write'],
)]
class Cat
{
  #[ORM\Id]
  #[ORM\GeneratedValue]
  #[ORM\Column]
  #[Groups('read')]
  public int $id;
  #[ORM\Column]
  #[SerializedName('name')]
  #[Groups(['read', 'write'])]
  public string $nickname;
}@matarld
the takeaway
@matarld
#[ORM\Entity]
class CatEntity
{
  #[ORM\Id]
  #[ORM\GeneratedValue]
  #[ORM\Column]
  public int $id;
  #[ORM\Column]
  public string $nickname;
}#[Get]
class CatGetResource
{
  public int $id;
  public string $name;
}#[Post]
class CatPostResource
{
  public string $name;
}@matarld
class CatGetResourceProvider implements ProviderInterface
{
    public function __construct(
        #[Autowire(service: ItemProvider::class)]
        private ProviderInterface $provider
    ) {
    }
    public function provide(Operation $operation, array $uriVariables, array $context)
    {
        $catEntity = $this->provider->provide($operation, $uriVariables, $context);
        
        $catGetResource = new CatGetResource();
        $catGetResource->id = $catEntity->id;
        $catGetResource->name = ucfirst($catEntity->nickname);
        
        return $catGetResource;
    }
}@matarld
@matarld
#[Map(CatGetResource::class)]
class CatEntity
{
  #[Map(target: 'id')]
  public int $id;
  #[Map(
    target: 'name',
    transform: 'ucfirst',
  )]
  public string $nickname;
}#[Get]
class CatGetResource
{
  public int $id;
  public string $name;
}@matarld
class CatGetResourceProvider implements ProviderInterface
{
    public function __construct(
        #[Autowire(service: ItemProvider::class)]
        private ProviderInterface $provider,
        private ObjectMapperInterface $mapper,
    ) {
    }
    public function provide(Operation $operation, array $uriVariables, array $context)
    {
        return $this->mapper->map(
          source: $this->provider->provide($operation, $uriVariables, $context),
          target: CatGetResource::class,
        );
    }
}@matarld
JsonEncoder
+ Mapper
Serializer
@matarldJsonEncoder
+ Mapper
Serializer
@matarld
@matarld
@matarld
{
  "name": "Jose",
  "aggressivity": "none",
  "mustaches": 4
}normalization
encoding
@matarld
{
  "name": "Jose",
  "aggressivity": "none",
  "mustaches": 4
}decoding
denormalization
@matarld
@matarld
BC policy
AbstractNormalizer
AbstractObjectNormalizer
AbstractItemNormalizer
ItemNormalizer
By Mathias Arlaud