apidays Paris 2022 - France Televisions : How we leverage API Platform for our content APIs, Georges-King Njock-Bôt, Freelance Developer
- 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
- 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
- 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
- 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
- 20. @gknjockbot
06.2 - The challenges for PIC
Additional concerns :
● Must share editorial concepts
● Must share a generic codebase
● Must have dedicated deployments
- 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
- 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
- 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