Mathias Arlaud
Co-Founder & COO @Bakslash - Co-Founder & CTO @Synegram
@matarld
mtarld
les-tilleuls.coop
@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
https://symfony.com/doc/current/components/serializer.html
@matarld
#[ApiResource]
class Robot
{
public int $id;
public string $name;
public string $mission;
public string $unofficialMission;
}
@matarld
#[ApiResource]
class Robot
{
public int $id;
public string $name;
public string $mission;
public string $unofficialMission;
}
{
"id": 1,
"name": "Persévérance",
"mission": "Find life on Mars",
"unofficialMission": "Wipe out all life on Mars"
}
> curl /api/robots/1.json
@matarld
#[ApiResource]
class Robot
{
public int $id;
public string $name;
public string $mission;
public string $unofficialMission;
}
{
"id": 1,
"name": "Persévérance",
"mission": "Find life on Mars",
"unofficialMission": "Wipe out all life on Mars"
}
> curl /api/robots/1.json
@matarld
#[ApiResource]
class Robot
{
public int $id;
public string $name;
public string $mission;
#[Ignore]
public string $unofficialMission;
}
@matarld
#[ApiResource]
class Robot
{
public int $id;
public string $name;
public string $mission;
#[ApiProperty(readable: false, writable: false)]
public string $unofficialMission;
}
@matarld
#[ApiResource]
class Robot
{
// ...
#[ApiProperty(readable: false, writable: false)]
public string $unofficialMission;
}
#[ApiResource]
class Robot
{
// ...
#[Ignore]
public string $unofficialMission;
}
> curl /api/robots/1.json
> curl /api/robots.json
...
Groups!
@matarld
public function serialize($data, string $format, array $context = []);
@matarld
class Robot
{
#[Groups(['group-one', 'group-two'])]
public string $name;
#[Groups(['group-one'])]
public string $mission;
public string $unofficialMission;
}
@matarld
class Robot
{
#[Groups(['group-one', 'group-two'])]
public string $name;
#[Groups(['group-one'])]
public string $mission;
public string $unofficialMission;
}
serialize(..., ['groups' => ['group-one']]);
serialize(..., ['groups' => ['group-two']]);
{"name": "foo", "mission": "bar"}
{"name": "foo"}
@matarld
> curl /api/robots/1.json
['item']
> curl /api/robots.json
['list']
#[ApiResource(
new Get(
normalizationContext: ['groups' => ['item']],
),
new GetCollection(
normalizationContext: ['groups' => ['list']],
),
)]
@matarld
> curl /api/robots/1.json
SerializeListener
ContextBuilder, Serializer
(RespondListener)
Yep, I know it!
ViewEvent
Does anyone know how to convert a to a ?
Controller
Response
@matarld
#[ApiResource(
normalizationContext: ['groups' => ['read']],
)]
class Robot
{
#[Groups(['read'])]
public string $name;
#[Groups(['read'])]
public string $mission;
#[Groups(['secret_service'])]
public string $unofficialMission;
}
@matarld
SerializeListener
ContextBuilder
interface SerializerContextBuilderInterface {
public function createFromRequest(
Request $request,
bool $normalization,
?array $attributes = null,
): array;
}
Serializer
@matarld
class SecretServiceContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(...): array
{
// $this->decorated <=> 'api_platform.serializer.context_builder' service
$context = $this->decorated->createFromRequest(...);
if (Robot::class === ($context['resource_class'] ?? null)
&& $this->authorizationChecker->isGranted('ROLE_SECRET_SERVICE')
) {
$context['groups'][] = 'secret_service';
}
return $context;
}
}
Context used by the serializer
Custom logic
API Platform generated context
@matarld
ExpressionLanguage
user, object, is_granted, ...
#[ApiResource(normalizationContext: ['groups' => ['read']])]
class Robot
{
#[Groups(['read'])]
public string $unofficialMission;
}
#[ApiProperty(
security: 'is_granted("ROLE_SECRET_SERVICE")',
)]
@matarld
#[ApiResource(normalizationContext: ['groups' => ['read']])]
class Robot {
#[Groups(['read'])]
public string $name;
#[Groups(['creator'])]
public string $mentalHealth;
public User $creator;
}
@matarld
SerializeListener
ContextBuilder
interface NormalizerInterface
{
public function normalize(
$data,
string $format = null,
array $context = []
);
public function supportsNormalization(
$data,
string $format = null,
array $context = []
): bool;
}
Serializer
@matarld
class RobotNormalizer implements NormalizerInterface
{
public function normalize(...)
{
if ($this->security->getUser() === $data->creator) {
$context['groups'][] = 'creator';
}
return $this->normalizer->normalize($data, $format, $context);
}
public function supportsNormalization(...): bool
{
return ... && $data instanceof Robot;
}
}
Scope the normalizer
Custom logic
Regular normalization
@matarld
#[ApiResource(normalizationContext: ['groups' => ['read']])]
class Robot
{
#[Groups(['read'])]
public string $unofficialMission;
}
#[ApiProperty(
security: 'object.creator == user',
)]
@matarld
#[ApiResource(
new Get(
normalizationContext: ['groups' => ['read']],
),
new GetCollection(
normalizationContext: ['groups' => ['read']],
),
)]
class Robot {}
#[ApiResource(
new Get(
normalizationContext: ['groups' => ['read']],
),
new GetCollection(
normalizationContext: ['groups' => ['read']],
),
)]
class Datasheet {}
@matarld
#[ApiResource(
normalizationContext: ['groups' => ['read']],
)]
class Datasheet {}
#[ApiResource(
normalizationContext: ['groups' => ['read']],
)]
class Robot {}
@matarld
#[ApiResource]
class Datasheet {}
#[ApiResource]
class Robot {}
api_platform:
defaults:
normalizationContext:
groups: ["read"]
# ...
@matarld
#[ApiResource]
class Robot {
#[Groups(['read'])]
public Datasheet $datasheet;
}
#[ApiResource]
class Datasheet {
#[Groups(['read'])]
public string $reference;
#[Groups(['read'])]
public array $specs;
}
{
"datasheet": {
"reference": "PE-01",
"specs": ["lot of data", "..."]
}
}
@matarld
#[ApiResource(
normalizationContext: ['groups' => ['robot:read']],
)]
class Robot {
#[Groups(['robot:read'])]
public Datasheet $datasheet;
}
#[ApiResource(
normalizationContext: ['groups' => ['datasheet:read']],
)]
class Datasheet {
#[Groups(['datasheet:read', 'robot:read'])]
public string $reference;
#[Groups(['datasheet:read'])]
public array $specs;
}
{
"datasheet": {
"reference": "PE-01"
}
}
@matarld
ResourceMetadataCollectionFactory
App\Entity\Robot
SerializeListener
Serializer
ResourceMetadataCollection
ContextBuilder
Operation
@matarld
class GroupResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
public function create(string $resourceClass): ResourceMetadataCollection
{
$metadata = $this->decorated->create($resourceClass);
foreach ($resources as $i => $resource) {
$metadata[$i] = $resource->withOperations($this->addGroupsToOperations($resource));
}
return $metadata;
}
// Return operations with dynamic groups (eg: robot:read, robot:list, or robot:item).
private function addGroupsToOperations(ApiResource $metadata): ApiResource {}
}
@matarld
SerializeListener
Serializer
ContextBuilder
#[ApiResource]
class Robot {
#[Groups(['robot:read'])]
public Datasheet $datasheet;
}
#[ApiResource]
class Datasheet {
#[Groups(['datasheet:read', 'robot:read'])]
public string $reference;
#[Groups(['datasheet:read'])]
public array $specs;
}
@matarld
ViewEvent
ContextBuilder
By resource type By operation By request
Defaults
By operation Documentation friendly
ResourceMetadata
By resource type By operation Documentation friendly
$context
@matarld
ViewEvent
Serializer
By context By resource instance
$context, $data
{"..."}
@matarld
class Robot
{
public string $name;
public string $mission;
public int $battery;
public Datasheet $datasheet;
}
class Astronaut
{
public string $name;
public string $task;
public bool $hasOxygen;
}
@matarld
#[ApiResource(
operations: [
new Get(),
]
)]
class Robot {}
#[ApiResource(
operations: [
new GetCollection(
uriTemplate: '/robots',
provider: AstronautsProvider::class,
),
]
)]
class Astronaut {}
@matarld
final class AstronautsProvider implements ProviderInterface
{
public function provide($operation, $uriVariables, $context): array
{
$robots = $this->robotRepository->findAll();
return array_map(static function (Robot $r): Astronaut {
$a = new Astronaut();
$a->name = $r->name;
$a->task = $r->mission;
$a->hasOxygen = $r->battery > 0;
return $a;
}, $robots);
}
@matarld
New resource
By resource type By operation Documentation friendly
By Mathias Arlaud