src/Framework/Controller/APIController.php line 151

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace App\Framework\Controller;
  3. use App\Authentication\Entity\User;
  4. use App\Framework\Exception\APIException;
  5. use App\Framework\Exception\InvalidRequestException;
  6. use App\Framework\Entity\PaginationInput;
  7. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpFoundation\RequestStack;
  10. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  11. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  12. use Symfony\Contracts\Service\Attribute\Required;
  13. abstract class APIController extends AbstractController
  14. {
  15.     private ?RequestStack $requestStack null;
  16.     private ?AuthorizationCheckerInterface $authChecker null;
  17.     #[Required]
  18.     public function setAuthChecker(AuthorizationCheckerInterface $authChecker): void
  19.     {
  20.         $this->authChecker $authChecker;
  21.     }
  22.     #[Required]
  23.     public function setRequestStack(RequestStack $requestStack): void
  24.     {
  25.         $this->requestStack $requestStack;
  26.     }
  27.     protected function getRequest(): ?Request
  28.     {
  29.         return $this->requestStack->getCurrentRequest();
  30.     }
  31.     protected function getRequestBody()
  32.     {
  33.         return json_decode($this->getRequest()->getContent(), true);
  34.     }
  35.     protected function getRequestBodyData(string $fieldbool $required false$default null)
  36.     {
  37.         // TODO: Cache the decode
  38.         $data json_decode($this->getRequest()->getContent(), true);
  39.         if (!is_array($data)) {
  40.             if ($required) {
  41.                 throw new InvalidRequestException([$field => 'error.field_required']);
  42.             }
  43.             return $default;
  44.         }
  45.         if (array_key_exists($field$data)) {
  46.             return $data[$field];
  47.         }
  48.         if ($required) {
  49.             throw new InvalidRequestException([$field => 'error.field_required']);
  50.         }
  51.         return $default;
  52.     }
  53.     protected function getRequestParameterData(string $fieldbool $required false$default null)
  54.     {
  55.         $data $this->getRequest()->request->all();
  56.         if (array_key_exists($field$data)) {
  57.             return $data[$field];
  58.         }
  59.         if ($required) {
  60.             throw new InvalidRequestException([$field => 'Field is required']);
  61.         }
  62.         return $default;
  63.     }
  64.     protected function getRequestFileData(string $fieldbool $required false)
  65.     {
  66.         return $this->getRequest()->files->get($field);
  67.     }
  68.     /**
  69.      * @return mixed[]
  70.      */
  71.     protected function getRequestData(array $rules = []): array
  72.     {
  73.         $requestContent $this->getRequest()->getContent();
  74.         $src json_decode($requestContenttrue);
  75.         $dst = [];
  76.         foreach ($rules as $field => $rule) {
  77.             if (!array_key_exists($field$src) && $rule) {
  78.                 throw new InvalidRequestException(['Missing field: ' $field]);
  79.             }
  80.             if (!array_key_exists($field$src)) {
  81.                 $dst[$field] = '';
  82.             }
  83.             if (array_key_exists($field$src)) {
  84.                 $dst[$field] = $src[$field];
  85.             }
  86.         }
  87.         return $dst;
  88.     }
  89.     /**
  90.      * @return mixed[]
  91.      */
  92.     protected function getRequestFilters(): array
  93.     {
  94.         return $this->getRequest()->query->all();
  95.     }
  96.     /**
  97.      * @return mixed[]
  98.      */
  99.     protected function getCollectionFilters(): array
  100.     {
  101.         $query $this->getRequest()->query->all();
  102.         return array_filter($query, fn ($k) => !in_array(
  103.             $k,
  104.             ['fields''q''sortBy''sortDirection''offset''limit''lastId''search'],
  105.             true
  106.         ), ARRAY_FILTER_USE_KEY);
  107.     }
  108.     protected function getRequestFilterBool($key$required false, ?bool $default null): ?bool {
  109.         $uncasted $this->getRequestFilter(key$keyrequired$required, default: $default);
  110.         if ($uncasted == null) {
  111.             return null;
  112.         }
  113.         return $uncasted == 'true';
  114.     }
  115.     protected function getRequestFilterInt($key$required false, ?int $default null): ?int {
  116.         $uncasted $this->getRequestFilter(key$keyrequired$required, default: $default);
  117.         if ($uncasted == null) {
  118.             return null;
  119.         }
  120.         return (int) $uncasted;
  121.     }
  122.     protected function getRequestFilter($key$required false$default null)
  123.     {
  124.         $filters $this->getRequest()->query->all();
  125.         if (array_key_exists($key$filters)) {
  126.             return $filters[$key];
  127.         }
  128.         if ($required) {
  129.             throw new InvalidRequestException(['Missing field: ' $key]);
  130.         }
  131.         return $default;
  132.     }
  133.     /**
  134.      * Returns the pagination arguments if specified in the request.
  135.      *
  136.      * @return array<string, mixed>
  137.      */
  138.     protected function getRequestPagination(): array
  139.     {
  140.         $first = (int) $this->getRequestFilter('offset'false0);
  141.         $limit = (int) $this->getRequestFilter('limit'false1000);
  142.         $lastId = (int) $this->getRequestFilter('lastId'false0);
  143.         return [
  144.             'first' => $first,
  145.             'limit' => $limit,
  146.             'lastId' => $lastId,
  147.         ];
  148.     }
  149.     /**
  150.      * Returns the pagination input.
  151.      *
  152.      * @return PaginationInput
  153.      */
  154.     protected function getRequestPaginationInput(): PaginationInput {
  155.         $offset = (int) $this->getRequestFilter(key'offset'requiredfalse, default: 0);
  156.         $limit = (int) $this->getRequestFilter(key'limit'requiredfalse, default: 1000);
  157.         return new PaginationInput(limit$limitoffset$offset);
  158.     }
  159.     /**
  160.      * Return the sort arguments if specified in the request.
  161.      *
  162.      * @return array<int|string, mixed>
  163.      */
  164.     protected function getRequestSorting(): array
  165.     {
  166.         $sortBy $this->getRequestFilter('sortBy'false'id');
  167.         $sortDirection $this->getRequestFilter('sortDirection'false'desc');
  168.         return [
  169.             $sortBy => $sortDirection $sortDirection 'asc',
  170.         ];
  171.     }
  172.     protected function getRequestFields(): ?array
  173.     {
  174.         $requestFiltersQuery $this->getRequestFilters();
  175.         if (!array_key_exists('fields'$requestFiltersQuery)) {
  176.             return null;
  177.         }
  178.         $fields explode(','$requestFiltersQuery['fields']);
  179.         foreach ($fields as $key => $value) {
  180.             $subFields explode('.'$value);
  181.             if (count($subFields) <= 1) {
  182.                 continue;
  183.             }
  184.             // remove the key and start populating with arrays
  185.             unset($fields[$key]);
  186.             $arrRef = &$fields;
  187.             $subFieldsCount count($subFields);
  188.             for ($i 0$i $subFieldsCount 1; ++$i) {
  189.                 $subField $subFields[$i];
  190.                 if (!array_key_exists($subField$arrRef)) {
  191.                     $arrRef[$subField] = [];
  192.                 }
  193.                 $arrRef = &$arrRef[$subField];
  194.             }
  195.             $arrRef[] = $subFields[count($subFields) - 1];
  196.         }
  197.         return $fields;
  198.     }
  199.     protected function assert($condition$errorCode$debugMessage): void
  200.     {
  201.         if (!$condition) {
  202.             throw new APIException(400$errorCode$debugMessage);
  203.         }
  204.     }
  205.     protected function assertHasRequestFilter($key$message): void
  206.     {
  207.         $filters $this->getRequestFilters();
  208.         if (!array_key_exists($key$filters)) {
  209.             throw new InvalidRequestException([$key => $message]);
  210.         }
  211.     }
  212.     protected function denyAccessUnlessOwner($entity): void
  213.     {
  214.         if (!$this->authChecker->isGranted('IS_AUTHENTICATED_FULLY')) {
  215.             throw new AccessDeniedException('Access denied for ' $entity::class);
  216.         }
  217.         /** @var User $user */
  218.         $user $this->getUser();
  219.         if (!$user) {
  220.             throw new AccessDeniedException('Access denied for ' $entity::class);
  221.         }
  222.         // If admin, then we can query anything
  223.         if ($this->authChecker->isGranted('ROLE_ADMIN')) {
  224.             return;
  225.         }
  226.         // Kinda like a hack, but if the entities have a user id or a customer id, we can check the ownership
  227.         if (method_exists($entity'getCustomer')) {
  228.             if ($user !== $entity->getCustomer()->getUser()) {
  229.                 throw new AccessDeniedException('Access denied for ' $entity::class);
  230.             }
  231.         } elseif (method_exists($entity'getUser')) {
  232.             if ($user !== $entity->getUser()) {
  233.                 throw new AccessDeniedException('Access denied for ' $entity::class);
  234.             }
  235.         }
  236.     }
  237.     protected function denyAccessUnlessOwnerOrAdministrator($entity): void
  238.     {
  239.         if ($this->isGranted('ROLE_ADMIN')) {
  240.             return;
  241.         }
  242.         $this->denyAccessUnlessOwner($entity);
  243.     }
  244. }