Adds possibility to extend certain models using annotations.

This commit is contained in:
Vassyli
2018-01-06 09:12:56 +01:00
parent aba0d53a68
commit ff713ac333
14 changed files with 275 additions and 6 deletions
+22 -1
View File
@@ -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);
}
}
}
}
+51
View File
@@ -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;
}
}
+1
View File
@@ -10,4 +10,5 @@ namespace LotGD\Core;
interface GameAwareInterface
{
public function setGame(Game $g);
public function getGame(): Game;
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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;
+80
View File
@@ -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];
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
interface ExtendableModelInterface
{
public function __call($method, $arguments);
}
+18
View File
@@ -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));
}
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ trait GameAware
$this->game = $game;
}
private function getGame(): Game {
public function getGame(): Game {
return $this->game;
}
}
+1
View File
@@ -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());
}
}
+4 -1
View File
@@ -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 [];
}
}
}
+2
View File
@@ -1,3 +1,5 @@
entityNamespace: "LotGD\\Core\\Tests\\FakeModule\\Models\\"
subscriptionPatterns:
- "e/lotgd/core/tests/event"
modelExtensions:
- "LotGD\\Core\\Tests\\FakeModule\\Models\\UserTestExtension"