Model changes

Adds basic Scene model
Extends Character model with reusable properties (Using the two traits
Properties and PropertyManager).
This commit is contained in:
Basilius Sauter
2016-04-19 21:43:33 +02:00
parent 15ff6e8898
commit 6a72a43cda
16 changed files with 572 additions and 46 deletions
+2
View File
@@ -1,6 +1,8 @@
### Project related
vendor/ vendor/
dbtest.sqlite dbtest.sqlite
.sqlite
################# #################
## NetBeans ## NetBeans
BIN
View File
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
*/
class ArgumentEmptyException extends CoreException {
}
+1 -1
View File
@@ -6,6 +6,6 @@ namespace LotGD\Core\Exceptions;
/** /**
* Exception if a specific, required argument is missing * Exception if a specific, required argument is missing
*/ */
class AttributeMissingException extends CoreException { class NoParentSetException extends CoreException {
} }
+1 -1
View File
@@ -6,6 +6,6 @@ namespace LotGD\Core\Exceptions;
/** /**
* Exception if a specific, required argument is missing * Exception if a specific, required argument is missing
*/ */
class AttributeMissingException extends CoreException { class ParentAlreadySetException extends CoreException {
} }
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
*/
class WrongParentException extends CoreException {
}
+45 -13
View File
@@ -3,18 +3,24 @@ declare(strict_types=1);
namespace LotGD\Core\Models; namespace LotGD\Core\Models;
use LotGD\Core\Tools\Model\{Creator, Deletor}; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Entity;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
use LotGD\Core\Tools\Model\PropertyManager;
/** /**
* Description of Character * Description of Character
* *
* @Entity * @Entity
* @Table(name="characters") * @Table(name="characters")
*/ */
class Character { class Character
{
use Creator; use Creator;
use Deletor; use Deletor;
use PropertyManager;
/** @Id @Column(type="integer") @GeneratedValue */ /** @Id @Column(type="integer") @GeneratedValue */
private $id; private $id;
@@ -25,20 +31,39 @@ class Character {
/** @Column(type="integer", options={"default" = 10}) */ /** @Column(type="integer", options={"default" = 10}) */
private $maxHealth = 10; private $maxHealth = 10;
/** @Column(type="integer", options={"default" = 10}) */ /** @Column(type="integer", options={"default" = 10}) */
private $health; private $health = 10;
/** @OneToMany(targetEntity="CharacterProperty", mappedBy="owner", cascade={"persist"}) */
private $properties; private $properties;
/** @var string fqcn of the property sub class */
private static $propertyClass = CharacterProperty::class;
/** @var array */ /** @var array */
private static $fillable = [ private static $fillable = [
"name", "name",
"maxHealth", "maxHealth",
]; ];
/**
* Creates a character at full health
*/
public static function createAtFullHealth(array $arguments): self
{
$newCharacter = self::create($arguments);
$newCharacter->setHealth($newCharacter->getMaxHealth());
return $newCharacter;
}
public function __construct() {
$this->properties = new ArrayCollection();
}
/** /**
* Returns the entity's id * Returns the entity's id
* @return int The id * @return int The id
*/ */
public function getId(): int { public function getId(): int
{
return $this->id; return $this->id;
} }
@@ -46,7 +71,8 @@ class Character {
* Sets the character's name and generates the display name * Sets the character's name and generates the display name
* @param string $name The name to set * @param string $name The name to set
*/ */
public function setName(string $name) { public function setName(string $name)
{
$this->name = $name; $this->name = $name;
$this->generateDisplayName(); $this->generateDisplayName();
} }
@@ -55,14 +81,16 @@ class Character {
* Returns the character's name * Returns the character's name
* @return string The name * @return string The name
*/ */
public function getName(): string { public function getName(): string
{
return $this->name; return $this->name;
} }
/** /**
* Generates the display name which is a composition of title and name. * Generates the display name which is a composition of title and name.
*/ */
protected function generateDisplayName() { protected function generateDisplayName()
{
$this->displayName = $this->name; $this->displayName = $this->name;
} }
@@ -70,7 +98,8 @@ class Character {
* Returns displayName, a combination of title, name and suffix, mixed with colour codes * Returns displayName, a combination of title, name and suffix, mixed with colour codes
* @return string The displayName * @return string The displayName
*/ */
public function getDisplayName(): string { public function getDisplayName(): string
{
return $this->displayName; return $this->displayName;
} }
@@ -79,16 +108,17 @@ class Character {
* health if none has been set yet. * health if none has been set yet.
* @param int $maxhealth * @param int $maxhealth
*/ */
public function setMaxHealth(int $maxHealth) { public function setMaxHealth(int $maxHealth)
{
$this->maxHealth = $maxHealth; $this->maxHealth = $maxHealth;
$this->health = $this->health ?? $this->maxHealth;
} }
/** /**
* Returns the maximum health * Returns the maximum health
* @return int * @return int
*/ */
public function getMaxHealth(): int { public function getMaxHealth(): int
{
return $this->maxHealth; return $this->maxHealth;
} }
@@ -96,7 +126,8 @@ class Character {
* Sets current health * Sets current health
* @param int $health * @param int $health
*/ */
public function setHealth(int $health) { public function setHealth(int $health)
{
$this->health = $health; $this->health = $health;
} }
@@ -104,7 +135,8 @@ class Character {
* Returns current health * Returns current health
* @return int * @return int
*/ */
public function getHealth(): int { public function getHealth(): int
{
return $this->health; return $this->health;
} }
} }
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace LotGD\Core\Models;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Properties;
/**
* Properties for Characters
* @Entity
* @Table(name="character_properties")
*/
class CharacterProperty {
use Properties;
/** @Id @ManyToOne(targetEntity="Character") */
private $owner;
/**
* Returns the owner
* @return \LotGD\Core\Models\Character
*/
public function getOwner(): Character
{
return $this->owner;
}
/**
* Sets the owner
* @param \LotGD\Core\Models\Character $owner
*/
public function setOwner(Character $owner)
{
$this->owner = $owner;
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use LotGD\Core\Exceptions\NoParentSetException;
use LotGD\Core\Exceptions\WrongParentException;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
/**
* Description of Scene
* @Entity
* @Table(name="scenes")
*/
class Scene
{
use Creator;
use Deletor;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string", length=255) */
private $title = "";
/** @Column(type="text") */
private $description = "";
/**
* @ManyToOne(targetEntity="Scene")
* @JoinColumn(name="parent", referencedColumnName="id", nullable=true)
*/
private $parent = null;
/**
* @OneToMany(targetEntity="Scene", mappedBy="parent")
*/
private $children = [];
/**
* @var array
*/
private static $fillable = [
"title",
"description",
"parent"
];
/**
* Constructor
*/
public function __construct()
{
$this->children = ArrayCollection();
}
/**
* Returns primary id
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Sets scene title
* @param string $title
*/
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* Returns scene title
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Sets scene description
* @param string $description
*/
public function setDescription(string $description)
{
$this->description = $description;
}
/**
* Returns scene description
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Sets or removes the parent of this scene.
* @param \LotGD\Core\Models\Scene $parent The new parent or NULL
*/
public function setParent(Scene $parent = null)
{
// Get old parent and remove $this from it
if ($this->parent !== null) {
$oldParent = $this->parent;
$oldParent->removeChild($this);
$this->parent = null;
}
// New parent is not null
if ($parent !== null) {
$this->parent = $parent;
$parent->addChild($this);
}
}
/**
* Returns the parent of this scene
* @return \LotGD\Core\Models\Scene
* @throws \LotGD\Core\Exceptions\NoParentSetException
*/
public function getParent(): Scene
{
if ($this->parent === null) {
throw new NoParentSetException("This child does not have a parent set to return. Check with hasParent first.");
}
return $this->parent;
}
/**
* Returns true if this entity has a parent
* @return bool
*/
public function hasParent(): bool
{
return !(empty($this->parent));
}
/**
* Returns a list of all children registered for this entity
* @return Collection
*/
public function getChildren(): Collection
{
return $this->children;
}
/**
* Returns true if the number of children registered for this entitiy is > 0
* @return bool
*/
public function hasChildren(): bool
{
return count($this->children) > 0 ? true : false;
}
/**
* Registers a child for this entity.
* @param \LotGD\Core\Models\Scene $child
*/
protected function addChild(Scene $child)
{
$this->children->add($child);
}
/**
* Removes a child from this entity.
* @param \LotGD\Core\Models\Scene $child
* @throws WrongParentException
*/
protected function removeChild(Scene $child)
{
if ($child->getParent() !== $this) {
throw new WrongParentException("This Scene is not the parent of the given child.");
}
$this->children->removeElement($child);
}
}
+14 -13
View File
@@ -5,16 +5,15 @@ namespace LotGD\Core\Tools\Model;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Exceptions\{ use LotGD\Core\Exceptions\AttributeMissingException;
AttributeMissingException, use LotGD\Core\Exceptions\UnexpectedArrayKeyException;
UnexpectedArrayKeyException, use LotGD\Core\Exceptions\WrongTypeException;
WrongTypeException
};
/** /**
* Provides methods for creating new entities * Provides methods for creating new entities
*/ */
trait Creator { trait Creator
{
/** /**
* Creates and returns an entity instance and fills values * Creates and returns an entity instance and fills values
* @param array $arguments The values the instance should get * @param array $arguments The values the instance should get
@@ -22,19 +21,20 @@ trait Creator {
* @throws AttributeMissingException * @throws AttributeMissingException
* @throws WrongTypeException * @throws WrongTypeException
*/ */
public static function create(array $arguments) { public static function create(array $arguments)
if(isset(self::$fillable) === false) { {
if (isset(self::$fillable) === false) {
throw new AttributeMissingException('self::$fillable is not defined.'); throw new AttributeMissingException('self::$fillable is not defined.');
} }
if(is_array(self::$fillable) === false) { if (is_array(self::$fillable) === false) {
throw new WrongTypeException('self::$fillable needs to be an array.'); throw new WrongTypeException('self::$fillable needs to be an array.');
} }
$entity = new self(); $entity = new self();
foreach(self::$fillable as $field) { foreach (self::$fillable as $field) {
if(isset($arguments[$field])) { if (isset($arguments[$field])) {
$methodname = "set".$field; $methodname = "set".$field;
$value = $arguments[$field]; $value = $arguments[$field];
@@ -43,7 +43,7 @@ trait Creator {
} }
} }
if(count($arguments) > 0) { if (count($arguments) > 0) {
throw new UnexpectedArrayKeyException('self::$fillable does allow the properties "'.implode(", ", array_keys($arguments)).'" to be set.'); throw new UnexpectedArrayKeyException('self::$fillable does allow the properties "'.implode(", ", array_keys($arguments)).'" to be set.');
} }
@@ -54,7 +54,8 @@ trait Creator {
* Marks the entity as permanent and saves it into the database. * Marks the entity as permanent and saves it into the database.
* @param EntityManagerInterface $em The Entity Manager * @param EntityManagerInterface $em The Entity Manager
*/ */
public function save(EntityManagerInterface $em) { public function save(EntityManagerInterface $em)
{
$em->persist($this); $em->persist($this);
$em->flush(); $em->flush();
} }
+59
View File
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
const PROPERTY_STRING = 0;
const PROPERTY_INT = 1;
const PROPERTY_FLOAT = 2;
/**
* Provides method and doctrine annotation for a property submodel
*/
trait Properties
{
/** @Id @Column(type="string", length=255) */
private $propertyName = "";
/** @Column(type="text") */
private $propertyValue = null;
/**
* Returns the name of the property
* @return string
*/
public function getName(): string
{
return $this->propertyName;
}
/**
* Sets the name of the property
* @param string $name
* @throws ArgumentEmptyException If parameter $name is empty
*/
public function setName(string $name)
{
if($name === "") {
throw new ArgumentEmptyException('The argument $name must not be empty.');
}
$this->propertyName = $name;
}
/**
* Returns the stored property
* @return mixed
*/
public function getValue()
{
return unserialize($this->propertyValue);
}
/**
* Sets the stored property
* @param mixed $value
*/
public function setValue($value) {
$this->propertyValue = serialize($value);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
/**
* Provides method and doctrine annotation for a property submodel
*/
trait PropertyManager
{
private $propertyStorage = null;
public function loadProperties()
{
if ($this->propertyStorage !== null) {
return;
}
foreach ($this->properties as $property) {
$this->propertyStorage[$property->getName()] = $property;
}
}
public function getProperty(string $name, $default = null)
{
$this->loadProperties();
if (isset($this->propertyStorage[$name])) {
return $this->propertyStorage[$name]->getValue();
}
else {
return $default;
}
}
public function setProperty(string $name, $value)
{
$this->loadProperties();
if (isset($this->propertyStorage[$name])) {
$this->propertyStorage[$name]->setValue($value);
}
else {
$className = $this->properties->getTypeClass()->name;
$property = new $className();
$property->setOwner($this);
$property->setName($name);
$property->setValue($value);
$this->propertyStorage[$name] = $property;
$this->properties->add($property);
}
}
}
+38 -8
View File
@@ -6,8 +6,6 @@ namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Character; use LotGD\Core\Models\Character;
use LotGD\Core\Tests\ModelTestCase; use LotGD\Core\Tests\ModelTestCase;
use Doctrine\ORM\Mapping as ORM;
/** /**
* Description of CharacterModelTest * Description of CharacterModelTest
* *
@@ -21,7 +19,8 @@ class CharacterModelTest extends ModelTestCase {
* Returns data to create valid characters * Returns data to create valid characters
* @return array $futureId => $characterData * @return array $futureId => $characterData
*/ */
public function validCharacters() { public function validCharacters(): array
{
return [ return [
[[ [[
"name" => "Testcharacter", "name" => "Testcharacter",
@@ -38,7 +37,8 @@ class CharacterModelTest extends ModelTestCase {
* Returns data to create invalid characters * Returns data to create invalid characters
* @return array A list of faulty characters * @return array A list of faulty characters
*/ */
public function invalidCharacters() { public function invalidCharacters(): array
{
return [ return [
[[ [[
"name" => 16, "name" => 16,
@@ -56,7 +56,8 @@ class CharacterModelTest extends ModelTestCase {
* @param array $characterData * @param array $characterData
* @dataProvider validCharacters * @dataProvider validCharacters
*/ */
public function testCreation(array $characterData) { public function testCreation(array $characterData)
{
$em = $this->getEntityManager(); $em = $this->getEntityManager();
$characterEntity = Character::create($characterData); $characterEntity = Character::create($characterData);
@@ -71,7 +72,8 @@ class CharacterModelTest extends ModelTestCase {
* @dataProvider invalidCharacters * @dataProvider invalidCharacters
* @expectedException TypeError * @expectedException TypeError
*/ */
public function testFaultyCreation(array $characterData) { public function testFaultyCreation(array $characterData)
{
Character::create($characterData); Character::create($characterData);
} }
@@ -79,7 +81,8 @@ class CharacterModelTest extends ModelTestCase {
* Tests if invalid array key given during Character::create throws an exception * Tests if invalid array key given during Character::create throws an exception
* @expectedException \LotGD\Core\Exceptions\UnexpectedArrayKeyException * @expectedException \LotGD\Core\Exceptions\UnexpectedArrayKeyException
*/ */
public function testUnknownArrayKey() { public function testUnknownArrayKey()
{
Character::create([ Character::create([
"name" => "Walter", "name" => "Walter",
"maxHealth" => 15, "maxHealth" => 15,
@@ -90,7 +93,8 @@ class CharacterModelTest extends ModelTestCase {
/** /**
* Tests if Deletor does it's work * Tests if Deletor does it's work
*/ */
public function testDeletion() { public function testDeletion()
{
$em = $this->getEntityManager(); $em = $this->getEntityManager();
// Count rows before // Count rows before
@@ -104,4 +108,30 @@ class CharacterModelTest extends ModelTestCase {
$this->assertEquals($rowsBefore - 1, $rowsAfter); $this->assertEquals($rowsBefore - 1, $rowsAfter);
} }
/**
* Tests character properties
*/
public function testProperties()
{
$em = $this->getEntityManager();
// test default values
$firstCharacter = $em->getRepository(Character::class)->find(1);
$this->assertSame(5, $firstCharacter->getProperty("dragonkills", 5));
$this->assertNotSame(5, $firstCharacter->getProperty("dragonkills", "5"));
$this->assertSame("hanniball", $firstCharacter->getProperty("petname", "hanniball"));
// test setting variables, then getting
$firstCharacter->setProperty("dragonkills", 5);
$this->assertSame(5, $firstCharacter->getProperty("dragonkills"));
$this->assertNotSame("5", $firstCharacter->getProperty("dragonkills"));
$firstCharacter->setProperty("dragonkills", "20");
$this->assertNotSame(20, $firstCharacter->getProperty("dragonkills"));
$this->assertSame("20", $firstCharacter->getProperty("dragonkills"));
// test precreated property
$this->assertSame("hallo", $firstCharacter->getProperty("test"));
}
} }
+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Scene;
use LotGD\Core\Tests\ModelTestCase;
/**
* Description of CharacterModelTest
*/
class SceneModelTest extends ModelTestCase
{
/** @var string default data set */
protected $dataset = "scene";
/**
* Test getter methods
*/
public function testGetters()
{
$em = $this->getEntityManager();
$scene = $em->getRepository(Scene::class)->find(2);
$this->assertEquals("The Forest", $scene->getTitle());
$this->assertEquals("This is a very dangerous and dark forest", $scene->getDescription());
$this->assertInstanceOf(Scene::class, $scene->getParent());
$this->assertEquals(true, $scene->hasParent());
$this->assertEquals(false, $scene->hasChildren());
}
/**
* Test if parent<=>child relationship is working.
*/
public function testChildParentRelationships()
{
$em = $this->getEntityManager();
$parentScene = $em->getRepository(Scene::class)->find(1);
$childScene = $em->getRepository(Scene::class)->find(2);
$this->assertEquals($parentScene, $childScene->getParent());
$this->assertContains($childScene, $parentScene->getChildren());
}
/**
* Test if the scene can be removed.
*/
public function testMoveScene()
{
$em = $this->getEntityManager();
$parentScene1 = $em->getRepository(Scene::class)->find(1);
$parentScene2 = $em->getRepository(Scene::class)->find(4);
$orphanScene = $em->getRepository(scene::class)->find(5);
$this->assertEquals(false, $orphanScene->hasParent());
$this->assertEquals(false, $orphanScene->hasChildren());
// Assign orphanScene to parentScene1 and check relationships
$orphanScene->setParent($parentScene1);
$this->assertEquals(true, $orphanScene->hasParent());
$this->assertCount(3, $parentScene1->getChildren());
$this->assertEquals($parentScene1, $orphanScene->getParent());
$this->assertContains($orphanScene, $parentScene1->getChildren());
// Move the scene now to parentScene2 and check relationships
$orphanScene->setParent($parentScene2);
$this->assertCount(2, $parentScene1->getChildren());
$this->assertCount(1, $parentScene2->getChildren());
$this->assertEquals($parentScene2, $orphanScene->getParent());
$this->assertContains($orphanScene, $parentScene2->getChildren());
// Make an orphan out of it again
$orphanScene->setParent(null);
$this->assertCount(2, $parentScene1->getChildren());
$this->assertCount(0, $parentScene2->getChildren());
$this->assertEquals(false, $orphanScene->hasParent());
$this->assertNotContains($orphanScene, $parentScene1->getChildren());
$this->assertNotContains($orphanScene, $parentScene2->getChildren());
}
}
+6 -1
View File
@@ -10,4 +10,9 @@ characters:
name: "Testcharacter 2" name: "Testcharacter 2"
displayName: "Testcharacter 2" displayName: "Testcharacter 2"
health: 90 health: 90
maxhealth: 90 maxhealth: 90
character_properties:
-
owner_id: 1
propertyName: "test"
propertyValue: 's:5:"hallo";'
+22 -9
View File
@@ -1,13 +1,26 @@
characters: scenes:
- -
id: 1 id: 1
name: "Testcharacter 1" title: "The Village"
displayName: "Testcharacter 1" description: "This is the village."
health: 0 parent:
maxhealth: 100
- -
id: 2 id: 2
name: "Testcharacter 2" title: "The Forest"
displayName: "Testcharacter 2" description: "This is a very dangerous and dark forest"
health: 90 parent: 1
maxhealth: 90 -
id: 3
title: "The Weaponry"
description: "This is the place where you can buy awesome weapons"
parent: 1
-
id: 4
title: "Another Village"
description: "This is another village"
parent:
-
id: 5
title: "Orphan"
description: "This is an orphan scene"
parent: