diff --git a/src/Models/Scene.php b/src/Models/Scene.php index bfec901..9ec8085 100644 --- a/src/Models/Scene.php +++ b/src/Models/Scene.php @@ -7,17 +7,26 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\JoinTable; +use Doctrine\ORM\Mapping\ManyToMany; use Doctrine\ORM\Mapping\OneToMany; use Doctrine\ORM\Mapping\Table; use Doctrine\ORM\Mapping\Column; - +use JetBrains\PhpStorm\Deprecated; use LotGD\Core\Exceptions\ArgumentException; +use LotGD\Core\Exceptions\AttributeMissingException; +use LotGD\Core\Exceptions\UnexpectedArrayKeyException; +use LotGD\Core\Exceptions\WrongTypeException; use LotGD\Core\Tools\Model\Creator; use LotGD\Core\Tools\Model\Deletor; use LotGD\Core\Tools\Model\PropertyManager; +use LotGD\Core\Tools\Model\Saveable; use LotGD\Core\Tools\Model\SceneBasics; use Ramsey\Uuid\Uuid; +use function array_merge; +use function count; + /** * A scene is a location within the game, such as the Village or the Tavern. Designed * to be a kind of "template" for generating the specific location information for @@ -27,7 +36,7 @@ use Ramsey\Uuid\Uuid; */ class Scene implements CreateableInterface, SceneConnectable { - use Creator; + use Saveable; use Deletor; use SceneBasics; use PropertyManager; @@ -65,6 +74,20 @@ class Scene implements CreateableInterface, SceneConnectable */ private ?Collection $properties; + /** + * @ManyToMany(targetEntity="SceneAttachment", inversedBy="scenes", cascade={"persist"}) + * @JoinTable( + * name="scenes_x_scene_attachments", + * joinColumns={ + * @JoinColumn(name="scene_id", referencedColumnName="id") + * }, + * inverseJoinColumns={ + * @JoinColumn(name="attachment_id", referencedColumnName="class") + * } + * ) + */ + private ?Collection $attachments; + // required for PropertyManager to now which class the properties belong to. private string $propertyClass = SceneProperty::class; @@ -80,15 +103,45 @@ class Scene implements CreateableInterface, SceneConnectable private ?Collection $connectedScenes = null; /** - * Constructor for a scene. + * Creates and returns an entity instance and fills values. + * @param array $arguments The values the instance should get + * @return CreateableInterface The created Entity + * @throws WrongTypeException|UnexpectedArrayKeyException + * @throws AttributeMissingException */ - public function __construct() + #[Deprecated("Use constructor directly.")] + public static function create(array $arguments): CreateableInterface + { + if (isset(self::$fillable) === false) { + throw new AttributeMissingException('self::$fillable is not defined.'); + } + + if (\is_array(self::$fillable) === false) { + throw new WrongTypeException('self::$fillable needs to be an array.'); + } + + $entity = new self($arguments["title"], $arguments["description"], $arguments["template"]); + + return $entity; + } + + /** + * Constructor for a scene. + * @param string $title + * @param string $description + * @param SceneTemplate|null $template + */ + public function __construct(string $title, string $description, ?SceneTemplate $template = null) { $this->id = Uuid::uuid4()->toString(); + $this->setTitle($title); + $this->setDescription($description); + $this->setTemplate($template); $this->connectionGroups = new ArrayCollection(); $this->outgoingConnections = new ArrayCollection(); $this->incomingConnections = new ArrayCollection(); + $this->attachments = new ArrayCollection(); } public function __toString(): string @@ -143,7 +196,7 @@ class Scene implements CreateableInterface, SceneConnectable */ public function hasConnectionGroup(string $name): bool { - return \count($this->filterConnectionGroupCollectionByName($name)) === 1; + return count($this->filterConnectionGroupCollectionByName($name)) === 1; } /** @@ -259,6 +312,11 @@ class Scene implements CreateableInterface, SceneConnectable return false; } + /** + * Returns a connection to another scene if it exists. Returns null if it does not exist. + * @param Scene $scene + * @return SceneConnection|null + */ public function getConnectionTo(self $scene): ?SceneConnection { foreach ($this->outgoingConnections as $outgoingConnection) { @@ -283,7 +341,7 @@ class Scene implements CreateableInterface, SceneConnectable public function getConnections(): Collection { return new ArrayCollection( - \array_merge( + array_merge( $this->outgoingConnections->toArray(), $this->incomingConnections->toArray() ) @@ -363,4 +421,31 @@ class Scene implements CreateableInterface, SceneConnectable return $connection; } + + /** + * @return Collection + */ + public function getAttachments(): Collection + { + return $this->attachments; + } + + /** + * @param SceneAttachment $sceneAttachment + * @return bool + */ + public function hasAttachment(SceneAttachment $sceneAttachment): bool + { + return $this->attachments->contains($sceneAttachment); + } + + /** + * @param SceneAttachment $attachmentClass + */ + public function addAttachment(SceneAttachment $attachmentClass): void + { + if (!$this->hasAttachment($attachmentClass)) { + $this->attachments->add($attachmentClass); + } + } } diff --git a/src/Models/SceneAttachment.php b/src/Models/SceneAttachment.php new file mode 100644 index 0000000..80d42c2 --- /dev/null +++ b/src/Models/SceneAttachment.php @@ -0,0 +1,77 @@ +class = $class; + $this->title = $title; + + $this->scenes = new ArrayCollection(); + } + + /** + * @return string + */ + public function getClass(): string + { + return $this->class; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @return Collection + */ + public function getScenes(): Collection + { + return $this->scenes; + } +} \ No newline at end of file diff --git a/src/Tools/Model/Creator.php b/src/Tools/Model/Creator.php index d25afc4..fdd155c 100644 --- a/src/Tools/Model/Creator.php +++ b/src/Tools/Model/Creator.php @@ -18,9 +18,9 @@ trait Creator /** * Creates and returns an entity instance and fills values. * @param array $arguments The values the instance should get - * @throws AttributeMissingException - * @throws WrongTypeException * @return CreateableInterface The created Entity + * @throws WrongTypeException|UnexpectedArrayKeyException + * @throws AttributeMissingException */ public static function create(array $arguments): CreateableInterface { diff --git a/src/Tools/Model/SceneBasics.php b/src/Tools/Model/SceneBasics.php index 02eec7c..e588727 100644 --- a/src/Tools/Model/SceneBasics.php +++ b/src/Tools/Model/SceneBasics.php @@ -17,7 +17,7 @@ trait SceneBasics private string $title = "{No scene set}"; /** @Column(type="text") */ private string $description = "{No scene set}"; - /** @Column(type="string", length=255) */ + /** * @ManyToOne(targetEntity="SceneTemplate", fetch="EAGER") * @JoinColumn(name="template", referencedColumnName="class", nullable=true) diff --git a/tests/Models/SceneAttachmentTest.php b/tests/Models/SceneAttachmentTest.php new file mode 100644 index 0000000..f5791c7 --- /dev/null +++ b/tests/Models/SceneAttachmentTest.php @@ -0,0 +1,118 @@ +assertInstanceOf(SceneAttachment::class, $sceneAttachment); + } + + public function testIfSceneAttachmentCreationFailsIfAttachmentIsNotSubclassOfAttachment() + { + $this->expectException(ArgumentException::class); + + $sceneAttachment = new SceneAttachment(InvalidTestAttachment::class, "Invalid Test Attachment"); + } + + public function testIfValidSceneAttachmentCanBePersistedAndGottenBackFromTheDatabase() + { + $em = $this->getEntityManager(); + $sceneAttachment = new SceneAttachment(TestAttachment::class, "Test Attachment"); + + // persist + $em->persist($sceneAttachment); + $em->flush(); + $em->clear(); + + // retrieve + $retrievedSceneAttachment = $em->getRepository(SceneAttachment::class)->find(TestAttachment::class); + + $this->assertInstanceOf(SceneAttachment::class, $retrievedSceneAttachment); + + // Delete again + $em->remove($retrievedSceneAttachment); + $em->flush(); + } + + public function testIfSceneGettersReturnGivenValuesProperly() + { + $sceneAttachment = new SceneAttachment(TestAttachment::class, "Test Attachment"); + + $this->assertSame($sceneAttachment->getClass(), TestAttachment::class); + $this->assertSame($sceneAttachment->getTitle(), "Test Attachment"); + } + + public function testIfPersistingASceneAlsoPersistsASceneAttachment() + { + $em = $this->getEntityManager(); + + $scene = new Scene("Test scene", "A test scene"); + $sceneAttachment = new SceneAttachment(TestAttachment::class, "Test Attachment"); + + $scene->addAttachment($sceneAttachment); + $sceneId = $scene->getId(); + + // persist + $em->persist($scene); + $em->flush(); + $em->clear(); + + // retrieve + $retrievedSceneAttachment = $em->getRepository(SceneAttachment::class)->find(TestAttachment::class); + + // assert + $this->assertInstanceOf(SceneAttachment::class, $retrievedSceneAttachment); + $this->assertCount(1, $retrievedSceneAttachment->getScenes()); + + // remove scene + $scene = $retrievedSceneAttachment->getScenes()[0]; + $em->remove($scene); + $em->flush(); + $em->clear(); + + // retrieve + $retrievedSceneAttachment = $em->getRepository(SceneAttachment::class)->find(TestAttachment::class); + + // assert + $this->assertInstanceOf(SceneAttachment::class, $retrievedSceneAttachment); + $this->assertCount(0, $retrievedSceneAttachment->getScenes()); + + // remove attachment + $em->remove($retrievedSceneAttachment); + $em->flush(); + $em->clear(); + + // retrieve + $retrievedSceneAttachment = $em->getRepository(SceneAttachment::class)->find(TestAttachment::class); + + // assert + $this->assertNull($retrievedSceneAttachment); + } +}