Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

apidays
January 05, 2023

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

apidays Paris 2022 - APIs the next 10 years: Software, Society, Sovereignty, Sustainability
December 14, 15 & 16, 2022

France Televisions : How we leverage API Platform for our content APIs
Georges-King Njock-Bôt, Freelance Symfony PHP Backend Developer
------

Check out our conferences at https://www.apidays.global/

Do you want to sponsor or talk at one of our conferences?
https://apidays.typeform.com/to/ILJeAaV8

Learn more on APIscene, the global media made by the community for the community:
https://www.apiscene.io

Explore the API ecosystem with the API Landscape:
https://apilandscape.apiscene.io/

Deep dive into the API industry with our reports:
https://www.apidays.global/industry-reports/

Subscribe to our global newsletter:
https://apidays.typeform.com/to/i1MPEW

apidays

January 05, 2023
Tweet

More Decks by apidays

Other Decks in Programming

Transcript

  1. 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
  2. @gknjockbot 02 - France Télévisions 866 755 articles 29M visitors

    / month 860 770 articles 157M visitors / month 220 771 articles 11M visitors / month
  3. @gknjockbot 03 - What are we trying to solve ?

    Franceinfo needed rebuilding :
  4. @gknjockbot 03 - What are we trying to solve ?

    Franceinfo needed rebuilding : • Front-Back coupling
  5. @gknjockbot 03 - What are we trying to solve ?

    Franceinfo needed rebuilding : • Front-Back coupling • Competing DB access
  6. @gknjockbot 03 - What are we trying to solve ?

    Franceinfo needed rebuilding : • Front-Back coupling • Competing DB access • Obsolete Technology
  7. @gknjockbot 03 - What are we trying to solve ?

    Franceinfo needed rebuilding : • Front-Back coupling • Competing DB access • Obsolete Technology • Monolithic platform
  8. @gknjockbot 05 - Choosing an implementation Proof Of Concept MariaDB

    MongoDB ElasticSearch PHP / Symfony Doctrine ORM Doctrine ODM - Node.js / Koa Sequelize Mongoose Built In
  9. @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
  10. @gknjockbot 06.2 - The challenges for PIC Additional concerns :

    • Must share editorial concepts • Must share a generic codebase • Must have dedicated deployments
  11. @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
  12. @gknjockbot 08 - API Front API Call : GET /contents/<id>

    Endpoint : /contents Filter : N/A Groups : default, content, media, taxonomy
  13. @gknjockbot 08 - API Front / resource endpoints #[ORM\Entity] #[ORM\Table(name:

    'direct_tv' )] class DirectTv extends Content { // ... }
  14. @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)] #[ORM\Entity] #[ORM\Table(name: 'direct_tv' )] class DirectTv extends Content { // ... }
  15. @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)] #[ORM\Entity] #[ORM\Table(name: 'direct_tv' )] class DirectTv extends Content { // ... }
  16. @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; } }
  17. @gknjockbot 08 - API Front / resource payload class DirectTv

    extends Content { #[ORM\Column(type: 'text', nullable: true)] private ?string $presentation = null; #[ORM\Column(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; } }
  18. @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' ]])] #[ORM\Column(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORM\Column(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; } }
  19. @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' ]])] #[ORM\Column(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORM\Column(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; } }
  20. @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' ]])] #[ORM\Column(type: 'text', nullable: true)] private ?string $presentation = null; #[Groups(['content:get:default' ])] #[ApiProperty ()] #[ORM\Column(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; } }
  21. @gknjockbot #[ApiResource ( /* ... */ )] #[ApiFilter (GroupFilter ::class,

    arguments: [ 'parameterName' => 'groups', 'overrideDefaultGroups' => true, 'whitelist' => ['indexation' , 'sdk', 'details'] ])] #[ApiFilter ( /* ... */ )] abstract class Content implements Lockable, Loggable { // ... } 09 - API Back
  22. @gknjockbot #[ApiResource ( /* ... */ )] #[ApiFilter (GroupFilter ::class,

    arguments: [ 'parameterName' => 'groups', 'overrideDefaultGroups' => true, 'whitelist' => ['indexation' , 'sdk', 'details'] ])] #[ApiFilter ( /* ... */ )] abstract class Content implements Lockable, Loggable { // ... } 09 - API Back
  23. @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']] )] #[ORM\Entity] #[ORM\Table(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORM\Column(name: 'art_text', type: 'text', nullable: true)] #[Assert\NotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  24. @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']] )] #[ORM\Entity] #[ORM\Table(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORM\Column(name: 'art_text', type: 'text', nullable: true)] #[Assert\NotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  25. @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']] )] #[ORM\Entity] #[ORM\Table(name: 'article')] class Article extends Content { /** * Texte de l'article. */ #[ApiProperty ()] #[ORM\Column(name: 'art_text', type: 'text', nullable: true)] #[Assert\NotBlank(groups: ['publication' ], message: 'article.text.message' )] #[Groups(['article:*:default' ])] private ?string $text = null;
  26. @gknjockbot 09 - API Back article : post : *

    media : get : default * : * : indexation resource method group
  27. @gknjockbot 09 - API Back # config/services.yaml App\Serializer\ContextBuilder : arguments:

    $decorated: '@App\Serializer\ContextBuilder.inner' decorates: api_platform.serializer.context_builder.filter
  28. @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; }
  29. @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; }
  30. @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; }
  31. @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; }
  32. @gknjockbot 09 - API Back # config/services.yaml App\EventListener\Cms\ContentEntityListener : tags:

    - { name: 'doctrine.orm.entity_listener' , entity: App\Entity\Cms\Content, event: postPersist, entity_manager : edito } - { name: 'doctrine.orm.entity_listener' , entity: App\Entity\Cms\Content, event: postPersist, method: postPersistPublish, entity_manager : publish }
  33. @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); } // ... }
  34. @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; }
  35. @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; }
  36. @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; }
  37. @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; }
  38. @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; }
  39. @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; } // ... }
  40. @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; } // ... }
  41. @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; } // ... }
  42. @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; } // ... }
  43. @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; } // ... }
  44. @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
  45. @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