skqr/hateoas-bundle

View on GitHub
Controller/MagicAlterController.php

Summary

Maintainability
F
5 days
Test Coverage
<?php
/**
 * @copyright 2014 Integ S.A.
 * @license http://opensource.org/licenses/MIT The MIT License (MIT)
 * @author Javier Lorenzana <javier.lorenzana@gointegro.com>
 */

namespace GoIntegro\Bundle\HateoasBundle\Controller;

// Controladores.
use Symfony\Bundle\FrameworkBundle\Controller\Controller as SymfonyController,
    Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
// Colecciones.
use Doctrine\Common\Collections\Collection;
// HTTP.
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException,
    Symfony\Component\HttpKernel\Exception\ConflictHttpException,
    Symfony\Component\HttpKernel\Exception\BadRequestHttpException,
    Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException,
    Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException,
    Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException,
    GoIntegro\Bundle\HateoasBundle\Http\UnprocessableEntityHttpException,
    GoIntegro\Bundle\HateoasBundle\Http\DocumentTooLargeException;
// JSON-API.
use GoIntegro\Bundle\HateoasBundle\Http\JsonResponse,
    GoIntegro\Hateoas\JsonApi\Exception\DocumentTooLargeHttpException,
    GoIntegro\Hateoas\JsonApi\ResourceEntityInterface,
    GoIntegro\Hateoas\JsonApi\Request\Params,
    GoIntegro\Hateoas\JsonApi\Document,
    GoIntegro\Hateoas\JsonApi\Exception\ConflictException,
    GoIntegro\Hateoas\JsonApi\Exception\NotFoundException,
    GoIntegro\Hateoas\JsonApi\Exception\UnsupportedMediaTypeException;
// Utils.
use GoIntegro\Hateoas\Util\Inflector;
// Security.
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// Validator.
use GoIntegro\Hateoas\Entity\Validation\EntityConflictExceptionInterface,
    GoIntegro\Hateoas\Entity\Validation\ValidationExceptionInterface;
// Request.
use GoIntegro\Hateoas\JsonApi\Request\ParseException,
    GoIntegro\Hateoas\JsonApi\Request\ActionNotAllowedException,
    GoIntegro\Hateoas\JsonApi\Request\EntityAccessDeniedException;

class MagicAlterController extends SymfonyController
{
    use CommonResponseTrait;

    const ERROR_ACCESS_DENIED = "Access to the resource was denied.",
        ERROR_RESOURCE_NOT_FOUND = "The resource was not found.",
        ERROR_RELATIONSHIP_NOT_FOUND = "No relationship by that name found.",
        ERROR_FIELD_NOT_FOUND = "No field by that name found.";

    /**
     * @Route("/{primaryType}/{id}/links/{relationship}", name="hateoas_magic_link", methods="POST")
     * @Route("/{primaryType}/{id}/links/{relationship}", name="hateoas_magic_update_link", methods="PUT")
     * @Route("/{primaryType}/{id}/links/{relationship}", name="hateoas_magic_unlink_one", methods="DELETE")
     * @Route("/{primaryType}/{id}/links/{relationship}/{ids}", name="hateoas_magic_unlink_many", methods="DELETE")
     * @param string $primaryType
     * @param string $id
     * @param string $relationship
     * @param string $ids
     * @throws AccessDeniedHttpException
     * @throws NotFoundHttpException
     * @throws BadRequestHttpException
     * @see http://jsonapi.org/format/#crud-updating-relationships
     * @todo The 409 response should happend during parsing.
     */
    public function linkAction($primaryType, $id, $relationship, $ids = NULL)
    {
        try {
            $params = $this->get('hateoas.request_parser')
                ->parse($this->getRequest());
        } catch (NotFoundException $e) {
            throw new NotFoundHttpException($e->getMessage(), $e);
        } catch (ActionNotAllowedException $e) {
            throw new MethodNotAllowedHttpException(
                $e->getAllowedMethods(), $e->getMessage(), $e
            );
        } catch (ParseException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        } catch (EntityAccessDeniedException $e) {
            throw new AccessDeniedHttpException($e->getMessage(), $e);
        } catch (ConflictException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        } catch (UnsupportedMediaTypeException $e) {
            throw new UnsupportedMediaTypeHttpException($e->getMessage(), $e);
        }

        $em = $this->getDoctrine()->getManager();
        $em->getConnection()->beginTransaction();

        try {
            foreach ($params->entities->primary as &$entity) {
                $data = $params->resources[$entity->getId()];
                $links = $this->extractLinks($data);
                $metadata = $this->extractMetadata($data);

                try {
                    $entity = $this->get('hateoas.entity.mutator')
                        ->update($params, $entity, $data, $links, $metadata);
                } catch (EntityConflictExceptionInterface $e) {
                    throw new ConflictHttpException($e->getMessage(), $e);
                } catch (ValidationExceptionInterface $e) {
                    throw new UnprocessableEntityHttpException(
                        $e->getMessage(), $e
                    );
                }
            }

            $em->getConnection()->commit();
        } catch (\Exception $e) {
            $em->getConnection()->rollback();
            throw $e;
        }

        return $this->createNoCacheResponse(
            NULL, JsonResponse::HTTP_NO_CONTENT
        );
    }

    /**
     * @Route("/{primaryType}", name="hateoas_magic_create", methods="POST")
     * @param string $primaryType
     * @throws AccessDeniedHttpException
     * @throws NotFoundHttpException
     * @throws BadRequestHttpException
     * @see http://jsonapi.org/format/#crud-creating-resources
     * @todo Support multi-create.
     * @todo Rollback everything if anything goes wrong.
     */
    public function createAction($primaryType)
    {
        try {
            $params = $this->get('hateoas.request_parser')->parse($this->getRequest());
        } catch (NotFoundException $e) {
            throw new NotFoundHttpException($e->getMessage(), $e);
        } catch (ActionNotAllowedException $e) {
            throw new MethodNotAllowedHttpException(
                $e->getAllowedMethods(), $e->getMessage(), $e
            );
        } catch (ParseException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        } catch (EntityAccessDeniedException $e) {
            throw new AccessDeniedHttpException($e->getMessage(), $e);
        } catch (DocumentTooLargeException $e) {
            throw new DocumentTooLargeHttpException($e->getMessage(), $e);
        } catch (ConflictException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        } catch (UnsupportedMediaTypeException $e) {
            throw new UnsupportedMediaTypeHttpException($e->getMessage(), $e);
        }

        $em = $this->getDoctrine()->getManager();
        $em->getConnection()->beginTransaction();

        try {
            $entities = [];

            foreach ($params->resources as $data) {
                try {
                    $links = $this->extractLinks($data);
                    $metadata = $this->extractMetadata($data);
                    $entities[] = $this->get('hateoas.entity.builder')
                        ->create($params, $data, $links, $metadata);
                } catch (EntityConflictExceptionInterface $e) {
                    throw new ConflictHttpException($e->getMessage(), $e);
                } catch (ValidationExceptionInterface $e) {
                    throw new UnprocessableEntityHttpException(
                        $e->getMessage(), $e
                    );
                }
            }

            $em->getConnection()->commit();
        } catch (\Exception $e) {
            $em->getConnection()->rollback();
            throw $e;
        }

        $resources = 1 < count($entities)
            ? $this->get('hateoas.resource_manager')
                ->createCollectionFactory()
                ->setParams($params)
                ->addEntities($entities)
                ->create()
            : $this->get('hateoas.resource_manager')
                ->createResourceFactory()
                ->setEntity(reset($entities))
                ->create();

        $document = $this->get('hateoas.resource_manager')
            ->createDocumentFactory()
            ->setParams($params)
            ->setResources($resources)
            ->create();

        return $this->createCreatedResponse($document);
    }

    /**
     * @Route("/{primaryType}/{ids}", name="hateoas_magic_update", methods="PUT")
     * @param string $primaryType
     * @param string $ids
     * @throws AccessDeniedHttpException
     * @throws NotFoundHttpException
     * @throws BadRequestHttpException
     * @see http://jsonapi.org/format/#crud-updating
     * @todo Rollback everything if anything goes wrong.
     */
    public function updateAction($primaryType, $ids)
    {
        try {
            $params = $this->get('hateoas.request_parser')->parse($this->getRequest());
        } catch (NotFoundException $e) {
            throw new NotFoundHttpException($e->getMessage(), $e);
        } catch (ActionNotAllowedException $e) {
            throw new MethodNotAllowedHttpException(
                $e->getAllowedMethods(), $e->getMessage(), $e
            );
        } catch (ParseException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        } catch (EntityAccessDeniedException $e) {
            throw new AccessDeniedHttpException($e->getMessage(), $e);
        } catch (DocumentTooLargeException $e) {
            throw new DocumentTooLargeHttpException($e->getMessage(), $e);
        } catch (ConflictException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        } catch (UnsupportedMediaTypeException $e) {
            throw new UnsupportedMediaTypeHttpException($e->getMessage(), $e);
        }

        $em = $this->getDoctrine()->getManager();
        $em->getConnection()->beginTransaction();

        try {
            foreach ($params->entities->primary as &$entity) {
                $data = $params->resources[$entity->getId()];
                $links = $this->extractLinks($data);
                $metadata = $this->extractMetadata($data);

                try {
                    $entity = $this->get('hateoas.entity.mutator')
                        ->update($params, $entity, $data, $links, $metadata);
                } catch (EntityConflictExceptionInterface $e) {
                    throw new ConflictHttpException($e->getMessage(), $e);
                } catch (ValidationExceptionInterface $e) {
                    throw new UnprocessableEntityHttpException(
                        $e->getMessage(), $e
                    );
                }
            }

            $em->getConnection()->commit();
        } catch (\Exception $e) {
            $em->getConnection()->rollback();
            throw $e;
        }

        $resources = 1 < count($params->entities->primary)
            ? $this->get('hateoas.resource_manager')
                ->createCollectionFactory()
                ->setParams($params)
                ->addEntities($params->entities->primary->toArray())
                ->create()
            : $this->get('hateoas.resource_manager')
                ->createResourceFactory()
                ->setEntity($params->entities->primary->first())
                ->create();

        $document = $this->get('hateoas.resource_manager')
            ->createDocumentFactory()
            ->setParams($params)
            ->setResources($resources)
            ->create();

        $json = $this->get('hateoas.serializer.document')
            ->serialize($document);

        return $this->createNoCacheResponse($json);
    }

    /**
     * @Route("/{primaryType}/{ids}", name="hateoas_magic_delete", methods="DELETE")
     * @param string $primaryType
     * @param string $ids
     * @throws AccessDeniedHttpException
     * @throws NotFoundHttpException
     * @throws BadRequestHttpException
     * @see http://jsonapi.org/format/#crud-deleting
     * @todo Rollback everything if anything goes wrong.
     */
    public function deleteAction($primaryType, $ids)
    {
        try {
            $params = $this->get('hateoas.request_parser')->parse($this->getRequest());
        } catch (NotFoundException $e) {
            throw new NotFoundHttpException($e->getMessage(), $e);
        } catch (ActionNotAllowedException $e) {
            throw new MethodNotAllowedHttpException(
                $e->getAllowedMethods(), $e->getMessage(), $e
            );
        } catch (ParseException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        } catch (EntityAccessDeniedException $e) {
            throw new AccessDeniedHttpException($e->getMessage(), $e);
        } catch (DocumentTooLargeException $e) {
            throw new DocumentTooLargeHttpException($e->getMessage(), $e);
        } catch (ConflictException $e) {
            throw new ConflictHttpException($e->getMessage(), $e);
        }

        $em = $this->getDoctrine()->getManager();
        $em->getConnection()->beginTransaction();

        try {
            foreach ($params->entities->primary as $entity) {
                $this->get('hateoas.entity.deleter')
                    ->delete($params, $entity);
            }

            $em->getConnection()->commit();
        } catch (\Exception $e) {
            $em->getConnection()->rollback();
            throw $e;
        }

        return $this->createNoCacheResponse(
            NULL, JsonResponse::HTTP_NO_CONTENT
        );
    }

    /**
     * @param array &$data
     * @return array
     * @todo Move.
     */
    private function extractLinks(array &$data)
    {
        $links = isset($data['links']) ? $data['links'] : [];
        unset($data['links']);

        return $links;
    }

    /**
     * @param array &$data
     * @return array
     * @todo Move.
     */
    private function extractMetadata(array &$data)
    {
        $metadata = isset($data['meta']) ? $data['meta'] : [];
        unset($data['meta']);

        return $metadata;
    }
}