SlideShare a Scribd company logo
1 of 80
Download to read offline
France Télévisions
Leveraging API Platform
for our content APIs
2023 SERIES OF EVENT
New York
May 16&17
Australia
October 11&12
Singapore
April 12&13
Helsinki & North
June 5&6
Paris
SEPTEMBER
London
November
15&16
June 28-30
SILICON VALLEY
March 14&15
Dubai & Middle East
February 22&23
France Télévisions :
Leveraging API Platform
for our content APIs
@gknjockbot
01 - whoami
Georges-King
Strasbourg, Grand-Est, France
Software Engineer since 2009
Freelance IT Consultant
@gknjockbot
02 - France Télévisions
News Articles
TV Broadcasts
@gknjockbot
02 - France Télévisions
866 755 articles
29M visitors / month
860 770 articles
157M visitors / month
220 771 articles
11M visitors / month
@gknjockbot
03 - What are we trying to solve ?
Franceinfo needed rebuilding :
@gknjockbot
03 - What are we trying to solve ?
Franceinfo needed rebuilding :
● Front-Back coupling
@gknjockbot
03 - What are we trying to solve ?
Franceinfo needed rebuilding :
● Front-Back coupling
● Competing DB access
@gknjockbot
03 - What are we trying to solve ?
Franceinfo needed rebuilding :
● Front-Back coupling
● Competing DB access
● Obsolete Technology
@gknjockbot
03 - What are we trying to solve ?
Franceinfo needed rebuilding :
● Front-Back coupling
● Competing DB access
● Obsolete Technology
● Monolithic platform
@gknjockbot
04 - The proposed architecture
@gknjockbot
05 - Choosing an implementation
Proof Of Concept
MariaDB MongoDB ElasticSearch
PHP / Symfony Doctrine ORM Doctrine ODM -
Node.js / Koa Sequelize Mongoose Built In
@gknjockbot
05 - Choosing an implementation
Cons Pros
Node / ElasticSearch
Reindexing
Scalability
Performance
Resource consumption
Node / MariaDB
Performance
Sequelize
Simple
Node / MongoDB Reindexing
Performance
Mongoose
Symfony / MongoDB New DB
Unified stacks (Front/Back)
ODM / Framework
Symfony / MariaDB
Well known DB stack internally
Unified stacks (Front/Back)
ORM / Framework
@gknjockbot
05 - Choosing an implementation
@gknjockbot
05 - Choosing an implementation
@gknjockbot
06 - Towards a shared platform
@gknjockbot
06 - Towards a shared platform
@gknjockbot
06.1 - Introducing PIC - Plateforme d’Information Commune
@gknjockbot
06.2 - The challenges for PIC
Additional concerns :
● Must share editorial concepts
● Must share a generic codebase
● Must have dedicated deployments
@gknjockbot
06.2 - PIC platform high level overview
@gknjockbot
07 - The Tech Stack
@gknjockbot
08 - API Front
@gknjockbot
08 - API Front
API Call : GET /contents?taxonomy=les-jeux-olympiques/paris-2024
Endpoint : /contents
Filter : taxonomy=les-jeux-olympiques/paris-2024
Groups : default, content
@gknjockbot
08 - API Front
@gknjockbot
08 - API Front
API Call : GET /contents/<id>
Endpoint : /contents
Filter : N/A
Groups : default, content, media, taxonomy
@gknjockbot
08 - API Front / resource endpoints
class DirectTv extends Content
{
// ...
}
@gknjockbot
08 - API Front / resource endpoints
#[ORMEntity]
#[ORMTable(name: 'direct_tv' )]
class DirectTv extends Content
{
// ...
}
@gknjockbot
08 - API Front / resource endpoints
#[ApiResource (
collectionOperations: [
'list' => [
'method' => 'GET',
'normalization_context' => ['enable_max_depth' => true]
]],
itemOperations: [
'get' => [
'method' => 'GET'
]],
attributes: [
'order' => ['lastPublicationDate' => 'DESC'],
'filters' => ['snapshot.code' , 'media.type' ]
]
)]
#[ApiFilter(ProgramFilter ::class, properties: ['program.channel' ,
'program.type' ])]
#[ApiFilter(BeforeBeginDateFilter ::class)]
#[ApiFilter(OrderBeginDateFilter ::class)]
#[ORMEntity]
#[ORMTable(name: 'direct_tv' )]
class DirectTv extends Content
{
// ...
}
@gknjockbot
08 - API Front / resource endpoints
#[ApiResource (
collectionOperations: [
'list' => [
'method' => 'GET',
'normalization_context' => ['enable_max_depth' => true]
]],
itemOperations: [
'get' => [
'method' => 'GET'
]],
attributes: [
'order' => ['lastPublicationDate' => 'DESC'],
'filters' => ['snapshot.code' , 'media.type' ]
]
)]
#[ApiFilter(ProgramFilter ::class, properties: ['program.channel' ,
'program.type' ])]
#[ApiFilter(BeforeBeginDateFilter ::class)]
#[ApiFilter(OrderBeginDateFilter ::class)]
#[ORMEntity]
#[ORMTable(name: 'direct_tv' )]
class DirectTv extends Content
{
// ...
}
@gknjockbot
08 - API Front / resource payload
class DirectTv extends Content
{
private ?string $presentation = null;
private ?bool $isSubject = false;
public function getPresentation (): ?string
{
return $this->presentation ;
}
public function isIsSubject (): ?bool
{
return $this->isSubject;
}
}
@gknjockbot
08 - API Front / resource payload
class DirectTv extends Content
{
#[ORMColumn(type: 'text', nullable: true)]
private ?string $presentation = null;
#[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])]
private ?bool $isSubject = false;
public function getPresentation (): ?string
{
return $this->presentation ;
}
public function isIsSubject (): ?bool
{
return $this->isSubject;
}
}
@gknjockbot
08 - API Front / resource payload
class DirectTv extends Content
{
#[Groups(['content:get:default' ])]
#[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])]
#[ORMColumn(type: 'text', nullable: true)]
private ?string $presentation = null;
#[Groups(['content:get:default' ])]
#[ApiProperty ()]
#[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])]
private ?bool $isSubject = false;
public function getPresentation (): ?string
{
return $this->presentation ;
}
public function isIsSubject (): ?bool
{
return $this->isSubject;
}
}
@gknjockbot
08 - API Front / resource payload
class DirectTv extends Content
{
#[Groups(['content:get:default' ])]
#[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])]
#[ORMColumn(type: 'text', nullable: true)]
private ?string $presentation = null;
#[Groups(['content:get:default' ])]
#[ApiProperty ()]
#[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])]
private ?bool $isSubject = false;
public function getPresentation (): ?string
{
return $this->presentation ;
}
public function isIsSubject (): ?bool
{
return $this->isSubject;
}
}
@gknjockbot
08 - API Front / resource payload
class DirectTv extends Content
{
#[Groups(['content:get:default' ])]
#[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])]
#[ORMColumn(type: 'text', nullable: true)]
private ?string $presentation = null;
#[Groups(['content:get:default' ])]
#[ApiProperty ()]
#[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])]
private ?bool $isSubject = false;
public function getPresentation (): ?string
{
return $this->presentation ;
}
public function isIsSubject (): ?bool
{
return $this->isSubject;
}
}
@gknjockbot
09 - API Back
@gknjockbot
09 - API Back
@gknjockbot
#[ApiResource ( /* ... */ )]
#[ApiFilter (GroupFilter ::class, arguments: [
'parameterName' => 'groups',
'overrideDefaultGroups' => true,
'whitelist' => ['indexation' , 'sdk', 'details']
])]
#[ApiFilter ( /* ... */ )]
abstract class Content implements Lockable, Loggable
{
// ...
}
09 - API Back
@gknjockbot
#[ApiResource ( /* ... */ )]
#[ApiFilter (GroupFilter ::class, arguments: [
'parameterName' => 'groups',
'overrideDefaultGroups' => true,
'whitelist' => ['indexation' , 'sdk', 'details']
])]
#[ApiFilter ( /* ... */ )]
abstract class Content implements Lockable, Loggable
{
// ...
}
09 - API Back
@gknjockbot
09 - API Back
#[ApiResource (
collectionOperations: [
'get',
'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ],
],
itemOperations: [
'get',
'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ],
],
attributes: ['order' => ['lastUpdateDate' => 'DESC']],
normalizationContext: ['groups' => ['resource']]
)]
#[ORMEntity]
#[ORMTable(name: 'article')]
class Article extends Content
{
/**
* Texte de l'article.
*/
#[ApiProperty ()]
#[ORMColumn(name: 'art_text', type: 'text', nullable: true)]
#[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )]
#[Groups(['article:*:default' ])]
private ?string $text = null;
@gknjockbot
09 - API Back
#[ApiResource (
collectionOperations: [
'get',
'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ],
],
itemOperations: [
'get',
'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ],
],
attributes: ['order' => ['lastUpdateDate' => 'DESC']],
normalizationContext: ['groups' => ['resource']]
)]
#[ORMEntity]
#[ORMTable(name: 'article')]
class Article extends Content
{
/**
* Texte de l'article.
*/
#[ApiProperty ()]
#[ORMColumn(name: 'art_text', type: 'text', nullable: true)]
#[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )]
#[Groups(['article:*:default' ])]
private ?string $text = null;
@gknjockbot
09 - API Back
#[ApiResource (
collectionOperations: [
'get',
'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ],
],
itemOperations: [
'get',
'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ],
],
attributes: ['order' => ['lastUpdateDate' => 'DESC']],
normalizationContext: ['groups' => ['resource']]
)]
#[ORMEntity]
#[ORMTable(name: 'article')]
class Article extends Content
{
/**
* Texte de l'article.
*/
#[ApiProperty ()]
#[ORMColumn(name: 'art_text', type: 'text', nullable: true)]
#[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )]
#[Groups(['article:*:default' ])]
private ?string $text = null;
@gknjockbot
09 - API Back
article : post : *
media : get : default
* : * : indexation
resource method group
@gknjockbot
09 - API Back
@gknjockbot
09 - API Back
# config/services.yaml
AppSerializerContextBuilder :
arguments:
$decorated: '@AppSerializerContextBuilder.inner'
decorates: api_platform.serializer.context_builder.filter
@gknjockbot
09 - API Back
final class ContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (empty($context[AbstractNormalizer::GROUPS])) {
return $context;
}
$context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? [];
$groups = $context[AbstractNormalizer::GROUPS];
$groups[] = 'default';
$operation = $this->extractOperation($extractedAttributes);
$context[AbstractNormalizer::GROUPS] = [];
foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) {
foreach ($groups as $group) {
$context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*';
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group;
}
}
$context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS]));
return $context;
}
@gknjockbot
09 - API Back
final class ContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (empty($context[AbstractNormalizer::GROUPS])) {
return $context;
}
$context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? [];
$groups = $context[AbstractNormalizer::GROUPS];
$groups[] = 'default';
$operation = $this->extractOperation($extractedAttributes);
$context[AbstractNormalizer::GROUPS] = [];
foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) {
foreach ($groups as $group) {
$context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*';
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group;
}
}
$context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS]));
return $context;
}
@gknjockbot
09 - API Back
final class ContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (empty($context[AbstractNormalizer::GROUPS])) {
return $context;
}
$context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? [];
$groups = $context[AbstractNormalizer::GROUPS];
$groups[] = 'default';
$operation = $this->extractOperation($extractedAttributes);
$context[AbstractNormalizer::GROUPS] = [];
foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) {
foreach ($groups as $group) {
$context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*';
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group;
}
}
$context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS]));
return $context;
}
@gknjockbot
09 - API Back
final class ContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (empty($context[AbstractNormalizer::GROUPS])) {
return $context;
}
$context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? [];
$groups = $context[AbstractNormalizer::GROUPS];
$groups[] = 'default';
$operation = $this->extractOperation($extractedAttributes);
$context[AbstractNormalizer::GROUPS] = [];
foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) {
foreach ($groups as $group) {
$context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group;
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*';
$context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group;
}
}
$context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS]));
return $context;
}
@gknjockbot
09 - API Back
@gknjockbot
09 - API Back
@gknjockbot
09 - API Back
# config/services.yaml
AppEventListenerCmsContentEntityListener :
tags:
- { name: 'doctrine.orm.entity_listener' ,
entity: AppEntityCmsContent,
event: postPersist,
entity_manager : edito }
- { name: 'doctrine.orm.entity_listener' ,
entity: AppEntityCmsContent,
event: postPersist,
method: postPersistPublish,
entity_manager : publish }
@gknjockbot
09 - API Back
class ContentEntityListener
{
public function postPersist (Content $content): void
{
$this->logger->logCreateEntity ($content);
$this->index($content, IndexerSubscriberAction ::ACTION_SAVE);
$this->registerForCacheInvalidation ($content, CacheInvalidatorInterface ::CONTEXT_EDITO);
}
public function postPersistPublish (Content $content): void
{
$this->logger->logPublishEntity ($content);
$this->index($content, IndexerSubscriberAction ::ACTION_PUBLISH);
$this->registerForCacheInvalidation ($content, CacheInvalidatorInterface ::CONTEXT_PUBLISH);
}
// ...
}
@gknjockbot
10 - API Mobile
@gknjockbot
10 - API Mobile
@gknjockbot
10 - API Mobile
@gknjockbot
10.1 - API Mobile | DataProvider
final class ContentItemDataProvider implements ItemDataProviderInterface,
SerializerAwareDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Content::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object
{
// Retrieve Content with all dependencies all at once: CHT, CHM.
$response = $this->apiFront->callContent($id);
if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) {
throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id));
}
$apiFrontContent = $this->getSerializer()
->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context);
$this->setContentsByRelateds($apiFrontContent, $context);
return $apiFrontContent;
}
@gknjockbot
10.1 - API Mobile | DataProvider
final class ContentItemDataProvider implements ItemDataProviderInterface,
SerializerAwareDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Content::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object
{
// Retrieve Content with all dependencies all at once: CHT, CHM.
$response = $this->apiFront->callContent($id);
if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) {
throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id));
}
$apiFrontContent = $this->getSerializer()
->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context);
$this->setContentsByRelateds($apiFrontContent, $context);
return $apiFrontContent;
}
@gknjockbot
10.1 - API Mobile | DataProvider
final class ContentItemDataProvider implements ItemDataProviderInterface,
SerializerAwareDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Content::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object
{
// Retrieve Content with all dependencies all at once: CHT, CHM.
$response = $this->apiFront->callContent($id);
if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) {
throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id));
}
$apiFrontContent = $this->getSerializer()
->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context);
$this->setContentsByRelateds($apiFrontContent, $context);
return $apiFrontContent;
}
@gknjockbot
10.1 - API Mobile | DataProvider
final class ContentItemDataProvider implements ItemDataProviderInterface,
SerializerAwareDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Content::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object
{
// Retrieve Content with all dependencies all at once: CHT, CHM.
$response = $this->apiFront->callContent($id);
if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) {
throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id));
}
$apiFrontContent = $this->getSerializer()
->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context);
$this->setContentsByRelateds($apiFrontContent, $context);
return $apiFrontContent;
}
@gknjockbot
10.1 - API Mobile | DataProvider
final class ContentItemDataProvider implements ItemDataProviderInterface,
SerializerAwareDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Content::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object
{
// Retrieve Content with all dependencies all at once: CHT, CHM.
$response = $this->apiFront->callContent($id);
if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) {
throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id));
}
$apiFrontContent = $this->getSerializer()
->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context);
$this->setContentsByRelateds($apiFrontContent, $context);
return $apiFrontContent;
}
@gknjockbot
10.2 - API Mobile | DataTransformer
class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface,
DataTransformerInterface
{
public function supportsTransformation ($data, string $to, array $context = []): bool
{
return $data instanceof ArticleInput && !$this->transformer ->isDigest($context);
}
public function transform($object, string $to, array $context = [])
{
$content = $this->decorated->transform($object, $to, $context);
$this->hydrateDetail ($content, $object, $context);
$richText = $this->parser->parse($object->getText());
$this->mergeRichTextHtml ($richText);
$this->populateRichTextEntity ($richText, $object, [
'operation_type' => 'collection'
]);
$content->setText($richText);
return $content;
}
// ...
}
@gknjockbot
10.2 - API Mobile | DataTransformer
class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface,
DataTransformerInterface
{
public function supportsTransformation ($data, string $to, array $context = []): bool
{
return $data instanceof ArticleInput && !$this->transformer ->isDigest($context);
}
public function transform($object, string $to, array $context = [])
{
$content = $this->decorated->transform($object, $to, $context);
$this->hydrateDetail ($content, $object, $context);
$richText = $this->parser->parse($object->getText());
$this->mergeRichTextHtml ($richText);
$this->populateRichTextEntity ($richText, $object, [
'operation_type' => 'collection'
]);
$content->setText($richText);
return $content;
}
// ...
}
@gknjockbot
10.2 - API Mobile | DataTransformer
class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface,
DataTransformerInterface
{
public function supportsTransformation ($data, string $to, array $context = []): bool
{
return $data instanceof ArticleInput && !$this->transformer ->isDigest($context);
}
public function transform($object, string $to, array $context = [])
{
$content = $this->decorated->transform($object, $to, $context);
$this->hydrateDetail ($content, $object, $context);
$richText = $this->parser->parse($object->getText());
$this->mergeRichTextHtml ($richText);
$this->populateRichTextEntity ($richText, $object, [
'operation_type' => 'collection'
]);
$content->setText($richText);
return $content;
}
// ...
}
@gknjockbot
10.2 - API Mobile | DataTransformer
class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface,
DataTransformerInterface
{
public function supportsTransformation ($data, string $to, array $context = []): bool
{
return $data instanceof ArticleInput && !$this->transformer ->isDigest($context);
}
public function transform($object, string $to, array $context = [])
{
$content = $this->decorated->transform($object, $to, $context);
$this->hydrateDetail ($content, $object, $context);
$richText = $this->parser->parse($object->getText());
$this->mergeRichTextHtml ($richText);
$this->populateRichTextEntity ($richText, $object, [
'operation_type' => 'collection'
]);
$content->setText($richText);
return $content;
}
// ...
}
@gknjockbot
10.2 - API Mobile | DataTransformer
class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface,
DataTransformerInterface
{
public function supportsTransformation ($data, string $to, array $context = []): bool
{
return $data instanceof ArticleInput && !$this->transformer ->isDigest($context);
}
public function transform($object, string $to, array $context = [])
{
$content = $this->decorated->transform($object, $to, $context);
$this->hydrateDetail ($content, $object, $context);
$richText = $this->parser->parse($object->getText());
$this->mergeRichTextHtml ($richText);
$this->populateRichTextEntity ($richText, $object, [
'operation_type' => 'collection'
]);
$content->setText($richText);
return $content;
}
// ...
}
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
● Backend : Varnish Cache Plus
● Tag-based : xkeys module
xkey pattern
{context}/{type}/{id}
publish/Cms-Article/1
edito/Cms-MediaImage/1
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
11 - HTTP Cache
@gknjockbot
12 - What about the future ?
@gknjockbot
12 - What about the future ?
UPGRADE :
● Standards/RFCs : compliance
● Symfony Framework : 5.4 → 6.x → ?
● API Platform : 2.7 → 3.x → ?
ENHANCE :
● Autogenerate API Front from API Back resource metadata
● ElasticSearch as backend for resource collections endpoints
@gknjockbot
Thank you !
@gknjockbot
linkedin.com/in/georgeskingnjockbot

More Related Content

Similar to apidays Paris 2022 - France Televisions : How we leverage API Platform for our content APIs, Georges-King Njock-Bôt, Freelance Developer

Salesforce meetup | Lightning Web Component
Salesforce meetup | Lightning Web ComponentSalesforce meetup | Lightning Web Component
Salesforce meetup | Lightning Web ComponentAccenture Hungary
 
How to code to code less
How to code to code lessHow to code to code less
How to code to code lessAnton Novikau
 
InterConnect2016: WebApp Architectures with Java and Node.js
InterConnect2016: WebApp Architectures with Java and Node.jsInterConnect2016: WebApp Architectures with Java and Node.js
InterConnect2016: WebApp Architectures with Java and Node.jsChris Bailey
 
Deploying Next Gen Systems with Zero Downtime
Deploying Next Gen Systems with Zero DowntimeDeploying Next Gen Systems with Zero Downtime
Deploying Next Gen Systems with Zero DowntimeTwilio Inc
 
WCMTL 15 - Create your own shortcode (Fr)
WCMTL 15 - Create your own shortcode (Fr)WCMTL 15 - Create your own shortcode (Fr)
WCMTL 15 - Create your own shortcode (Fr)MichaelBontyes
 
Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Yuriy Senko
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfNuttavutThongjor1
 
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...Wim Selles
 
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
Creating REST Applications with the Slim Micro-Framework by Vikram VaswaniCreating REST Applications with the Slim Micro-Framework by Vikram Vaswani
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswanivvaswani
 
Deploy your contents with entity share
Deploy your contents with entity share   Deploy your contents with entity share
Deploy your contents with entity share Smile I.T is open
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsIván López Martín
 
Creating sub zero dashboard plugin for apex with google
Creating sub zero dashboard plugin for apex with googleCreating sub zero dashboard plugin for apex with google
Creating sub zero dashboard plugin for apex with googleRoel Hartman
 
OpenStack API's and WSGI
OpenStack API's and WSGIOpenStack API's and WSGI
OpenStack API's and WSGIMike Pittaro
 
Cloud native programming model comparison
Cloud native programming model comparisonCloud native programming model comparison
Cloud native programming model comparisonEmily Jiang
 
Web automation with #d8rules (European Drupal Days 2015)
Web automation with #d8rules (European Drupal Days 2015)Web automation with #d8rules (European Drupal Days 2015)
Web automation with #d8rules (European Drupal Days 2015)Eugenio Minardi
 
Understanding router state in angular 7 passing data through angular router s...
Understanding router state in angular 7 passing data through angular router s...Understanding router state in angular 7 passing data through angular router s...
Understanding router state in angular 7 passing data through angular router s...Katy Slemon
 
Protocol-Oriented Programming in Swift
Protocol-Oriented Programming in SwiftProtocol-Oriented Programming in Swift
Protocol-Oriented Programming in SwiftOleksandr Stepanov
 
XebiConFr 15 - Brace yourselves Angular 2 is coming
XebiConFr 15 - Brace yourselves Angular 2 is comingXebiConFr 15 - Brace yourselves Angular 2 is coming
XebiConFr 15 - Brace yourselves Angular 2 is comingPublicis Sapient Engineering
 

Similar to apidays Paris 2022 - France Televisions : How we leverage API Platform for our content APIs, Georges-King Njock-Bôt, Freelance Developer (20)

Salesforce meetup | Lightning Web Component
Salesforce meetup | Lightning Web ComponentSalesforce meetup | Lightning Web Component
Salesforce meetup | Lightning Web Component
 
How to code to code less
How to code to code lessHow to code to code less
How to code to code less
 
InterConnect2016: WebApp Architectures with Java and Node.js
InterConnect2016: WebApp Architectures with Java and Node.jsInterConnect2016: WebApp Architectures with Java and Node.js
InterConnect2016: WebApp Architectures with Java and Node.js
 
Sprint 69
Sprint 69Sprint 69
Sprint 69
 
Deploying Next Gen Systems with Zero Downtime
Deploying Next Gen Systems with Zero DowntimeDeploying Next Gen Systems with Zero Downtime
Deploying Next Gen Systems with Zero Downtime
 
WCMTL 15 - Create your own shortcode (Fr)
WCMTL 15 - Create your own shortcode (Fr)WCMTL 15 - Create your own shortcode (Fr)
WCMTL 15 - Create your own shortcode (Fr)
 
Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdf
 
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...
How React Native, Appium and me made each other shine @ContinuousDeliveryAmst...
 
Sst hackathon express
Sst hackathon expressSst hackathon express
Sst hackathon express
 
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
Creating REST Applications with the Slim Micro-Framework by Vikram VaswaniCreating REST Applications with the Slim Micro-Framework by Vikram Vaswani
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
 
Deploy your contents with entity share
Deploy your contents with entity share   Deploy your contents with entity share
Deploy your contents with entity share
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
Creating sub zero dashboard plugin for apex with google
Creating sub zero dashboard plugin for apex with googleCreating sub zero dashboard plugin for apex with google
Creating sub zero dashboard plugin for apex with google
 
OpenStack API's and WSGI
OpenStack API's and WSGIOpenStack API's and WSGI
OpenStack API's and WSGI
 
Cloud native programming model comparison
Cloud native programming model comparisonCloud native programming model comparison
Cloud native programming model comparison
 
Web automation with #d8rules (European Drupal Days 2015)
Web automation with #d8rules (European Drupal Days 2015)Web automation with #d8rules (European Drupal Days 2015)
Web automation with #d8rules (European Drupal Days 2015)
 
Understanding router state in angular 7 passing data through angular router s...
Understanding router state in angular 7 passing data through angular router s...Understanding router state in angular 7 passing data through angular router s...
Understanding router state in angular 7 passing data through angular router s...
 
Protocol-Oriented Programming in Swift
Protocol-Oriented Programming in SwiftProtocol-Oriented Programming in Swift
Protocol-Oriented Programming in Swift
 
XebiConFr 15 - Brace yourselves Angular 2 is coming
XebiConFr 15 - Brace yourselves Angular 2 is comingXebiConFr 15 - Brace yourselves Angular 2 is coming
XebiConFr 15 - Brace yourselves Angular 2 is coming
 

More from apidays

apidays Australia 2023 - A programmatic approach to API success including Ope...
apidays Australia 2023 - A programmatic approach to API success including Ope...apidays Australia 2023 - A programmatic approach to API success including Ope...
apidays Australia 2023 - A programmatic approach to API success including Ope...apidays
 
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile API
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile APIapidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile API
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile APIapidays
 
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wise
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wiseapidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wise
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wiseapidays
 
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Ventures
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Venturesapidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Ventures
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Venturesapidays
 
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...apidays Singapore 2023 - Digitalising agreements with data, design & technolo...
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...apidays
 
apidays Singapore 2023 - Building a digital-first investment management model...
apidays Singapore 2023 - Building a digital-first investment management model...apidays Singapore 2023 - Building a digital-first investment management model...
apidays Singapore 2023 - Building a digital-first investment management model...apidays
 
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...apidays Singapore 2023 - Changing the culture of building software, Aman Dham...
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...apidays
 
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...apidays
 
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBM
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBMapidays Singapore 2023 - Beyond REST, Claudio Tag, IBM
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBMapidays
 
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...apidays
 
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartner
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartnerapidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartner
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartnerapidays
 
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...apidays
 
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...apidays
 
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IO
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IOApidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IO
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IOapidays
 
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...apidays
 
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...apidays
 
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...apidays
 
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...apidays
 
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...apidays
 
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...apidays
 

More from apidays (20)

apidays Australia 2023 - A programmatic approach to API success including Ope...
apidays Australia 2023 - A programmatic approach to API success including Ope...apidays Australia 2023 - A programmatic approach to API success including Ope...
apidays Australia 2023 - A programmatic approach to API success including Ope...
 
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile API
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile APIapidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile API
apidays Singapore 2023 - Addressing the Data Gap, Jerome Eger, Smile API
 
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wise
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wiseapidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wise
apidays Singapore 2023 - Iterate Faster with Dynamic Flows, Yee Hui Poh, Wise
 
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Ventures
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Venturesapidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Ventures
apidays Singapore 2023 - Banking the Ecosystem, Apurv Suri, SC Ventures
 
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...apidays Singapore 2023 - Digitalising agreements with data, design & technolo...
apidays Singapore 2023 - Digitalising agreements with data, design & technolo...
 
apidays Singapore 2023 - Building a digital-first investment management model...
apidays Singapore 2023 - Building a digital-first investment management model...apidays Singapore 2023 - Building a digital-first investment management model...
apidays Singapore 2023 - Building a digital-first investment management model...
 
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...apidays Singapore 2023 - Changing the culture of building software, Aman Dham...
apidays Singapore 2023 - Changing the culture of building software, Aman Dham...
 
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...
apidays Singapore 2023 - Connecting the trade ecosystem, CHOO Wai Yee, Singap...
 
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBM
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBMapidays Singapore 2023 - Beyond REST, Claudio Tag, IBM
apidays Singapore 2023 - Beyond REST, Claudio Tag, IBM
 
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...
apidays Singapore 2023 - Securing and protecting our digital way of life, Ver...
 
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartner
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartnerapidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartner
apidays Singapore 2023 - State of the API Industry, Manjunath Bhat, Gartner
 
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...
apidays Australia 2023 - Curb your Enthusiasm:Sustainable Scaling of APIs, Sa...
 
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...
Apidays Paris 2023 - API Security Challenges for Cloud-native Software Archit...
 
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IO
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IOApidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IO
Apidays Paris 2023 - State of Tech Sustainability 2023, Gaël Duez, Green IO
 
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...
Apidays Paris 2023 - 7 Mistakes When Putting In Place An API Program, Francoi...
 
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...
Apidays Paris 2023 - Building APIs That Developers Love: Feedback Collection ...
 
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...
Apidays Paris 2023 - Product Managers and API Documentation, Gareth Faull, Lo...
 
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...
Apidays Paris 2023 - How to use NoCode as a Microservice, Benjamin Buléon and...
 
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...
Apidays Paris 2023 - Boosting Event-Driven Development with AsyncAPI and Micr...
 
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...
Apidays Paris 2023 - API Observability: Improving Governance, Security and Op...
 

Recently uploaded

Brighton SEO | April 2024 | Data Storytelling
Brighton SEO | April 2024 | Data StorytellingBrighton SEO | April 2024 | Data Storytelling
Brighton SEO | April 2024 | Data StorytellingNeil Barnes
 
Call Girls In Dwarka 9654467111 Escorts Service
Call Girls In Dwarka 9654467111 Escorts ServiceCall Girls In Dwarka 9654467111 Escorts Service
Call Girls In Dwarka 9654467111 Escorts ServiceSapana Sha
 
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...Sapana Sha
 
Data Science Jobs and Salaries Analysis.pptx
Data Science Jobs and Salaries Analysis.pptxData Science Jobs and Salaries Analysis.pptx
Data Science Jobs and Salaries Analysis.pptxFurkanTasci3
 
DBA Basics: Getting Started with Performance Tuning.pdf
DBA Basics: Getting Started with Performance Tuning.pdfDBA Basics: Getting Started with Performance Tuning.pdf
DBA Basics: Getting Started with Performance Tuning.pdfJohn Sterrett
 
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一F sss
 
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改atducpo
 
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024thyngster
 
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...Suhani Kapoor
 
Dubai Call Girls Wifey O52&786472 Call Girls Dubai
Dubai Call Girls Wifey O52&786472 Call Girls DubaiDubai Call Girls Wifey O52&786472 Call Girls Dubai
Dubai Call Girls Wifey O52&786472 Call Girls Dubaihf8803863
 
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...dajasot375
 
20240419 - Measurecamp Amsterdam - SAM.pdf
20240419 - Measurecamp Amsterdam - SAM.pdf20240419 - Measurecamp Amsterdam - SAM.pdf
20240419 - Measurecamp Amsterdam - SAM.pdfHuman37
 
RadioAdProWritingCinderellabyButleri.pdf
RadioAdProWritingCinderellabyButleri.pdfRadioAdProWritingCinderellabyButleri.pdf
RadioAdProWritingCinderellabyButleri.pdfgstagge
 
RA-11058_IRR-COMPRESS Do 198 series of 1998
RA-11058_IRR-COMPRESS Do 198 series of 1998RA-11058_IRR-COMPRESS Do 198 series of 1998
RA-11058_IRR-COMPRESS Do 198 series of 1998YohFuh
 
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...Jack DiGiovanna
 
Call Girls In Mahipalpur O9654467111 Escorts Service
Call Girls In Mahipalpur O9654467111  Escorts ServiceCall Girls In Mahipalpur O9654467111  Escorts Service
Call Girls In Mahipalpur O9654467111 Escorts ServiceSapana Sha
 
04242024_CCC TUG_Joins and Relationships
04242024_CCC TUG_Joins and Relationships04242024_CCC TUG_Joins and Relationships
04242024_CCC TUG_Joins and Relationshipsccctableauusergroup
 
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM TRACKING WITH GOOGLE ANALYTICS.pptx
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM  TRACKING WITH GOOGLE ANALYTICS.pptxEMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM  TRACKING WITH GOOGLE ANALYTICS.pptx
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM TRACKING WITH GOOGLE ANALYTICS.pptxthyngster
 

Recently uploaded (20)

꧁❤ Aerocity Call Girls Service Aerocity Delhi ❤꧂ 9999965857 ☎️ Hard And Sexy ...
꧁❤ Aerocity Call Girls Service Aerocity Delhi ❤꧂ 9999965857 ☎️ Hard And Sexy ...꧁❤ Aerocity Call Girls Service Aerocity Delhi ❤꧂ 9999965857 ☎️ Hard And Sexy ...
꧁❤ Aerocity Call Girls Service Aerocity Delhi ❤꧂ 9999965857 ☎️ Hard And Sexy ...
 
Brighton SEO | April 2024 | Data Storytelling
Brighton SEO | April 2024 | Data StorytellingBrighton SEO | April 2024 | Data Storytelling
Brighton SEO | April 2024 | Data Storytelling
 
Call Girls In Dwarka 9654467111 Escorts Service
Call Girls In Dwarka 9654467111 Escorts ServiceCall Girls In Dwarka 9654467111 Escorts Service
Call Girls In Dwarka 9654467111 Escorts Service
 
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...
Saket, (-DELHI )+91-9654467111-(=)CHEAP Call Girls in Escorts Service Saket C...
 
Data Science Jobs and Salaries Analysis.pptx
Data Science Jobs and Salaries Analysis.pptxData Science Jobs and Salaries Analysis.pptx
Data Science Jobs and Salaries Analysis.pptx
 
DBA Basics: Getting Started with Performance Tuning.pdf
DBA Basics: Getting Started with Performance Tuning.pdfDBA Basics: Getting Started with Performance Tuning.pdf
DBA Basics: Getting Started with Performance Tuning.pdf
 
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一
办理学位证中佛罗里达大学毕业证,UCF成绩单原版一比一
 
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改
代办国外大学文凭《原版美国UCLA文凭证书》加州大学洛杉矶分校毕业证制作成绩单修改
 
VIP Call Girls Service Charbagh { Lucknow Call Girls Service 9548273370 } Boo...
VIP Call Girls Service Charbagh { Lucknow Call Girls Service 9548273370 } Boo...VIP Call Girls Service Charbagh { Lucknow Call Girls Service 9548273370 } Boo...
VIP Call Girls Service Charbagh { Lucknow Call Girls Service 9548273370 } Boo...
 
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024
Consent & Privacy Signals on Google *Pixels* - MeasureCamp Amsterdam 2024
 
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...
VIP High Profile Call Girls Amravati Aarushi 8250192130 Independent Escort Se...
 
Dubai Call Girls Wifey O52&786472 Call Girls Dubai
Dubai Call Girls Wifey O52&786472 Call Girls DubaiDubai Call Girls Wifey O52&786472 Call Girls Dubai
Dubai Call Girls Wifey O52&786472 Call Girls Dubai
 
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...
Indian Call Girls in Abu Dhabi O5286O24O8 Call Girls in Abu Dhabi By Independ...
 
20240419 - Measurecamp Amsterdam - SAM.pdf
20240419 - Measurecamp Amsterdam - SAM.pdf20240419 - Measurecamp Amsterdam - SAM.pdf
20240419 - Measurecamp Amsterdam - SAM.pdf
 
RadioAdProWritingCinderellabyButleri.pdf
RadioAdProWritingCinderellabyButleri.pdfRadioAdProWritingCinderellabyButleri.pdf
RadioAdProWritingCinderellabyButleri.pdf
 
RA-11058_IRR-COMPRESS Do 198 series of 1998
RA-11058_IRR-COMPRESS Do 198 series of 1998RA-11058_IRR-COMPRESS Do 198 series of 1998
RA-11058_IRR-COMPRESS Do 198 series of 1998
 
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...
Building on a FAIRly Strong Foundation to Connect Academic Research to Transl...
 
Call Girls In Mahipalpur O9654467111 Escorts Service
Call Girls In Mahipalpur O9654467111  Escorts ServiceCall Girls In Mahipalpur O9654467111  Escorts Service
Call Girls In Mahipalpur O9654467111 Escorts Service
 
04242024_CCC TUG_Joins and Relationships
04242024_CCC TUG_Joins and Relationships04242024_CCC TUG_Joins and Relationships
04242024_CCC TUG_Joins and Relationships
 
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM TRACKING WITH GOOGLE ANALYTICS.pptx
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM  TRACKING WITH GOOGLE ANALYTICS.pptxEMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM  TRACKING WITH GOOGLE ANALYTICS.pptx
EMERCE - 2024 - AMSTERDAM - CROSS-PLATFORM TRACKING WITH GOOGLE ANALYTICS.pptx
 

apidays Paris 2022 - France Televisions : How we leverage API Platform for our content APIs, Georges-King Njock-Bôt, Freelance Developer

  • 1. France Télévisions Leveraging API Platform for our content APIs
  • 2. 2023 SERIES OF EVENT New York May 16&17 Australia October 11&12 Singapore April 12&13 Helsinki & North June 5&6 Paris SEPTEMBER London November 15&16 June 28-30 SILICON VALLEY March 14&15 Dubai & Middle East February 22&23
  • 3. France Télévisions : Leveraging API Platform for our content APIs
  • 4. @gknjockbot 01 - whoami Georges-King Strasbourg, Grand-Est, France Software Engineer since 2009 Freelance IT Consultant
  • 5. @gknjockbot 02 - France Télévisions News Articles TV Broadcasts
  • 6. @gknjockbot 02 - France Télévisions 866 755 articles 29M visitors / month 860 770 articles 157M visitors / month 220 771 articles 11M visitors / month
  • 7. @gknjockbot 03 - What are we trying to solve ? Franceinfo needed rebuilding :
  • 8. @gknjockbot 03 - What are we trying to solve ? Franceinfo needed rebuilding : ● Front-Back coupling
  • 9. @gknjockbot 03 - What are we trying to solve ? Franceinfo needed rebuilding : ● Front-Back coupling ● Competing DB access
  • 10. @gknjockbot 03 - What are we trying to solve ? Franceinfo needed rebuilding : ● Front-Back coupling ● Competing DB access ● Obsolete Technology
  • 11. @gknjockbot 03 - What are we trying to solve ? Franceinfo needed rebuilding : ● Front-Back coupling ● Competing DB access ● Obsolete Technology ● Monolithic platform
  • 12. @gknjockbot 04 - The proposed architecture
  • 13. @gknjockbot 05 - Choosing an implementation Proof Of Concept MariaDB MongoDB ElasticSearch PHP / Symfony Doctrine ORM Doctrine ODM - Node.js / Koa Sequelize Mongoose Built In
  • 14. @gknjockbot 05 - Choosing an implementation Cons Pros Node / ElasticSearch Reindexing Scalability Performance Resource consumption Node / MariaDB Performance Sequelize Simple Node / MongoDB Reindexing Performance Mongoose Symfony / MongoDB New DB Unified stacks (Front/Back) ODM / Framework Symfony / MariaDB Well known DB stack internally Unified stacks (Front/Back) ORM / Framework
  • 15. @gknjockbot 05 - Choosing an implementation
  • 16. @gknjockbot 05 - Choosing an implementation
  • 17. @gknjockbot 06 - Towards a shared platform
  • 18. @gknjockbot 06 - Towards a shared platform
  • 19. @gknjockbot 06.1 - Introducing PIC - Plateforme d’Information Commune
  • 20. @gknjockbot 06.2 - The challenges for PIC Additional concerns : ● Must share editorial concepts ● Must share a generic codebase ● Must have dedicated deployments
  • 21. @gknjockbot 06.2 - PIC platform high level overview
  • 22. @gknjockbot 07 - The Tech Stack
  • 24. @gknjockbot 08 - API Front API Call : GET /contents?taxonomy=les-jeux-olympiques/paris-2024 Endpoint : /contents Filter : taxonomy=les-jeux-olympiques/paris-2024 Groups : default, content
  • 26. @gknjockbot 08 - API Front API Call : GET /contents/<id> Endpoint : /contents Filter : N/A Groups : default, content, media, taxonomy
  • 27. @gknjockbot 08 - API Front / resource endpoints class DirectTv extends Content { // ... }
  • 28. @gknjockbot 08 - API Front / resource endpoints #[ORMEntity] #[ORMTable(name: 'direct_tv' )] class DirectTv extends Content { // ... }
  • 29. @gknjockbot 08 - API Front / resource endpoints #[ApiResource ( collectionOperations: [ 'list' => [ 'method' => 'GET', 'normalization_context' => ['enable_max_depth' => true] ]], itemOperations: [ 'get' => [ 'method' => 'GET' ]], attributes: [ 'order' => ['lastPublicationDate' => 'DESC'], 'filters' => ['snapshot.code' , 'media.type' ] ] )] #[ApiFilter(ProgramFilter ::class, properties: ['program.channel' , 'program.type' ])] #[ApiFilter(BeforeBeginDateFilter ::class)] #[ApiFilter(OrderBeginDateFilter ::class)] #[ORMEntity] #[ORMTable(name: 'direct_tv' )] class DirectTv extends Content { // ... }
  • 30. @gknjockbot 08 - API Front / resource endpoints #[ApiResource ( collectionOperations: [ 'list' => [ 'method' => 'GET', 'normalization_context' => ['enable_max_depth' => true] ]], itemOperations: [ 'get' => [ 'method' => 'GET' ]], attributes: [ 'order' => ['lastPublicationDate' => 'DESC'], 'filters' => ['snapshot.code' , 'media.type' ] ] )] #[ApiFilter(ProgramFilter ::class, properties: ['program.channel' , 'program.type' ])] #[ApiFilter(BeforeBeginDateFilter ::class)] #[ApiFilter(OrderBeginDateFilter ::class)] #[ORMEntity] #[ORMTable(name: 'direct_tv' )] class DirectTv extends Content { // ... }
  • 31. @gknjockbot 08 - API Front / resource payload class DirectTv extends Content { private ?string $presentation = null; private ?bool $isSubject = false; public function getPresentation (): ?string { return $this->presentation ; } public function isIsSubject (): ?bool { return $this->isSubject; } }
  • 32. @gknjockbot 08 - API Front / resource payload class DirectTv extends Content { #[ORMColumn(type: 'text', nullable: true)] private ?string $presentation = null; #[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])] private ?bool $isSubject = false; public function getPresentation (): ?string { return $this->presentation ; } public function isIsSubject (): ?bool { return $this->isSubject; } }
  • 33. @gknjockbot 08 - API Front / resource payload class DirectTv extends Content { #[Groups(['content:get:default' ])] #[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])] #[ORMColumn(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])] private ?bool $isSubject = false; public function getPresentation (): ?string { return $this->presentation ; } public function isIsSubject (): ?bool { return $this->isSubject; } }
  • 34. @gknjockbot 08 - API Front / resource payload class DirectTv extends Content { #[Groups(['content:get:default' ])] #[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])] #[ORMColumn(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])] private ?bool $isSubject = false; public function getPresentation (): ?string { return $this->presentation ; } public function isIsSubject (): ?bool { return $this->isSubject; } }
  • 35. @gknjockbot 08 - API Front / resource payload class DirectTv extends Content { #[Groups(['content:get:default' ])] #[ApiProperty (attributes: ['swagger_context' => ['example' => 'JT du mercredi 6 février 2019' ]])] #[ORMColumn(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORMColumn(type: 'boolean', nullable: true, options: ['default' => 0])] private ?bool $isSubject = false; public function getPresentation (): ?string { return $this->presentation ; } public function isIsSubject (): ?bool { return $this->isSubject; } }
  • 38. @gknjockbot #[ApiResource ( /* ... */ )] #[ApiFilter (GroupFilter ::class, arguments: [ 'parameterName' => 'groups', 'overrideDefaultGroups' => true, 'whitelist' => ['indexation' , 'sdk', 'details'] ])] #[ApiFilter ( /* ... */ )] abstract class Content implements Lockable, Loggable { // ... } 09 - API Back
  • 39. @gknjockbot #[ApiResource ( /* ... */ )] #[ApiFilter (GroupFilter ::class, arguments: [ 'parameterName' => 'groups', 'overrideDefaultGroups' => true, 'whitelist' => ['indexation' , 'sdk', 'details'] ])] #[ApiFilter ( /* ... */ )] abstract class Content implements Lockable, Loggable { // ... } 09 - API Back
  • 40. @gknjockbot 09 - API Back #[ApiResource ( collectionOperations: [ 'get', 'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ], ], itemOperations: [ 'get', 'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ], ], attributes: ['order' => ['lastUpdateDate' => 'DESC']], normalizationContext: ['groups' => ['resource']] )] #[ORMEntity] #[ORMTable(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORMColumn(name: 'art_text', type: 'text', nullable: true)] #[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  • 41. @gknjockbot 09 - API Back #[ApiResource ( collectionOperations: [ 'get', 'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ], ], itemOperations: [ 'get', 'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ], ], attributes: ['order' => ['lastUpdateDate' => 'DESC']], normalizationContext: ['groups' => ['resource']] )] #[ORMEntity] #[ORMTable(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORMColumn(name: 'art_text', type: 'text', nullable: true)] #[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  • 42. @gknjockbot 09 - API Back #[ApiResource ( collectionOperations: [ 'get', 'post' => ['security' => "is_granted('CREATE_CONTENT_ARTICLE')" ], ], itemOperations: [ 'get', 'put' => ['security' => "is_granted('UPDATE_CONTENT_ARTICLE')" ], ], attributes: ['order' => ['lastUpdateDate' => 'DESC']], normalizationContext: ['groups' => ['resource']] )] #[ORMEntity] #[ORMTable(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORMColumn(name: 'art_text', type: 'text', nullable: true)] #[AssertNotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  • 43. @gknjockbot 09 - API Back article : post : * media : get : default * : * : indexation resource method group
  • 45. @gknjockbot 09 - API Back # config/services.yaml AppSerializerContextBuilder : arguments: $decorated: '@AppSerializerContextBuilder.inner' decorates: api_platform.serializer.context_builder.filter
  • 46. @gknjockbot 09 - API Back final class ContextBuilder implements SerializerContextBuilderInterface { public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); if (empty($context[AbstractNormalizer::GROUPS])) { return $context; } $context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? []; $groups = $context[AbstractNormalizer::GROUPS]; $groups[] = 'default'; $operation = $this->extractOperation($extractedAttributes); $context[AbstractNormalizer::GROUPS] = []; foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) { foreach ($groups as $group) { $context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*'; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group; } } $context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS])); return $context; }
  • 47. @gknjockbot 09 - API Back final class ContextBuilder implements SerializerContextBuilderInterface { public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); if (empty($context[AbstractNormalizer::GROUPS])) { return $context; } $context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? []; $groups = $context[AbstractNormalizer::GROUPS]; $groups[] = 'default'; $operation = $this->extractOperation($extractedAttributes); $context[AbstractNormalizer::GROUPS] = []; foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) { foreach ($groups as $group) { $context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*'; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group; } } $context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS])); return $context; }
  • 48. @gknjockbot 09 - API Back final class ContextBuilder implements SerializerContextBuilderInterface { public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); if (empty($context[AbstractNormalizer::GROUPS])) { return $context; } $context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? []; $groups = $context[AbstractNormalizer::GROUPS]; $groups[] = 'default'; $operation = $this->extractOperation($extractedAttributes); $context[AbstractNormalizer::GROUPS] = []; foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) { foreach ($groups as $group) { $context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*'; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group; } } $context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS])); return $context; }
  • 49. @gknjockbot 09 - API Back final class ContextBuilder implements SerializerContextBuilderInterface { public function createFromRequest(Request $request, bool $normalization, array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); if (empty($context[AbstractNormalizer::GROUPS])) { return $context; } $context[AbstractNormalizer::GROUPS] = $context[AbstractNormalizer::GROUPS] ?? []; $groups = $context[AbstractNormalizer::GROUPS]; $groups[] = 'default'; $operation = $this->extractOperation($extractedAttributes); $context[AbstractNormalizer::GROUPS] = []; foreach ($this->extractResourceNames($extractedAttributes) as $resourceName) { foreach ($groups as $group) { $context[AbstractNormalizer::GROUPS][] = '*'.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = '*'.':'.$operation.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.'*'.':'.$group; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.'*'; $context[AbstractNormalizer::GROUPS][] = $resourceName.':'.$operation.':'.$group; } } $context[AbstractNormalizer::GROUPS] = array_merge([], array_unique($context[AbstractNormalizer::GROUPS])); return $context; }
  • 52. @gknjockbot 09 - API Back # config/services.yaml AppEventListenerCmsContentEntityListener : tags: - { name: 'doctrine.orm.entity_listener' , entity: AppEntityCmsContent, event: postPersist, entity_manager : edito } - { name: 'doctrine.orm.entity_listener' , entity: AppEntityCmsContent, event: postPersist, method: postPersistPublish, entity_manager : publish }
  • 53. @gknjockbot 09 - API Back class ContentEntityListener { public function postPersist (Content $content): void { $this->logger->logCreateEntity ($content); $this->index($content, IndexerSubscriberAction ::ACTION_SAVE); $this->registerForCacheInvalidation ($content, CacheInvalidatorInterface ::CONTEXT_EDITO); } public function postPersistPublish (Content $content): void { $this->logger->logPublishEntity ($content); $this->index($content, IndexerSubscriberAction ::ACTION_PUBLISH); $this->registerForCacheInvalidation ($content, CacheInvalidatorInterface ::CONTEXT_PUBLISH); } // ... }
  • 57. @gknjockbot 10.1 - API Mobile | DataProvider final class ContentItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface, RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return Content::class === $resourceClass; } public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve Content with all dependencies all at once: CHT, CHM. $response = $this->apiFront->callContent($id); if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) { throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id)); } $apiFrontContent = $this->getSerializer() ->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context); $this->setContentsByRelateds($apiFrontContent, $context); return $apiFrontContent; }
  • 58. @gknjockbot 10.1 - API Mobile | DataProvider final class ContentItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface, RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return Content::class === $resourceClass; } public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve Content with all dependencies all at once: CHT, CHM. $response = $this->apiFront->callContent($id); if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) { throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id)); } $apiFrontContent = $this->getSerializer() ->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context); $this->setContentsByRelateds($apiFrontContent, $context); return $apiFrontContent; }
  • 59. @gknjockbot 10.1 - API Mobile | DataProvider final class ContentItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface, RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return Content::class === $resourceClass; } public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve Content with all dependencies all at once: CHT, CHM. $response = $this->apiFront->callContent($id); if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) { throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id)); } $apiFrontContent = $this->getSerializer() ->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context); $this->setContentsByRelateds($apiFrontContent, $context); return $apiFrontContent; }
  • 60. @gknjockbot 10.1 - API Mobile | DataProvider final class ContentItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface, RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return Content::class === $resourceClass; } public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve Content with all dependencies all at once: CHT, CHM. $response = $this->apiFront->callContent($id); if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) { throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id)); } $apiFrontContent = $this->getSerializer() ->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context); $this->setContentsByRelateds($apiFrontContent, $context); return $apiFrontContent; }
  • 61. @gknjockbot 10.1 - API Mobile | DataProvider final class ContentItemDataProvider implements ItemDataProviderInterface, SerializerAwareDataProviderInterface, RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return Content::class === $resourceClass; } public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve Content with all dependencies all at once: CHT, CHM. $response = $this->apiFront->callContent($id); if (Response::HTTP_NOT_FOUND === $response->getStatusCode()) { throw new NotFoundHttpException(sprintf('Content not found with ID : %s', $id)); } $apiFrontContent = $this->getSerializer() ->deserialize($response->getContent(), '', JsonEncoder::FORMAT, $context); $this->setContentsByRelateds($apiFrontContent, $context); return $apiFrontContent; }
  • 62. @gknjockbot 10.2 - API Mobile | DataTransformer class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface, DataTransformerInterface { public function supportsTransformation ($data, string $to, array $context = []): bool { return $data instanceof ArticleInput && !$this->transformer ->isDigest($context); } public function transform($object, string $to, array $context = []) { $content = $this->decorated->transform($object, $to, $context); $this->hydrateDetail ($content, $object, $context); $richText = $this->parser->parse($object->getText()); $this->mergeRichTextHtml ($richText); $this->populateRichTextEntity ($richText, $object, [ 'operation_type' => 'collection' ]); $content->setText($richText); return $content; } // ... }
  • 63. @gknjockbot 10.2 - API Mobile | DataTransformer class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface, DataTransformerInterface { public function supportsTransformation ($data, string $to, array $context = []): bool { return $data instanceof ArticleInput && !$this->transformer ->isDigest($context); } public function transform($object, string $to, array $context = []) { $content = $this->decorated->transform($object, $to, $context); $this->hydrateDetail ($content, $object, $context); $richText = $this->parser->parse($object->getText()); $this->mergeRichTextHtml ($richText); $this->populateRichTextEntity ($richText, $object, [ 'operation_type' => 'collection' ]); $content->setText($richText); return $content; } // ... }
  • 64. @gknjockbot 10.2 - API Mobile | DataTransformer class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface, DataTransformerInterface { public function supportsTransformation ($data, string $to, array $context = []): bool { return $data instanceof ArticleInput && !$this->transformer ->isDigest($context); } public function transform($object, string $to, array $context = []) { $content = $this->decorated->transform($object, $to, $context); $this->hydrateDetail ($content, $object, $context); $richText = $this->parser->parse($object->getText()); $this->mergeRichTextHtml ($richText); $this->populateRichTextEntity ($richText, $object, [ 'operation_type' => 'collection' ]); $content->setText($richText); return $content; } // ... }
  • 65. @gknjockbot 10.2 - API Mobile | DataTransformer class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface, DataTransformerInterface { public function supportsTransformation ($data, string $to, array $context = []): bool { return $data instanceof ArticleInput && !$this->transformer ->isDigest($context); } public function transform($object, string $to, array $context = []) { $content = $this->decorated->transform($object, $to, $context); $this->hydrateDetail ($content, $object, $context); $richText = $this->parser->parse($object->getText()); $this->mergeRichTextHtml ($richText); $this->populateRichTextEntity ($richText, $object, [ 'operation_type' => 'collection' ]); $content->setText($richText); return $content; } // ... }
  • 66. @gknjockbot 10.2 - API Mobile | DataTransformer class ArticleDetailDataTransformer implements CmsDataTransformerAwareInterface, DataTransformerInterface { public function supportsTransformation ($data, string $to, array $context = []): bool { return $data instanceof ArticleInput && !$this->transformer ->isDigest($context); } public function transform($object, string $to, array $context = []) { $content = $this->decorated->transform($object, $to, $context); $this->hydrateDetail ($content, $object, $context); $richText = $this->parser->parse($object->getText()); $this->mergeRichTextHtml ($richText); $this->populateRichTextEntity ($richText, $object, [ 'operation_type' => 'collection' ]); $content->setText($richText); return $content; } // ... }
  • 69. @gknjockbot 11 - HTTP Cache ● Backend : Varnish Cache Plus ● Tag-based : xkeys module xkey pattern {context}/{type}/{id} publish/Cms-Article/1 edito/Cms-MediaImage/1
  • 78. @gknjockbot 12 - What about the future ?
  • 79. @gknjockbot 12 - What about the future ? UPGRADE : ● Standards/RFCs : compliance ● Symfony Framework : 5.4 → 6.x → ? ● API Platform : 2.7 → 3.x → ? ENHANCE : ● Autogenerate API Front from API Back resource metadata ● ElasticSearch as backend for resource collections endpoints