Adds MotD and Message model as well as tests

This commit adds the MotD model for storing and retrieving
messages-of-the-day.

It also adds a model for messages. Messages have an author as well as a
thread they belong to, the thread can be read by a specific number of
authors. FUrthermore, the message model supports system messages.

This commit introduces a number of needed changes:
- Character is now implementing the CharacterInterface
- MissingCharacter and SystemCharacter are supporting "pseudo-characters"
- trait MockCharacter implements non-implemented methods for
  MissingCharacter and SytemCharacter
- Characters are now soft-deletable. Models wanting to load soft-deleted
  characters need to fetch them eagerly.

Closes #17
This commit is contained in:
Basilius Sauter
2016-05-03 14:56:09 +02:00
parent 53d82646bf
commit 0ff9958830
37 changed files with 1662 additions and 85 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
<?php
$autoloader = require __DIR__ . "/../vendor/autoload.php";
$autoloader = require __DIR__ . "/../vendor/autoload.php";
\date_default_timezone_set("UTC");
+2 -1
View File
@@ -7,7 +7,8 @@
},
"require": {
"monolog/monolog": "1.16.0",
"doctrine/orm": "2.5.*"
"doctrine/orm": "2.5.*",
"gedmo/doctrine-extensions": "*"
},
"require-dev": {
"phpunit/phpunit": "*",
Generated
+142 -23
View File
@@ -4,9 +4,49 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "575df4f449ff2da32ce6665491eb6c06",
"content-hash": "da6478b97984cb6a40ba877b477bc774",
"hash": "ba904a6508c475cbab9d6d85a557a4fb",
"content-hash": "f13234acbc23ca06e0181146f8aef31f",
"packages": [
{
"name": "behat/transliterator",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Transliterator.git",
"reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
"reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-0": {
"Behat\\Transliterator": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Artistic-1.0"
],
"description": "String transliterator",
"keywords": [
"i18n",
"slug",
"transliterator"
],
"time": "2015-09-28 16:26:35"
},
{
"name": "doctrine/annotations",
"version": "v1.2.7",
@@ -606,6 +646,85 @@
],
"time": "2016-01-05 21:34:58"
},
{
"name": "gedmo/doctrine-extensions",
"version": "v2.4.13",
"source": {
"type": "git",
"url": "https://github.com/Atlantic18/DoctrineExtensions.git",
"reference": "5f4b6c848f0d6834a434a62a09a0935d147082e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/5f4b6c848f0d6834a434a62a09a0935d147082e1",
"reference": "5f4b6c848f0d6834a434a62a09a0935d147082e1",
"shasum": ""
},
"require": {
"behat/transliterator": "~1.0",
"doctrine/common": "~2.4",
"php": ">=5.3.2"
},
"require-dev": {
"doctrine/common": ">=2.5.0",
"doctrine/mongodb-odm": ">=1.0.2",
"doctrine/orm": ">=2.5.0",
"phpunit/phpunit": "~4.4",
"phpunit/phpunit-mock-objects": "~2.3",
"symfony/yaml": "~2.6"
},
"suggest": {
"doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM",
"doctrine/orm": "to use the extensions with the ORM"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Gedmo\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "David Buchmann",
"email": "david@liip.ch"
},
{
"name": "Gediminas Morkevicius",
"email": "gediminas.morkevicius@gmail.com"
},
{
"name": "Gustavo Falco",
"email": "comfortablynumb84@gmail.com"
}
],
"description": "Doctrine2 behavioral extensions",
"homepage": "http://gediminasm.org/",
"keywords": [
"Blameable",
"behaviors",
"doctrine2",
"extensions",
"gedmo",
"loggable",
"nestedset",
"sluggable",
"sortable",
"timestampable",
"translatable",
"tree",
"uploadable"
],
"time": "2016-02-18 15:33:43"
},
{
"name": "monolog/monolog",
"version": "1.16.0",
@@ -722,16 +841,16 @@
},
{
"name": "symfony/console",
"version": "v3.0.4",
"version": "v3.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6b1175135bc2a74c08a28d89761272de8beed8cd"
"reference": "34a214710e0714b6efcf40ba3cd1e31373a97820"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd",
"reference": "6b1175135bc2a74c08a28d89761272de8beed8cd",
"url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820",
"reference": "34a214710e0714b6efcf40ba3cd1e31373a97820",
"shasum": ""
},
"require": {
@@ -778,7 +897,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-03-16 17:00:50"
"time": "2016-04-28 09:48:42"
},
{
"name": "symfony/polyfill-mbstring",
@@ -843,16 +962,16 @@
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.5.0",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc"
"reference": "a8773992b362b58498eed24bf85005f363c34771"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc",
"reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771",
"reference": "a8773992b362b58498eed24bf85005f363c34771",
"shasum": ""
},
"require": {
@@ -881,7 +1000,7 @@
"object",
"object graph"
],
"time": "2015-11-07 22:20:37"
"time": "2015-11-20 12:04:31"
},
{
"name": "phpdocumentor/reflection-docblock",
@@ -1367,16 +1486,16 @@
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "3.1.2",
"version": "3.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "7c34c9bdde4131b824086457a3145e27dba10ca1"
"reference": "151c96874bff6fe61a25039df60e776613a61489"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/7c34c9bdde4131b824086457a3145e27dba10ca1",
"reference": "7c34c9bdde4131b824086457a3145e27dba10ca1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/151c96874bff6fe61a25039df60e776613a61489",
"reference": "151c96874bff6fe61a25039df60e776613a61489",
"shasum": ""
},
"require": {
@@ -1419,7 +1538,7 @@
"mock",
"xunit"
],
"time": "2016-03-24 05:58:25"
"time": "2016-04-20 14:39:26"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -1584,16 +1703,16 @@
},
{
"name": "sebastian/environment",
"version": "1.3.5",
"version": "1.3.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
"reference": "2292b116f43c272ff4328083096114f84ea46a56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56",
"reference": "2292b116f43c272ff4328083096114f84ea46a56",
"shasum": ""
},
"require": {
@@ -1630,7 +1749,7 @@
"environment",
"hhvm"
],
"time": "2016-02-26 18:40:46"
"time": "2016-05-04 07:59:13"
},
{
"name": "sebastian/exporter",
@@ -1935,7 +2054,7 @@
},
{
"name": "symfony/yaml",
"version": "v3.0.4",
"version": "v3.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
+2 -1
View File
@@ -6,6 +6,7 @@ namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
*/
class ArgumentEmptyException extends CoreException {
class ArgumentEmptyException extends ArgumentException
{
}
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
*/
class ArgumentException extends CoreException
{
}
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific combination of model values is invalid.
*/
class InvalidModelException 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 IsNullException extends CoreException {
}
+52 -11
View File
@@ -4,28 +4,34 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\EntityManagerInterface;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
use LotGD\Core\Tools\Model\PropertyManager;
use LotGD\Core\Tools\Model\SoftDeletable;
use LotGD\Core\Models\Repositories\CharacterRepository;
/**
* Description of Character
* Model for a character
*
* @Entity
* @Entity(repositoryClass="LotGD\Core\Models\Repositories\CharacterRepository")
* @Table(name="characters")
*/
class Character
class Character implements CharacterInterface, CreateableInterface
{
use Creator;
use Deletor;
use SoftDeletable;
use PropertyManager;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string", length=50, unique=true); */
/** @Column(type="string", length=50); */
private $name;
/** @Column(type="text"); */
private $displayName;
@@ -36,7 +42,20 @@ class Character
/** @OneToMany(targetEntity="CharacterProperty", mappedBy="owner", cascade={"persist"}) */
private $properties;
/** @OneToMany(targetEntity="CharacterViewpoint", mappedBy="owner", cascade={"persist"}) */
private $characterScene;
private $characterViewpoint;
/**
* @ManyToMany(targetEntity="MessageThread", inversedBy="participants", cascade={"persist"})
* @JoinTable(
* name="message_threads_x_characters",
* joinColumns={
* @JoinColumn(name="character_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* @JoinColumn(name="messagethread_id", referencedColumnName="id")
* }
* )
*/
private $messageThreads;
/** @var array */
private static $fillable = [
@@ -57,7 +76,8 @@ class Character
public function __construct()
{
$this->properties = new ArrayCollection();
$this->characterScene = new ArrayCollection();
$this->characterViewpoint = new ArrayCollection();
$this->messageThreads = new ArrayCollection();
}
/**
@@ -146,13 +166,34 @@ class Character
* Returns the current character scene and creates one if it is non-existant
* @return \LotGD\Core\Models\CharacterViewpoint
*/
public function getCharacterScene(): CharacterViewpoint
public function getCharacterViewpoint(): CharacterViewpoint
{
if (count($this->characterScene) === 0) {
if (count($this->characterViewpoint) === 0) {
$characterScene = CharacterViewpoint::Create(["owner" => $this]);
$this->characterScene->add($characterScene);
$this->characterViewpoint->add($characterScene);
}
return $this->characterScene->first();
return $this->characterViewpoint->first();
}
/**
* Returns a list of message threads this user has created.
* @return Collection
*/
public function getMessageThreads(): Collection
{
return $this->messageThreads;
}
public function sendMessageTo(Character $recipient)
{
// ToDo: implement later
throw new \LotGD\Core\Exceptions\NotImplementedException;
}
public function receiveMessageFrom(Character $author)
{
// ToDo: implement later
throw new \LotGD\Core\Exceptions\NotImplementedException;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
# use LotGD\Core\Tools\Optional\Optional;
/**
* Interface for the character model and all objects that mimick such a model.
*/
interface CharacterInterface
{
public function getId(): int;
public function getName(): string;
public function getDisplayName(): string;
public function getHealth(): int;
public function getMaxHealth(): int;
public function getCharacterViewpoint(): CharacterViewpoint;
public function getProperty(string $name, $default = null);
}
+5 -1
View File
@@ -3,6 +3,9 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\Properties;
/**
@@ -10,7 +13,8 @@ use LotGD\Core\Tools\Model\Properties;
* @Entity
* @Table(name="character_properties")
*/
class CharacterProperty {
class CharacterProperty
{
use Properties;
/** @Id @ManyToOne(targetEntity="Character") */
+4 -1
View File
@@ -3,6 +3,9 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\SceneBasics;
@@ -12,7 +15,7 @@ use LotGD\Core\Tools\Model\SceneBasics;
* @Entity
* @Table(name="character_viewpoints")
*/
class CharacterViewpoint
class CharacterViewpoint implements CreateableInterface
{
use Creator;
use SceneBasics;
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models;
use Doctrine\ORM\EntityManagerInterface;
/**
* Interface for createable models
*/
interface CreateableInterface extends SaveableInterface
{
public static function create(array $arguments): CreateableInterface;
}
+1 -1
View File
@@ -48,4 +48,4 @@ class GameConfiguration
{
$this->setProperty($configurationName, $configurationValue);
}
}
}
+2
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use LotGD\Core\Tools\Model\Properties;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
* Properties for Characters
+196
View File
@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Exceptions\InvalidModelException;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Tools\Model\Deletor;
use LotGD\Core\Tools\Model\Saveable;
/**
* Model for messages
* @Entity
* @Table(name="messages")
*/
class Message
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/**
* @ManyToOne(targetEntity="Character", fetch="EAGER")
* @JoinColumn(name="author_id", referencedColumnName="id", nullable=true)
*/
private $author;
/** @Column(type="text", nullable=false) */
private $message;
/** @ManyToOne(targetEntity="MessageThread", inversedBy="messages", fetch="EAGER") */
private $thread;
/** @Column(type="datetime", nullable=false) */
private $createdAt;
/** @Column(type="boolean", nullable=false) */
private $systemMessage = false;
/**
* Sends a message to a MessageThread
* @param \LotGD\Core\Models\Character $from
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @param bool $systemMessage
* @return \LotGD\Core\Models\Message
*/
public static function send(
Character $from,
string $message,
MessageThread $thread,
bool $systemMessage = false
) {
$thread->addMessage(new self($from, $message, $thread, $systemMessage));
}
/**
* Sends a system message to a MessageThread
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @return \LotGD\Core\Models\Message
*/
public static function sendSystemMessage(
string $message,
MessageThread $thread
) {
$thread->addMessage(new self(SystemCharacter::getInstance(), $message, $thread, true));
}
/**
* Constructs the message.
*
* This method has been made protected to prevent from accessing it directly. Use
* the static methods self::send() and self::sendSystemMessage() instead.
* @param \LotGD\Core\Models\Character $from
* @param string $message
* @param \LotGD\Core\Models\Thread $thread
* @param bool $systemMessage
* @throws ArgumentException
*/
protected function __construct(CharacterInterface $from, string $message, MessageThread $thread, bool $systemMessage)
{
if ($from instanceof Character) {
if ($from->isDeleted() === true) {
throw new ArgumentException("A message cannot get written by a deleted character.");
}
$this->author = $from;
} elseif ($systemMessage === false) {
// This should not happen since the constructor is not a public method
throw new ArgumentException(
sprintf(
'If $from is not an instance of %s, $systemMessage must be true',
Character::class
)
);
}
$this->message = $message;
$this->thread = $thread;
$this->createdAt = new DateTime("now");
$this->systemMessage = $systemMessage;
}
/**
* Returns the id
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Returns the true character of the message
* @return \LotGD\Core\Models\CharacterInterface
*/
public function getAuthor(): CharacterInterface
{
if (is_null($this->author)) {
return SystemCharacter::getInstance();
}
else {
return $this->author;
}
}
/**
* Returns the apparant character of the message.
*
* If a character sends a system message, this method will return the SystemCharacter message
* instead of the true author.
* @return \LotGD\Core\Models\CharacterInterface
*/
public function getApparantAuthor(): CharacterInterface
{
if ($this->isSystemMessage()) {
return SystemCharacter::getInstance();
}
else {
return $this->getAuthor();
}
}
/**
* Returns the message
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Returns the thread this message belongs to
* @return \LotGD\Core\Models\MessageThread
*/
public function getThread(): MessageThread
{
return $this->thread;
}
/**
* Sets the thread this message belongs to, once.
*
* A message that belongs to a thread needs to stay there - there is no need for messages to
* switch the thread and end up in a complete different discussion.
* @param \LotGD\Core\Models\MessageThread $thread
* @throws ParentAlreadySetException
*/
public function setThread(MessageThread $thread)
{
if (is_null($this->thread) === false) {
throw new ParentAlreadySetException("A message's thread cannot be changed.");
}
$this->thread = $thread;
}
/**
* Returns the datetime this message was created at
* @return DateTime
*/
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
/**
* Returns true if the message is a system message
* @return bool
*/
public function isSystemMessage(): bool
{
return $this->systemMessage;
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Exceptions\CoreException;
use LotGD\Core\Tools\Model\Saveable;
/**
* A Thread of messages
*
* @Entity(repositoryClass="LotGD\Core\Models\Repositories\MessageThreadRepository")
* @Table(name="message_threads")
*/
class MessageThread implements SaveableInterface
{
use Saveable;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string", length=255, unique=true) */
private $threadKey;
/** @Column(type="boolean", options={"default"=false}) */
private $readonly = false;
/** @ManyToMany(targetEntity="Character", cascade={"persist"}, mappedBy="messageThreads") */
private $participants;
/** @OneToMany(targetEntity="Message", mappedBy="thread", cascade={"persist"}) */
private $messages;
/**
* Constructor. Sets the (unique) threadKey.
* @param string $threadKey
* @param type $participants
* @param type $readonly
*/
public function __construct(string $threadKey, array $participants, $readonly = false)
{
$this->threadKey = $threadKey;
$this->readonly = $readonly;
$this->participants = new ArrayCollection();
$this->messages = new ArrayCollection();
foreach ($participants as $participant) {
$this->participants->add($participant);
}
}
/**
* Returns the primary id of this message
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Returns a list of messages inside this thread
* @return Collection
*/
public function getMessages(): Collection
{
return $this->messages;
}
/**
*
* @param \LotGD\Core\Models\Message $message
* @throws CoreException
*/
public function addMessage(Message $message)
{
if ($this->isReadonly() && $message->getApparantAuthor() instanceof SystemCharacter === false) {
throw new CoreException("Cannot write a normal message to a readonly thread");
}
else {
$this->messages->add($message);
}
}
/**
* Get a collection of participants in this thread
* @return Collection
*/
public function getParticipants(): Collection
{
return $this->participants;
}
/**
* Returns true if the thread is "readonly"
* @return bool
*/
public function isReadonly(): bool
{
return $this->readonly;
}
/**
* Persists the MessageThread and adds itself to the participants.
* @param EntityManagerInterface $em
*/
public function save(EntityManagerInterface $em)
{
foreach ($this->participants as $participant) {
$participantsMessageThreads = $participant->getMessageThreads();
if ($participantsMessageThreads->contains($this) === false) {
$participantsMessageThreads->add($this);
}
}
$em->persist($this);
$em->flush();
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models;
use LotGD\Core\Tools\Model\MockCharacter;
/**
* Provides a basic implementation of CharacterInterface to return the most
* important data a missing character might still need.
*/
class MissingCharacter implements CharacterInterface
{
use MockCharacter;
private $displayname;
/**
* Sets the name of the missing character, defautls to "Nobody"
* @param string $displayname
*/
public function __construct(string $displayname = "Nobody")
{
$this->displayname = $displayname;
}
/**
* Returns the name
* @return string
*/
public function getDisplayName(): string
{
return $this->displayname;
}
/**
* Returns the name
* @return string
*/
public function getName(): string
{
return $this->displayname;
}
}
+170
View File
@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
/**
* Model for the message of the day
*
* @Entity
* @Table(name="motd")
*/
class MotD implements CreateableInterface
{
use Creator;
use Deletor;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/**
* @ManyToOne(targetEntity="Character", cascade={"persist"}, fetch="EAGER")
* @JoinColumn(name="author_id", referencedColumnName="id", nullable=false)
*/
private $author;
/** @Column(type="string", length=255, nullable=false) */
private $title;
/** @Column(type="text", nullable=false) */
private $body;
/** @Column(type="datetime", nullable=false) */
private $creationTime;
/** @Column(type="boolean", nullable=false) */
private $systemMessage = false;
/** @var array */
private static $fillable = [
"author",
"title",
"body",
"systemMessage",
];
/**
* Constructs an entity and sets default datetime to now.
*/
public function __construct()
{
$this->creationTime = new \DateTime("now");
}
/**
* Returns the entities ID
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Returns the character who wrote this motd
*
* Returns always the real author of the message, even if it is a
* system message. Use $this->getSystemMessage() to check if it is a system
* message or $this->getAppearentAuthor() to get the appearent author.
* @return \LotGD\Core\Models\Character
*/
public function getAuthor(): CharacterInterface
{
return $this->author;
}
/**
* Returns the appearent author of this message.
* @return \LotGD\Core\Models\CharacterInterface
*/
public function getApparantAuthor(): CharacterInterface
{
if ($this->getSystemMessage() === true) {
return SystemCharacter::getInstance();
} else {
return $this->getAuthor();
}
}
/**
* Sets the author of this motd
* @param \LotGD\Core\Models\Character $author
*/
public function setAuthor(Character $author)
{
$this->author = $author;
}
/**
* Returns the title of the message
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Sets the title of the message
* @param string $title
*/
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* Returns the body of the message
* @return string
*/
public function getBody(): string
{
return $this->body;
}
/**
* Sets the body of the message
* @param string $body
*/
public function setBody(string $body)
{
$this->body = $body;
}
/**
* Returns the creation time. Modification of this has no effect.
* @return \DateTime
*/
public function getCreationTime(): \DateTime
{
return $this->creationTime;
}
/**
* Sets the creation time. Needs to be set to a new datetime instance.
* @param \DateTime $creationTime
*/
public function setCreationTime(\DateTime $creationTime)
{
$this->creationTime = $creationTime;
}
/**
* Returns true if the motd is a system message
* @return bool
*/
public function getSystemMessage(): bool
{
return $this->systemMessage;
}
/**
* Set to true of the message should be a system message
* @param bool $isSystemMessage
*/
public function setSystemMessage(bool $isSystemMessage = true)
{
$this->systemMessage = $isSystemMessage;
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use LotGD\Core\Models\Character;
/**
* Description of CharacterRepository
*/
class CharacterRepository extends EntityRepository
{
const SKIP_SOFTDELETED = 0;
const INCLUDE_SOFTDELETED = 1;
const ONLY_SOFTDELETED = 2;
protected function modifyQuery(QueryBuilder $queryBuilder, int $level)
{
switch ($level) {
case self::SKIP_SOFTDELETED:
$queryBuilder->andWhere(
$queryBuilder->expr()->orX(
$queryBuilder->expr()->isNull('c.deletedAt'),
$queryBuilder->expr()->gt('c.deletedAt', 'CURRENT_TIMESTAMP()')
)
);
break;
case self::ONLY_SOFTDELETED:
$queryBuilder->andWhere(
$queryBuilder->expr()->lte('c.deletedAt', 'CURRENT_TIMESTAMP()')
);
break;
}
}
public function find($id, int $level = self::SKIP_SOFTDELETED)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select("c")
->from(Character::class, "c")
->where($queryBuilder->expr()->eq("c.id", ":id"))
->setParameter("id", $id);
$this->modifyQuery($queryBuilder, $level);
try {
return $queryBuilder->getQuery()->getSingleResult();
} catch (NoResultException $e) {
return null;
}
}
public function findAll(int $level = self::SKIP_SOFTDELETED)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select("c")
->from(Character::class, "c");
$this->modifyQuery($queryBuilder, $level);
return $queryBuilder->getQuery()->getResult();
}
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use LotGD\Core\Models\MessageThread;
/**
* Repository for MessageThreads
*/
class MessageThreadRepository extends EntityRepository
{
/**
* Creates a thread key based on the participating characters and a schema.
* @param array $listOfCharacters
* @param string $messageSchema
* @return string
*/
public static function createThreadKey(array $listOfCharacters, string $messageSchema): string
{
// ToDo: Replace array with CharacterCollection
usort(
$listOfCharacters,
function($a, $b) {
return $a->getId() <=> $b->getId();
}
);
$threadParticipants = "";
foreach($listOfCharacters as $character) {
$threadParticipants .= $character->getId() . ".";
}
return $messageSchema . "://" . md5($threadParticipants);
}
/**
* Finds a messageThread
* @param array $listOfCharacters
* @return MessageThread
*/
public function findOrCreateFor(array $listOfCharacters): MessageThread
{
$threadKey = self::createThreadKey($listOfCharacters, "messageThread");
try {
$thread = $this->getEntityManager()->createQueryBuilder()
->select("e")
->from($this->getEntityName(), "e")
->where("e.threadKey = :threadKey")
->setParameter("threadKey", $threadKey)
->getQuery()
->getSingleResult();
return $thread;
} catch (NoResultException $e) {
$newMessageThread = new MessageThread($threadKey, $listOfCharacters, false);
$newMessageThread->save($this->getEntityManager());
return $newMessageThread;
}
}
/**
* Finds a systemMessage or returns a newly created, read-only thread.
* @param array $listOfCharacters
* @return MessageThread
*/
public function findOrCreateReadonlyFor(array $listOfCharacters): MessageThread
{
$threadKey = self::createThreadKey($listOfCharacters, "systemMessage");
try {
$thread = $this->getEntityManager()->createQueryBuilder()
->select("e")
->from($this->getEntityName(), "e")
->where("e.threadKey = :threadKey")
->setParameter("threadKey", $threadKey)
->getQuery()
->getSingleResult();
return $thread;
} catch (NoResultException $e) {
$newMessageThread = new MessageThread($threadKey, $listOfCharacters, true);
$newMessageThread->save($this->getEntityManager());
return $newMessageThread;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models;
use Doctrine\ORM\EntityManagerInterface;
/**
* Interface for createable models
*/
interface SaveableInterface
{
public static function _save(SaveableInterface $object, EntityManagerInterface $em);
public function save(EntityManagerInterface $em);
}
+4 -1
View File
@@ -5,6 +5,9 @@ namespace LotGD\Core\Models;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Column;
use LotGD\Core\Exceptions\NoParentSetException;
use LotGD\Core\Exceptions\WrongParentException;
@@ -17,7 +20,7 @@ use LotGD\Core\Tools\Model\SceneBasics;
* @Entity
* @Table(name="scenes")
*/
class Scene
class Scene implements CreateableInterface
{
use Creator;
use Deletor;
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Models;
use LotGD\Core\Tools\Model\MockCharacter;
/**
* Provides a basic system character to provide system information.
*
* Whenever a message should be sent by the System instead of a standard character,
* this class is returned by the entity containing the message instead of a standard
* character instance.
*/
class SystemCharacter implements CharacterInterface
{
use MockCharacter;
static $instance = null;
static $characterName = "System";
public static function getInstance()
{
self::$instance = self::$instance ?? new self();
return self::$instance;
}
private function __construct()
{
}
public function getDisplayName(): string
{
return self::$characterName;
}
public function getName(): string
{
return self::$characterName;
}
}
+5 -12
View File
@@ -8,12 +8,15 @@ use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Exceptions\AttributeMissingException;
use LotGD\Core\Exceptions\UnexpectedArrayKeyException;
use LotGD\Core\Exceptions\WrongTypeException;
use LotGD\Core\Models\CreateableInterface;
/**
* Provides methods for creating new entities
*/
trait Creator
{
use Saveable;
/**
* Creates and returns an entity instance and fills values
* @param array $arguments The values the instance should get
@@ -21,7 +24,7 @@ trait Creator
* @throws AttributeMissingException
* @throws WrongTypeException
*/
public static function create(array $arguments)
public static function create(array $arguments): CreateableInterface
{
if (isset(self::$fillable) === false) {
throw new AttributeMissingException('self::$fillable is not defined.');
@@ -34,7 +37,7 @@ trait Creator
$entity = new self();
foreach (self::$fillable as $field) {
if (isset($arguments[$field])) {
if (array_key_exists($field, $arguments)) {
$methodname = "set".$field;
$value = $arguments[$field];
@@ -49,14 +52,4 @@ trait Creator
return $entity;
}
/**
* Marks the entity as permanent and saves it into the database.
* @param EntityManagerInterface $em The Entity Manager
*/
public function save(EntityManagerInterface $em)
{
$em->persist($this);
$em->flush();
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types = 1);
namespace LotGD\Core\Tools\Model;
use LotGD\Core\Exceptions\IsNullException;
use LotGD\Core\Models\CharacterViewpoint;
/**
* Provides basic implementation to mock CharacterInterface.
*/
trait MockCharacter
{
public function __call($name, $arguments) {
throw new IsNullException();
}
public function getId(): int
{
throw new IsNullException();
}
public function getName(): string
{
throw new IsNullException();
}
public function getDisplayName(): string
{
throw new IsNullException();
}
public function getHealth(): int
{
throw new IsNullException();
}
public function getMaxHealth(): int
{
throw new IsNullException();
}
public function getCharacterViewpoint(): CharacterViewpoint
{
throw new IsNullException();
}
public function getProperty(string $name, $default = null)
{
return $default;
}
}
+4 -6
View File
@@ -27,20 +27,18 @@ trait PropertyManager
if (isset($this->propertyStorage[$name])) {
return $this->propertyStorage[$name]->getValue();
}
else {
} else {
return $default;
}
}
public function setProperty(string $name, $value)
public function setProperty(string $name, $value)
{
$this->loadProperties();
if (isset($this->propertyStorage[$name])) {
$this->propertyStorage[$name]->setValue($value);
}
else {
} else {
$className = $this->properties->getTypeClass()->name;
$property = new $className();
if (method_exists($property, "setOwner")) {
@@ -53,4 +51,4 @@ trait PropertyManager
$this->properties->add($property);
}
}
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Models\SaveableInterface;
/**
* Provides methods for persisting Saveable entities.
*/
trait Saveable
{
/**
* Static, protected save function to call from trait-overwriting methods.
* @param \LotGD\Core\Tools\Model\CreateableInterface $object
* @param EntityManagerInterface $em
*/
public static function _save(SaveableInterface $object, EntityManagerInterface $em)
{
$em->persist($object);
$em->flush();
}
/**
* Marks the entity as permanent and saves it into the database.
* @param EntityManagerInterface $em The Entity Manager
*/
public function save(EntityManagerInterface $em)
{
self::_save($this, $em);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides methods for deleting entities.
*/
trait SoftDeletable
{
/** @Column(type="datetime", nullable=true) */
private $deletedAt;
/**
* Deletes the entity
* @param EntityManagerInterface $em
*/
public function delete(EntityManagerInterface $em)
{
$this->setDeletedAt(new DateTime("now"));
$em->flush();
}
/**
* Restores an entity back
* @param EntityManagerInterface $em
*/
public function restore(EntityManagerInterface $em)
{
$this->setDeletedAt(null);
$em->flush();
}
/**
* Sets deletedAt to a specific date
* @param DateTime $deletedAt
*/
public function setDeletedAt(DateTime $deletedAt = null)
{
$this->deletedAt = $deletedAt;
}
/**
* Returns when the entry got deleted
* @return DateTime
*/
public function getDeletedAt(): DateTime
{
return $this->deletedAt;
}
/**
* Returns true if this entity is soft deleted
* @return bool
*/
public function isDeleted(): bool
{
return is_null($this->deletedAt) ? false : true;
}
}
+37 -13
View File
@@ -3,32 +3,43 @@ declare(strict_types=1);
namespace LotGD\Core\Tests;
use Doctrine\ORM\{
EntityManager,
EntityManagerInterface,
Mapping\AnsiQuoteStrategy,
Tools\Setup,
Tools\SchemaTool
};
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\Tools\SchemaTool;
/**
* Description of ModelTestCase
*/
abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase {
abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase
{
/** @var \PDO */
static private $pdo = null;
/** @var EntityManager */
static private $em = null;
/** @var \PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection */
private $connection = null;
final public function getConnection() {
if($this->connection === null) {
if(self::$pdo === null) {
/**
* Returns a connection to test models
* @return \PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection
*/
final public function getConnection(): \PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection
{
if ($this->connection === null) {
if (self::$pdo === null) {
self::$pdo = new \PDO($GLOBALS['DB_DSN'], $GLOBALS["DB_USER"], $GLOBALS["DB_PASSWORD"]);
// Read db annotations from model files
$configuration = Setup::createAnnotationMetadataConfiguration(["src/Models"], true);
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());
$configuration->addFilter("soft-deleteable", 'Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter');
self::$em = EntityManager::create(["pdo" => self::$pdo], $configuration);
self::$em->getFilters()->enable("soft-deleteable");
self::$em->getEventManager()->addEventSubscriber(new \Gedmo\SoftDeleteable\SoftDeleteableListener());
// Create Schema
$metaData = self::$em->getMetadataFactory()->getAllMetadata();
@@ -39,16 +50,29 @@ abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase {
$this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS["DB_NAME"]);
}
// It is important to clear the cache of the entity manager every time a new test runs!
self::$em->clear();
return $this->conn;
}
protected function getDataSet(): \PHPUnit_Extensions_Database_DataSet_YamlDataSet {
/**
* Returns a .yml dataset under this name
* @return \PHPUnit_Extensions_Database_DataSet_YamlDataSet
*/
protected function getDataSet(): \PHPUnit_Extensions_Database_DataSet_YamlDataSet
{
return new \PHPUnit_Extensions_Database_DataSet_YamlDataSet(
__DIR__."/datasets/".$this->dataset.".yml"
);
}
protected function getEntityManager(): EntityManagerInterface {
/**
* Returns the current entity manager
* @return EntityManagerInterface
*/
protected function getEntityManager(): EntityManagerInterface
{
return self::$em;
}
}
+37 -1
View File
@@ -6,14 +6,46 @@ namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\CharacterProperty;
use LotGD\Core\Tests\ModelTestCase;
use LotGD\Core\Models\Repositories\CharacterRepository;
/**
* Tests the management of Characters
*/
class CharacterModelTest extends ModelTestCase {
class CharacterModelTest extends ModelTestCase
{
/** @var string default data set */
protected $dataset = "character";
/**
* Tests for soft deletion
*/
public function testSoftDeletion()
{
$chars = $this->getEntityManager()->getRepository(Character::class)->find(3);
$this->assertSame(null, $chars);
$allChars = $this->getEntityManager()->getRepository(Character::class)->findAll();
$this->assertSame(2, count($allChars));
$char = $this->getEntityManager()->getRepository(Character::class)->find(1);
$char->delete($this->getEntityManager());
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
$allChars = $this->getEntityManager()
->getRepository(Character::class)
->findAll();
$this->assertSame(1, count($allChars));
$allChars = $this->getEntityManager()
->getRepository(Character::class)
->findAll(CharacterRepository::INCLUDE_SOFTDELETED);
$this->assertSame(3, count($allChars));
$this->getEntityManager()->getFilters()->enable("soft-deleteable");
}
/**
* Returns data to create valid characters
* @return array $futureId => $characterData
@@ -62,6 +94,8 @@ class CharacterModelTest extends ModelTestCase {
$characterEntity = Character::create($characterData);
$characterEntity->save($em);
$em->flush();
$this->assertInternalType("int", $characterEntity->getId());
$em->flush();
@@ -105,6 +139,8 @@ class CharacterModelTest extends ModelTestCase {
$character = $em->getRepository(Character::class)->find(1);
$character->delete($em);
$em->clear();
$rowsAfter = count($em->getRepository(Character::class)->findAll());
$this->assertEquals($rowsBefore - 1, $rowsAfter);
+13 -11
View File
@@ -9,20 +9,21 @@ use LotGD\Core\Models\Scene;
use LotGD\Core\Tests\ModelTestCase;
/**
* Tests the management of CharacterScenes
* Tests the management of CharacterViewpoints
*/
class CharacterViewpointTest extends ModelTestCase
{
/** @var string default data set */
protected $dataset = "characterViewpoints";
public function testGetters() {
public function testGetters()
{
$em = $this->getEntityManager();
// Test character with a characterScene
$testCharacter = $em->getRepository(Character::class)->find(2);
$this->assertSame(2, $testCharacter->getId());
$characterScene = $testCharacter->getCharacterScene();
$characterScene = $testCharacter->getCharacterViewpoint();
$this->assertInstanceOf(CharacterViewpoint::class, $characterScene);
$this->assertSame("The Village", $characterScene->getTitle());
@@ -31,7 +32,7 @@ class CharacterViewpointTest extends ModelTestCase
// Test character without a characterScene
$testCharacter = $em->getRepository(Character::class)->find(1);
$this->assertSame(1, $testCharacter->getId());
$characterScene = $testCharacter->getCharacterScene();
$characterScene = $testCharacter->getCharacterViewpoint();
$this->assertInstanceOf(CharacterViewpoint::class, $characterScene);
@@ -39,7 +40,8 @@ class CharacterViewpointTest extends ModelTestCase
}
// Tests if a scene can be changed correctly.
public function testSceneChange() {
public function testSceneChange()
{
$em = $this->getEntityManager();
$testCharacters = [
@@ -52,14 +54,14 @@ class CharacterViewpointTest extends ModelTestCase
$em->getRepository(Scene::class)->find(2),
];
$this->assertSame("{No scene set}", $testCharacters[0]->getCharacterScene()->getTitle());
$this->assertSame("The Village", $testCharacters[1]->getCharacterScene()->getTitle());
$this->assertSame("{No scene set}", $testCharacters[0]->getCharacterViewpoint()->getTitle());
$this->assertSame("The Village", $testCharacters[1]->getCharacterViewpoint()->getTitle());
$testCharacters[0]->getCharacterScene()->changeFromScene($testScenes[0]);
$testCharacters[1]->getCharacterScene()->changeFromScene($testScenes[1]);
$testCharacters[0]->getCharacterViewpoint()->changeFromScene($testScenes[0]);
$testCharacters[1]->getCharacterViewpoint()->changeFromScene($testScenes[1]);
$this->assertSame("The Village", $testCharacters[0]->getCharacterScene()->getTitle());
$this->assertSame("The Forest", $testCharacters[1]->getCharacterScene()->getTitle());
$this->assertSame("The Village", $testCharacters[0]->getCharacterViewpoint()->getTitle());
$this->assertSame("The Forest", $testCharacters[1]->getCharacterViewpoint()->getTitle());
$em->flush();
}
+171
View File
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\MessageThread;
use LotGD\Core\Models\Message;
use LotGD\Core\Models\Repositories\CharacterRepository;
use LotGD\Core\Tests\ModelTestCase;
/**
* Tests the management of Characters
*/
class MessageModelTest extends ModelTestCase
{
/** @var string default data set */
protected $dataset = "messages";
public function testSendMessageToSingleCharacter()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(4);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character2, $character1]);
$this->assertSame($thread1, $thread2);
Message::send($character1, "Hi, how are you?", $thread1);
Message::send($character2, "I'm fine, and you?", $thread1);
Message::send($character1, "Sorry, I need to leave~", $thread1);
$this->assertSame(3, count($thread1->getMessages()));
// Write changes to database and clear entity cache.
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(4);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
// $thread1 should come from the database
$this->assertInternalType("int", $thread1->getId());
$this->assertSame(3, count($thread1->getMessages()));
$this->assertSame(2, count($character1->getMessageThreads()));
$this->assertSame(1, count($character2->getMessageThreads()));
$this->assertContains($thread1, $character1->getMessageThreads());
$this->assertContains($thread1, $character2->getMessageThreads());
}
public function testSendMessageToMultipleCharacters()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character3 = $em->getRepository(Character::class)->find(3, CharacterRepository::INCLUDE_SOFTDELETED);
$character4 = $em->getRepository(Character::class)->find(4);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2, $character3, $character4]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character4, $character2, $character1, $character3]);
$this->assertSame($thread1, $thread2);
Message::send($character1, "Multi-User-Message", $thread1);
Message::send($character2, "Multi-User-Message", $thread1);
try {
$exception = false;
Message::send($character3, "Multi-User-Message", $thread1);
} catch(\LotGD\Core\Exceptions\ArgumentException $e) {
$exception = true;
}
Message::send($character4, "Multi-User-Message", $thread1);
$this->assertTrue($exception);
$this->assertSame(3, count($thread1->getMessages()));
$this->assertSame(2, count($character1->getMessageThreads()));
$this->assertSame(2, count($character2->getMessageThreads()));
$this->assertSame(1, count($character3->getMessageThreads()));
$this->assertSame(1, count($character4->getMessageThreads()));
$em->flush();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character3 = $em->getRepository(Character::class)->find(3, CharacterRepository::INCLUDE_SOFTDELETED);
$character4 = $em->getRepository(Character::class)->find(4);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2, $character3, $character4]);
// $thread1 should come from the database
$this->assertInternalType("int", $thread1->getId());
$this->assertSame(3, count($thread1->getMessages()));
$this->assertSame(2, count($character1->getMessageThreads()));
$this->assertSame(2, count($character2->getMessageThreads()));
$this->assertSame(1, count($character3->getMessageThreads()));
$this->assertSame(1, count($character4->getMessageThreads()));
}
public function testSendSystemMessageToSingleCharacter()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character2]);
$this->assertNotSame($thread1, $thread2);
Message::sendSystemMessage("This is a Systemmessage for Character 1.", $thread1);
Message::sendSystemMessage("This is a Systemmessage for Character 2.", $thread2);
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character2]);
$this->assertSame(1, count($thread1->getMessages()));
$this->assertSame(1, count($thread2->getMessages()));
// Test the impossibility to answer to a system message thread, but another system message
// needs to be able to get attached
try {
$exception = false;
Message::send($character1, "A normal message", $thread1);
} catch (\LotGD\Core\Exceptions\CoreException $ex) {
$exception = true;
}
Message::sendSystemMessage("A second system Message", $thread1);
$this->assertTrue($exception);
$this->assertSame(2, count($thread1->getMessages()));
}
public function testSendSystemMessageToMultipleCharacters()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1, $character2]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
$this->assertNotSame($thread1, $thread2);
Message::sendSystemMessage("A system message to 2 recipients", $thread1);
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1, $character2]);
$this->assertSame(1, count($thread1->getMessages()));
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\MotD;
use LotGD\Core\Tests\ModelTestCase;
/**
* Tests the management of Characters
*/
class MotDModelTest extends ModelTestCase
{
/** @var string default data set */
protected $dataset = "motd";
public function testFetching()
{
$em = $this->getEntityManager();
$author = $em->getRepository(Character::class)->find(1);
// Test normal message
$motd1 = $em->getRepository(MotD::class)->find(1);
$this->assertSame("This is the title", $motd1->getTitle());
$this->assertSame("This is the body of the message", $motd1->getBody());
$this->assertSame($author, $motd1->getAuthor());
$this->assertSame($author, $motd1->getApparantAuthor());
$this->assertFalse($motd1->getSystemMessage());
// Test System message
$motd2 = $em->getRepository(MotD::class)->find(2);
$this->assertTrue($motd2->getSystemMessage());
$this->assertNotSame($motd2->getAuthor(), $motd2->getApparantAuthor());
$this->assertSame($author, $motd2->getAuthor());
$this->assertNotSame("Deleted Character Name", $motd2->getAuthor()->getDisplayName());
// Test message with unknown author
$motd3 = $em->getRepository(Motd::class)->find(3);
$this->assertSame("Deleted Testcharacter", $motd3->getAuthor()->getDisplayName());
}
public function testTimezone()
{
$em = $this->getEntityManager();
$time1 = $em->getRepository(MotD::class)->find(1)->getCreationTime();
$time2 = new \DateTime("2016-05-03 14:19:12");
$time3 = $time2->setTimezone(new \DateTimeZone("Europe/Zurich"));
$time4 = new \DateTime("2016-05-03 14:19:12");
$time4->setTimezone(new \DateTimeZone("America/Los_Angeles"));
$this->assertSame($time1->getTimestamp(), $time2->getTimestamp());
$this->assertEquals($time1, $time2);
$this->assertSame($time2, $time3);
$this->assertEquals($time2->getTimezone(), $time3->getTimezone());
$this->assertNotEquals($time1->getTimezone(), $time2->getTimezone());
$this->assertSame($time1->getTimestamp(), $time3->getTimestamp());
$this->assertNotEquals($time2->getTimezone(), $time4->getTimezone());
}
public function dataCreateSaveAndRetrieve(): array
{
return [
[[
"author" => 1,
"title" => "ABC_\"EFG",
"body" => "Lorem îpsum etc pp",
"systemMessage" => false,
]],
[[
"author" => 1,
"title" => "AnotherOne",
"body" => "Test a Second One",
"systemMessage" => true,
]],
];
}
/**
* @dataProvider dataCreateSaveAndRetrieve
*/
public function testCreateSaveAndRetrieve(array $motdCreationArguments)
{
$em = $this->getEntityManager();
// Set Author to the correct author instance. Cannot be moved to the dataProvider.
$motdCreationArguments["author"] = $em->getRepository(Character::class)->find($motdCreationArguments["author"]);
$motd = MotD::create($motdCreationArguments);
$motd->save($em);
$id = $motd->getId();
$em->flush();
$em->clear();
$checkMotd = $this->getEntityManager()->getRepository(MotD::class)->find($id);
$this->assertSame($motdCreationArguments["author"]->getName(), $checkMotd->getAuthor()->getName());
$this->assertSame($motdCreationArguments["title"], $checkMotd->getTitle());
$this->assertSame($motdCreationArguments["body"], $checkMotd->getBody());
$this->assertEquals($motd->getCreationTime(), $checkMotd->getCreationTime());
if ($motdCreationArguments["systemMessage"] === true) {
$this->assertNotSame($motdCreationArguments["author"]->getName(), $checkMotd->getApparantAuthor()->getName());
} else {
$this->assertSame($motdCreationArguments["author"]->getName(), $checkMotd->getApparantAuthor()->getName());
}
$em->flush();
}
}
+3 -1
View File
@@ -105,7 +105,9 @@ class OneToManyCollectionTest extends ModelTestCase
$this->assertSame(5, count($collection));
// Test OneToManyCollection::get, remove and contains
$testElement1 = $this->getEntityManager()->getRepository(GameConfigurationElement::class)->findByPropertyName("gameVersion")[0];
$testElement1 = $this->getEntityManager()
->getRepository(GameConfigurationElement::class)
->findByPropertyName("gameVersion")[0];
$testElement2 = $collection->get(4);
$this->assertSame("testConfig", $testElement2->getName());
$collection->remove(4);
+7
View File
@@ -11,6 +11,13 @@ characters:
displayName: "Testcharacter 2"
health: 90
maxhealth: 90
-
id: 3
name: "Testcharacter 3"
displayName: "Testcharacter 3 (deleted)"
health: 90
maxhealth: 90
deletedAt: "2011-11-11 11:11:11"
character_properties:
-
owner_id: 1
+37
View File
@@ -0,0 +1,37 @@
characters:
-
id: 1
name: "Testcharacter 1"
displayName: "Testcharacter 1"
-
id: 2
name: "Testcharacter 2"
displayName: "Testcharacter 2"
-
id: 3
name: "Testcharacter 1"
displayName: "Deleted Testcharacter"
deletedAt: "2011-11-11 11:11:11"
-
id: 4
name: "Testcharacter 3"
displayName: "Testcharacter 3"
message_threads:
-
id: 1
threadKey: "messageThread://94ed406c5b7809bbdbf1e092cdbc2e4a"
messages:
-
id: 1
author_id: 1
thread_id: 1
message: "Hi!"
createdAt: "2000-01-01 00:00:01"
systemMessage: false
message_threads_x_characters:
-
messagethread_id: 1
character_id: 1
-
messagethread_id: 1
character_id: 2
+43
View File
@@ -0,0 +1,43 @@
characters:
-
id: 1
name: "Testcharacter 1"
displayName: "Testcharacter 1"
health: 0
maxhealth: 100
-
id: 2
name: "Testcharacter 2"
displayName: "Testcharacter 2"
health: 90
maxhealth: 90
-
id: 3
name: "Testcharacter 1"
displayName: "Deleted Testcharacter"
health: 200
maxhealth: 200
deletedAt: "2011-11-11 11:11:11"
motd:
-
id: 1
author_id: 1
title: "This is the title"
body: "This is the body of the message"
creationTime: "2016-05-03 14:19:12"
systemMessage: false
-
id: 2
author_id: 1
title: "This is a system message"
body: "This is the body of the system message"
creationTime: "2016-05-04 14:19:12"
systemMessage: true
-
id: 3
author_id: 3
title: "This is an old message."
body: "This is an old message."
creationTime: "2002-12-09 15:13:59"
systemMessage: false