at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
@@ -0,0 +1,201 @@
<?php
namespace ApiBundle\Controller;
use ApiBundle\ApiBundleServices;
use ApiBundle\Response\View;
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Class AbstractApiController
* @package ApiBundle\Controller
*/
class AbstractApiController
{
/**
* Default page value in pagination if 'page' filter not provided.
*/
const DEFAULT_PAGE = 1;
/**
* Default limit (max entities per page) value in pagination if 'limit'
* filter not provided.
*/
const DEFAULT_LIMIT = 100;
/**
* @var ContainerInterface
*/
protected $container;
/**
* @param ContainerInterface $container A ContainerInterface instance.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Get service from container.
*
* @param string $id The service identifier.
*
* @return object
*/
protected function get($id)
{
return $this->container->get($id);
}
/**
* Gets a container configuration parameter by its name.
*
* @param string $name The parameter name.
*
* @return mixed
*/
protected function getParameter($name)
{
return $this->container->getParameter($name);
}
/**
* Creates and returns a Form instance from the type of the form.
*
* @param string $type The fully qualified class name of the form type.
* @param mixed $data The initial data for the form.
* @param array $options Options for the form.
*
* @return FormInterface
*/
protected function createForm($type, $data = null, array $options = [])
{
return $this->container->get('form.factory')
->create($type, $data, $options);
}
/**
* Return default entity manager.
*
* @return ObjectManager
*/
protected function getManager()
{
/** @var Registry $doctrine */
$doctrine = $this->container->get('doctrine');
$manager = $doctrine->getManager();
if (! $manager instanceof ObjectManager) {
throw new \LogicException('Should be instance of '. ObjectManager::class);
}
return $manager;
}
/**
* Get current User entity instance.
*
* @return \UserBundle\Entity\User
*/
protected function getCurrentUser()
{
/** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface $storage */
$storage = $this->container->get('security.token_storage');
return $storage->getToken()->getUser();
}
/**
* @param string $action Action name.
* @param object|object[] $entity A Entity instance or array of entity instances.
*
* @return string[] Array of restriction reasons.
*/
protected function checkAccess($action, $entity)
{
/** @var AccessCheckerInterface $checker */
$checker = $this->container->get(ApiBundleServices::ACCESS_CHECKER);
if ($entity instanceof \Traversable) {
$entity = iterator_to_array($entity);
} elseif (is_object($entity)) {
$entity = [ $entity ];
}
if (! is_array($entity)) {
throw new \InvalidArgumentException('Expects single object or array of objects.');
}
$grantChecker = \nspl\f\partial([ $checker, 'isGranted' ], $action);
return \nspl\a\flatten(\nspl\a\map($grantChecker, $entity));
}
/**
* Generate proper server response.
*
* @param mixed $data A data sent to client.
* @param integer $code Response http code.
* @param array $groups Serialization groups.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function generateResponse(
$data = null,
$code = null,
array $groups = []
) {
return new View($data, $groups, $code);
}
/**
* Paginate given data.
*
* @param Request $request A Request instance.
* @param mixed $results Any values which have proper pagination listener.
* @param integer $defaultLimit Default limit.
*
* @return \Knp\Component\Pager\Pagination\PaginationInterface
*/
protected function paginate(Request $request, $results, $defaultLimit = self::DEFAULT_LIMIT)
{
/** @var PaginatorInterface $paginator */
$paginator = $this->get('knp_paginator');
$page = $request->query->getInt('page', self::DEFAULT_PAGE);
$limit = $request->query->getInt('limit', $defaultLimit);
return $paginator->paginate($results, $page, $limit);
}
/**
* Forwards the request to another controller.
*
* @param string $controller The controller name (a string like BlogBundle:Post:index).
* @param array $path An array of path parameters.
* @param array $query An array of query parameters.
*
* @return \Symfony\Component\HttpFoundation\Response A Response instance
*/
protected function forward($controller, array $path = [], array $query = [])
{
$request = $this->container->get('request_stack')->getCurrentRequest();
$path['_forwarded'] = $request->attributes;
$path['_controller'] = $controller;
$subRequest = $request->duplicate($query, null, $path);
return $this->container
->get('http_kernel')
->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
@@ -0,0 +1,265 @@
<?php
namespace ApiBundle\Controller;
use ApiBundle\Entity\ManageableEntityInterface;
use ApiBundle\Form\EntitiesBatchType;
use ApiBundle\Security\Inspector\InspectorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class AbstractApiController
* @package AppBundle\Controller
*/
class AbstractCRUDController extends AbstractApiController
{
/**
* Entity fqcn.
*
* @var string
*/
protected $entity;
/**
* @param ContainerInterface $container A ContainerInterface instance.
* @param string $entity Managed entity fqcn.
*/
public function __construct(
ContainerInterface $container,
$entity
) {
parent::__construct($container);
$this->entity = $entity;
}
/**
* Create new entity.
*
* @param Request $request A Request instance.
* @param ManageableEntityInterface $entity A ManageableEntityInterface
* instance.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function createEntity(Request $request, ManageableEntityInterface $entity)
{
$form = $this->createForm($entity->getCreateFormClass(), $entity);
// Submit data into form.
$form->submit($request->request->all());
if ($form->isValid()) {
// Check that current user can create this entity.
// If user don't have rights to create this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::CREATE, $entity);
if (count($reasons) > 0) {
// User don't have rights to create this entity so send all
// founded restriction reasons to client.
return $this->generateResponse($reasons, 403);
}
$em = $this->getManager();
$em->persist($entity);
$em->flush();
return $entity;
}
// Client send invalid data.
return $this->generateResponse($form, 400);
}
/**
* Get information about single entity.
*
* @param integer|ManageableEntityInterface|null $id A entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function getEntity($id)
{
$foundedEntity = $id;
if (is_numeric($id)) {
$repository = $this->getManager()->getRepository($this->entity);
$foundedEntity = $repository->find($id);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$id}.", 404);
}
// Check that current user can read this entity.
// If user don't have rights to read this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::READ, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
return $foundedEntity;
}
/**
* Update entity.
*
* @param Request $request A Request instance.
* @param integer|ManageableEntityInterface|null $entity A entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function putEntity(Request $request, $entity)
{
$em = $this->getManager();
$foundedEntity = $entity;
if (is_numeric($entity)) {
$repository = $em->getRepository($this->entity);
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
$foundedEntity = $repository->find($entity);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
}
$form = $this->createForm($foundedEntity->getUpdateFormClass(), $foundedEntity, [
'method' => 'PUT',
]);
$form->submit($request->request->all());
if ($form->isValid()) {
// Check that current user can update this entity.
// If user don't have rights to update this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$em->persist($foundedEntity);
$em->flush();
return $foundedEntity;
}
return $this->generateResponse($form, 400);
}
/**
* Delete entity.
*
* @param integer|ManageableEntityInterface|null $entity A entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function deleteEntity($entity)
{
$em = $this->getManager();
$foundedEntity = $entity;
if (is_numeric($entity)) {
$repository = $em->getRepository($this->entity);
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
$foundedEntity = $repository->find($entity);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
}
// Check that current user can delete this entity.
// If user don't have rights to delete this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::DELETE, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$em->remove($foundedEntity);
$em->flush();
return $this->generateResponse();
}
/**
* @param Request $request A Request instance.
* @param string|callable $permission A requested permission.
* @param string $formClass Form class fqcn.
* @param callable $processor Function which process founded entities.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function batchProcessing(
Request $request,
$permission,
$formClass,
callable $processor
) {
$this->checkFormClass($formClass);
$form = $this->createForm($formClass, null, [ 'class' => $this->entity ]);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if (is_callable($permission)) {
$permission = call_user_func_array($permission, $data);
}
if (! is_string($permission)) {
throw new \InvalidArgumentException('$permission should be string or callable');
}
$reasons = $this->checkAccess($permission, $data['entities']);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$response = call_user_func_array($processor, $data);
if ($response === null) {
$response = $this->generateResponse();
}
return $response;
}
return $this->generateResponse($form, 400);
}
/**
* @param string $formClass Form class fqcn.
*
* @return void
*/
private function checkFormClass($formClass)
{
if (! is_string($formClass) || ! class_exists($formClass)) {
throw new \InvalidArgumentException('$formClass should be fqcn');
}
if (($formClass !== EntitiesBatchType::class)
&& ! in_array(EntitiesBatchType::class, class_parents($formClass), true)) {
throw new \InvalidArgumentException('Invalid form class '. $formClass);
}
}
}
@@ -0,0 +1,43 @@
<?php
namespace ApiBundle\Controller\Annotation;
use Common\Annotation\AbstractAppAnnotation;
/**
* Class Roles
* @package ApiBundle\Controller\Annotation
*
* @Annotation
*/
class Roles extends AbstractAppAnnotation
{
/**
* Expected role or array of roles.
*
* @var array
*/
public $roles;
/**
* Return name of default property.
*
* @return string
*/
public function getDefault()
{
return 'roles';
}
/**
* Normalize annotation parameters.
* Called after all parameters set in constrictor.
*
* @return void
*/
protected function normalize()
{
$this->roles = (array) $this->roles;
}
}