- First commit -- splitted from Ulmus, but still untested
This commit is contained in:
commit
504c87b134
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 Dave Mc Nicoll
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "mcnd/notes",
|
||||||
|
"description": "Easy annotation based on PHP array syntax",
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Dave Mc Nicoll",
|
||||||
|
"email": "mcndave@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Notes\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Notes;
|
||||||
|
|
||||||
|
interface Annotation {}
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Notes;
|
||||||
|
|
||||||
|
use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod;
|
||||||
|
|
||||||
|
class AnnotationReader
|
||||||
|
{
|
||||||
|
const PHP_TYPES = [ "string", "int", "float", "object", "double", "closure", ];
|
||||||
|
|
||||||
|
public string $class;
|
||||||
|
|
||||||
|
public function __construct($class) {
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromClass($class) : self
|
||||||
|
{
|
||||||
|
return new static($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProperty(ReflectionProperty $property)
|
||||||
|
{
|
||||||
|
return $this->parseDocComment($property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass(ReflectionClass $class)
|
||||||
|
{
|
||||||
|
return $this->parseDocComment($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethod(ReflectionMethod $method)
|
||||||
|
{
|
||||||
|
return $this->parseDocComment($method);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseDocComment(Reflector $reflect)
|
||||||
|
{
|
||||||
|
$namespace = $this->getObjectNamespace($reflect);
|
||||||
|
$tags = [];
|
||||||
|
|
||||||
|
foreach(preg_split("/\r\n|\n|\r/", $reflect->getDocComment()) as $line) {
|
||||||
|
$line = ltrim($line, "* \t\/");
|
||||||
|
$line = rtrim($line, "\t ");
|
||||||
|
|
||||||
|
if ( substr($line, 0, 1) === '@' ) {
|
||||||
|
$line = ltrim($line, '@');
|
||||||
|
|
||||||
|
$open = strpos($line, "(");
|
||||||
|
$close = strrpos($line, ")");
|
||||||
|
|
||||||
|
if ( ! in_array(false, [ $open, $close ], true) && ( ++$open !== $close ) ) {
|
||||||
|
$arguments = substr($line, $open, $close - $open);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$tags[] = [
|
||||||
|
'tag' => substr($line, 0, $open - 1),
|
||||||
|
'arguments' => eval("namespace $namespace; return [ $arguments ];"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
catch(\Throwable $error) {
|
||||||
|
throw new \InvalidArgumentException("An error occured while parsing annotation from '" . $this->getObjectName($reflect) . "' : @$line -- " . $error->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$tags[] = [
|
||||||
|
'tag' => $line,
|
||||||
|
'arguments' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectName(Reflector $reflect) : string
|
||||||
|
{
|
||||||
|
switch(true) {
|
||||||
|
case $reflect instanceof ReflectionMethod :
|
||||||
|
case $reflect instanceof ReflectionProperty :
|
||||||
|
return $reflect->class . "::" . $reflect->name;
|
||||||
|
|
||||||
|
case $reflect instanceof ReflectionClass :
|
||||||
|
return $reflect->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getObjectNamespace(Reflector $reflect) : string
|
||||||
|
{
|
||||||
|
switch(true) {
|
||||||
|
case $reflect instanceof ReflectionMethod :
|
||||||
|
case $reflect instanceof ReflectionProperty :
|
||||||
|
return $reflect->getDeclaringClass()->getNamespaceName();
|
||||||
|
|
||||||
|
case $reflect instanceof ReflectionClass :
|
||||||
|
return $reflect->getNamespaceName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Notes;
|
||||||
|
|
||||||
|
use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod;
|
||||||
|
|
||||||
|
class ObjectReflection {
|
||||||
|
|
||||||
|
public AnnotationReader $annotationReader;
|
||||||
|
|
||||||
|
public function __construct($class, AnnotationReader $annotationReader = null) {
|
||||||
|
$this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
|
||||||
|
$this->annotationReader = $annotationReader ?: AnnotationReader::fromClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromClass($class) : self
|
||||||
|
{
|
||||||
|
return new static($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'uses' => $this->gatherUses(true),
|
||||||
|
'class' => $this->gatherClass(true),
|
||||||
|
'method' => $this->gatherMethods(true),
|
||||||
|
'property' => $this->gatherProperties(true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge($this->getUsesStatements(), $list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gatherClass(bool $full = true) : array
|
||||||
|
{
|
||||||
|
$class = [];
|
||||||
|
|
||||||
|
if ( $full ) {
|
||||||
|
if ( $parentClass = $this->classReflection->getParentClass() ) {
|
||||||
|
$class = static::fromClass($parentClass)->gatherClass(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$itemName = function($item) {
|
||||||
|
return $item->getName();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tags' => array_merge($class, $this->annotationReader->getClass($this->classReflection))
|
||||||
|
] + ( ! $full ? [] : [
|
||||||
|
'traits' => array_map($itemName, $this->classReflection->getTraits()),
|
||||||
|
'interfaces' => array_map($itemName, $this->classReflection->getInterfaces()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gatherProperties(bool $full = true, int $filter =
|
||||||
|
ReflectionProperty::IS_PUBLIC |
|
||||||
|
ReflectionProperty::IS_PROTECTED |
|
||||||
|
ReflectionProperty::IS_PRIVATE
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$properties = [];
|
||||||
|
$defaultValues = $this->classReflection->getDefaultProperties();
|
||||||
|
|
||||||
|
if ( $full ) {
|
||||||
|
if ( $parentClass = $this->classReflection->getParentClass() ) {
|
||||||
|
$properties = static::fromClass($parentClass)->gatherProperties($full, $filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$properties = array_merge($properties, $this->classReflection->getProperties($filter));
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
|
||||||
|
foreach($properties as $property) {
|
||||||
|
$current = [
|
||||||
|
'name' => $property->getName()
|
||||||
|
];
|
||||||
|
|
||||||
|
# Default value can be 'null', so isset() it not suitable here
|
||||||
|
if ( array_key_exists($current['name'], $defaultValues) ) {
|
||||||
|
$current['value'] = $defaultValues[ $current['name'] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $property->hasType() ) {
|
||||||
|
$current['type'] = $property->getType()->getName();
|
||||||
|
$current['nullable'] = $property->getType()->allowsNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
$current['tags'] = $this->annotationReader->getProperty($property);
|
||||||
|
|
||||||
|
if ( $this->ignoreElementAnnotation($current['tags']) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list[ $current['name'] ] = $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gatherMethods(bool $full = true, int $filter =
|
||||||
|
ReflectionMethod::IS_PUBLIC |
|
||||||
|
ReflectionMethod::IS_PROTECTED |
|
||||||
|
ReflectionMethod::IS_PRIVATE |
|
||||||
|
ReflectionMethod::IS_STATIC
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$list = $methods = [];
|
||||||
|
|
||||||
|
if ( $full ) {
|
||||||
|
if ( $parentClass = $this->classReflection->getParentClass() ) {
|
||||||
|
$methods = static::fromClass($parentClass)->gatherMethods($full, $filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$methods = array_merge($methods, $this->classReflection->getMethods($filter));
|
||||||
|
|
||||||
|
foreach($methods as $method) {
|
||||||
|
$parameters = [];
|
||||||
|
|
||||||
|
foreach($method->getParameters() as $parameter) {
|
||||||
|
$parameters[$parameter->getName()] = [
|
||||||
|
'null' => $parameter->allowsNull(),
|
||||||
|
'position' => $parameter->getPosition(),
|
||||||
|
'type' => $parameter->hasType() ? $parameter->getType()->getName() : false,
|
||||||
|
'array' => $parameter->isArray(),
|
||||||
|
'callable' => $parameter->isCallable(),
|
||||||
|
'optional' => $parameter->isOptional(),
|
||||||
|
'byReference' => $parameter->isPassedByReference(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = [
|
||||||
|
'name' => $method->getName(),
|
||||||
|
'type' => $method->hasReturnType() ? $method->getReturnType()->getName() : false,
|
||||||
|
'constructor' => $method->isConstructor(),
|
||||||
|
'destructor' => $method->isDestructor(),
|
||||||
|
'parameters' => $parameters,
|
||||||
|
];
|
||||||
|
|
||||||
|
$current['tags'] = $this->annotationReader->getMethod($method);
|
||||||
|
|
||||||
|
if ( $this->ignoreElementAnnotation($current['tags']) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list[ $current['name'] ] = $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function ignoreElementAnnotation($tags) : bool
|
||||||
|
{
|
||||||
|
return in_array('IGNORE', array_map('strtoupper', array_column($tags, 'tag') ));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function readCode() : string
|
||||||
|
{
|
||||||
|
static $code = [];
|
||||||
|
$fileName = $this->classReflection->getFilename();
|
||||||
|
return $code[$fileName] ?? $code[$fileName] = file_get_contents($fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getUsesStatements() : array
|
||||||
|
{
|
||||||
|
$uses = [];
|
||||||
|
$tokens = token_get_all( $c = $this->readCode() );
|
||||||
|
|
||||||
|
while ( $token = array_shift($tokens) ) {
|
||||||
|
|
||||||
|
if ( is_array($token) ) {
|
||||||
|
list($token, $value) = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($token) {
|
||||||
|
case T_CLASS:
|
||||||
|
case T_TRAIT:
|
||||||
|
case T_INTERFACE:
|
||||||
|
break 2;
|
||||||
|
|
||||||
|
case T_USE:
|
||||||
|
$isUse = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_NS_SEPARATOR:
|
||||||
|
$isNamespace = $isUse;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_STRING:
|
||||||
|
if ( $isNamespace && $latestString ) {
|
||||||
|
$statement[] = $latestString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latestString = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_AS:
|
||||||
|
# My\Name\Space\aClassHere `as` ClassAlias;
|
||||||
|
$replacedClass = implode("\\", array_merge($statement, [ $latestString ]));
|
||||||
|
$latestString = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_WHITESPACE:
|
||||||
|
case T_COMMENT:
|
||||||
|
case T_DOC_COMMENT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
# opening a sub-namespace -> \My\Name\Space\`{`OneItem, AnotherItem}
|
||||||
|
if ( $isNamespace ) {
|
||||||
|
$inNamespace = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ';';
|
||||||
|
case ',':
|
||||||
|
case '}':
|
||||||
|
if ( $isUse ) {
|
||||||
|
if ( $replacedClass ) {
|
||||||
|
$uses[$replacedClass] = $latestString;
|
||||||
|
$replacedClass = "";
|
||||||
|
}
|
||||||
|
elseif ( $latestString ) {
|
||||||
|
$uses[implode("\\", array_merge($statement, [ $latestString ]))] = $latestString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $inNamespace ) {
|
||||||
|
$latestString = "";
|
||||||
|
|
||||||
|
# \My\Name\Space\{OneItem, AnotherItem`}` <- closing a sub-namespace
|
||||||
|
if ( $token !== "}" ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case T_OPEN_TAG:
|
||||||
|
default:
|
||||||
|
$statement = [];
|
||||||
|
$latestString = "";
|
||||||
|
$replacedClass = null;
|
||||||
|
$isNamespace = $inNamespace = false;
|
||||||
|
$isUse = ( $isUse ?? false ) && ( $token === ',' );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uses;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue