cs: Fix code style (safe rules only).

This commit is contained in:
Pol Dellaiera
2021-11-23 14:06:38 +01:00
parent 149d7ce991
commit 8f96a1121d
1223 changed files with 65199 additions and 64625 deletions

View File

@@ -1,33 +1,19 @@
<?php
/*
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\CRUD\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Chill\MainBundle\Routing\MenuComposer;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Alias;
/**
*
* 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\CompilerPass;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
class CRUDControllerCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
@@ -45,15 +31,15 @@ class CRUDControllerCompilerPass implements CompilerPassInterface
}
/**
* Add a controller for each definition, and add a methodCall to inject crud configuration to controller
* Add a controller for each definition, and add a methodCall to inject crud configuration to controller.
*/
private function configureCrudController(ContainerBuilder $container, array $crudEntry, string $apiOrCrud): void
{
$controllerClass = $crudEntry['controller'];
$controllerServiceName = 'cs'.$apiOrCrud.'_'.$crudEntry['name'].'_controller';
$controllerServiceName = 'cs' . $apiOrCrud . '_' . $crudEntry['name'] . '_controller';
// create config parameter in container
$param = 'chill_main_'.$apiOrCrud.'_config_'.$crudEntry['name'];
$param = 'chill_main_' . $apiOrCrud . '_config_' . $crudEntry['name'];
$container->setParameter($param, $crudEntry);
if ($container->hasDefinition($controllerClass)) {
@@ -63,8 +49,7 @@ class CRUDControllerCompilerPass implements CompilerPassInterface
// add the "addMethodCall"
$container->getDefinition($controllerClass)
->addMethodCall('setCrudConfig', ['%'.$param.'%']);
->addMethodCall('setCrudConfig', ['%' . $param . '%']);
} else {
$controller = new Definition($controllerClass);
@@ -72,10 +57,9 @@ class CRUDControllerCompilerPass implements CompilerPassInterface
$controller->setAutoconfigured(true);
$controller->setPublic(true);
$controller->addMethodCall('setCrudConfig', ['%'.$param.'%']);
$controller->addMethodCall('setCrudConfig', ['%' . $param . '%']);
$container->setDefinition($controllerServiceName, $controller);
}
}
}

View File

@@ -1,96 +1,63 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\CRUD\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
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\CRUD\Resolver\Resolver;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use function array_merge;
abstract class AbstractCRUDController extends AbstractController
{
/**
* The crud configuration
* The crud configuration.
*
* This configuration si defined by `chill_main['crud']` or `chill_main['apis']`
*/
protected array $crudConfig = [];
/**
* 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($action, string $id, Request $request): object
public static function getSubscribedServices(): array
{
$e = $this
->getDoctrine()
->getRepository($this->getEntityClass())
->find($id);
return array_merge(
parent::getSubscribedServices(),
[
'chill_main.paginator_factory' => PaginatorFactory::class,
if (null === $e) {
throw $this->createNotFoundException(sprintf("The object %s for id %s is not found", $this->getEntityClass(), $id));
}
return $e;
}
protected function createEntity(string $action, Request $request): object
{
$class = $this->getEntityClass();
return new $class;
'translator' => TranslatorInterface::class,
AuthorizationHelper::class => AuthorizationHelper::class,
EventDispatcherInterface::class => EventDispatcherInterface::class,
Resolver::class => Resolver::class,
SerializerInterface::class => SerializerInterface::class,
'validator' => ValidatorInterface::class,
]
);
}
/**
* Count the number of entities
* Set the crud configuration.
*
* By default, count all entities. You can customize the query by
* using the method `customizeQuery`.
* Used by the container to inject configuration for this crud.
*/
protected function countEntities(string $action, Request $request, $_format): int
public function setCrudConfig(array $config): void
{
return $this->buildQueryEntities($action, $request)
->select('COUNT(e)')
->getQuery()
->getSingleScalarResult();
}
/**
* 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);
}
/**
* Add ordering fields in the query build by self::queryEntities
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query;
$this->crudConfig = $config;
}
/**
@@ -117,16 +84,157 @@ abstract class AbstractCRUDController extends AbstractController
return $qb;
}
protected function customizeQuery(string $action, Request $request, $query): void {}
/**
* 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 \Symfony\Component\Security\Core\Exception\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`.
*
* @param mixed $_format
*/
protected function countEntities(string $action, Request $request, $_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
*
* @param mixed $action
*/
protected function getEntity($action, string $id, Request $request): object
{
$e = $this
->getDoctrine()
->getRepository($this->getEntityClass())
->find($id);
if (null === $e) {
throw $this->createNotFoundException(sprintf('The object %s for id %s is not found', $this->getEntityClass(), $id));
}
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');
}
/**
* Get the result of the query.
*
* @param mixed $query
*/
protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query)
{
return $query->getQuery()->getResult();
}
protected function getValidator(): ValidatorInterface
{
return $this->get('validator');
}
/**
* Called on post check ACL.
*
* @param mixed $entity
*/
protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response
{
return null;
}
/**
* Called on post fetch entity.
*
* @param mixed $entity
* @param mixed $_format
*/
protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response
{
return null;
}
/**
* Method used by indexAction.
*
* @param mixed $query
*/
protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response
{
return null;
}
/**
* Method used by indexAction.
*
* @param mixed $entities
*/
protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response
{
return null;
}
protected function onPreIndex(string $action, Request $request, string $_format): ?Response
{
return null;
@@ -141,111 +249,33 @@ abstract class AbstractCRUDController extends AbstractController
}
/**
* Method used by indexAction.
*/
protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response
{
return null;
}
/**
* Method used by indexAction.
*/
protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response
{
return null;
}
/**
* Get the FQDN of the class.
* Add ordering fields in the query build by self::queryEntities.
*
* @return class-string The FQDN of the class
* @param mixed $query
* @param mixed $_format
*/
protected function getEntityClass(): string
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $this->crudConfig['class'];
return $query;
}
/**
* Called on post fetch entity.
*/
protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response
{
return null;
}
/**
* Called on post check ACL.
*/
protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response
{
return null;
}
/**
* check the acl. Called by every action.
* Query the entity.
*
* By default, check the role given by `getRoleFor` for the value given in
* $entity.
* By default, get all entities. You can customize the query by using the
* method `customizeQuery`.
*
* Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
* if not accessible.
* The method `orderEntity` is called internally to order entities.
*
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
* It returns, by default, a query builder.
*/
protected function checkACL(string $action, Request $request, string $_format, $entity = null)
protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator)
{
// @TODO: Implements abstract getRoleFor method or do it in the interface.
$this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity);
}
$query = $this->buildQueryEntities($action, $request)
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
->setMaxResults($paginator->getItemsPerPage());
/**
* @return string The crud name.
*/
protected function getCrudName(): string
{
return $this->crudConfig['name'];
}
protected function getActionConfig(string $action)
{
return $this->crudConfig['actions'][$action];
}
/**
* Set the crud configuration
*
* Used by the container to inject configuration for this crud.
*/
public function setCrudConfig(array $config): void
{
$this->crudConfig = $config;
}
protected function getPaginatorFactory(): PaginatorFactory
{
return $this->container->get('chill_main.paginator_factory');
}
protected function getValidator(): ValidatorInterface
{
return $this->get('validator');
}
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,
]
);
// allow to order queries and return the new query
return $this->orderQuery($action, $query, $request, $paginator, $_format);
}
}

View File

@@ -1,80 +1,38 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\CRUD\Controller;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Exception;
use LogicException;
use RuntimeException;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use function array_merge;
use function ucfirst;
class ApiController extends AbstractCRUDController
{
/**
* The view action.
* Base method for handling api 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. Serialize the entity and return the result. The serialization context is given by `getSerializationContext`
*
*/
protected function entityGet(string $action, Request $request, $id, $_format = 'html'): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
if ($postFetch instanceof Response) {
return $postFetch;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
if ($_format === 'json') {
$context = $this->getContextForSerialization($action, $request, $_format, $entity);
return $this->json($entity, Response::HTTP_OK, [], $context);
} else {
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented");
}
}
public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response
{
return null;
}
/**
* Base method for handling api action
* @param mixed $id
* @param mixed $_format
*
* @return void
*/
@@ -84,163 +42,45 @@ class ApiController extends AbstractCRUDController
case Request::METHOD_GET:
case Request::METHOD_HEAD:
return $this->entityGet('_entity', $request, $id, $_format);
case Request::METHOD_PUT:
case Request::METHOD_PATCH:
return $this->entityPut('_entity', $request, $id, $_format);
case Request::METHOD_POST:
return $this->entityPostAction('_entity', $request, $id);
case Request::METHOD_DELETE:
return $this->entityDelete('_entity', $request, $id, $_format);
default:
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented");
}
}
public function entityPost(Request $request, $_format): Response
{
switch($request->getMethod()) {
case Request::METHOD_POST:
return $this->entityPostAction('_entity', $request, $_format);
default:
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented");
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented');
}
}
protected function entityPostAction($action, Request $request, string $_format): Response
{
$entity = $this->createEntity($action, $request);
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
throw new BadRequestException("invalid json", 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
if ($errors->count() > 0) {
$response = $this->json($errors);
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
return $response;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$this->getDoctrine()->getManager()->persist($entity);
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
return $this->json(
$entity,
Response::HTTP_OK,
[],
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
);
}
public function entityPut($action, Request $request, $id, string $_format): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
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, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
throw new BadRequestException("invalid json", 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
if ($errors->count() > 0) {
$response = $this->json($errors);
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
return $response;
}
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
return $this->json(
$entity,
Response::HTTP_OK,
[],
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
);
}
public function entityDelete($action, Request $request, $id, string $_format): 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));
if (null === $entity) {
throw $this->createNotFoundException(sprintf('The %s with id %s '
. 'is not found', $this->getCrudName(), $id));
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
@@ -248,6 +88,7 @@ class ApiController extends AbstractCRUDController
$errors = $this->validate($action, $request, $_format, $entity);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
@@ -263,6 +104,7 @@ class ApiController extends AbstractCRUDController
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
@@ -270,51 +112,89 @@ class ApiController extends AbstractCRUDController
return $this->json(Response::HTTP_OK);
}
protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
public function entityPost(Request $request, $_format): Response
{
return null;
switch ($request->getMethod()) {
case Request::METHOD_POST:
return $this->entityPostAction('_entity', $request, $_format);
default:
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented');
}
}
protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
public function entityPut($action, Request $request, $id, string $_format): Response
{
return null;
}
$entity = $this->getEntity($action, $id, $request);
protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array
{
return null;
}
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface
{
$validationGroups = $this->getValidationGroups($action, $request, $_format, $entity);
return $this->getValidator()->validate($entity, null, $validationGroups);
}
/**
* Deserialize the content of the request into the class associated with the curd
*/
protected function deserialize(string $action, Request $request, string $_format, $entity = null): object
{
$default = [];
if (NULL !== $entity) {
$default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity;
if ($postFetch instanceof Response) {
return $postFetch;
}
$context = \array_merge(
$default,
$this->getContextForSerialization($action, $request, $_format, $entity)
);
if (null === $entity) {
throw $this->createNotFoundException(sprintf('The %s with id %s '
. 'is not found', $this->getCrudName(), $id));
}
return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context);
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
throw new BadRequestException('invalid json', 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
if ($errors->count() > 0) {
$response = $this->json($errors);
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
return $response;
}
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
return $this->json(
$entity,
Response::HTTP_OK,
[],
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
);
}
/**
* Base action for indexing entities
* Base action for indexing entities.
*/
public function indexApi(Request $request, string $_format)
{
@@ -322,82 +202,15 @@ class ApiController extends AbstractCRUDController
case Request::METHOD_GET:
case REQUEST::METHOD_HEAD:
return $this->indexApiAction('_index', $request, $_format);
default:
throw $this->createNotFoundException("This method is not supported");
throw $this->createNotFoundException('This method is not supported');
}
}
/**
* Build an index page.
*
* Some steps may be overriden during this process of rendering.
*
* This method:
*
* 1. Launch `onPreIndex`
* x. check acl. If it does return a response instance, return it
* x. launch `onPostCheckACL`. If it does return a response instance, return it
* 1. count the items, using `countEntities`
* 2. build a paginator element from the the number of entities ;
* 3. Launch `onPreIndexQuery`. If it does return a response instance, return it
* 3. build a query, using `queryEntities`
* x. fetch the results, using `getQueryResult`
* x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
* 4. Serialize the entities in a Collection, using `SerializeCollection`
*
* @param string $action
* @param Request $request
*/
protected function indexApiAction($action, Request $request, $_format)
public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response
{
$this->onPreIndex($action, $request, $_format);
$response = $this->checkACL($action, $request, $_format);
if ($response instanceof Response) {
return $response;
}
$entity = '';
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$totalItems = $this->countEntities($action, $request, $_format);
$paginator = $this->getPaginatorFactory()->create($totalItems);
$response = $this->onPreIndexBuildQuery(
$action,
$request,
$_format,
$totalItems,
$paginator
);
if ($response instanceof Response) {
return $response;
}
$query = $this->queryEntities($action, $request, $_format, $paginator);
$response = $this->onPostIndexBuildQuery($action, $request, $_format, $totalItems,
$paginator, $query);
if ($response instanceof Response) {
return $response;
}
$entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query);
$response = $this->onPostIndexFetchQuery($action, $request, $_format, $totalItems,
$paginator, $entities);
if ($response instanceof Response) {
return $response;
}
return $this->serializeCollection($action, $request, $_format, $paginator, $entities);
return null;
}
/**
@@ -420,36 +233,38 @@ class ApiController extends AbstractCRUDController
*
* @param string action
* @param mixed id
* @param Request $request
* @param string $_format
* @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method
* @param string $postedDataType the type of the posted data (the content)
* @param string $postedDataContext a context to deserialize posted data (the content)
* @param bool $forcePersist force to persist the created element (only for POST request)
* @param mixed $id
* @throw BadRequestException if unable to deserialize the posted data
* @throw BadRequestException if the method is not POST or DELETE
*
*/
protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, array $postedDataContext = [], bool $forcePersist = false): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
if ($postFetch instanceof Response) {
return $postFetch;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
@@ -457,25 +272,30 @@ class ApiController extends AbstractCRUDController
try {
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
throw new BadRequestException(sprintf("Unable to deserialize posted ".
"data: %s", $e->getMessage()), 0, $e);
throw new BadRequestException(sprintf('Unable to deserialize posted ' .
'data: %s', $e->getMessage()), 0, $e);
}
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
// oups... how to use property accessor to remove element ?
$entity->{'remove'.\ucfirst($property)}($postedData);
$entity->{'remove' . ucfirst($property)}($postedData);
break;
case Request::METHOD_POST:
$entity->{'add'.\ucfirst($property)}($postedData);
$entity->{'add' . ucfirst($property)}($postedData);
break;
default:
throw new BadRequestException("this method is not supported");
throw new BadRequestException('this method is not supported');
}
$errors = $this->validate($action, $request, $_format, $entity, [$postedData]);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors, [$postedData]);
if ($response instanceof Response) {
return $response;
}
@@ -491,8 +311,8 @@ class ApiController extends AbstractCRUDController
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors, [$postedData]);
if ($response instanceof Response) {
return $response;
}
@@ -500,6 +320,7 @@ class ApiController extends AbstractCRUDController
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
return $this->json('', Response::HTTP_OK);
case Request::METHOD_POST:
return $this->json(
$postedData,
@@ -509,12 +330,308 @@ class ApiController extends AbstractCRUDController
);
}
throw new \Exception('Unable to handle such request method.');
throw new Exception('Unable to handle such request method.');
}
/**
* Serialize collections
* Deserialize the content of the request into the class associated with the curd.
*
* @param mixed|null $entity
*/
protected function deserialize(string $action, Request $request, string $_format, $entity = null): object
{
$default = [];
if (null !== $entity) {
$default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity;
}
$context = array_merge(
$default,
$this->getContextForSerialization($action, $request, $_format, $entity)
);
return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context);
}
/**
* 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. Serialize the entity and return the result. The serialization context is given by `getSerializationContext`
*
* @param mixed $id
* @param mixed $_format
*/
protected function entityGet(string $action, Request $request, $id, $_format = 'html'): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
if ($postFetch instanceof Response) {
return $postFetch;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
if ('json' === $_format) {
$context = $this->getContextForSerialization($action, $request, $_format, $entity);
return $this->json($entity, Response::HTTP_OK, [], $context);
}
throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented');
}
protected function entityPostAction($action, Request $request, string $_format): Response
{
$entity = $this->createEntity($action, $request);
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
throw new BadRequestException('invalid json', 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
if ($errors->count() > 0) {
$response = $this->json($errors);
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
return $response;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$this->getDoctrine()->getManager()->persist($entity);
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
return $this->json(
$entity,
Response::HTTP_OK,
[],
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
);
}
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
{
switch ($request->getMethod()) {
case Request::METHOD_GET:
return ['groups' => ['read']];
case Request::METHOD_PUT:
case Request::METHOD_PATCH:
case Request::METHOD_POST:
return ['groups' => ['write']];
default:
throw new LogicException('get context for serialization is not implemented for this method');
}
}
/**
* Get the context for serialization post alter query (in case of
* PATCH, PUT, or POST method).
*
* This is called **after** the entity was altered.
*
* @param mixed $entity
*/
protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array
{
return ['groups' => ['read']];
}
/**
* get the role given from the config.
*
* @param mixed $entity
* @param mixed $_format
*/
protected function getRoleFor(string $action, Request $request, $entity, $_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()));
}
protected function getSerializer(): SerializerInterface
{
return $this->get('serializer');
}
protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array
{
return null;
}
/**
* Build an index page.
*
* Some steps may be overriden during this process of rendering.
*
* This method:
*
* 1. Launch `onPreIndex`
* x. check acl. If it does return a response instance, return it
* x. launch `onPostCheckACL`. If it does return a response instance, return it
* 1. count the items, using `countEntities`
* 2. build a paginator element from the the number of entities ;
* 3. Launch `onPreIndexQuery`. If it does return a response instance, return it
* 3. build a query, using `queryEntities`
* x. fetch the results, using `getQueryResult`
* x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
* 4. Serialize the entities in a Collection, using `SerializeCollection`
*
* @param string $action
* @param mixed $_format
*/
protected function indexApiAction($action, Request $request, $_format)
{
$this->onPreIndex($action, $request, $_format);
$response = $this->checkACL($action, $request, $_format);
if ($response instanceof Response) {
return $response;
}
$entity = '';
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$totalItems = $this->countEntities($action, $request, $_format);
$paginator = $this->getPaginatorFactory()->create($totalItems);
$response = $this->onPreIndexBuildQuery(
$action,
$request,
$_format,
$totalItems,
$paginator
);
if ($response instanceof Response) {
return $response;
}
$query = $this->queryEntities($action, $request, $_format, $paginator);
$response = $this->onPostIndexBuildQuery(
$action,
$request,
$_format,
$totalItems,
$paginator,
$query
);
if ($response instanceof Response) {
return $response;
}
$entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query);
$response = $this->onPostIndexFetchQuery(
$action,
$request,
$_format,
$totalItems,
$paginator,
$entities
);
if ($response instanceof Response) {
return $response;
}
return $this->serializeCollection($action, $request, $_format, $paginator, $entities);
}
protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
{
return null;
}
protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
{
return null;
}
/**
* Serialize collections.
*
* @param mixed $entities
*/
protected function serializeCollection(string $action, Request $request, string $_format, PaginatorInterface $paginator, $entities): Response
{
@@ -525,55 +642,10 @@ class ApiController extends AbstractCRUDController
return $this->json($model, Response::HTTP_OK, [], $context);
}
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface
{
switch ($request->getMethod()) {
case Request::METHOD_GET:
return [ 'groups' => [ 'read' ]];
case Request::METHOD_PUT:
case Request::METHOD_PATCH:
case Request::METHOD_POST:
return [ 'groups' => [ 'write' ]];
default:
throw new \LogicException("get context for serialization is not implemented for this method");
}
}
$validationGroups = $this->getValidationGroups($action, $request, $_format, $entity);
/**
* Get the context for serialization post alter query (in case of
* PATCH, PUT, or POST method)
*
* This is called **after** the entity was altered.
*/
protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array
{
return [ 'groups' => [ 'read' ]];
}
/**
* get the role given from the config.
*/
protected function getRoleFor(string $action, Request $request, $entity, $_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()));
}
protected function getSerializer(): SerializerInterface
{
return $this->get('serializer');
return $this->getValidator()->validate($entity, null, $validationGroups);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,18 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2020, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\CRUD\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
/**
* Class CRUDDeleteEntityForm
*
* @package Chill\MainBundle\CRUD\Form
* Class CRUDDeleteEntityForm.
*/
class CRUDDeleteEntityForm extends AbstractType
{

View File

@@ -1,122 +1,114 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2019, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\CRUD\Resolver;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use LogicException;
use function array_key_exists;
use function strtoupper;
/**
* Class Resolver
*
* @package Chill\MainBundle\CRUD\Resolver
* Class Resolver.
*/
class Resolver
{
/**
* @var EntityManagerInterface
* The key to get the role necessary for the action.
*/
protected $em;
public const ROLE = 'role';
/**
* @var \Symfony\Component\PropertyAccess\PropertyAccessor
* @deprecated
*/
protected $propertyAccess;
public const ROLE_EDIT = 'role.edit';
/**
* @deprecated
*/
public const ROLE_VIEW = 'role.view';
/**
* @var array
*/
protected $crudConfig;
/**
* @deprecated
* @var EntityManagerInterface
*/
const ROLE_VIEW = 'role.view';
protected $em;
/**
* @deprecated
* @var \Symfony\Component\PropertyAccess\PropertyAccessor
*/
const ROLE_EDIT = 'role.edit';
/**
* The key to get the role necessary for the action
*/
const ROLE = 'role';
protected $propertyAccess;
/**
* Resolver constructor.
*
* @param EntityManagerInterface $em
* @param array $crudConfig
*/
function __construct(EntityManagerInterface $em, array $crudConfig)
public function __construct(EntityManagerInterface $em, array $crudConfig)
{
$this->em = $em;
foreach($crudConfig as $conf) {
foreach ($crudConfig as $conf) {
$this->crudConfig[$conf['name']] = $conf;
}
}
/**
* @param $key
* @param $crudName
* @param null $action
* @return string
*/
public function getConfigValue($key, $crudName, $action = null)
{
$config = $this->crudConfig[$crudName];
switch ($key) {
case self::ROLE:
return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action);
}
}
/**
* @param $crudName
* @param $action
*
* @return string
*/
public function buildDefaultRole($crudName, $action)
{
if (empty($this->crudConfig[$crudName]['base_role'])) {
throw new \LogicException(sprintf("the base role is not defined. You must define "
. "on or override %s or %s methods", __METHOD__, "getRoleFor"));
throw new LogicException(sprintf('the base role is not defined. You must define '
. 'on or override %s or %s methods', __METHOD__, 'getRoleFor'));
}
return \strtoupper(
$this->crudConfig[$crudName]['base_role'].
'_'.
$action);
return strtoupper(
$this->crudConfig[$crudName]['base_role'] .
'_' .
$action
);
}
/**
* @param $key
* @param $crudName
* @param null $action
*
* @return string
*/
public function getConfigValue($key, $crudName, $action = null)
{
$config = $this->crudConfig[$crudName];
switch ($key) {
case self::ROLE:
return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action);
}
}
/**
* @param $crudName
* @param $action
*
* @return bool
*/
public function hasAction($crudName, $action)
{
return \array_key_exists($action,
$this->crudConfig[$crudName]['actions']);
return array_key_exists(
$action,
$this->crudConfig[$crudName]['actions']
);
}
}

View File

@@ -1,32 +1,42 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\CRUD\Routing;
use RuntimeException;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpFoundation\Request;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use function array_filter;
use function array_keys;
use function array_search;
use function in_array;
class CRUDRoutesLoader extends Loader
{
protected array $crudConfig = [];
protected array $apiCrudConfig = [];
private bool $isLoaded = false;
private const ALL_INDEX_METHODS = [Request::METHOD_GET, Request::METHOD_HEAD];
private const ALL_SINGLE_METHODS = [
Request::METHOD_GET,
Request::METHOD_POST,
Request::METHOD_PUT,
Request::METHOD_DELETE
Request::METHOD_DELETE,
];
private const ALL_INDEX_METHODS = [ Request::METHOD_GET, Request::METHOD_HEAD ];
protected array $apiCrudConfig = [];
protected array $crudConfig = [];
private bool $isLoaded = false;
public function __construct(array $crudConfig, array $apiCrudConfig)
{
@@ -37,22 +47,15 @@ class CRUDRoutesLoader extends Loader
}
/**
* Load routes for CRUD and CRUD Api.
*
* @param mixed $resource
* @param null $type
* @return bool
*/
public function supports($resource, $type = null)
{
return 'CRUD' === $type;
}
/**
* Load routes for CRUD and CRUD Api
* @param mixed|null $type
*/
public function load($resource, $type = null): RouteCollection
{
if (true === $this->isLoaded) {
throw new \RuntimeException('Do not add the "CRUD" loader twice');
throw new RuntimeException('Do not add the "CRUD" loader twice');
}
$collection = new RouteCollection();
@@ -60,6 +63,7 @@ class CRUDRoutesLoader extends Loader
foreach ($this->crudConfig as $crudConfig) {
$collection->addCollection($this->loadCrudConfig($crudConfig));
}
foreach ($this->apiCrudConfig as $crudConfig) {
$collection->addCollection($this->loadApi($crudConfig));
}
@@ -68,56 +72,30 @@ class CRUDRoutesLoader extends Loader
}
/**
* Load routes for CRUD (without api)
* @param mixed $resource
* @param null $type
*
* @param $crudConfig
* @return RouteCollection
* @return bool
*/
protected function loadCrudConfig($crudConfig): RouteCollection
public function supports($resource, $type = null)
{
$collection = new RouteCollection();
$controller ='cscrud_'.$crudConfig['name'].'_controller';
foreach ($crudConfig['actions'] as $name => $action) {
// defaults (controller name)
$defaults = [
'_controller' => $controller.':'.($action['controller_action'] ?? $name)
];
if ($name === 'index') {
$path = "{_locale}".$crudConfig['base_path'];
$route = new Route($path, $defaults);
} elseif ($name === 'new') {
$path = "{_locale}".$crudConfig['base_path'].'/'.$name;
$route = new Route($path, $defaults);
} else {
$path = "{_locale}".$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name);
$requirements = $action['requirements'] ?? [
'{id}' => '\d+'
];
$route = new Route($path, $defaults, $requirements);
}
$collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route);
}
return $collection;
return 'CRUD' === $type;
}
/**
* Load routes for api single
* Load routes for api single.
*
* @param $crudConfig
* @return RouteCollection
*/
protected function loadApi(array $crudConfig): RouteCollection
{
$collection = new RouteCollection();
$controller = 'csapi_'.$crudConfig['name'].'_controller';
$controller = 'csapi_' . $crudConfig['name'] . '_controller';
foreach ($crudConfig['actions'] as $name => $action) {
// filter only on single actions
$singleCollection = $action['single_collection'] ?? $name === '_entity' ? 'single' : NULL;
$singleCollection = $action['single_collection'] ?? '_entity' === $name ? 'single' : null;
if ('collection' === $singleCollection) {
// continue;
}
@@ -126,46 +104,61 @@ class CRUDRoutesLoader extends Loader
switch ($name) {
case '_entity':
$controllerAction = 'entityApi';
break;
case '_index':
$controllerAction = 'indexApi';
break;
default:
$controllerAction = $name.'Api';
$controllerAction = $name . 'Api';
break;
}
$defaults = [
'_controller' => $controller.':'.($action['controller_action'] ?? $controllerAction)
'_controller' => $controller . ':' . ($action['controller_action'] ?? $controllerAction),
];
// path are rewritten
// if name === 'default', we rewrite it to nothing :-)
$localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name;
$localName = in_array($name, ['_entity', '_index']) ? '' : '/' . $name;
if ('collection' === $action['single_collection'] || '_index' === $name) {
$localPath = $action['path'] ?? $localName.'.{_format}';
$localPath = $action['path'] ?? $localName . '.{_format}';
} else {
$localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}';
$localPath = $action['path'] ?? '/{id}' . $localName . '.{_format}';
}
$path = $crudConfig['base_path'].$localPath;
$path = $crudConfig['base_path'] . $localPath;
$requirements = $action['requirements'] ?? [ '{id}' => '\d+' ];
$requirements = $action['requirements'] ?? ['{id}' => '\d+'];
$methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; },
ARRAY_FILTER_USE_BOTH));
$methods = array_keys(array_filter(
$action['methods'],
function ($value, $key) { return $value; },
ARRAY_FILTER_USE_BOTH
));
if (count($methods) === 0) {
throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ".
"does not have any allowed methods. You should remove this action from the config ".
"or allow, at least, one method");
throw new RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", " .
'does not have any allowed methods. You should remove this action from the config ' .
'or allow, at least, one method');
}
if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods)) {
unset($methods[\array_search(Request::METHOD_POST, $methods)]);
$entityPostRoute = $this->createEntityPostRoute($name, $crudConfig, $action,
$controller);
$collection->add("chill_api_single_{$crudConfig['name']}_{$name}_create",
$entityPostRoute);
if ('_entity' === $name && in_array(Request::METHOD_POST, $methods)) {
unset($methods[array_search(Request::METHOD_POST, $methods)]);
$entityPostRoute = $this->createEntityPostRoute(
$name,
$crudConfig,
$action,
$controller
);
$collection->add(
"chill_api_single_{$crudConfig['name']}_{$name}_create",
$entityPostRoute
);
}
if (count($methods) === 0) {
@@ -177,7 +170,43 @@ class CRUDRoutesLoader extends Loader
$route = new Route($path, $defaults, $requirements);
$route->setMethods($methods);
$collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route);
$collection->add('chill_api_single_' . $crudConfig['name'] . '_' . $name, $route);
}
return $collection;
}
/**
* Load routes for CRUD (without api).
*
* @param $crudConfig
*/
protected function loadCrudConfig($crudConfig): RouteCollection
{
$collection = new RouteCollection();
$controller = 'cscrud_' . $crudConfig['name'] . '_controller';
foreach ($crudConfig['actions'] as $name => $action) {
// defaults (controller name)
$defaults = [
'_controller' => $controller . ':' . ($action['controller_action'] ?? $name),
];
if ('index' === $name) {
$path = '{_locale}' . $crudConfig['base_path'];
$route = new Route($path, $defaults);
} elseif ('new' === $name) {
$path = '{_locale}' . $crudConfig['base_path'] . '/' . $name;
$route = new Route($path, $defaults);
} else {
$path = '{_locale}' . $crudConfig['base_path'] . ($action['path'] ?? '/{id}/' . $name);
$requirements = $action['requirements'] ?? [
'{id}' => '\d+',
];
$route = new Route($path, $defaults, $requirements);
}
$collection->add('chill_crud_' . $crudConfig['name'] . '_' . $name, $route);
}
return $collection;
@@ -185,14 +214,14 @@ class CRUDRoutesLoader extends Loader
private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route
{
$localPath = $action['path'].'.{_format}';
$localPath = $action['path'] . '.{_format}';
$defaults = [
'_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost')
'_controller' => $controller . ':' . ($action['controller_action'] ?? 'entityPost'),
];
$path = $crudConfig['base_path'].$localPath;
$path = $crudConfig['base_path'] . $localPath;
$requirements = $action['requirements'] ?? [];
$route = new Route($path, $defaults, $requirements);
$route->setMethods([ Request::METHOD_POST ]);
$route->setMethods([Request::METHOD_POST]);
return $route;
}

View File

@@ -1,36 +1,21 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2019, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\CRUD\Templating;
use Chill\MainBundle\CRUD\Resolver\Resolver;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
use Twig\Environment;
use Twig\TwigFunction;
/**
* Class TwigCRUDResolver
* Twig filters to display data in crud template
*
* @package Chill\MainBundle\CRUD\Templating
* Twig filters to display data in crud template.
*/
class TwigCRUDResolver extends AbstractExtension
{
@@ -38,49 +23,54 @@ class TwigCRUDResolver extends AbstractExtension
* @var Resolver
*/
protected $resolver;
/**
* TwigCRUDResolver constructor.
*
* @param Resolver $resolver
*/
function __construct(Resolver $resolver)
public function __construct(Resolver $resolver)
{
$this->resolver = $resolver;
}
/**
* @return array|TwigFunction[]
*/
public function getFunctions()
{
return [
new TwigFunction('chill_crud_config', [$this, 'getConfig'],
['is_safe' => 'html']),
new TwigFunction('chill_crud_action_exists', [$this, 'hasAction'],
[]),
];
}
/**
* @param $configKey
* @param $crudName
* @param null $action
*
* @return string
*/
public function getConfig($configKey, $crudName, $action = null)
{
return $this->resolver->getConfigValue($configKey, $crudName, $action);
}
/**
* @return array|TwigFunction[]
*/
public function getFunctions()
{
return [
new TwigFunction(
'chill_crud_config',
[$this, 'getConfig'],
['is_safe' => 'html']
),
new TwigFunction(
'chill_crud_action_exists',
[$this, 'hasAction'],
[]
),
];
}
/**
* @param $crudName
* @param $action
*
* @return bool
*/
public function hasAction($crudName, $action)
{
return $this->resolver->hasAction($crudName, $action);
}
}