Files
chill-bundles/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php
Julien Fastré 03b2496817 Fix dependency injection issues in AbstractCRUDController
Replaced incorrect service definitions in AbstractCRUDController to ensure proper dependency injection. Specifically, fixed retrieval of the ManagerRegistry and Validator services to resolve CalendarRange save errors (Issue #362). No schema changes were introduced.
2025-03-04 23:02:19 +01:00

304 lines
9.7 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\Resolver\Resolver;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
abstract class AbstractCRUDController extends AbstractController
{
/**
* The crud configuration.
*
* This configuration si defined by `chill_main['crud']` or `chill_main['apis']`
*/
protected array $crudConfig = [];
/**
* get the role given from the config.
*/
protected function getRoleFor(string $action, Request $request, mixed $entity, mixed $_format): string
{
$actionConfig = $this->getActionConfig($action);
if (null !== $actionConfig['roles'][$request->getMethod()]) {
return $actionConfig['roles'][$request->getMethod()];
}
if ($this->crudConfig['base_role']) {
return $this->crudConfig['base_role'];
}
throw new \RuntimeException(sprintf('the config does not have any role for the method %s nor a global role for the whole action. Add those to your configuration or override the required method', $request->getMethod()));
}
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,
'validator' => ValidatorInterface::class,
]
);
}
/**
* Set the crud configuration.
*
* Used by the container to inject configuration for this crud.
*/
public function setCrudConfig(array $config): void
{
$this->crudConfig = $config;
}
/**
* Build the base query for listing all entities.
*
* This method is used internally by `countEntities` `queryEntities`
*
* This base query does not contains any `WHERE` or `SELECT` clauses. You
* can add some by using the method `customizeQuery`.
*
* The alias for the entity is "e".
*
* @return QueryBuilder
*/
protected function buildQueryEntities(string $action, Request $request)
{
$qb = $this->getManagerRegistry()->getManager()
->createQueryBuilder()
->select('e')
->from($this->getEntityClass(), 'e');
$this->customizeQuery($action, $request, $qb);
return $qb;
}
/**
* 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.
*
* @param mixed|null $entity
*
* @throws AccessDeniedHttpException
*/
protected function checkACL(string $action, Request $request, string $_format, $entity = null)
{
// @TODO: Implements abstract getRoleFor method or do it in the interface.
$this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity);
}
/**
* Count the number of entities.
*
* By default, count all entities. You can customize the query by
* using the method `customizeQuery`.
*/
protected function countEntities(string $action, Request $request, mixed $_format): int
{
return $this->buildQueryEntities($action, $request)
->select('COUNT(e)')
->getQuery()
->getSingleScalarResult();
}
protected function createEntity(string $action, Request $request): object
{
$class = $this->getEntityClass();
return new $class();
}
protected function customizeQuery(string $action, Request $request, $query): void {}
protected function getActionConfig(string $action)
{
return $this->crudConfig['actions'][$action];
}
/**
* @return string the crud name
*/
protected function getCrudName(): string
{
return $this->crudConfig['name'];
}
/**
* get the instance of the entity with the given id.
*
* @throw Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the object is not found
*/
protected function getEntity(mixed $action, string $id, Request $request): object
{
$e = $this->getManagerRegistry()
->getRepository($this->getEntityClass())
->find($id);
if (null === $e) {
throw $this->createNotFoundException(sprintf('The object %s for id %s is not found', $this->getEntityClass(), $id));
}
if ($request->query->has('entity_version')) {
$expectedVersion = $request->query->getInt('entity_version');
try {
$manager = $this->getManagerRegistry()->getManagerForClass($this->getEntityClass());
if ($manager instanceof EntityManagerInterface) {
$manager->lock($e, LockMode::OPTIMISTIC, $expectedVersion);
} else {
throw new \LogicException('This manager does not allow locking.');
}
} catch (OptimisticLockException $e) {
throw new ConflictHttpException('Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again', $e);
}
}
return $e;
}
/**
* Get the FQDN of the class.
*
* @return class-string The FQDN of the class
*/
protected function getEntityClass(): string
{
return $this->crudConfig['class'];
}
protected function getPaginatorFactory(): PaginatorFactory
{
return $this->container->get('chill_main.paginator_factory');
}
protected function getManagerRegistry(): ManagerRegistry
{
return $this->container->get('doctrine');
}
/**
* Get the result of the query.
*/
protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, mixed $query)
{
return $query->getQuery()->getResult();
}
protected function getValidator(): ValidatorInterface
{
return $this->container->get('validator');
}
/**
* Called on post check ACL.
*/
protected function onPostCheckACL(string $action, Request $request, string $_format, mixed $entity): ?Response
{
return null;
}
/**
* Called on post fetch entity.
*/
protected function onPostFetchEntity(string $action, Request $request, mixed $entity, mixed $_format): ?Response
{
return null;
}
/**
* Method used by indexAction.
*/
protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, mixed $query): ?Response
{
return null;
}
/**
* Method used by indexAction.
*/
protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, mixed $entities): ?Response
{
return null;
}
protected function onPreIndex(string $action, Request $request, string $_format): ?Response
{
return null;
}
/**
* Method used by indexAction.
*/
protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response
{
return null;
}
/**
* Add ordering fields in the query build by self::queryEntities.
*/
protected function orderQuery(string $action, mixed $query, Request $request, PaginatorInterface $paginator, mixed $_format)
{
return $query;
}
/**
* Query the entity.
*
* By default, get all entities. You can customize the query by using the
* method `customizeQuery`.
*
* The method `orderEntity` is called internally to order entities.
*
* It returns, by default, a query builder.
*/
protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator)
{
$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, $_format);
}
}