Skip to content

Commit

Permalink
feat: Documented custom form via API, tag and document DTO examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ambroisemaupate committed Aug 5, 2022
1 parent fb0cceb commit cf70b46
Show file tree
Hide file tree
Showing 10 changed files with 519 additions and 0 deletions.
340 changes: 340 additions & 0 deletions src/developer/api/web_response.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,343 @@ API will expose a WebResponse single item containing:
"realms": [],
"hidingBlocks": false
}
Retrieve common content
-----------------------

Now that we can fetch each page data, we need to get all unique content for building Menus, Homepage reference, headers, footers, etc.
We could extend our _WebResponse_ to inject theses common data to each request, but it would bloat HTTP responses, and
affect API performances.

For these common content, you can create a ``/api/common_content`` API endpoint in your project which will fetched only once in your
frontend application.

.. code-block:: yaml
# config/api_resources/common_content.yml
App\Api\Model\CommonContent:
collectionOperations: {}
itemOperations:
getCommonContent:
method: 'GET'
path: '/common_content'
read: false
controller: App\Controller\GetCommonContentController
pagination_enabled: false
normalization_context:
pagination_enabled: false
groups:
- get
- common_content
- web_response
- walker
- walker_level
- children
- children_count
- nodes_sources_base
- nodes_sources_default
- urls
- tag_base
- translation_base
- document_display
Then create you own custom resource to hold your menus tree-walkers and common content:

.. code-block:: php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Model\CommonContent;
use App\TreeWalker\MenuNodeSourceWalker;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Cache\CacheItemPoolInterface;
use RZ\Roadiz\CoreBundle\Api\Model\NodesSourcesHeadFactory;
use RZ\Roadiz\Core\AbstractEntities\TranslationInterface;
use RZ\Roadiz\CoreBundle\Api\TreeWalker\AutoChildrenNodeSourceWalker;
use RZ\Roadiz\CoreBundle\Bag\Settings;
use RZ\Roadiz\CoreBundle\EntityApi\NodeSourceApi;
use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface;
use RZ\Roadiz\CoreBundle\Repository\TranslationRepository;
use RZ\TreeWalker\WalkerContextInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
final class GetCommonContentController extends AbstractController
{
private RequestStack $requestStack;
private ManagerRegistry $managerRegistry;
private WalkerContextInterface $walkerContext;
private Settings $settingsBag;
private NodeSourceApi $nodeSourceApi;
private CacheItemPoolInterface $cacheItemPool;
private NodesSourcesHeadFactory $nodesSourcesHeadFactory;
private PreviewResolverInterface $previewResolver;
public function __construct(
RequestStack $requestStack,
ManagerRegistry $managerRegistry,
WalkerContextInterface $walkerContext,
Settings $settingsBag,
NodeSourceApi $nodeSourceApi,
NodesSourcesHeadFactory $nodesSourcesHeadFactory,
CacheItemPoolInterface $cacheItemPool,
PreviewResolverInterface $previewResolver
) {
$this->requestStack = $requestStack;
$this->walkerContext = $walkerContext;
$this->cacheItemPool = $cacheItemPool;
$this->nodeSourceApi = $nodeSourceApi;
$this->managerRegistry = $managerRegistry;
$this->nodesSourcesHeadFactory = $nodesSourcesHeadFactory;
$this->settingsBag = $settingsBag;
$this->previewResolver = $previewResolver;
}
public function __invoke(): ?CommonContent
{
try {
$request = $this->requestStack->getMainRequest();
$translation = $this->getTranslationFromRequest($request);
$home = $this->nodeSourceApi->getOneBy([
'node.home' => true,
'translation' => $translation
]);
$mainMenu = $this->nodeSourceApi->getOneBy([
'node.nodeName' => 'main-menu',
'translation' => $translation
]);
$footerMenu = $this->nodeSourceApi->getOneBy([
'node.nodeName' => 'footer-menu',
'translation' => $translation
]);
$errorPage = $this->nodeSourceApi->getOneBy([
'node.nodeName' => 'error-page',
'translation' => $translation
]);
$resource = new CommonContent();
if (null !== $home) {
$resource->home = $home;
}
if (null !== $mainMenu) {
$resource->mainMenuWalker = MenuNodeSourceWalker::build(
$mainMenu,
$this->walkerContext,
3,
$this->cacheItemPool
);
}
if (null !== $footerMenu) {
$resource->footerMenuWalker = MenuNodeSourceWalker::build(
$footerMenu,
$this->walkerContext,
3,
$this->cacheItemPool
);
}
if (null !== $footer) {
$resource->footerWalker = AutoChildrenNodeSourceWalker::build(
$footer,
$this->walkerContext,
3,
$this->cacheItemPool
);
}
if (null !== $errorPage) {
$resource->errorPageWalker = AutoChildrenNodeSourceWalker::build(
$errorPage,
$this->walkerContext,
3,
$this->cacheItemPool
);
}
if (null !== $request) {
$request->attributes->set('data', $resource);
}
$resource->head = $this->nodesSourcesHeadFactory->createForTranslation($translation);
return $resource;
} catch (ResourceNotFoundException $exception) {
throw new NotFoundHttpException($exception->getMessage(), $exception);
}
}
protected function getTranslationFromRequest(?Request $request): TranslationInterface
{
$locale = null;
if (null !== $request) {
$locale = $request->query->get('_locale');
/*
* If no _locale query param is defined check Accept-Language header
*/
if (null === $locale) {
$locale = $request->getPreferredLanguage($this->getTranslationRepository()->getAllLocales());
}
}
/*
* Then fallback to default CMS locale
*/
if (null === $locale) {
$translation = $this->getTranslationRepository()->findDefault();
} elseif ($this->previewResolver->isPreview()) {
$translation = $this->getTranslationRepository()
->findOneByLocaleOrOverrideLocale((string) $locale);
} else {
$translation = $this->getTranslationRepository()
->findOneAvailableByLocaleOrOverrideLocale((string) $locale);
}
if (null === $translation) {
throw new NotFoundHttpException('No translation for locale ' . $locale);
}
return $translation;
}
protected function getTranslationRepository(): TranslationRepository
{
return $this->managerRegistry->getRepository(TranslationInterface::class);
}
}
Then, the following resource will be exposed:

.. code-block:: json
{
"@context": "/api/contexts/CommonContent",
"@id": "/api/common_content?id=unique",
"@type": "CommonContent",
"home": {
"@id": "/api/pages/11",
"@type": "Page",
"content": null,
"image": [],
"title": "Accueil",
"publishedAt": "2022-04-12T16:24:00+02:00",
"node": {
"@type": "Node",
"@id": "/api/nodes/10",
"visible": true,
"tags": []
},
"slug": "accueil",
"url": "/fr"
},
"mainMenuWalker": {
"@type": "MenuNodeSourceWalker",
"@id": "_:3341",
"children": [],
"childrenCount": 0,
"item": {
"@id": "/api/menus/2",
"@type": "Menu",
"title": "Menu principal",
"publishedAt": "2022-04-12T00:39:00+02:00",
"node": {
"@type": "Node",
"@id": "/api/nodes/1",
"visible": false,
"tags": []
},
"slug": "main-menu"
},
"level": 0,
"maxLevel": 3
},
"footerMenuWalker": {
"@type": "MenuNodeSourceWalker",
"@id": "_:2381",
"children": [],
"childrenCount": 0,
"item": {
"@id": "/api/menus/3",
"@type": "Menu",
"linkInternalReference": [],
"title": "Menu du pied de page",
"publishedAt": "2022-04-12T11:18:12+02:00",
"node": {
"@type": "Node",
"@id": "/api/nodes/2",
"visible": false,
"tags": []
},
"slug": "footer-menu"
},
"level": 0,
"maxLevel": 3
},
"footerWalker": {
"@type": "AutoChildrenNodeSourceWalker",
"@id": "_:2377",
"children": [],
"childrenCount": 0,
"item": {
"@id": "/api/footers/16",
"@type": "Footer",
"content": "",
"title": "Pied de page",
"publishedAt": "2022-04-12T19:02:47+02:00",
"node": {
"@type": "Node",
"@id": "/api/nodes/15",
"visible": false,
"tags": []
},
"slug": "footer"
},
"level": 0,
"maxLevel": 3
},
"errorPageWalker": {
"@type": "AutoChildrenNodeSourceWalker",
"@id": "_:3465",
"children": [],
"childrenCount": 0,
"item": {
"@id": "/api/pages/153",
"@type": "Page",
"title": "Page d'erreur",
"publishedAt": "2022-05-12T17:16:40+02:00",
"node": {
"@type": "Node",
"@id": "/api/nodes/146",
"visible": false,
"tags": []
},
"slug": "error-page",
"url": "/fr/error-page"
},
"level": 0,
"maxLevel": 3
},
"head": {
"@type": "NodesSourcesHead",
"@id": "_:14679",
"googleAnalytics": null,
"googleTagManager": null,
"matomoUrl": null,
"matomoSiteId": null,
"siteName": "Roadiz dev website",
"metaTitle": "Contact – Roadiz dev website",
"metaDescription": "Contact, Roadiz dev website",
"policyUrl": null,
"mainColor": null,
"facebookUrl": null,
"instagramUrl": null,
"twitterUrl": null,
"youtubeUrl": null,
"linkedinUrl": null,
"homePageUrl": "/",
"shareImage": null
}
}
36 changes: 36 additions & 0 deletions src/developer/documents-system/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.. _documents-system-intro:

================
Documents system
================


Exposing documents in API
-------------------------

When using API Platform data transfer objects, Documents are ready-to-use with translations set-up on
``name`` and ``description`` fields. Made sure to configure your API operations with at least ``document_display``
serialization group:

.. code-block:: json
{
"@type": "Document",
"@id": "/api/documents/52",
"relativePath": "fffb9adc/my_image.jpg",
"type": "image",
"mimeType": "image/jpeg",
"name": null,
"description": null,
"embedId": null,
"embedPlatform": null,
"imageAverageColor": "#141414",
"imageWidth": 1000,
"imageHeight": 750,
"mediaDuration": 0,
"copyright": "© John Doe",
"externalUrl": null,
"processable": true,
"thumbnail": null,
"alt": "This is an image"
}

0 comments on commit cf70b46

Please sign in to comment.