partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit 4721036ad58fd943f6656356e31a4075f53ee88b
parent 9578c749a471594f49a9571ae151c1d9f869eb75
Author: Felicia Hummel <felicia@partkeepr.com>
Date:   Wed,  1 Nov 2017 16:04:42 +0100

Merge remote-tracking branch 'origin/master'

Diffstat:
Msrc/PartKeepr/CoreBundle/Entity/BaseEntity.php | 5+++++
Asrc/PartKeepr/CoreBundle/EventListener/RequestExceptionListener.php | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/CoreBundle/Exceptions/TranslatableException.php | 2+-
Msrc/PartKeepr/CoreBundle/Resources/config/services.xml | 9+++++++++
Asrc/PartKeepr/DoctrineReflectionBundle/Exception/EntityInUseException.php | 35+++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/DoctrineReflectionBundle/Exception/ExceptionWrapperHandler.php | 5+++--
Msrc/PartKeepr/DoctrineReflectionBundle/Resources/config/services.xml | 4++++
Asrc/PartKeepr/DoctrineReflectionBundle/Services/DeletionService.php | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/PartKeepr/PartBundle/Entity/PartImage.php | 51---------------------------------------------------
Msrc/PartKeepr/ProjectBundle/Entity/ProjectPart.php | 6++++++
Msrc/PartKeepr/ProjectBundle/Entity/ProjectRunPart.php | 8++++++++
Msrc/PartKeepr/ProjectBundle/Entity/ReportPart.php | 7+++++++
Msrc/PartKeepr/StockBundle/Entity/StockEntry.php | 2+-
13 files changed, 276 insertions(+), 55 deletions(-)

diff --git a/src/PartKeepr/CoreBundle/Entity/BaseEntity.php b/src/PartKeepr/CoreBundle/Entity/BaseEntity.php @@ -27,4 +27,9 @@ abstract class BaseEntity { return $this->id; } + + public function __toString() + { + return get_class($this) . " #" . $this->getId(); + } } diff --git a/src/PartKeepr/CoreBundle/EventListener/RequestExceptionListener.php b/src/PartKeepr/CoreBundle/EventListener/RequestExceptionListener.php @@ -0,0 +1,108 @@ +<?php +/** + * Created by PhpStorm. + * User: felicitus + * Date: 10/25/17 + * Time: 11:01 PM + */ + +namespace PartKeepr\CoreBundle\EventListener; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Dunglas\ApiBundle\Action\ActionUtilTrait; +use Dunglas\ApiBundle\Api\IriConverter; +use Dunglas\ApiBundle\JsonLd\Response; +use Dunglas\ApiBundle\Model\DataProviderInterface; +use PartKeepr\DoctrineReflectionBundle\Exception\EntityInUseException; +use PartKeepr\DoctrineReflectionBundle\Services\DeletionService; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * Handle referential integrity errors. + */ +class RequestExceptionListener extends \Dunglas\ApiBundle\Hydra\EventListener\RequestExceptionListener +{ + use ActionUtilTrait; + + const FORMAT = 'jsonld'; + + /** + * @var DataProviderInterface + */ + private $dataProvider; + + /** + * @var IriConverter + */ + private $iriConverter; + + /** + * @var DeletionService + */ + private $deletionService; + + public function __construct( + NormalizerInterface $normalizer, + DataProviderInterface $dataProvider, + IriConverter $iriConverter, + DeletionService $deletionService + ) { + $this->dataProvider = $dataProvider; + $this->iriConverter = $iriConverter; + $this->deletionService = $deletionService; + parent::__construct($normalizer); + } + + /** + * @param GetResponseForExceptionEvent $event + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + $request = $event->getRequest(); + if (!$request->attributes->has('_resource_type') || self::FORMAT !== $request->attributes->get('_api_format')) { + return; + } + + $exception = $event->getException(); + $headers = []; + + if ($exception instanceof ForeignKeyConstraintViolationException) { + + $item = $this->iriConverter->getItemFromIri($request->getRequestUri()); + + $usedIn = $this->deletionService->findUndeletableUsages($item); + + $data = new EntityInUseException((string)$item, $usedIn); + + $status = Response::HTTP_FAILED_DEPENDENCY; + + + $event->setResponse(new \Symfony\Component\HttpFoundation\Response( + json_encode($data), + $status, + $headers + ));//list($resourceType) = $this->extractAttributes($request); + ; + + //$systemNotice = $this->getItem($this->dataProvider, $resourceType, $id); + /** + * @var $exception DBALException + */ + + /*$headers = $exception->getHeaders(); + $data = $exception; + + $event->setResponse(new Response( + $this->normalizer->normalize($data, 'hydra-error'), + $status, + $headers + ));*/ + } else { + parent::onKernelException($event); + } + + + } +} diff --git a/src/PartKeepr/CoreBundle/Exceptions/TranslatableException.php b/src/PartKeepr/CoreBundle/Exceptions/TranslatableException.php @@ -4,7 +4,7 @@ namespace PartKeepr\CoreBundle\Exceptions; abstract class TranslatableException extends \Exception { - public function __construct() + public function __construct($code = 0, \Throwable $previous = null) { parent::__construct($this->getMessageKey()); } diff --git a/src/PartKeepr/CoreBundle/Resources/config/services.xml b/src/PartKeepr/CoreBundle/Resources/config/services.xml @@ -19,5 +19,14 @@ <argument type="service" id="translator"/> <argument type="service" id="partkeepr.remote_file_loader.factory"/> </service> + <service id="partkeepr.listener.request_exception" class="PartKeepr\CoreBundle\EventListener\RequestExceptionListener"> + <argument type="service" id="api.serializer" /> + <argument type="service" id="api.data_provider" /> + <argument type="service" id="api.iri_converter" /> + <argument type="service" id="doctrine_reflection_deletion_service"/> + + + <tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" priority="999"/> + </service> </services> </container> diff --git a/src/PartKeepr/DoctrineReflectionBundle/Exception/EntityInUseException.php b/src/PartKeepr/DoctrineReflectionBundle/Exception/EntityInUseException.php @@ -0,0 +1,34 @@ +<?php +/** + * Created by PhpStorm. + * User: felicitus + * Date: 10/25/17 + * Time: 11:45 PM + */ + +namespace PartKeepr\DoctrineReflectionBundle\Exception; + + +use PartKeepr\CoreBundle\Exceptions\TranslatableException; + +class EntityInUseException extends TranslatableException +{ + public $usedBy = []; + + public $entityName; + + public function __construct($entityName, array $usedBy, $code = 0, \Throwable $previous = null) + { + $this->usedBy = $usedBy; + $this->entityName = $entityName; + $this->{'hydra:title'} = $this->getMessageKey(); + $this->{'hydra:description'} = implode("<br/>", $usedBy); + + parent::__construct($code, $previous); + } + + public function getMessageKey() + { + return sprintf('%s is in use by:', (string)$this->entityName); + } +}+ \ No newline at end of file diff --git a/src/PartKeepr/DoctrineReflectionBundle/Exception/ExceptionWrapperHandler.php b/src/PartKeepr/DoctrineReflectionBundle/Exception/ExceptionWrapperHandler.php @@ -4,6 +4,7 @@ namespace PartKeepr\DoctrineReflectionBundle\Exception; use FOS\RestBundle\View\ExceptionWrapperHandlerInterface; +// @todo check if this is obsolete class ExceptionWrapperHandler implements ExceptionWrapperHandlerInterface { /** @@ -14,8 +15,8 @@ class ExceptionWrapperHandler implements ExceptionWrapperHandlerInterface public function wrap($data) { $data = [ - '@type' => 'Error', - 'hydra:title' => isset($context['title']) ? $context['title'] : 'An error occurred', + '@type' => 'Error', + 'hydra:title' => isset($context['title']) ? $context['title'] : 'An error occurred', 'hydra:description' => $data['message'], ]; diff --git a/src/PartKeepr/DoctrineReflectionBundle/Resources/config/services.xml b/src/PartKeepr/DoctrineReflectionBundle/Resources/config/services.xml @@ -24,6 +24,10 @@ <argument type="service" id="doctrine_filter_service"/> </service> + <service id="doctrine_reflection_deletion_service" class="PartKeepr\DoctrineReflectionBundle\Services\DeletionService"> + <argument type="service" id="doctrine.orm.default_entity_manager"/> + </service> + <service id="partkeepr.exceptionwrapper" class="PartKeepr\DoctrineReflectionBundle\Exception\ExceptionWrapperHandler"> </service> diff --git a/src/PartKeepr/DoctrineReflectionBundle/Services/DeletionService.php b/src/PartKeepr/DoctrineReflectionBundle/Services/DeletionService.php @@ -0,0 +1,88 @@ +<?php +/** + * Created by PhpStorm. + * User: felicitus + * Date: 10/25/17 + * Time: 5:38 PM + */ + +namespace PartKeepr\DoctrineReflectionBundle\Services; + + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadata; + +class DeletionService +{ + /** + * @var EntityManager + */ + private $em; + + + public function __construct(EntityManager $entityManager) + { + $this->em = $entityManager; + } + + + public function findUndeletableUsages($entity) + { + $realClassName = $this->em->getClassMetadata(get_class($entity))->name; + + $meta = $this->em->getMetadataFactory()->getAllMetadata(); + $sourceEntityMetaData = $this->getAllMetadataInfoFor($realClassName); + + $usedIn = []; + + foreach ($meta as $foo) { + /* @var ClassMetadata $foo */ + foreach ($foo->getAssociationMappings() as $associationMapping) { + if ($associationMapping["targetEntity"] === $realClassName && $associationMapping["isOwningSide"]) { + + //var_dump($associationMapping-> + if ($associationMapping["inversedBy"] !== null) { + $inverseAssociationMapping = $sourceEntityMetaData->getAssociationMapping($associationMapping["inversedBy"]); + + if ($inverseAssociationMapping["isCascadeRemove"]) { + continue; + } + } + + $qb = $this->em->createQueryBuilder(); + $qb->select("q")->from($associationMapping["sourceEntity"], "q")->where( + $qb->expr()->eq("q." . $associationMapping["fieldName"], ":query") + ); + + $qb->setParameter(":query", $entity); + + foreach ($qb->getQuery()->getResult() as $result) { + $usedIn[] = (string)$result; + } + } + } + } + + return $usedIn; + } + + /** + * @param $className + * @return ClassMetadata|null + */ + protected function getAllMetadataInfoFor($className) + { + $data = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($data as $info) { + /** + * @var ClassMetadata $info + */ + if ($info->getName() == $className) { + return $info; + } + } + + return null; + } +}+ \ No newline at end of file diff --git a/src/PartKeepr/PartBundle/Entity/PartImage.php b/src/PartKeepr/PartBundle/Entity/PartImage.php @@ -1,51 +0,0 @@ -<?php - -namespace PartKeepr\PartBundle\Entity; - -use Doctrine\ORM\Mapping as ORM; -use PartKeepr\ImageBundle\Entity\Image; - -/** - * Holds a part image. - * - * @ORM\Entity - **/ -class PartImage extends Image -{ - /** - * The part object. - * - * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="images") - * - * @var Part - */ - private $part = null; - - /** - * Creates a new part image instance. - */ - public function __construct() - { - parent::__construct(Image::IMAGE_PART); - } - - /** - * Sets the part. - * - * @param Part $part The part to set - */ - public function setPart(Part $part) - { - $this->part = $part; - } - - /** - * Returns the part. - * - * @return Part the part - */ - public function getPart() - { - return $this->part; - } -} diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php b/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php @@ -265,4 +265,10 @@ class ProjectPart extends BaseEntity { $this->remarks = $remarks; } + + public function __toString() + { + //@todo i18n + return sprintf("Used in project %s", $this->getProject()->getName()) . " / " . parent::__toString(); + } } diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectRunPart.php b/src/PartKeepr/ProjectBundle/Entity/ProjectRunPart.php @@ -117,4 +117,12 @@ class ProjectRunPart extends BaseEntity { $this->quantity = $quantity; } + + public function __toString() + { + // @todo i18n + return sprintf("Used in project run for project %s on %s", + $this->getProjectRun()->getProject()->getName(), + $this->getProjectRun()->getRunDateTime()->format("Y-m-d H:i:s")) . " / " . parent::__toString(); + } } diff --git a/src/PartKeepr/ProjectBundle/Entity/ReportPart.php b/src/PartKeepr/ProjectBundle/Entity/ReportPart.php @@ -336,4 +336,11 @@ class ReportPart extends BaseEntity return $this; } + + public function __toString() + { + // @todo i18n + return sprintf("Used in project report %s %s", $this->getReport()->getName(), + $this->getReport()->getCreateDateTime()->format("Y-m-d H:i:s")) . " / " . parent::__toString(); + } } diff --git a/src/PartKeepr/StockBundle/Entity/StockEntry.php b/src/PartKeepr/StockBundle/Entity/StockEntry.php @@ -23,7 +23,7 @@ class StockEntry extends BaseEntity private $stockLevel; /** - * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="stockEntries") + * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="stockLevels") * @Groups({"default"}) */ private $part;