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

Making the most out of kubernetes audit logs
Making the most out of kubernetes audit logsMaking the most out of kubernetes audit logs
Making the most out of kubernetes audit logsLaurent Bernaille
 
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
 

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

Making the most out of kubernetes audit logs
Making the most out of kubernetes audit logsMaking the most out of kubernetes audit logs
Making the most out of kubernetes audit logs
 
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
 

More from apidays

Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...apidays
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...apidays
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...apidays
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...apidays
 
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...apidays
 
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...apidays
 
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...apidays
 
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...apidays
 
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...apidays
 
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...apidays
 
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...apidays
 
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...apidays
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...apidays
 
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...apidays
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbuapidays
 
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...apidays
 

More from apidays (20)

Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...
Apidays New York 2024 - The secrets to Graph success, by Leah Hurwich Adler, ...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...
Apidays New York 2024 - API Discovery - From Crawl to Run by Rob Dickinson, G...
 
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...
Apidays Singapore 2024 - Building with the Planet in Mind by Sandeep Joshi, M...
 
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...
Apidays Singapore 2024 - Connecting Cross Border Commerce with Payments by Gu...
 
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...
Apidays Singapore 2024 - Privacy Enhancing Technologies for AI by Mark Choo, ...
 
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...
Apidays Singapore 2024 - Blending AI and IoT for Smarter Health by Matthew Ch...
 
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...
Apidays Singapore 2024 - OpenTelemetry for API Monitoring by Danielle Kayumbi...
 
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...
Apidays Singapore 2024 - Connecting Product and Engineering Teams with Testin...
 
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...
Apidays Singapore 2024 - The Growing Carbon Footprint of Digitalization and H...
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...
Apidays Singapore 2024 - API Monitoring x SRE by Ryan Ashneil and Eugene Wong...
 
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...
Apidays Singapore 2024 - A nuanced approach on AI costs and benefits for the ...
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...
Apidays Singapore 2024 - How APIs drive business at BNP Paribas by Quy-Doan D...
 

Recently uploaded

Detecting Credit Card Fraud: A Machine Learning Approach
Detecting Credit Card Fraud: A Machine Learning ApproachDetecting Credit Card Fraud: A Machine Learning Approach
Detecting Credit Card Fraud: A Machine Learning ApproachBoston Institute of Analytics
 
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...gajnagarg
 
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...amitlee9823
 
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...karishmasinghjnh
 
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...SUHANI PANDEY
 
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...gajnagarg
 
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...gajnagarg
 
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men 🔝Sambalpur🔝 Esc...
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men  🔝Sambalpur🔝   Esc...➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men  🔝Sambalpur🔝   Esc...
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men 🔝Sambalpur🔝 Esc...amitlee9823
 
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...gajnagarg
 
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...gajnagarg
 
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men 🔝Dindigul🔝 Escor...
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men  🔝Dindigul🔝   Escor...➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men  🔝Dindigul🔝   Escor...
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men 🔝Dindigul🔝 Escor...amitlee9823
 
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...Elaine Werffeli
 
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...only4webmaster01
 
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men 🔝Bangalore🔝 Esc...
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men  🔝Bangalore🔝   Esc...➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men  🔝Bangalore🔝   Esc...
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men 🔝Bangalore🔝 Esc...amitlee9823
 
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night StandCall Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Standamitlee9823
 
Aspirational Block Program Block Syaldey District - Almora
Aspirational Block Program Block Syaldey District - AlmoraAspirational Block Program Block Syaldey District - Almora
Aspirational Block Program Block Syaldey District - AlmoraGovindSinghDasila
 

Recently uploaded (20)

(NEHA) Call Girls Katra Call Now 8617697112 Katra Escorts 24x7
(NEHA) Call Girls Katra Call Now 8617697112 Katra Escorts 24x7(NEHA) Call Girls Katra Call Now 8617697112 Katra Escorts 24x7
(NEHA) Call Girls Katra Call Now 8617697112 Katra Escorts 24x7
 
Detecting Credit Card Fraud: A Machine Learning Approach
Detecting Credit Card Fraud: A Machine Learning ApproachDetecting Credit Card Fraud: A Machine Learning Approach
Detecting Credit Card Fraud: A Machine Learning Approach
 
CHEAP Call Girls in Saket (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Saket (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Saket (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Saket (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls kakinada Escorts ☎️9352988975 Two shot with one girl...
 
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 7737669865 👗 Top Class Call Girl Service B...
 
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...
👉 Amritsar Call Girl 👉📞 6367187148 👉📞 Just📲 Call Ruhi Call Girl Phone No Amri...
 
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...
VIP Model Call Girls Hinjewadi ( Pune ) Call ON 8005736733 Starting From 5K t...
 
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...
Just Call Vip call girls roorkee Escorts ☎️9352988975 Two shot with one girl ...
 
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...
Just Call Vip call girls Mysore Escorts ☎️9352988975 Two shot with one girl (...
 
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men 🔝Sambalpur🔝 Esc...
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men  🔝Sambalpur🔝   Esc...➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men  🔝Sambalpur🔝   Esc...
➥🔝 7737669865 🔝▻ Sambalpur Call-girls in Women Seeking Men 🔝Sambalpur🔝 Esc...
 
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...
Just Call Vip call girls Palakkad Escorts ☎️9352988975 Two shot with one girl...
 
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...
Just Call Vip call girls Erode Escorts ☎️9352988975 Two shot with one girl (E...
 
Predicting Loan Approval: A Data Science Project
Predicting Loan Approval: A Data Science ProjectPredicting Loan Approval: A Data Science Project
Predicting Loan Approval: A Data Science Project
 
Call Girls In Shalimar Bagh ( Delhi) 9953330565 Escorts Service
Call Girls In Shalimar Bagh ( Delhi) 9953330565 Escorts ServiceCall Girls In Shalimar Bagh ( Delhi) 9953330565 Escorts Service
Call Girls In Shalimar Bagh ( Delhi) 9953330565 Escorts Service
 
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men 🔝Dindigul🔝 Escor...
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men  🔝Dindigul🔝   Escor...➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men  🔝Dindigul🔝   Escor...
➥🔝 7737669865 🔝▻ Dindigul Call-girls in Women Seeking Men 🔝Dindigul🔝 Escor...
 
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...
SAC 25 Final National, Regional & Local Angel Group Investing Insights 2024 0...
 
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
Call Girls Indiranagar Just Call 👗 9155563397 👗 Top Class Call Girl Service B...
 
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men 🔝Bangalore🔝 Esc...
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men  🔝Bangalore🔝   Esc...➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men  🔝Bangalore🔝   Esc...
➥🔝 7737669865 🔝▻ Bangalore Call-girls in Women Seeking Men 🔝Bangalore🔝 Esc...
 
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night StandCall Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Doddaballapur Road ☎ 7737669865 🥵 Book Your One night Stand
 
Aspirational Block Program Block Syaldey District - Almora
Aspirational Block Program Block Syaldey District - AlmoraAspirational Block Program Block Syaldey District - Almora
Aspirational Block Program Block Syaldey District - Almora
 

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