- First commit -- splitted from Ulmus, but still untested
This commit is contained in:
		
						commit
						504c87b134
					
				
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -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. | ||||
							
								
								
									
										18
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							| @ -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/" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/Annotation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/Annotation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Notes; | ||||
| 
 | ||||
| interface Annotation {} | ||||
							
								
								
									
										100
									
								
								src/AnnotationReader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/AnnotationReader.php
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										267
									
								
								src/ObjectReflection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								src/ObjectReflection.php
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user