diff --git a/src/ObjectReflection.php b/src/ObjectReflection.php index 5f1c67c..53b2f7d 100644 --- a/src/ObjectReflection.php +++ b/src/ObjectReflection.php @@ -6,9 +6,12 @@ use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod; class ObjectReflection { + protected string $classname; + public AnnotationReader $annotationReader; public function __construct($class, AnnotationReader $annotationReader = null) { + $this->classname = ltrim($class, '\\'); $this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class); $this->annotationReader = $annotationReader ?: AnnotationReader::fromClass($class); } @@ -18,53 +21,61 @@ class ObjectReflection { return new static($class); } - public function read() : array + public function read(bool $fullUses = true, bool $fullObject = true, $fullMethod = true, $fullProperty = true) : array { return [ - 'uses' => $this->gatherUses(true), - 'class' => $this->gatherClass(true), - 'method' => $this->gatherMethods(true), - 'property' => $this->gatherProperties(true), + 'uses' => $this->gatherUses($fullUses), + 'class' => $this->gatherClass($fullObject), + 'method' => $this->gatherMethods($fullMethod), + 'property' => $this->gatherProperties($fullProperty), ]; } public function gatherUses(bool $full = true) : array { - $list = []; - if ( $full ) { if ( $parentClass = $this->classReflection->getParentClass() ) { $list = static::fromClass($parentClass)->gatherUses(true); } foreach($this->classReflection->getTraits() as $trait) { - $list = array_merge($list, static::fromClass($trait)->gatherUses(true)); + $list = array_replace(static::fromClass($trait)->gatherUses(true), $list ?? []); } } - return array_merge($this->getUsesStatements(), $list); + return array_replace($list ?? [], $this->getUsesStatements()); } public function gatherClass(bool $full = true) : array { - $class = []; - if ( $full ) { if ( $parentClass = $this->classReflection->getParentClass() ) { $class = static::fromClass($parentClass)->gatherClass(true); } + if ( $traits = $this->classReflection->getTraits() ) { + foreach($traits as $key => $value) { + $traitTags = static::fromClass($key)->gatherClass(true); + } + } + + if ( $interfaces = $this->classReflection->getInterfaces() ) { + foreach($interfaces as $key => $value) { + $interfaceTags = static::fromClass($key)->gatherClass(true); + } + } + $itemName = function($item) { return $item->getName(); }; } - return [ - 'tags' => array_merge($class, $this->annotationReader->getClass($this->classReflection)) + return array_merge_recursive($class ?? [], $traitTags ?? [], $interfaceTags ?? [], [ + 'tags' => $this->annotationReader->getClass($this->classReflection) ] + ( ! $full ? [] : [ - 'traits' => array_map($itemName, $this->classReflection->getTraits()), - 'interfaces' => array_map($itemName, $this->classReflection->getInterfaces()), - ]); + 'traits' => array_map($itemName, $traits), + 'interfaces' => array_map($itemName, $interfaces), + ] )); } public function gatherProperties(bool $full = true, int $filter = @@ -73,7 +84,6 @@ class ObjectReflection { ReflectionProperty::IS_PRIVATE ) : array { - $properties = []; $defaultValues = $this->classReflection->getDefaultProperties(); if ( $full ) { @@ -82,11 +92,9 @@ class ObjectReflection { } } - $properties = array_merge($properties, $this->classReflection->getProperties($filter)); - $list = []; - foreach($properties as $property) { + foreach($this->classReflection->getProperties($filter) as $property) { $current = [ 'name' => $property->getName() ]; @@ -110,7 +118,7 @@ class ObjectReflection { $list[ $current['name'] ] = $current; } - return $list; + return array_merge($properties ?? [], $list); } public function gatherMethods(bool $full = true, int $filter = @@ -120,7 +128,7 @@ class ObjectReflection { ReflectionMethod::IS_STATIC ) : array { - $list = $methods = []; + $list = []; if ( $full ) { if ( $parentClass = $this->classReflection->getParentClass() ) { @@ -128,9 +136,11 @@ class ObjectReflection { } } - $methods = array_merge($methods, $this->classReflection->getMethods($filter)); + foreach($this->classReflection->getMethods($filter) as $method) { + if ( ! $full && ( $method->class !== $this->classname ) ) { + continue; + } - foreach($methods as $method) { $parameters = []; foreach($method->getParameters() as $parameter) { @@ -162,7 +172,7 @@ class ObjectReflection { $list[ $current['name'] ] = $current; } - return $list; + return array_merge($methods ?? [], $list); } protected function ignoreElementAnnotation($tags) : bool diff --git a/src/ObjectResolver.php b/src/ObjectResolver.php new file mode 100644 index 0000000..1e7cfe6 --- /dev/null +++ b/src/ObjectResolver.php @@ -0,0 +1,158 @@ +objectClass = $objectClass; + + list($this->uses, $this->class, $this->methods, $this->properties) = array_values( + ObjectReflection::fromClass($objectClass)->read($fullUses, $fullObject, $fullMethod, $fullProperty) + ); + + $this->resolveAnnotations(); + } + + /** + * Transform an annotation into it's object's counterpart + */ + public function getAnnotationFromClassname(string $className) : ? object + { + if ( $name = $this->uses[$className] ?? false) { + foreach($this->class['tags'] as $item) { + if ( $item['tag'] === $name ) { + return $this->instanciateAnnotationObject($item); + } + + foreach($this->properties as $property) { + foreach($property['tags'] as $item) { + if ( $item['tag'] === $name ) { + return $this->instanciateAnnotationObject($item); + } + } + } + + foreach($this->methods as $method) { + foreach($method['tags'] as $item) { + if ( $item['tag'] === $name ) { + return $this->instanciateAnnotationObject($item); + } + } + } + } + + throw new \TypeError("Annotation `$className` could not be found within your object `{$this->objectClass}`"); + } + else { + throw new \InvalidArgumentException("Class `$className` was not found within {$this->objectClass} uses statement (or it's children / traits)"); + } + + return null; + } + /** + * Transform an annotation into it's object's counterpart + */ + public function getAnnotationListFromClassname(string $className, bool $throwOnError = true) : array + { + $list = []; + + if ( $name = $this->uses[$className] ?? false) { + foreach($this->class['tags'] as $item) { + if ( $item['tag'] === $name ) { + $list[] = $this->instanciateAnnotationObject($item); + } + + foreach($this->properties as $property) { + foreach($property['tags'] as $item) { + if ( $item['tag'] === $name ) { + $list[$property['name']] = $this->instanciateAnnotationObject($item); + } + } + } + + foreach($this->methods as $method) { + foreach($method['tags'] as $item) { + if ( $item['tag'] === $name ) { + $list[$method['name']] = $this->instanciateAnnotationObject($item); + } + } + } + } + } + else { + if ($throwOnError) throw new \InvalidArgumentException("Class `$className` was not found within {$this->objectClass} uses statement (or it's children / traits)"); + } + + return $list; + } + + public function instanciateAnnotationObject(array $tagDefinition) : Annotation + { + $arguments = $this->extractArguments($tagDefinition['arguments']); + + if ( false === $class = array_search($tagDefinition['tag'], $this->uses) ) { + throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->objectClass} uses statement (or it's children / traits)"); + } + + $obj = new $class(... $arguments['constructor']); + + foreach($arguments['setter'] as $key => $value) { + $obj->$key = $value; + } + + return $obj; + } + + /** + * Extracts arguments from an Annotation definition, easing object's declaration. + */ + protected function extractArguments(array $arguments) : array + { + $list = [ + 'setter' => [], + 'constructor' => [], + ]; + + ksort($arguments); + + foreach($arguments as $key => $value) { + $list[ is_int($key) ? 'constructor' : 'setter' ][$key] = $value; + } + + return $list; + } + + protected function resolveAnnotations() + { + foreach($this->class['tags'] as $key => &$tag) { + $tag['object'] = $this->instanciateAnnotationObject($tag); + } + + foreach($this->properties as &$property) { + foreach($property['tags'] as &$tag){ + $tag['object'] = $this->instanciateAnnotationObject($tag); + } + } + + foreach($this->methods as &$method) { + foreach($method['tags'] as &$tag){ + $tag['object'] = $this->instanciateAnnotationObject($tag); + } + } + } +}