From 1310d53589c4fbc0ca5ce6080aee18bcf195cb15 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 26 Jun 2024 13:45:15 +0200 Subject: [PATCH] Implement context-specific voters for all current entities that can be linked to a document For reusability an AbstractStoredObjectVoter was created and a StoredObjectVoterInterface. A WorkflowDocumentService checks whether the StoredObject is involved in a workflow. --- .../Repository/ActivityRepository.php | 17 ++++- .../AccompanyingCourseDocumentRepository.php | 14 ++-- ...ssociatedEntityToStoredObjectInterface.php | 10 +++ .../Repository/PersonDocumentRepository.php | 13 +++- ...panyingCourseDocumentStoredObjectVoter.php | 61 ----------------- .../Authorization/StoredObjectVoter.php | 8 ++- .../StoredObjectVoterInterface.php | 5 +- .../AbstractStoredObjectVoter.php | 67 +++++++++++++++++++ .../AccompanyingCourseStoredObjectVoter.php | 46 +++++++++++++ ...gPeriodWorkEvaluationStoredObjectVoter.php | 50 ++++++++++++++ .../ActivityStoredObjectVoter.php | 50 ++++++++++++++ .../EventStoredObjectVoter.php | 49 ++++++++++++++ .../PersonStoredObjectVoter.php | 49 ++++++++++++++ .../Service/WorkflowDocumentService.php | 14 ++-- src/Bundle/ChillEventBundle/Entity/Event.php | 2 +- .../Repository/EventRepository.php | 59 ++++++++++++++-- .../Workflow/EntityWorkflowRepository.php | 16 +++++ ...PeriodWorkEvaluationDocumentRepository.php | 14 +++- 18 files changed, 456 insertions(+), 88 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php delete mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AbstractStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingCourseStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingPeriodWorkEvaluationStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/ActivityStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/EventStoredObjectVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/PersonStoredObjectVoter.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index a7025d4a9..54672c6b7 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; +use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; @@ -23,7 +25,7 @@ use Doctrine\Persistence\ManagerRegistry; * @method Activity[] findAll() * @method Activity[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ActivityRepository extends ServiceEntityRepository +class ActivityRepository extends ServiceEntityRepository implements AssociatedEntityToStoredObjectInterface { public function __construct(ManagerRegistry $registry) { @@ -97,4 +99,17 @@ class ActivityRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object + { + $qb = $this->createQueryBuilder('a'); + $query = $qb + ->join('a.documents', 'ad') + ->join('ad.storedObject', 'so') + ->where('so.id = :storedObjectId') + ->setParameter('storedObjectId', $storedObject->getId()) + ->getQuery(); + + return $query->getResult(); + } } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/AccompanyingCourseDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/AccompanyingCourseDocumentRepository.php index 93cf00ecb..246c7f2d9 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/AccompanyingCourseDocumentRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/AccompanyingCourseDocumentRepository.php @@ -19,7 +19,7 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -class AccompanyingCourseDocumentRepository implements ObjectRepository +class AccompanyingCourseDocumentRepository implements ObjectRepository, AssociatedEntityToStoredObjectInterface { private readonly EntityRepository $repository; @@ -46,12 +46,12 @@ class AccompanyingCourseDocumentRepository implements ObjectRepository return $qb->getQuery()->getSingleScalarResult(); } - public function findLinkedCourseDocument(StoredObject $storedObject): ?AccompanyingCourseDocument { - + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object + { $qb = $this->repository->createQueryBuilder('d'); $query = $qb->where('d.storedObject = :storedObject') - ->setParameter('storedObject', $storedObject) - ->getQuery(); + ->setParameter('storedObject', $storedObject) + ->getQuery(); return $query->getResult(); } @@ -66,7 +66,7 @@ class AccompanyingCourseDocumentRepository implements ObjectRepository return $this->repository->findAll(); } - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } @@ -76,7 +76,7 @@ class AccompanyingCourseDocumentRepository implements ObjectRepository return $this->findOneBy($criteria); } - public function getClassName() + public function getClassName(): string { return AccompanyingCourseDocument::class; } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php new file mode 100644 index 000000000..5349251c5 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php @@ -0,0 +1,10 @@ + */ -readonly class PersonDocumentRepository implements ObjectRepository +readonly class PersonDocumentRepository implements ObjectRepository, AssociatedEntityToStoredObjectInterface { private EntityRepository $repository; @@ -53,4 +54,14 @@ readonly class PersonDocumentRepository implements ObjectRepository { return PersonDocument::class; } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object + { + $qb = $this->repository->createQueryBuilder('d'); + $query = $qb->where('d.storedObject = :storedObject') + ->setParameter('storedObject', $storedObject) + ->getQuery(); + + return $query->getResult(); + } } diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentStoredObjectVoter.php deleted file mode 100644 index 931378a61..000000000 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/AccompanyingCourseDocumentStoredObjectVoter.php +++ /dev/null @@ -1,61 +0,0 @@ -repository->findLinkedCourseDocument($subject) instanceof AccompanyingCourseDocument; - } - - public function voteOnAttribute(string $attribute, StoredObject $subject, TokenInterface $token): bool - { - if (!$token->getUser() instanceof User) { - return false; - } - - // Retrieve the related accompanying course document - $accompanyingCourseDocument = $this->repository->findLinkedCourseDocument($subject); - - // Determine the attribute to pass to AccompanyingCourseDocumentVoter - $voterAttribute = match($attribute) { - self::SEE_AND_EDIT => AccompanyingCourseDocumentVoter::UPDATE, - default => AccompanyingCourseDocumentVoter::SEE_DETAILS, - }; - - // Check access using AccompanyingCourseDocumentVoter - if (false === $this->accompanyingCourseDocumentVoter->voteOnAttribute($voterAttribute, $accompanyingCourseDocument, $token)) { - return false; - } - - // Check if entity is related to a workflow, if so, check if user can apply transition - $relatedWorkflow = $this->workflowDocumentService->getRelatedWorkflow($accompanyingCourseDocument); - - if ($relatedWorkflow instanceof EntityWorkflow){ - return $this->workflowDocumentService->canApplyTransition($relatedWorkflow); - } - - return true; - } -} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter.php index 7e2becab4..3bb5fa396 100644 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter.php +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter.php @@ -48,15 +48,19 @@ class StoredObjectVoter extends Voter return false; } + $attributeAsEnum = StoredObjectRoleEnum::from($attribute); + // Loop through context-specific voters foreach ($this->storedObjectVoters as $storedObjectVoter) { - if ($storedObjectVoter->supports($attribute, $subject)) { - return $storedObjectVoter->voteOnAttribute($attribute, $subject, $token); + if ($storedObjectVoter->supports($attributeAsEnum, $subject)) { + return $storedObjectVoter->voteOnAttribute($attributeAsEnum, $subject, $token); } } // User role-based fallback if ($this->security->isGranted('ROLE_USER') || $this->security->isGranted('ROLE_ADMIN')) { + // TODO: this maybe considered as a security issue, as all authenticated users can reach a stored object which + // is potentially detached from an existing entity. return true; } diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoterInterface.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoterInterface.php index c0fd7e09f..516722654 100644 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoterInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoterInterface.php @@ -12,12 +12,13 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Security\Authorization; use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; interface StoredObjectVoterInterface { - public function supports(string $attribute, StoredObject $subject): bool; + public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool; - public function voteOnAttribute(string $attribute, StoredObject $subject, TokenInterface $token): bool; + public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool; } diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AbstractStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AbstractStoredObjectVoter.php new file mode 100644 index 000000000..7ca73f206 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AbstractStoredObjectVoter.php @@ -0,0 +1,67 @@ +getClass(); + return $this->getRepository()->findAssociatedEntityToStoredObject($subject) instanceof $class; + } + + public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool + { + if (!$token->getUser() instanceof User) { + return false; + } + + // Retrieve the related accompanying course document + $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject); + + // Determine the attribute to pass to AccompanyingCourseDocumentVoter + $voterAttribute = $this->attributeToRole($attribute); + + if (false === $this->security->isGranted($voterAttribute, $entity)) { + return false; + } + + if ($this->canBeAssociatedWithWorkflow()) { + if (null === $this->workflowDocumentService) { + throw new \LogicException("Provide a workflow document service"); + } + + return $this->workflowDocumentService->notBlockedByWorkflow($entity); + } + + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingCourseStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingCourseStoredObjectVoter.php new file mode 100644 index 000000000..a41ea3067 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingCourseStoredObjectVoter.php @@ -0,0 +1,46 @@ +repository; + } + + protected function attributeToRole(StoredObjectRoleEnum $attribute): string + { + return match ($attribute) { + StoredObjectRoleEnum::EDIT => AccompanyingCourseDocumentVoter::UPDATE, + StoredObjectRoleEnum::SEE => AccompanyingCourseDocumentVoter::SEE_DETAILS, + }; + } + + protected function getClass(): string + { + return AccompanyingCourseDocument::class; + } + + protected function canBeAssociatedWithWorkflow(): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingPeriodWorkEvaluationStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingPeriodWorkEvaluationStoredObjectVoter.php new file mode 100644 index 000000000..423730767 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/AccompanyingPeriodWorkEvaluationStoredObjectVoter.php @@ -0,0 +1,50 @@ +repository; + } + + /** + * @inheritDoc + */ + protected function getClass(): string + { + return AccompanyingPeriodWorkEvaluationDocument::class; + } + + protected function attributeToRole(StoredObjectRoleEnum $attribute): string + { + //Question: there is no update/edit check in AccompanyingPeriodWorkEvaluationDocumentVoter, so for both SEE and EDIT of the + // stored object I check with SEE right in AccompanyingPeriodWorkEvaluationDocumentVoter, correct? + return match ($attribute) { + StoredObjectRoleEnum::SEE, StoredObjectRoleEnum::EDIT => AccompanyingPeriodWorkEvaluationDocumentVoter::SEE, + }; + } + + protected function canBeAssociatedWithWorkflow(): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/ActivityStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/ActivityStoredObjectVoter.php new file mode 100644 index 000000000..a36535005 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/ActivityStoredObjectVoter.php @@ -0,0 +1,50 @@ +repository; + } + + /** + * @inheritDoc + */ + protected function getClass(): string + { + return Activity::class; + } + + protected function attributeToRole(StoredObjectRoleEnum $attribute): string + { + return match ($attribute) { + StoredObjectRoleEnum::EDIT => ActivityVoter::UPDATE, + StoredObjectRoleEnum::SEE => ActivityVoter::SEE_DETAILS, + }; + } + + protected function canBeAssociatedWithWorkflow(): bool + { + return false; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/EventStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/EventStoredObjectVoter.php new file mode 100644 index 000000000..218ce1980 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/EventStoredObjectVoter.php @@ -0,0 +1,49 @@ +repository; + } + + /** + * @inheritDoc + */ + protected function getClass(): string + { + return Event::class; + } + + protected function attributeToRole(StoredObjectRoleEnum $attribute): string + { + return match ($attribute) { + StoredObjectRoleEnum::EDIT => EventVoter::UPDATE, + StoredObjectRoleEnum::SEE => EventVoter::SEE_DETAILS, + }; + } + + protected function canBeAssociatedWithWorkflow(): bool + { + return false; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/PersonStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/PersonStoredObjectVoter.php new file mode 100644 index 000000000..6c3b0b807 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoters/PersonStoredObjectVoter.php @@ -0,0 +1,49 @@ +repository; + } + + /** + * @inheritDoc + */ + protected function getClass(): string + { + return PersonDocument::class; + } + + protected function attributeToRole(StoredObjectRoleEnum $attribute): string + { + return match ($attribute) { + StoredObjectRoleEnum::EDIT => PersonDocumentVoter::UPDATE, + StoredObjectRoleEnum::SEE => PersonDocumentVoter::SEE_DETAILS, + }; + } + + protected function canBeAssociatedWithWorkflow(): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Service/WorkflowDocumentService.php b/src/Bundle/ChillDocStoreBundle/Service/WorkflowDocumentService.php index 498348b96..f796fe56e 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/WorkflowDocumentService.php +++ b/src/Bundle/ChillDocStoreBundle/Service/WorkflowDocumentService.php @@ -13,19 +13,19 @@ class WorkflowDocumentService { } - public function getRelatedWorkflow($entity): ?EntityWorkflow + public function notBlockedByWorkflow($entity): bool { - return $this->repository->findByRelatedEntity(get_class($entity), $entity->getId()); - } + /** + * @var EntityWorkflow + */ + $workflow = $this->repository->findByRelatedEntity(get_class($entity), $entity->getId()); - public function canApplyTransition(EntityWorkflow $entityWorkflow): bool - { - if ($entityWorkflow->isFinal()) { + if ($workflow->isFinal()) { return false; } $currentUser = $this->security->getUser(); - if ($entityWorkflow->getCurrentStep()->getAllDestUser()->contains($currentUser)) { + if ($workflow->getCurrentStep()->getAllDestUser()->contains($currentUser)) { return true; } diff --git a/src/Bundle/ChillEventBundle/Entity/Event.php b/src/Bundle/ChillEventBundle/Entity/Event.php index 6f7ee7ef0..be5969363 100644 --- a/src/Bundle/ChillEventBundle/Entity/Event.php +++ b/src/Bundle/ChillEventBundle/Entity/Event.php @@ -31,7 +31,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** * Class Event. */ -#[ORM\Entity(repositoryClass: \Chill\EventBundle\Repository\EventRepository::class)] +#[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'chill_event_event')] class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInterface, TrackUpdateInterface diff --git a/src/Bundle/ChillEventBundle/Repository/EventRepository.php b/src/Bundle/ChillEventBundle/Repository/EventRepository.php index 7406748b4..24eb095ec 100644 --- a/src/Bundle/ChillEventBundle/Repository/EventRepository.php +++ b/src/Bundle/ChillEventBundle/Repository/EventRepository.php @@ -11,17 +11,66 @@ declare(strict_types=1); namespace Chill\EventBundle\Repository; +use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\EventBundle\Entity\Event; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ObjectRepository; /** * Class EventRepository. */ -class EventRepository extends ServiceEntityRepository +class EventRepository implements ObjectRepository, AssociatedEntityToStoredObjectInterface { - public function __construct(ManagerRegistry $registry) + private readonly EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Event::class); + $this->repository = $entityManager->getRepository(Event::class); + } + + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object + { + $qb = $this->createQueryBuilder('e'); + $query = $qb + ->join('e.documents', 'ed') + ->join('ed.storedObject', 'so') + ->where('so.id = :storedObjectId') + ->setParameter('storedObjectId', $storedObject->getId()) + ->getQuery(); + + return $query->getResult(); + } + + public function find($id) + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria) + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return Event::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php index 66b3ab379..7d2e047d3 100644 --- a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php @@ -99,6 +99,22 @@ class EntityWorkflowRepository implements ObjectRepository return $this->repository->findAll(); } + public function findByRelatedEntity($entityClass, $relatedEntityId): ?EntityWorkflow + { + $qb = $this->repository->createQueryBuilder('w'); + + $query = $qb->where( + $qb->expr()->andX( + $qb->expr()->eq('w.relatedEntityClass', ':entity_class'), + $qb->expr()->eq('w.relatedEntityId', ':entity_id'), + ) + )->setParameter('entity_class', $entityClass) + ->setParameter('entity_id', $relatedEntityId); + + return $query->getQuery()->getResult(); + + } + /** * @param mixed|null $limit * @param mixed|null $offset diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocumentRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocumentRepository.php index 59bb3f915..60dcdf1b1 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocumentRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocumentRepository.php @@ -11,12 +11,14 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository\AccompanyingPeriod; +use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ObjectRepository; -class AccompanyingPeriodWorkEvaluationDocumentRepository implements ObjectRepository +class AccompanyingPeriodWorkEvaluationDocumentRepository implements ObjectRepository, AssociatedEntityToStoredObjectInterface { private readonly EntityRepository $repository; @@ -58,4 +60,14 @@ class AccompanyingPeriodWorkEvaluationDocumentRepository implements ObjectReposi { return AccompanyingPeriodWorkEvaluationDocument::class; } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object + { + $qb = $this->repository->createQueryBuilder('ed'); + $query = $qb->where('ed.storedObject = :storedObject') + ->setParameter('storedObject', $storedObject) + ->getQuery(); + + return $query->getResult(); + } }