Adds possibility to extend certain models using annotations.
This commit is contained in:
+22
-1
@@ -3,7 +3,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\EventManager as DoctrineEventManager;
|
||||
use Doctrine\Common\Util\Debug;
|
||||
use Doctrine\ORM\Events as DoctrineEvents;
|
||||
use Doctrine\ORM\ {
|
||||
EntityManager,
|
||||
@@ -30,7 +32,8 @@ class Bootstrap
|
||||
{
|
||||
private $logger;
|
||||
private $game;
|
||||
private $libraryConfigurationManager = [];
|
||||
/** @var LibraryConfigurationManager */
|
||||
private $libraryConfigurationManager;
|
||||
private $annotationDirectories = [];
|
||||
|
||||
/**
|
||||
@@ -75,6 +78,9 @@ class Bootstrap
|
||||
$dem = $entityManager->getEventManager();
|
||||
$dem->addEventListener([DoctrineEvents::postLoad], new EntityPostLoadEventListener($this->game));
|
||||
|
||||
// Run model extender
|
||||
$this->extendModels();
|
||||
|
||||
return $this->game;
|
||||
}
|
||||
|
||||
@@ -203,4 +209,19 @@ class Bootstrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function extendModels()
|
||||
{
|
||||
AnnotationRegistry::registerLoader("class_exists");
|
||||
|
||||
$modelExtender = new ModelExtender();
|
||||
|
||||
foreach ($this->libraryConfigurationManager->getConfigurations() as $config) {
|
||||
$modelExtensions = $config->getSubKeyIfItExists(["modelExtensions"]);
|
||||
|
||||
if ($modelExtensions) {
|
||||
$modelExtender->addMore($modelExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Doctrine\Annotations;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
use Doctrine\Common\Annotations\Annotation\Attribute;
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
use LotGD\Core\Models\ExtendableModelInterface;
|
||||
|
||||
/**
|
||||
* Annotation that is used to flag which entity a class extends.
|
||||
* @package LotGD\Core\Doctrine
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
* @Attributes({
|
||||
* @Attribute("of", type = "string")
|
||||
* })
|
||||
*/
|
||||
class Extension
|
||||
{
|
||||
/** @var string */
|
||||
private $modelClass;
|
||||
|
||||
/**
|
||||
* Extension constructor.
|
||||
* @param array $attributes
|
||||
* @throws ArgumentException
|
||||
*/
|
||||
public function __construct(array $attributes) {
|
||||
$this->modelClass = $attributes["of"];
|
||||
|
||||
if (!class_exists($this->modelClass)) {
|
||||
throw new ArgumentException("The class given in of must be a valid class.");
|
||||
}
|
||||
|
||||
if (!in_array(ExtendableModelInterface::class, class_implements($this->modelClass))) {
|
||||
throw new ArgumentException("The class given in of must implement the ExtendableModelInterface.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model class name.
|
||||
* @return string
|
||||
*/
|
||||
public function getModelClass(): string
|
||||
{
|
||||
return $this->modelClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Doctrine\Annotations;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
use Doctrine\Common\Annotations\Annotation\Attribute;
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
|
||||
/**
|
||||
* Annotation that is used to link a static method to a model entity.
|
||||
* @package LotGD\Core\Doctrine\Annotations
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
* @Attributes({
|
||||
* @Attribute("as", type = "string")
|
||||
* })
|
||||
*/
|
||||
class ExtensionMethod
|
||||
{
|
||||
/** @var string */
|
||||
private $methodName = "";
|
||||
|
||||
/**
|
||||
* ExtensionMethod constructor.
|
||||
* @param array $attributes
|
||||
* @throws ArgumentException
|
||||
*/
|
||||
public function __construct(array $attributes) {
|
||||
$this->methodName = $attributes["as"];
|
||||
|
||||
if (!is_string($this->methodName)) {
|
||||
throw new ArgumentException("Property 'as' must be a string.");
|
||||
}
|
||||
|
||||
if (strlen($this->methodName) == 0) {
|
||||
throw new ArgumentException("Property 'as' must not be an empty string.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method name.
|
||||
* @return string
|
||||
*/
|
||||
public function getMethodName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,5 @@ namespace LotGD\Core;
|
||||
interface GameAwareInterface
|
||||
{
|
||||
public function setGame(Game $g);
|
||||
public function getGame(): Game;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class LibraryConfiguration
|
||||
* @param array $arguments
|
||||
* @return type
|
||||
*/
|
||||
protected function getSubKeyIfItExists(array $arguments)
|
||||
public function getSubKeyIfItExists(array $arguments)
|
||||
{
|
||||
$parent = $this->rawConfig;
|
||||
|
||||
@@ -145,7 +145,7 @@ class LibraryConfiguration
|
||||
* Tries to iterate an array element given by the arguments
|
||||
* @param scalar $argument1,... array keys, by increasing depth
|
||||
*/
|
||||
protected function iterateKey(...$arguments)
|
||||
public function iterateKey(...$arguments)
|
||||
{
|
||||
$result = $this->getSubKeyIfItExists($arguments);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class LibraryConfigurationManager
|
||||
|
||||
/**
|
||||
* Return an array of the library configurations.
|
||||
* @return array<LibraryConfiguration>
|
||||
* @return LibraryConfiguration[]
|
||||
*/
|
||||
public function getConfigurations(): array {
|
||||
return $this->configurations;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
|
||||
|
||||
use LotGD\Core\Doctrine\Annotations\ExtensionMethod;
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
use ReflectionClass;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use LotGD\Core\Doctrine\Annotations\Extension;
|
||||
|
||||
class ModelExtender
|
||||
{
|
||||
private $reader;
|
||||
private static $classes = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->reader = new AnnotationReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $classes
|
||||
*/
|
||||
public function addMore(array $classes): void {
|
||||
foreach($classes as $class) {
|
||||
$this->add($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @throws ArgumentException if the given class is not properly annotated.
|
||||
*/
|
||||
public function add(string $class): void
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($class);
|
||||
/** @var Extension $extensionAnnotation */
|
||||
$extensionAnnotation = $this->reader->getClassAnnotation($reflectionClass, Extension::class);
|
||||
|
||||
if ($extensionAnnotation === null) {
|
||||
throw new ArgumentException(sprintf("Class %s must have the class Annotation %s", $class, Extension::class));
|
||||
}
|
||||
|
||||
$modelClass = $extensionAnnotation->getModelClass();
|
||||
|
||||
if (empty(self::$classes[$modelClass])) {
|
||||
self::$classes[$modelClass] = [];
|
||||
}
|
||||
|
||||
// Run through static methods
|
||||
$reflectionMethods = $reflectionClass->getMethods();
|
||||
|
||||
foreach ($reflectionMethods as $method) {
|
||||
if ($method->isStatic() === false) {
|
||||
throw new ArgumentException(sprintf("Method %s must be static.", $method->getName()));
|
||||
}
|
||||
|
||||
/** @var ExtensionMethod $extensionMethodAnnotation */
|
||||
$extensionMethodAnnotation = $this->reader->getMethodAnnotation($method, ExtensionMethod::class);
|
||||
$methodName = $method->getName();
|
||||
|
||||
self::$classes[$modelClass][$extensionMethodAnnotation->getMethodName()] = [$class, $methodName];
|
||||
}
|
||||
}
|
||||
|
||||
public static function get(string $modelClassName, string $methodName): ?callable
|
||||
{
|
||||
if (empty(self::$classes[$modelClassName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty(self::$classes[$modelClassName][$methodName])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::$classes[$modelClassName][$methodName];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Models;
|
||||
|
||||
|
||||
interface ExtendableModelInterface
|
||||
{
|
||||
public function __call($method, $arguments);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Tools\Model;
|
||||
|
||||
use LotGD\Core\ModelExtender;
|
||||
|
||||
trait ExtendableModel
|
||||
{
|
||||
public function __call($method, $arguments)
|
||||
{
|
||||
$callback = ModelExtender::get(self::class, $method);
|
||||
|
||||
if ($callback) {
|
||||
return call_user_func_array($callback, array_merge([$this], $arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ trait GameAware
|
||||
$this->game = $game;
|
||||
}
|
||||
|
||||
private function getGame(): Game {
|
||||
public function getGame(): Game {
|
||||
return $this->game;
|
||||
}
|
||||
}
|
||||
@@ -150,5 +150,6 @@ class BootstrapTest extends \PHPUnit_Framework_TestCase
|
||||
$user = $game->getEntityManager()->getRepository(UserEntity::class)->find($id);
|
||||
|
||||
$this->assertSame($game, $user->returnGame());
|
||||
$this->assertSame([$user->getName()], $user->getNameAsArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use LotGD\Core\Game;
|
||||
use LotGD\Core\GameAwareInterface;
|
||||
use LotGD\Core\Models\ExtendableModelInterface;
|
||||
use LotGD\Core\Tools\Model\ExtendableModel;
|
||||
use LotGD\Core\Tools\Model\GameAware;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="Users")
|
||||
*/
|
||||
class UserEntity implements GameAwareInterface
|
||||
class UserEntity implements GameAwareInterface, ExtendableModelInterface
|
||||
{
|
||||
use GameAware;
|
||||
use ExtendableModel;
|
||||
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Tests\FakeModule\Models;
|
||||
|
||||
use LotGD\Core\Doctrine\Annotations\Extension;
|
||||
use LotGD\Core\Doctrine\Annotations\ExtensionMethod;
|
||||
use LotGD\Core\Models\Character;
|
||||
|
||||
/**
|
||||
* Class CharacterTestExtension
|
||||
* @package LotGD\Core\Tests\FakeModule\Models
|
||||
* @Extension(of="LotGD\Core\Tests\FakeModule\Models\UserEntity")
|
||||
*/
|
||||
class UserTestExtension
|
||||
{
|
||||
/**
|
||||
* @param UserEntity $user
|
||||
* @return array
|
||||
* @ExtensionMethod(as="getNameAsArray")
|
||||
*/
|
||||
public static function returnNameAsArrayForUser(UserEntity $user): array
|
||||
{
|
||||
$g = $user->getGame();
|
||||
|
||||
if ($g !== null) {
|
||||
return [$user->getName()];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
entityNamespace: "LotGD\\Core\\Tests\\FakeModule\\Models\\"
|
||||
subscriptionPatterns:
|
||||
- "e/lotgd/core/tests/event"
|
||||
modelExtensions:
|
||||
- "LotGD\\Core\\Tests\\FakeModule\\Models\\UserTestExtension"
|
||||
Reference in New Issue
Block a user