From 0a7ff4fd210fc2ab14a74f200dcbe598ad162d31 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Fri, 13 Jan 2023 21:45:42 -0500 Subject: [PATCH] done mergin --- src/Adapter/SQLite.php | 2 +- src/Common/ObjectReflection.php | 269 ++++++++++++++++++++++++++++++++ src/EntityTrait.php | 1 + 3 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 src/Common/ObjectReflection.php diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index 55d8692..497508e 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -194,4 +194,4 @@ class SQLite implements AdapterInterface { return (int) in_array($string, explode(',', $string_list)); }, 2); } -} +} \ No newline at end of file diff --git a/src/Common/ObjectReflection.php b/src/Common/ObjectReflection.php new file mode 100644 index 0000000..bda150c --- /dev/null +++ b/src/Common/ObjectReflection.php @@ -0,0 +1,269 @@ +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 array_merge_recursive($class, [ + 'tags' => $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); + } + } + + $list = []; + + foreach($this->classReflection->getProperties($filter) 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['builtin'] = $property->getType()->isBuiltIn(); + $current['nullable'] = $property->getType()->allowsNull(); + } + + $current['tags'] = $this->annotationReader->getProperty($property); + + if ( $this->ignoreElementAnnotation($current['tags']) ) { + continue; + } + + $list[ $current['name'] ] = $current; + } + + return array_merge($properties, $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); + } + } + + foreach($this->classReflection->getMethods($filter) 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' => \Notes\ObjectReflection::isType('array', $parameter), + 'callable' => \Notes\ObjectReflection::isType('callable', $parameter), + '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 array_merge($methods, $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: + case T_NAME_QUALIFIED: + 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; + } +} diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 36719f2..c062325 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -322,6 +322,7 @@ trait EntityTrait { */ public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField { + return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); }