1022 lines
33 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\CRUD\Controller;
use Chill\MainBundle\CRUD\Form\CRUDDeleteEntityForm;
use Chill\MainBundle\CRUD\Resolver\Resolver;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* The CRUDController class is an abstract class that provides basic CRUD (Create, Read, Update, Delete) functionality for entities in a Symfony application. It extends the Abstract
*Controller class.
*
* For the class \Chill\MainBundle\CRUD\Controller\CRUDController, the dependency injection system does not work as in
* the usual way, where dependencies are provided via the constructor method. Instead, it utilizes the capabilities
* provided by the \Symfony\Contracts\Service\ServiceSubscriberInterface.
*
* The \Symfony\Contracts\Service\ServiceSubscriberInterface is an interface that is used by objects needing explicit
* service references. It means this class has a direct dependency on the Symfony service container, allowing it to
* request specific services it needs for execution.
*
* By implementing the ServiceSubscriberInterface, this class effectively defines a method called getSubscribedServices,
* which returns an array mapping service IDs to their classes or interfaces. Symfony's service container uses this
* method to preconfigure the services required, optimizing the dependency injection process.
*
* This model can be used to keep the class itself concise by preventing the need to define a constructor and inject
* services through method calls. This approach also allows for more flexible service usage, as services can be fetched
* as and when required, rather than all services being loaded whether they are used or not. This makes for a more
* efficient and maintainable solution especially when dealing with larger services.
*
* For more details about how this works, you can refer to Symfony's Documentation
* on Service Subscribers & Service Locators: https://symfony.com/doc/current/service_container/service_subscribers_locators.html
*/
class CRUDController extends AbstractController
{
/**
* The crud configuration.
*
* This configuration si defined by `chill_main['crud']`.
*/
protected array $crudConfig;
public function CRUD(?string $parameter): Response
{
return new Response($parameter);
}
public function delete(Request $request, $id): Response
{
return $this->deleteAction('delete', $request, $id);
}
/**
* BAse method for edit action.
*
* IMplemented by the method formEditAction, with action as 'edit'
*/
public function edit(Request $request, mixed $id): Response
{
return $this->formEditAction('edit', $request, $id);
}
/**
* Get the context for the serialization.
*/
public function getContextForSerialization(string $action, Request $request, mixed $entity, string $_format): array
{
return [];
}
public static function getSubscribedServices(): array
{
return \array_merge(
parent::getSubscribedServices(),
[
'chill_main.paginator_factory' => PaginatorFactory::class,
'translator' => TranslatorInterface::class,
AuthorizationHelper::class => AuthorizationHelper::class,
EventDispatcherInterface::class => EventDispatcherInterface::class,
Resolver::class => Resolver::class,
SerializerInterface::class => SerializerInterface::class,
FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
ManagerRegistry::class => ManagerRegistry::class,
]
);
}
/**
* Base method called by index action.
*
* @return type
*/
public function index(Request $request)
{
return $this->indexEntityAction('index', $request);
}
/**
* Base method for new action.
*
* Implemented by the method formNewAction, with action as 'new'
*/
public function new(Request $request): Response
{
return $this->formCreateAction('new', $request);
}
public function setCrudConfig(array $config): void
{
$this->crudConfig = $config;
}
/**
* Base method for the view action.
*
* Implemented by the method viewAction, with action as 'view'
*/
public function view(Request $request, mixed $id): Response
{
return $this->viewAction('view', $request, $id);
}
/**
* build a default role name, using the crud resolver.
*
* This method should not be overriden. Override `getRoleFor` instead.
*
* @param string $action
*
* @return string
*/
protected function buildDefaultRole($action)
{
return $this->getCrudResolver()->buildDefaultRole(
$this->getCrudName(),
$action
);
}
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
{
return null;
}
/**
* Build the base query for listing all entities, normally use in a listing
* page.
*
* This base query does not contains any `WHERE` or `SELECT` clauses. Those
* are added by other methods, like `queryEntities` and `countQueries`.
*
* @return QueryBuilder
*/
protected function buildQueryEntities(string $action, Request $request)
{
$query = $this->getManagerRegistry()
->getManager()
->createQueryBuilder()
->select('e')
->from($this->getEntityClass(), 'e');
$this->customizeQuery($action, $request, $query);
return $query;
}
/**
* check the acl. Called by every action.
*
* By default, check the role given by `getRoleFor` for the value given in
* $entity.
*
* Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
* if not accessible.
*
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
*/
protected function checkACL(string $action, mixed $entity)
{
$this->denyAccessUnlessGranted($this->getRoleFor($action), $entity);
}
/**
* Count the number of entities.
*/
protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
{
return $this->buildQueryEntities($action, $request)
->select('COUNT(e)')
->getQuery()
->getSingleScalarResult();
}
/**
* Create an entity.
*/
protected function createEntity(string $action, Request $request): object
{
$type = $this->getEntityClass();
return new $type();
}
/**
* Create a form.
*
* use the method `getFormClassFor`
*
* A hook is available: `customizeForm` allow you to customize the form
* if needed.
*
* It is preferable to override customizeForm instead of overriding
* this method.
*/
protected function createFormFor(string $action, mixed $entity, ?string $formClass = null, array $formOptions = []): FormInterface
{
$formClass ??= $this->getFormClassFor($action);
$form = $this->createForm($formClass, $entity, $formOptions);
$this->customizeForm($action, $form);
return $form;
}
/**
* Customize the form created by createFormFor.
*/
protected function customizeForm(string $action, FormInterface $form) {}
protected function customizeQuery(string $action, Request $request, $query): void {}
/**
* @param null $formClass
*/
protected function deleteAction(string $action, Request $request, $id, $formClass = null): Response
{
$this->onPreDelete($action, $request);
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity);
if ($postFetch instanceof Response) {
return $postFetch;
}
if (null === $entity) {
throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$form = $this->createFormFor($action, $entity, $formClass);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->onFormValid($action, $entity, $form, $request);
$em = $this->getManagerRegistry()->getManager();
$this->onPreRemove($action, $entity, $form, $request);
$this->removeEntity($action, $entity, $form, $request);
$this->onPostRemove($action, $entity, $form, $request);
$this->onPreFlush($action, $entity, $form, $request);
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]);
}
if ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
}
$defaultTemplateParameters = [
'form' => $form->createView(),
'entity' => $entity,
'crud_name' => $this->getCrudName(),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
/**
* Duplicate an entity.
*/
protected function duplicateEntity(string $action, Request $request)
{
$id = $request->query->get('duplicate_id', 0);
$originalEntity = $this->getEntity($action, $id, $request);
$this->getManagerRegistry()->getManager()
->detach($originalEntity);
return $originalEntity;
}
/**
* The new (or creation) action.
*
* Some steps may be overriden during this process of rendering:
*
* This method:
*
* 1. Create or duplicate an entity:
*
* If the `duplicate` parameter is present, the entity is duplicated
* using the `duplicate` method.
*
* If not, the entity is created using the `create` method.
* 3. check ACL using `checkACL` ;
* 4. launch `onPostCheckACL`. If the result is an instance of Response,
* this response is returned ;
* 5. generate a form using `createFormFor`, and handle request on this form;
*
* If the form is valid, the entity is stored and flushed, and a redirection
* is returned.
*
* In this case, those hooks are available:
*
* * onFormValid
* * onPreFlush
* * onPostFlush
* * onBeforeRedirectAfterSubmission. If this method return an instance of
* Response, this response is returned.
*
* 5. generate default template parameters:
*
* * `entity`: the fetched entity ;
* * `crud_name`: the crud name ;
* * `form`: the formview instance.
*
* 6. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*
* @param type $formClass
*/
protected function formCreateAction(string $action, Request $request, $formClass = null): Response
{
if ($request->query->has('duplicate')) {
$entity = $this->duplicateEntity($action, $request);
} else {
$entity = $this->createEntity($action, $request);
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$form = $this->createFormFor($action, $entity, $formClass);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->onFormValid($action, $entity, $form, $request);
$em = $this->getManagerRegistry()->getManager();
$this->onPrePersist($action, $entity, $form, $request);
$em->persist($entity);
$this->onPostPersist($action, $entity, $form, $request);
$this->onPreFlush($action, $entity, $form, $request);
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]);
}
if ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
}
$defaultTemplateParameters = [
'form' => $form->createView(),
'entity' => $entity,
'crud_name' => $this->getCrudName(),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
/**
* The edit action.
*
* Some steps may be overriden during this process of rendering:
*
* This method:
*
* 1. fetch the entity, using `getEntity`
* 2. launch `onPostFetchEntity`. If postfetch is an instance of Response,
* this response is returned.
* 2. throw an HttpNotFoundException if entity is null
* 3. check ACL using `checkACL` ;
* 4. launch `onPostCheckACL`. If the result is an instance of Response,
* this response is returned ;
* 5. generate a form using `createFormFor`, and handle request on this form;
*
* If the form is valid, the entity is stored and flushed, and a redirection
* is returned.
*
* In this case, those hooks are available:
*
* * onFormValid
* * onPreFlush
* * onPostFlush
* * onBeforeRedirectAfterSubmission. If this method return an instance of
* Response, this response is returned.
*
* 5. generate default template parameters:
*
* * `entity`: the fetched entity ;
* * `crud_name`: the crud name ;
* * `form`: the formview instance.
*
* 6. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*
* @param class-string $formClass
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function formEditAction(string $action, Request $request, mixed $id, ?string $formClass = null, array $formOptions = []): Response
{
$entity = $this->getEntity($action, $id, $request);
if (null === $entity) {
throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$form = $this->createFormFor($action, $entity, $formClass, $formOptions);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->onFormValid($action, $entity, $form, $request);
$em = $this->getManagerRegistry()->getManager();
$this->onPreFlush($action, $entity, $form, $request);
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index');
}
if ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
}
$defaultTemplateParameters = [
'form' => $form->createView(),
'entity' => $entity,
'crud_name' => $this->getCrudName(),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
/**
* Generate a message which explains an error about the form.
*
* Used in form actions
*/
protected function generateFormErrorMessage(string $action, FormInterface $form): string
{
$msg = 'This form contains errors';
return $this->getTranslator()->trans($msg);
}
/**
* Generate a success message when a form could be flushed successfully.
*
* @param string $action
*/
protected function generateFormSuccessMessage($action, mixed $entity): string
{
$msg = match ($action) {
'edit' => 'crud.edit.success',
'new' => 'crud.new.success',
'delete' => 'crud.delete.success',
default => 'crud.default.success',
};
return $this->getTranslator()->trans($msg);
}
/**
* Customize template parameters.
*
* @return array
*/
protected function generateTemplateParameter(
string $action,
mixed $entity,
Request $request,
array $defaultTemplateParameters = []
) {
return $defaultTemplateParameters;
}
/**
* Include services.
*/
protected function getActionConfig(string $action)
{
return $this->crudConfig['actions'][$action];
}
protected function getAuthorizationHelper(): AuthorizationHelper
{
return $this->container->get(AuthorizationHelper::class);
}
/**
* @return string the crud name
*/
protected function getCrudName(): string
{
return $this->crudConfig['name'];
}
protected function getCrudResolver(): Resolver
{
return $this->get(Resolver::class);
}
protected function getDefaultDeleteFormClass($action)
{
return CRUDDeleteEntityForm::class;
}
/**
* get the instance of the entity with the given id.
*
* @param string $id
*/
protected function getEntity(mixed $action, $id, Request $request): ?object
{
return $this->getManagerRegistry()
->getRepository($this->getEntityClass())
->find($id);
}
/**
* @return string the complete fqdn of the class
*/
protected function getEntityClass(): string
{
return $this->crudConfig['class'];
}
protected function getEventDispatcher(): EventDispatcherInterface
{
return $this->get(EventDispatcherInterface::class);
}
protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
{
return $this->get(FilterOrderHelperFactoryInterface::class);
}
/**
* get the default form class from config.
*
* @param string $action
*
* @return string the FQDN of the form class
*/
protected function getFormClassFor($action)
{
if ('delete' === $action) {
return $this->crudConfig[$action]['form_class']
?? $this->getDefaultDeleteFormClass($action);
}
return $this->crudConfig[$action]['form_class']
?? $this->crudConfig['form_class'];
}
/**
* @todo (check how to do this with dependency injection and make changes...)
*/
protected function getPaginatorFactory(): PaginatorFactory
{
return $this->container->get('chill_main.paginator_factory');
}
protected function getManagerRegistry(): ManagerRegistry
{
return $this->container->get(ManagerRegistry::class);
}
/**
* Get the result of the query.
*/
protected function getQueryResult(
string $action,
Request $request,
int $totalItems,
PaginatorInterface $paginator,
?FilterOrderHelper $filterOrder = null
) {
$query = $this->queryEntities($action, $request, $paginator, $filterOrder);
return $query->getQuery()->getResult();
}
/**
* @return \Chill\MainBundle\Entity\Center[]
*/
protected function getReachableCenters(string $role, ?Scope $scope = null)
{
return $this->getAuthorizationHelper()
->getReachableCenters($this->getUser(), $role, $scope);
}
/**
* get the role given from the config.
*
* @param string $action
*
* @return string
*/
protected function getRoleFor($action)
{
if (\array_key_exists('role', $this->getActionConfig($action))) {
return $this->getActionConfig($action)['role'];
}
return $this->buildDefaultRole($action);
}
/**
* Get the template for the current crud.
*
* This template may be defined in configuration. If any template are
* defined, return the default template for the actions new, edit, index,
* and view.
*
* @param string $action
*
* @return string the path to the template
*
* @throws \LogicException if no template are available
*/
protected function getTemplateFor($action, mixed $entity, Request $request)
{
if ($this->hasCustomTemplate($action, $entity, $request)) {
return $this->getActionConfig($action)['template'];
}
return match ($action) {
'new' => '@ChillMain/CRUD/new.html.twig',
'edit' => '@ChillMain/CRUD/edit.html.twig',
'index' => '@ChillMain/CRUD/index.html.twig',
'view' => '@ChillMain/CRUD/view.html.twig',
'delete' => '@ChillMain/CRUD/delete.html.twig',
default => throw new \LogicException("the view for action {$action} is not ".'defined. You should override '.__METHOD__.' to add this action'),
};
}
protected function getTranslator(): TranslatorInterface
{
return $this->container->get('translator');
}
protected function hasCustomTemplate($action, $entity, Request $request): bool
{
return !empty($this->getActionConfig($action)['template']);
}
/**
* Build an index page.
*
* Some steps may be overriden during this process of rendering.
*
* This method:
*
* 1. Launch `onPreIndex`
* 2. check acl. If it does return a response instance, return it
* 3. launch `onPostCheckACL`. If it does return a response instance, return it
* 4. count the items, using `countEntities`
* 5. build a paginator element from the the number of entities ;
* 6. Launch `onPreIndexQuery`. If it does return a response instance, return it
* 7. fetch the results, using `getQueryResult`
*
* Internally, this build a query, using `queryEntities`
*
* 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
* 9. create default parameters:
*
* The default parameters are:
*
* * entities: the list en entities ;
* * crud_name: the name of the crud ;
* * paginator: a paginator element ;
* 10. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*
* @param string $action
*/
protected function indexEntityAction($action, Request $request)
{
$this->onPreIndex($action, $request);
$response = $this->checkACL($action, null);
if ($response instanceof Response) {
return $response;
}
$entity = '';
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$filterOrder = $this->buildFilterOrderHelper($action, $request);
$totalItems = $this->countEntities($action, $request, $filterOrder);
$paginator = $this->getPaginatorFactory()->create($totalItems);
$response = $this->onPreIndexBuildQuery(
$action,
$request,
$totalItems,
$paginator
);
if ($response instanceof Response) {
return $response;
}
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
$response = $this->onPostIndexFetchQuery(
$action,
$request,
$totalItems,
$paginator,
$entities
);
if ($response instanceof Response) {
return $response;
}
$defaultTemplateParameters = [
'entities' => $entities,
'crud_name' => $this->getCrudName(),
'paginator' => $paginator,
'filter_order' => $filterOrder,
];
return $this->render(
$this->getTemplateFor($action, $entities, $request),
$this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters)
);
}
/**
* Return a redirect response depending on the value of submit button.
*
* The handled values are :
*
* * save-and-close: return to index of current crud ;
* * save-and-new: return to new page of current crud ;
* * save-and-view: return to view page of current crud ;
*/
protected function onBeforeRedirectAfterSubmission(string $action, mixed $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close' => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'),
'save-and-new' => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', $request->query->all()),
default => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [
'id' => $entity->getId(),
]),
};
}
protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request) {}
protected function onPostCheckACL($action, Request $request, $entity): ?Response
{
return null;
}
protected function onPostFetchEntity($action, Request $request, $entity): ?Response
{
return null;
}
protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) {}
/**
* method used by indexAction.
*/
protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, mixed $query) {}
/**
* method used by indexAction.
*/
protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, mixed $entities) {}
protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) {}
protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {}
protected function onPreDelete(string $action, Request $request) {}
protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) {}
protected function onPreIndex(string $action, Request $request) {}
/**
* method used by indexAction.
*/
protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) {}
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) {}
protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {}
/**
* Add ordering fields in the query build by self::queryEntities.
*
* @param mixed|QueryBuilder $query by default, an instance of QueryBuilder
*
* @return mixed|QueryBuilder
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
return $query;
}
/**
* Query the entity.
*
* By default, get all entities.
*
* The method `orderEntity` is called internally to order entities.
*
* It returns, by default, a query builder.
*
* @return type
*/
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
{
$query = $this->buildQueryEntities($action, $request)
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
->setMaxResults($paginator->getItemsPerPage());
// allow to order queries and return the new query
return $this->orderQuery($action, $query, $request, $paginator);
}
protected function removeEntity(string $action, $entity, FormInterface $form, Request $request)
{
$this->getManagerRegistry()
->getManager()
->remove($entity);
}
/**
* The view action.
*
* Some steps may be overriden during this process of rendering:
*
* This method:
*
* 1. fetch the entity, using `getEntity`
* 2. launch `onPostFetchEntity`. If postfetch is an instance of Response,
* this response is returned.
* 2. throw an HttpNotFoundException if entity is null
* 3. check ACL using `checkACL` ;
* 4. launch `onPostCheckACL`. If the result is an instance of Response,
* this response is returned ;
* 5. generate default template parameters:
*
* * `entity`: the fetched entity ;
* * `crud_name`: the crud name
* 6. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*/
protected function viewAction(string $action, Request $request, mixed $id, mixed $_format = 'html'): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity);
if ($postFetch instanceof Response) {
return $postFetch;
}
if (null === $entity) {
throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
if ('html' === $_format) {
$defaultTemplateParameters = [
'entity' => $entity,
'crud_name' => $this->getCrudName(),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
if ('json' === $_format) {
$context = $this->getContextForSerialization($action, $request, $entity, $_format);
return $this->json($entity, Response::HTTP_OK, [], $context);
}
throw new BadRequestHttpException('This format is not implemented');
}
}