Mathias Arlaud
Co-Founder & COO @Bakslash - Co-Founder & CTO @Synegram
Mathias Arlaud
@matarld
@matarldmtarld@bakslahHQ
Mathias Arlaud
@matarld
@matarld
Figure 1. A regular AFUP Day conference room
@AFUP_Lyon
Wow!
This is
so nice
Figure 2. A conference room full of Mathias
Hello?
@matarld
@AFUP_Lyon
Figure 3. Where to find me during conferences
@matarld
@AFUP_Lyon
Try $i++
instead
Figure 4. Last two years of my free time
$i++
@matarld
@AFUP_Lyon
Figure 5. Expected vs actual project trajectory
a faster serializer
@matarld
@AFUP_Lyon
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
@AFUP_Lyon
https://symfony.com/doc/current/components/serializer.html
@matarld
@AFUP_Lyon
{
  "name":"Axel",
  "color":"Red",
  "age":2
}array(3) {
  ["name"]=> string(4) "Axel"
  ["color"]=> string(3) "Red"
  ["age"]=> int(2)
}object(Cat)#1 (3) {
  ["name"]=> string(4) "Axel"
  ["color"]=> string(3) "Red"
  ["age"]=> int(2)
}@matarld
@AFUP_Lyon
Metadata is data
, but about data
@matarld
@AFUP_Lyon
data
metadata
"thing"
"animal"
"cat"
"flying cat"
@matarld
@AFUP_Lyon
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
@AFUP_Lyon
interface PropertyTypeExtractorInterface
{
    /**
     * @return Type[]|null
     */
    public function getTypes(...): ?array;
}@matarld
@AFUP_Lyon
interface PropertyTypeExtractorInterface
{
    /**
     * @return Type[]|null
     */
    public function getTypes(...): ?array;
}only properties
no union/intersection difference
no list/dict difference
no generics
...
@matarld
@AFUP_Lyon
Figure 6. Forum PHP 2023 unequivocal evidence
@matarld
@AFUP_Lyon
@matarld
@AFUP_Lyon
ft. @Korbeil_
/**
 * @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
TypeInfo
@matarld
@AFUP_Lyon
@matarld
@AFUP_Lyon
$resolver = TypeResolver::create();
$resolver->resolve($reflectionClass->getProperty('isFlying'));
$resolver->resolve('list<int>');@matarld
@AFUP_Lyon
Type::nullable(Type::list(Type::int()));
Type::intersection(
  Type::object(\Stringable::class),
  Type::object(\Traversable::class),
);
Type::generic(
  Type::object(Collection::class),
  Type::object(User::class),
);@matarld
@AFUP_Lyon
@matarld
@AFUP_Lyon
AbstractNormalizer
allows attributes
instantiates objects
AbstractObjectNormalizer
normalization
denormalization
validation
ObjectNormalizer
extracts attributes
BC policy
@matarld
@AFUP_Lyon
Reflection is slow
Improved over PHP versions, but still...
Lot of caching to improve things
@matarld
@AFUP_Lyon
Huge collection of objects
Super big array
array
@matarld
@AFUP_Lyon
Huge collection of objects
array
@matarld
@AFUP_Lyon
Figure 7. API Platform conference 2023 evidence
@matarld
@AFUP_Lyon
@matarld
@AFUP_Lyon
PHP
metadata
Cat::class
class Cat
{
  public string $name;
  public bool $flying;
}
// ...
$this->jsonEncoder->encode($cat);Possible JSON
{
  "name": "any_string",
  "flying: true|false
}@matarld
@AFUP_Lyon
Encoder
Possible JSON
[  
  {
    "name": "any_string",
    "flying: true|false
  },
  {
    "name": "any_string",
    "flying: true|false
  }
]
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
@AFUP_Lyon
I want to serialize a cat!
Does an encoder exist?
/\_____/\ / o o \ ( == ^ == ) ) ( ( ) ( ( ) ( ) ) (__(__)___(__)__)
Encode the cat
Yep 👌
Compute the cat JSON shape
Generate and store the encoder
No
@matarld
@AFUP_Lyon
Read only needed
JSON part lazily
Blazing fast!
Flat memory usage
Stream ready
Generics ready
Simple API
Edit data on-the-fly
__ o-''|\_____/) \_/|_) ) \ __ / (_/ (_/
@matarld
@AFUP_Lyon
composer req jolicode/automapper__ o-''|\_____/) \_/|_) ) \ __ / (_/ (_/
@matarld
@AFUP_Lyon
use AutoMapper\Attribute as Mapper;
final class Cat
{
  #[MapTo(
    target: Dog::class,
    property: 'barkVolume',
  )]
  public int $meowVolume;
}
final class Dog
{
  public int $barkVolume;
}@matarld
@AFUP_Lyon
barkVolume
meowVolume
use AutoMapper\Attribute as Mapper;
final class Cat
{
  #[MapTo(
    target: Dog::class,
    transformer: [self::class, 'toDogAge'],
  )]
  public int $age;
  
  public static function toDogAge(int $value, Source $source, array $context): int
  {
    return floor($value * 1.3);
  }
}@matarld
@AFUP_Lyon
final class Cat
{
  #[MapTo(
    target: Dog::class,
    transformer: CatToDogAgeTransformer::class,
  )]
  public int $age;
}
class CatToDogAgeTransformer implements PropertyTransformerInterface
{
  public function __construct(
    private AgeConverter $ageConverter,
  ) {}
  public function transform(int $value, object|array $source, array $context): mixed
  {
    return $this->ageConverter->fromCatToDog($value);
  }
}@matarld
@AFUP_Lyon
Deserialization
Serializer
JsonEncoder
JsonEncoder + Automapper
@matarld
@AFUP_Lyon
JsonEncoder
Serializer
JsonEncoder + Automapper
Serialization
Mathias Arlaud
@matarld
👍
👎
I want to convert a cat to a dog!
Does a mapper exist?
__ o-''|\_____/) \_/|_) ) \ __ / (_/ (_/
Convert the cat
Yep 👌
Fetch the metadata
Generate and store the mapper
No
@matarld
@AFUP_Lyon
use Symfony\Component\Serializer\SerializerInterface;
final readonly class MyService
{
  public function __construct(
    private SerializerInterface $serializer,
  ) {
  }
}@matarld
@AFUP_Lyon
// config/bundles.php
return [
  // ...
  Mtarld\JsonEncoderBundle\JsonEncoderBundle::class => ['all' => true],
  AutoMapper\Bundle\AutoMapperBundle::class => ['all' => true],
  TurboSerializer\TurboSerializerBundle::class => ['all' => true],
];
composer require korbeil/turbo-serializer@matarld
@AFUP_Lyon
final readonly class ApiController
{
  public function __construct(
    private SerializerInterface $turboSerializer,
  ) {
  }
  #[Route('/api/cat-but-dogs')]
  public function get(): Response
  {
    $cats = $this->callCats();
    
    return new Response($this->turboSerializer->serialize(
      $cats, 
      'json',
      [Serializer::NORMALIZED_TYPE => Type::list(Type::object(Dog::class))]
    ));
  }
}@matarld
@AFUP_Lyon
By Mathias Arlaud