Skip to content

Commit

Permalink
Expand annotation handling
Browse files Browse the repository at this point in the history
  • Loading branch information
MrHash committed Jul 14, 2020
1 parent 8e9a90e commit 99a21b7
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 76 deletions.
42 changes: 8 additions & 34 deletions src/FromToNativeTrait.php
Expand Up @@ -12,7 +12,7 @@

trait FromToNativeTrait
{
use InheritanceReader;
use SupportsAnnotations;

/**
* @psalm-suppress MissingParamType
Expand All @@ -22,8 +22,7 @@ public static function fromNative($state): self
{
Assertion::isArray($state, 'This trait only works with array state.');

$classReflection = new ReflectionClass(static::class);
list($valueFactories, $product) = static::construct($classReflection, $state);
list($valueFactories, $product) = static::construct($state);
foreach ($valueFactories as $propertyName => $factory) {
if (array_key_exists($propertyName, $state)) {
$product->$propertyName = $factory($state[$propertyName]);
Expand All @@ -38,12 +37,12 @@ public static function fromNative($state): self
public function toNative(): array
{
$state = [];
$classReflection = new ReflectionClass($this);
foreach (static::getInheritance($classReflection) as $currentClass) {
$reflectionClass = new ReflectionClass($this);
foreach (static::getInheritance() as $currentClass) {
foreach ($currentClass->getProperties() as $property) {
$propertyName = $property->getName();
if ($currentClass->isTrait()) {
$property = $classReflection->getProperty($propertyName);
$property = $reflectionClass->getProperty($propertyName);
}
$property->setAccessible(true);
$value = $property->getValue($this);
Expand All @@ -58,10 +57,10 @@ public function toNative(): array
return $state;
}

private static function construct(ReflectionClass $classReflection, array $payload): array
private static function construct(array $payload): array
{
$valueFactories = static::inferValueFactories($classReflection);
$constructor = $classReflection->getConstructor();
$valueFactories = static::inferValueFactories();
$constructor = (new ReflectionClass(static::class))->getConstructor();
if (is_null($constructor) || $constructor->getNumberOfParameters() === 0) {
/** @psalm-suppress TooFewArguments */
return [$valueFactories, new static];
Expand Down Expand Up @@ -89,29 +88,4 @@ private static function construct(ReflectionClass $classReflection, array $paylo
/** @psalm-suppress TooManyArguments */
return [$valueFactories, new static(...$constructorArgs)];
}

private static function inferValueFactories(ReflectionClass $classReflection): array
{
$valueFactories = [];
foreach (static::getInheritance($classReflection) as $currentClass) {
if (!($docComment = $currentClass->getDocComment())) {
continue;
}
preg_match_all('/@(?:id|rev|map)\(((.+),(.+))\)/', $docComment, $matches);
//@todo don't allow duplicate id/rev
foreach ($matches[2] as $index => $propertyName) {
$callable = array_map('trim', explode('::', $matches[3][$index]));
if (!isset($callable[1]) && is_a($callable[0], FromNativeInterface::class, true)) {
$callable[1] = 'fromNative';
}
Assertion::isCallable(
$callable,
sprintf("Value factory '%s' is not callable in '%s'.", implode('::', $callable), static::class)
);
$valueFactories[$propertyName] = $callable;
}
}

return $valueFactories;
}
}
42 changes: 0 additions & 42 deletions src/InheritanceReader.php

This file was deleted.

111 changes: 111 additions & 0 deletions src/SupportsAnnotations.php
@@ -0,0 +1,111 @@
<?php declare(strict_types=1);
/**
* This file is part of the daikon-cqrs/interop project.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Daikon\Interop;

use ReflectionClass;

trait SupportsAnnotations
{
protected static function inferValidTypes(): array
{
return array_map(
fn(array $annotation): string => explode('::', $annotation['value'])[0],
static::getClassAnnotations('type')
);
}

/** @return callable[] */
protected static function inferTypeFactories(): array
{
$typeFactories = [];
foreach (static::getClassAnnotations('type') as $annotation) {
$callable = explode('::', $annotation['value']);
if (interface_exists($callable[0], false)) {
continue; //skip interfaces
}
if (!isset($callable[1]) && is_a($callable[0], FromNativeInterface::class, true)) {
$callable[1] = 'fromNative';
}
Assertion::isCallable(
$callable,
sprintf("Type factory '%s' is not callable in '%s'.", $annotation['value'], static::class)
);
$typeFactories[$callable[0]] = $callable;
}

return $typeFactories;
}

/** @return callable[] */
protected static function inferValueFactories(string ...$filter): array
{
$valueFactories = [];
foreach (static::getClassAnnotations(...($filter ?: ['id', 'rev', 'map'])) as $annotation) {
list($property, $factory) = array_map('trim', explode(',', $annotation['value']));
$callable = explode('::', $factory);
if (!isset($callable[1]) && is_a($callable[0], FromNativeInterface::class, true)) {
$callable[1] = 'fromNative';
}
Assertion::isCallable(
$callable,
sprintf("Value factory '%s' is not callable in '%s'.", $factory, static::class)
);
$valueFactories[$property] = $callable;
}

return $valueFactories;
}

private static function getClassAnnotations(string ...$keys): array
{
$annotations = $matches = [];
foreach (static::getInheritance() as $class) {
if (!($docComment = $class->getDocComment())
|| !preg_match_all('/^[\s\*]+@(?<key>\w+)\((?<value>.+)\)$/m', $docComment, $matches, PREG_SET_ORDER)
) {
continue;
}
foreach ($matches as $match) {
if (empty($keys) || in_array($match['key'], $keys)) {
$annotations[] = ['key' => trim($match['key']), 'value' => trim($match['value'])];
}
}
}

return $annotations;
}

/** @return ReflectionClass[] */
private static function getInheritance(): array
{
$currentReflection = new ReflectionClass(static::class);
$reflectionClasses = array_merge([$currentReflection], static::flatMapTraits($currentReflection));

while ($currentReflection = $currentReflection->getParentClass()) {
$reflectionClasses = array_merge(
$reflectionClasses,
[$currentReflection],
static::flatMapTraits($currentReflection)
);
}

return $reflectionClasses;
}

/** @return ReflectionClass[] */
private static function flatMapTraits(ReflectionClass $reflectionClass): array
{
$reflectionClasses = [];
foreach ($reflectionClass->getTraits() as $trait) {
$reflectionClasses = array_merge($reflectionClasses, [$trait], static::flatMapTraits($trait));
}

return $reflectionClasses;
}
}

0 comments on commit 99a21b7

Please sign in to comment.