create api for social issue consistency

This commit is contained in:
Julien Fastré 2021-08-21 23:18:55 +02:00
parent 2a3f869882
commit 09e5cc1545
6 changed files with 341 additions and 1 deletions

View File

@ -0,0 +1,28 @@
<?php
namespace Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency;
use Doctrine\Common\Collections\Collection;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
/**
* This interface must be implemented on entities which:
*
* * have both social issue and is linked to an AccompanyingPeriod
* * when the social issues in the entity should be added to the accompanying period
*
* A doctrine listener will list social issues which are associated to the entity, but
* not on the AccompanyingPeriod, and push them back to the accompanying period.
*/
interface AccompanyingPeriodLinkedWithSocialIssuesEntityInterface
{
public function getAccompanyingPeriod(): AccompanyingPeriod;
/**
* @return Collection|SocialIssue[]
*/
public function getSocialIssues(): Collection;
public function removeSocialIssue(SocialIssue $issue): self;
}

View File

@ -0,0 +1,66 @@
<?php
namespace Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* This service listens for preUpdate and prePersist events on some entities
* and ensure consistency of SocialIssue with an associated AccompanyingPeriod.
*
* The entity must implements interface Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface.
*
* This subscriber is not called automatically: for performance reasons, this
* apply only on entities which are configured. See https://symfony.com/doc/4.4/doctrine/events.html#doctrine-entity-listeners
*/
final class AccompanyingPeriodSocialIssueConsistencyEntityListener
{
public function prePersist(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity, LifecycleEventArgs $eventArgs)
{
$this->ensureConsistencyEntity($entity);
}
public function preUpdate(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity, LifecycleEventArgs $eventArgs)
{
$this->ensureConsistencyEntity($entity);
}
public function prePersistAccompanyingPeriod(AccompanyingPeriod $period, LifecycleEventArgs $eventArgs)
{
$this->ensureConsistencyAccompanyingPeriod($period);
}
public function preUpdateAccompanyingPeriod(AccompanyingPeriod $period, LifecycleEventArgs $eventArgs)
{
$this->ensureConsistencyAccompanyingPeriod($period);
}
private function ensureConsistencyEntity(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity): void
{
// remove issues parents on the entity itself
$ancestors = SocialIssue::findAncestorSocialIssues($entity->getSocialIssues());
foreach ($ancestors as $ancestor) {
$entity->removeSocialIssue($ancestor);
}
$period = $entity->getAccompanyingPeriod();
foreach ($entity->getSocialIssues() as $issue) {
// the entity itself test if the social issue is already associated, or not
$period->addSocialIssue($issue);
}
$this->ensureConsistencyAccompanyingPeriod($period);
}
private function ensureConsistencyAccompanyingPeriod(AccompanyingPeriod $period): void
{
$ancestors = SocialIssue::findAncestorSocialIssues($period->getSocialIssues());
foreach ($ancestors as $ancestor) {
$period->removeSocialIssue($ancestor);
}
}
}

View File

@ -2,11 +2,13 @@
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
@ -470,4 +472,4 @@ use Symfony\Component\Validator\Constraints as Assert;
return $this;
}
}
}

View File

@ -233,4 +233,57 @@ class SocialIssue
return $recursiveSocialActions;
}
/**
* Recursive method which return true if the current $issue is a descendant
* of the $issue given in parameter.
*
* @param SocialIssue $issue
* @return bool
*/
public function isDescendantOf(SocialIssue $issue): bool
{
if (!$this->hasParent()) {
return false;
}
if ($this->getParent() === $issue) {
return true;
}
return $this->getParent()->isDescendantOf($issue);
}
/**
* In a SocialIssues's collection, find the elements which are an ancestor of
* other elements.
*
* Removing those elements of the Collection (which is not done by this method)
* will ensure that only the most descendent elements are present in the collection,
* (any ancestor of another element are present).
*
* @param Collection|SocialIssue[] $socialIssues
* @return Collection|SocialIssue[]
*/
public static function findAncestorSocialIssues(Collection $socialIssues): Collection
{
$ancestors = new ArrayCollection();
foreach ($socialIssues as $candidateChild) {
if ($ancestors->contains($candidateChild)) {
continue;
}
foreach ($socialIssues as $candidateParent) {
if ($ancestors->contains($candidateParent)) {
continue;
}
if ($candidateChild->isDescendantOf($candidateParent)) {
$ancestors->add($candidateParent);
}
}
}
return $ancestors;
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace Chill\PersonBundle\Tests\AccompanyingPeriod\SocialIssueConsistency;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodSocialIssueConsistencyEntityListener;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Event\LifecycleEventArgs;
use PHPUnit\Framework\TestCase;
class AccompanyingPeriodSocialIssueConsistencyEntityListenerTest extends TestCase
{
public function testPrePersistAccompanyingPeriod()
{
$arraySocialIssues = [
$parent = new SocialIssue(),
$child = (new SocialIssue())->setParent($parent),
$grandChild = (new SocialIssue())->setParent($child),
$grandGrandChild = (new SocialIssue())->setParent($grandChild),
];
$period = new AccompanyingPeriod();
$consistency = new AccompanyingPeriodSocialIssueConsistencyEntityListener();
foreach ($arraySocialIssues as $issue) {
$period->addSocialIssue($issue);
}
$consistency->prePersistAccompanyingPeriod($period, $this->generateLifecycleArgs());
$this->assertCount(1, $period->getSocialIssues());
$this->assertSame($grandGrandChild, $period->getSocialIssues()->first());
}
public function testPreUpdate()
{
$socialIssues = new ArrayCollection([
$parent = new SocialIssue(),
$child = (new SocialIssue())->setParent($parent),
$grandChild = (new SocialIssue())->setParent($child),
$grandGrandChild = (new SocialIssue())->setParent($grandChild),
]);
$period = (new AccompanyingPeriod())->addSocialIssue($unrelated = new SocialIssue());
$entity = $this->generateClass($period, $socialIssues);
$consistency = new AccompanyingPeriodSocialIssueConsistencyEntityListener();
$consistency->preUpdate($entity, $this->generateLifecycleArgs());
$this->assertCount(2, $period->getSocialIssues());
$this->assertContains($grandGrandChild, $period->getSocialIssues());
$this->assertContains($unrelated, $period->getSocialIssues());
$this->assertCount(1, $entity->getSocialIssues());
$this->assertContains($grandGrandChild, $entity->getSocialIssues());
}
public function testPrePersist()
{
$socialIssues = new ArrayCollection([
$parent = new SocialIssue(),
$child = (new SocialIssue())->setParent($parent),
$grandChild = (new SocialIssue())->setParent($child),
$grandGrandChild = (new SocialIssue())->setParent($grandChild),
]);
$period = (new AccompanyingPeriod())->addSocialIssue($unrelated = new SocialIssue());
$entity = $this->generateClass($period, $socialIssues);
$consistency = new AccompanyingPeriodSocialIssueConsistencyEntityListener();
$consistency->prePersist($entity, $this->generateLifecycleArgs());
$this->assertCount(2, $period->getSocialIssues());
$this->assertContains($grandGrandChild, $period->getSocialIssues());
$this->assertContains($unrelated, $period->getSocialIssues());
$this->assertCount(1, $entity->getSocialIssues());
$this->assertContains($grandGrandChild, $entity->getSocialIssues());
}
public function testPreUpdateAccompanyingPeriod()
{
$arraySocialIssues = [
$parent = new SocialIssue(),
$child = (new SocialIssue())->setParent($parent),
$grandChild = (new SocialIssue())->setParent($child),
$grandGrandChild = (new SocialIssue())->setParent($grandChild),
];
$period = new AccompanyingPeriod();
$consistency = new AccompanyingPeriodSocialIssueConsistencyEntityListener();
foreach ($arraySocialIssues as $issue) {
$period->addSocialIssue($issue);
}
$consistency->prePersistAccompanyingPeriod($period, $this->generateLifecycleArgs());
$this->assertCount(1, $period->getSocialIssues());
$this->assertSame($grandGrandChild, $period->getSocialIssues()->first());
}
protected function generateLifecycleArgs(): LifecycleEventArgs
{
return $this->createMock(LifecycleEventArgs::class);
}
protected function generateClass(AccompanyingPeriod $period, Collection $socialIssues): AccompanyingPeriodLinkedWithSocialIssuesEntityInterface
{
return new class($period, $socialIssues) implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface
{
public Collection $socialIssues;
public AccompanyingPeriod $period;
public function __construct($period, $socialIssues)
{
$this->period = $period;
$this->socialIssues = $socialIssues;
}
public function getAccompanyingPeriod(): AccompanyingPeriod
{
return $this->period;
}
public function getSocialIssues(): Collection
{
return $this->socialIssues;
}
public function removeSocialIssue(SocialIssue $issue): AccompanyingPeriodLinkedWithSocialIssuesEntityInterface
{
$this->socialIssues->removeElement($issue);
return $this;
}
};
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Chill\PersonBundle\Tests\Entity\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;
class SocialIssueTest extends TestCase
{
public function testIsDescendantOf()
{
$parent = new SocialIssue();
$child = (new SocialIssue())->setParent($parent);
$grandChild = (new SocialIssue())->setParent($child);
$grandGrandChild = (new SocialIssue())->setParent($grandChild);
$unrelated = new SocialIssue();
$this->assertTrue($grandGrandChild->isDescendantOf($parent));
$this->assertTrue($grandGrandChild->isDescendantOf($grandChild));
$this->assertTrue($grandGrandChild->isDescendantOf($child));
$this->assertFalse($grandGrandChild->isDescendantOf($unrelated));
$this->assertTrue($grandChild->isDescendantOf($parent));
$this->assertTrue($grandChild->isDescendantOf($child));
$this->assertFalse($grandChild->isDescendantOf($unrelated));
$this->assertFalse($grandChild->isDescendantOf($grandChild));
$this->assertFalse($unrelated->isDescendantOf($parent));
$this->assertFalse($child->isDescendantOf($grandChild));
}
public function testFindSocialIssuesAncestors()
{
$socialIssues = new ArrayCollection([
$parent = new SocialIssue(),
$child = (new SocialIssue())->setParent($parent),
$grandChild = (new SocialIssue())->setParent($child),
$grandGrandChild = (new SocialIssue())->setParent($grandChild),
$unrelated = new SocialIssue(),
]);
$ancestors = SocialIssue::findAncestorSocialIssues($socialIssues);
$this->assertCount(3, $ancestors);
$this->assertContains($parent, $ancestors);
$this->assertContains($child, $ancestors);
$this->assertContains($grandChild, $ancestors);
}
}