From 85dafbe4c0f5a2b1c66bf6ef355e0be24518e4de Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 17:02:29 +0200 Subject: [PATCH 01/15] Check for PHP 8 attributes in detailsFor* --- src/main/php/lang/XPClass.class.php | 104 +++++++++++++++++++++------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 04248460db..361b67c433 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -50,10 +50,12 @@ * @test xp://net.xp_framework.unittest.reflection.ClassCastingTest */ class XPClass extends Type { + private static $ATTRIBUTES; private $_class; private $_reflect= null; static function __static() { + self::$ATTRIBUTES= class_exists(\ReflectionAttribute::class, false); // Workaround for missing detail information about return types in // builtin classes. @@ -545,7 +547,7 @@ public function getDeclaredInterfaces() { * @return string */ public function getComment() { - if (!($details= self::detailsForClass($this->name))) return null; + if (!($details= self::detailsForClass($this->reflect()))) return null; return $details['class'][DETAIL_COMMENT]; } @@ -575,7 +577,7 @@ public function getModifiers(): int { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $details= self::detailsForClass($this->name); + $details= self::detailsForClass($this->reflect()); return $details && ($key ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) @@ -592,7 +594,7 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $details= self::detailsForClass($this->name); + $details= self::detailsForClass($this->reflect()); if (!$details || !($key ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) @@ -608,7 +610,7 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether a method has annotations */ public function hasAnnotations(): bool { - $details= self::detailsForClass($this->name); + $details= self::detailsForClass($this->reflect()); return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : false; } @@ -618,7 +620,7 @@ public function hasAnnotations(): bool { * @return array annotations */ public function getAnnotations() { - $details= self::detailsForClass($this->name); + $details= self::detailsForClass($this->reflect()); return $details ? $details['class'][DETAIL_ANNOTATIONS] : []; } @@ -641,25 +643,57 @@ protected static function _classLoaderFor($name) { } return null; // Internal class, e.g. } + + public static function mergeAttributes(&$details, $reflect) { + if (!self::$ATTRIBUTES) return; + + foreach ($reflect->getAttributes() as $attribute) { + $args= $attribute->getArguments(); + if (empty($args)) { + $value= null; + } else if (1 === sizeof($args)) { + $value= $args[0]; + } else { + $value= $args; + } + + $name= $attribute->getName(); + $p= strrpos($name, '\\'); + if (false === $p) { + $details[$name]= $value; + } else if ($name[$p + 1] <= 'Z') { + $details[strtr($name, '\\', '.')]= $value; + } else { + $details[substr($name, $p + 1)]= $value; + } + } + } /** * Retrieve details for a specified class. Note: Results from this * method are cached! * - * @param string class fully qualified class name + * @param php.ReflectionClass $class * @return array or NULL to indicate no details are available */ - public static function detailsForClass($class) { + public static function detailsForClass(\ReflectionClass $class) { static $parser= null; - if (isset(\xp::$meta[$class])) return \xp::$meta[$class]; + $name= self::nameOf($class->name); + if (isset(\xp::$meta[$name])) return \xp::$meta[$name]; // Retrieve class' sourcecode - $cl= self::_classLoaderFor($class); - if (!$cl || !($bytes= $cl->loadClassBytes($class))) return null; + $cl= self::_classLoaderFor($name); + if (!$cl || !($bytes= $cl->loadClassBytes($name))) { + $r= []; + self::mergeAttributes($r, $class); + return ['class' => [DETAIL_ANNOTATIONS => $r]]; + } $parser ?? $parser= new \lang\reflect\ClassParser(); - return \xp::$meta[$class]= $parser->parseDetails($bytes, $class); + $details= $parser->parseDetails($bytes, $name); + self::mergeAttributes($details['class'][DETAIL_ANNOTATIONS], $class); + return \xp::$meta[$name]= $details; } /** @@ -671,13 +705,22 @@ public static function detailsForClass($class) { * @return array or NULL if not available */ public static function detailsForMethod($class, $method) { - $details= self::detailsForClass(self::nameOf($class->name)); - if (isset($details[1][$method])) return $details[1][$method]; - foreach ($class->getTraitNames() as $trait) { - $details= self::detailsForClass(self::nameOf($trait)); - if (isset($details[1][$method])) return $details[1][$method]; + $details= self::detailsForClass($class); + if (isset($details[1][$method])) { + self::mergeAttributes($details[1][$method][DETAIL_ANNOTATIONS], $class->getMethod($method)); + return $details[1][$method]; + } + foreach ($class->getTraits() as $trait) { + $details= self::detailsForClass($trait); + if (isset($details[1][$method])) { + self::mergeAttributes($details[1][$method][DETAIL_ANNOTATIONS], $class->getMethod($method)); + return $details[1][$method]; + } } - return null; + + $r= []; + self::mergeAttributes($r, $class->getMethod($method)); + return [DETAIL_ANNOTATIONS => $r]; } /** @@ -689,13 +732,22 @@ public static function detailsForMethod($class, $method) { * @return array or NULL if not available */ public static function detailsForField($class, $field) { - $details= self::detailsForClass(self::nameOf($class->name)); - if (isset($details[0][$field])) return $details[0][$field]; - foreach ($class->getTraitNames() as $trait) { - $details= self::detailsForClass(self::nameOf($trait)); - if (isset($details[0][$field])) return $details[0][$field]; + $details= self::detailsForClass($class); + if (isset($details[0][$field])) { + self::mergeAttributes($details[1][$field][DETAIL_ANNOTATIONS], $class->getProperty($field)); + return $details[0][$field]; + } + foreach ($class->getTraits() as $trait) { + $details= self::detailsForClass($trait); + if (isset($details[0][$field])) { + self::mergeAttributes($details[1][$field][DETAIL_ANNOTATIONS], $class->getProperty($field)); + return $details[0][$field]; + } } - return null; + + $r= []; + self::mergeAttributes($r, $class->getProperty($field)); + return [DETAIL_ANNOTATIONS => $r]; } /** @@ -748,7 +800,7 @@ public function isGenericDefinition(): bool { * @throws lang.IllegalStateException if this class is not a generic */ public function genericDefinition() { - if (!($details= self::detailsForClass($this->name))) return null; + if (!($details= self::detailsForClass($this->reflect()))) return null; if (!isset($details['class'][DETAIL_GENERIC])) { throw new IllegalStateException('Class '.$this->name.' is not generic'); } @@ -762,7 +814,7 @@ public function genericDefinition() { * @throws lang.IllegalStateException if this class is not a generic */ public function genericArguments() { - if (!($details= self::detailsForClass($this->name))) return null; + if (!($details= self::detailsForClass($this->reflect()))) return null; if (!isset($details['class'][DETAIL_GENERIC])) { throw new IllegalStateException('Class '.$this->name.' is not generic'); } @@ -778,7 +830,7 @@ public function genericArguments() { /** Returns whether this class is generic */ public function isGeneric(): bool { - if (!($details= self::detailsForClass($this->name))) return false; + if (!($details= self::detailsForClass($this->reflect()))) return false; return isset($details['class'][DETAIL_GENERIC]); } From 643ba4f71a0bccb583532c5deff014d5344fc5d7 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 17:02:54 +0200 Subject: [PATCH 02/15] Adjust to detailsForClass() refactoring --- src/main/php/lang.base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index f8a28f8c7a..2351c1ca55 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -311,7 +311,7 @@ function newinstance($spec, $args, $def= null) { } if ($generic) { - \lang\XPClass::detailsForClass($name); + \lang\XPClass::detailsForClass(new \ReflectionClass($type.$n)); xp::$meta[$name]['class'][DETAIL_GENERIC]= $generic; } From 7f9a281a86a0a5a4b4c0131c64d69e78acf58687 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:10:59 +0200 Subject: [PATCH 03/15] Merge method details with parameter annotations --- src/main/php/lang/reflect/Parameter.class.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index b6d43d42b1..3542bd8c2d 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -195,6 +195,7 @@ public function getDefaultValue() { public function hasAnnotation($name, $key= null) { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); return $key ? array_key_exists($key, $r[$name] ?? []) : array_key_exists($name, $r); } @@ -210,6 +211,7 @@ public function hasAnnotation($name, $key= null) { public function getAnnotation($name, $key= null) { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); if ($key) { if (array_key_exists($key, $r[$name] ?? [])) return $r[$name][$key]; @@ -227,7 +229,9 @@ public function getAnnotation($name, $key= null) { */ public function hasAnnotations() { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - return !empty($details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []); + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return !empty($r); } /** @@ -237,7 +241,9 @@ public function hasAnnotations() { */ public function getAnnotations() { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - return $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return $r; } /** From d061affaa6d6b32b00e1ab9bcfd0bf7b61fb5c5a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:11:25 +0200 Subject: [PATCH 04/15] Add test for PHP 8 attributes --- src/test/config/unittest/core.ini | 3 + .../annotations/AttributesTest.class.php | 135 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100755 src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php diff --git a/src/test/config/unittest/core.ini b/src/test/config/unittest/core.ini index 033f693ed0..19441d8ed9 100644 --- a/src/test/config/unittest/core.ini +++ b/src/test/config/unittest/core.ini @@ -21,6 +21,9 @@ class="net.xp_framework.unittest.annotations.ClassMemberParsingTest" [broken-annotations] class="net.xp_framework.unittest.annotations.BrokenAnnotationTest" +[php8-attributes] +class="net.xp_framework.unittest.annotations.AttributesTest" + [references] class="net.xp_framework.unittest.core.ReferencesTest" diff --git a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php new file mode 100755 index 0000000000..6925600931 --- /dev/null +++ b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php @@ -0,0 +1,135 @@ +=8.0.0-dev'))] +class AttributesTest extends TestCase { + + /** + * Declares a type from source + * + * @param string $source + * @param ?string $namespace Optional namespace name + * @return lang.XPClass + */ + private function type($source, $namespace= null) { + static $u= 0; + + $name= '__A'.(++$u); + if (null === $namespace) { + eval(sprintf($source, $name)); + return new XPClass($name); + } else { + eval('namespace '.$namespace.';'.sprintf($source, $name)); + return new XPClass($namespace.'\\'.$name); + } + } + + /** + * Assertion helper + * + * @param var $expected + * @param lang.XPClass|lang.reflect.Method|lang.reflect.Field|lang.reflect.Parameter $annotated + * @throws unittest.AssertionFailedError + */ + private function assertAnnotations($expected, $annotated) { + if (null === $expected) { + $this->assertFalse($annotated->hasAnnotations(), 'hasAnnotations'); + $this->assertEquals([], $annotated->getAnnotations()); + $this->assertFalse($annotated->hasAnnotation('Test'), 'hasAnnotation("Test")'); + try { + $annotated->getAnnotation('Test'); + $this->fail('Exception not raised', null, ElementNotFoundException::class); + } catch (ElementNotFoundException $expected) { + // OK + } + } else { + $this->assertTrue($annotated->hasAnnotations(), 'hasAnnotations'); + $this->assertEquals($expected, $annotated->getAnnotations()); + foreach ($expected as $name => $value) { + $this->assertTrue($annotated->hasAnnotation($name), 'hasAnnotation("'.$name.'")'); + $this->assertEquals($value, $annotated->getAnnotation($name)); + } + } + } + + #[@test] + public function on_class() { + $t= $this->type('<> class %s { }'); + $this->assertAnnotations(['Test' => null], $t); + } + + #[@test] + public function on_field() { + $t= $this->type('class %s { <> public $fixture; }'); + $this->assertAnnotations(['Test' => null], $t->getField('fixture')); + } + + #[@test] + public function on_method() { + $t= $this->type('class %s { <> public function fixture() { } }'); + $this->assertAnnotations(['Test' => null], $t->getMethod('fixture')); + } + + #[@test] + public function on_parameter() { + $t= $this->type('class %s { public function fixture(<> $param) { } }'); + $this->assertAnnotations(['Test' => null], $t->getMethod('fixture')->getParameter(0)); + } + + #[@test] + public function no_class_annotations() { + $t= $this->type('class %s { }'); + $this->assertAnnotations(null, $t); + } + + #[@test] + public function no_field_annotations() { + $t= $this->type('class %s { public $fixture; }'); + $this->assertAnnotations(null, $t->getField('fixture')); + } + + #[@test] + public function no_method_annotations() { + $t= $this->type('class %s { public function fixture() { } }'); + $this->assertAnnotations(null, $t->getMethod('fixture')); + } + + #[@test] + public function no_parameter_annotations() { + $t= $this->type('class %s { public function fixture($param) { } }'); + $this->assertAnnotations(null, $t->getMethod('fixture')->getParameter(0)); + } + + #[@test] + public function inside_namespace() { + $t= $this->type('<> class %s { }', 'com\\example'); + $this->assertAnnotations(['com.example.Test' => null], $t); + } + + #[@test] + public function resolved_against_imports_inside_namespace() { + $t= $this->type('use unittest\Test; <> class %s { }', 'com\\example'); + $this->assertAnnotations(['unittest.Test' => null], $t); + } + + #[@test] + public function lowercase_annotations_are_not_resolved() { + $t= $this->type('<> class %s { }', 'com\\example'); + $this->assertAnnotations(['test' => null], $t); + } + + #[@test] + public function with_value() { + $t= $this->type('<> class %s { }'); + $this->assertAnnotations(['Author' => 'Test'], $t); + } + + #[@test] + public function with_values() { + $t= $this->type('<> class %s { }'); + $this->assertAnnotations(['Product' => ['PHP', '8.0.0']], $t); + } +} From c39ca90a682a382929d15cb331cbed38d944bc5a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:17:33 +0200 Subject: [PATCH 05/15] Use PHP class names inside annotations so ::class works --- src/main/php/lang/XPClass.class.php | 2 +- .../unittest/reflection/ClassLoaderTest.class.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 361b67c433..c5a42b9bde 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -662,7 +662,7 @@ public static function mergeAttributes(&$details, $reflect) { if (false === $p) { $details[$name]= $value; } else if ($name[$p + 1] <= 'Z') { - $details[strtr($name, '\\', '.')]= $value; + $details[$name]= $value; } else { $details[substr($name, $p + 1)]= $value; } diff --git a/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php index 479904c9f2..5500d3726c 100755 --- a/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php @@ -196,14 +196,14 @@ public function doesNotProvideAPackage() { $this->assertFalse($this->libraryLoader->providesPackage('net.xp_frame')); } - #[@test] + #[@test, @ignore] public function doesNotProvideClassone() { $this->assertFalse(ClassLoader::getDefault() ->providesClass('net.xp_framework.unittest.reflection.classes.Classone') ); } - #[@test, @expect(ClassNotFoundException::class)] + #[@test, @ignore, @expect(ClassNotFoundException::class)] public function loadingClassoneFails() { ClassLoader::getDefault() ->loadClass('net.xp_framework.unittest.reflection.classes.Classone') From b9a04b6c61cc4bc7682ecdec1d7324403c1674b4 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:21:30 +0200 Subject: [PATCH 06/15] Simplify code inside XPClass --- src/main/php/lang/XPClass.class.php | 10 +++++++--- .../unittest/annotations/AttributesTest.class.php | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index c5a42b9bde..a3929d9026 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -644,6 +644,12 @@ protected static function _classLoaderFor($name) { return null; // Internal class, e.g. } + /** + * Merge attributes + * + * @param &[:var] $details + * @param php.ReflectionClass|php.ReflectionMethod|php.ReflectionProperty $reflect + */ public static function mergeAttributes(&$details, $reflect) { if (!self::$ATTRIBUTES) return; @@ -659,9 +665,7 @@ public static function mergeAttributes(&$details, $reflect) { $name= $attribute->getName(); $p= strrpos($name, '\\'); - if (false === $p) { - $details[$name]= $value; - } else if ($name[$p + 1] <= 'Z') { + if (false === $p || $name[$p + 1] <= 'Z') { $details[$name]= $value; } else { $details[substr($name, $p + 1)]= $value; diff --git a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php index 6925600931..a0994e7fa1 100755 --- a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php +++ b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php @@ -106,13 +106,13 @@ public function no_parameter_annotations() { #[@test] public function inside_namespace() { $t= $this->type('<> class %s { }', 'com\\example'); - $this->assertAnnotations(['com.example.Test' => null], $t); + $this->assertAnnotations(['com\\example\\Test' => null], $t); } #[@test] public function resolved_against_imports_inside_namespace() { - $t= $this->type('use unittest\Test; <> class %s { }', 'com\\example'); - $this->assertAnnotations(['unittest.Test' => null], $t); + $t= $this->type('use unittest\\Test; <> class %s { }', 'com\\example'); + $this->assertAnnotations(['unittest\\Test' => null], $t); } #[@test] From 8e8447dc8461f7df696c84a5d1025c0697e08d82 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:22:35 +0200 Subject: [PATCH 07/15] Revert accidental commit --- .../unittest/reflection/ClassLoaderTest.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php index 5500d3726c..479904c9f2 100755 --- a/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ClassLoaderTest.class.php @@ -196,14 +196,14 @@ public function doesNotProvideAPackage() { $this->assertFalse($this->libraryLoader->providesPackage('net.xp_frame')); } - #[@test, @ignore] + #[@test] public function doesNotProvideClassone() { $this->assertFalse(ClassLoader::getDefault() ->providesClass('net.xp_framework.unittest.reflection.classes.Classone') ); } - #[@test, @ignore, @expect(ClassNotFoundException::class)] + #[@test, @expect(ClassNotFoundException::class)] public function loadingClassoneFails() { ClassLoader::getDefault() ->loadClass('net.xp_framework.unittest.reflection.classes.Classone') From 5d5f0ec35525fdd6045a4c38649d3079f52437b7 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:46:57 +0200 Subject: [PATCH 08/15] Make XPClass implementation consistent --- src/main/php/lang/XPClass.class.php | 84 ++++++++----------- src/main/php/lang/reflect/Field.class.php | 28 ++++--- src/main/php/lang/reflect/Parameter.class.php | 20 ++--- src/main/php/lang/reflect/Routine.class.php | 28 ++++--- 4 files changed, 76 insertions(+), 84 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index a3929d9026..5f53b6ea4e 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -577,12 +577,14 @@ public function getModifiers(): int { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $details= self::detailsForClass($this->reflect()); - - return $details && ($key - ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) - : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) - ); + $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + self::mergeAttributes($r, $this->_reflect); + + if ($key) { + return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]); + } else { + return array_key_exists($name, $r); + } } /** @@ -594,24 +596,24 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $details= self::detailsForClass($this->reflect()); - if (!$details || !($key - ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) - : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) - )) { - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); + $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + self::mergeAttributes($r, $this->_reflect); + + if ($key) { + if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key]; + } else { + if (array_key_exists($name, $r)) return $r[$name]; } - return ($key - ? $details['class'][DETAIL_ANNOTATIONS][$name][$key] - : $details['class'][DETAIL_ANNOTATIONS][$name] - ); + throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); } /** Retrieve whether a method has annotations */ public function hasAnnotations(): bool { - $details= self::detailsForClass($this->reflect()); - return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : false; + $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + self::mergeAttributes($r, $this->_reflect); + + return !empty($r); } /** @@ -620,8 +622,10 @@ public function hasAnnotations(): bool { * @return array annotations */ public function getAnnotations() { - $details= self::detailsForClass($this->reflect()); - return $details ? $details['class'][DETAIL_ANNOTATIONS] : []; + $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + self::mergeAttributes($r, $this->_reflect); + + return $r; } /** Retrieve the class loader a class was loaded with */ @@ -688,16 +692,10 @@ public static function detailsForClass(\ReflectionClass $class) { // Retrieve class' sourcecode $cl= self::_classLoaderFor($name); - if (!$cl || !($bytes= $cl->loadClassBytes($name))) { - $r= []; - self::mergeAttributes($r, $class); - return ['class' => [DETAIL_ANNOTATIONS => $r]]; - } + if (!$cl || !($bytes= $cl->loadClassBytes($name))) return []; $parser ?? $parser= new \lang\reflect\ClassParser(); - $details= $parser->parseDetails($bytes, $name); - self::mergeAttributes($details['class'][DETAIL_ANNOTATIONS], $class); - return \xp::$meta[$name]= $details; + return \xp::$meta[$name]= $parser->parseDetails($bytes, $name); } /** @@ -710,21 +708,14 @@ public static function detailsForClass(\ReflectionClass $class) { */ public static function detailsForMethod($class, $method) { $details= self::detailsForClass($class); - if (isset($details[1][$method])) { - self::mergeAttributes($details[1][$method][DETAIL_ANNOTATIONS], $class->getMethod($method)); - return $details[1][$method]; - } + if (isset($details[1][$method])) return $details[1][$method]; + foreach ($class->getTraits() as $trait) { $details= self::detailsForClass($trait); - if (isset($details[1][$method])) { - self::mergeAttributes($details[1][$method][DETAIL_ANNOTATIONS], $class->getMethod($method)); - return $details[1][$method]; - } + if (isset($details[1][$method])) return $details[1][$method]; } - $r= []; - self::mergeAttributes($r, $class->getMethod($method)); - return [DETAIL_ANNOTATIONS => $r]; + return []; } /** @@ -737,21 +728,14 @@ public static function detailsForMethod($class, $method) { */ public static function detailsForField($class, $field) { $details= self::detailsForClass($class); - if (isset($details[0][$field])) { - self::mergeAttributes($details[1][$field][DETAIL_ANNOTATIONS], $class->getProperty($field)); - return $details[0][$field]; - } + if (isset($details[0][$field])) return $details[0][$field]; + foreach ($class->getTraits() as $trait) { $details= self::detailsForClass($trait); - if (isset($details[0][$field])) { - self::mergeAttributes($details[1][$field][DETAIL_ANNOTATIONS], $class->getProperty($field)); - return $details[0][$field]; - } + if (isset($details[0][$field])) return $details[0][$field]; } - $r= []; - self::mergeAttributes($r, $class->getProperty($field)); - return [DETAIL_ANNOTATIONS => $r]; + return []; } /** diff --git a/src/main/php/lang/reflect/Field.class.php b/src/main/php/lang/reflect/Field.class.php index 45205915d3..01614e0167 100755 --- a/src/main/php/lang/reflect/Field.class.php +++ b/src/main/php/lang/reflect/Field.class.php @@ -90,12 +90,13 @@ public function getTypeName(): string { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - return is_array($a) && array_key_exists($key, $a); + return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]); } else { - return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); + return array_key_exists($name, $r); } } @@ -108,12 +109,13 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; + if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key]; } else { - if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; + if (array_key_exists($name, $r)) return $r[$name]; } throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); @@ -121,8 +123,9 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether this field has annotations */ public function hasAnnotations(): bool { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? !empty($details[DETAIL_ANNOTATIONS]) : false; + $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return !empty($r); } /** @@ -131,8 +134,9 @@ public function hasAnnotations(): bool { * @return [:var] annotations */ public function getAnnotations() { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? $details[DETAIL_ANNOTATIONS] : []; + $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return $r; } /** diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index 3542bd8c2d..60ab495c9e 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -193,11 +193,14 @@ public function getDefaultValue() { * @return bool */ public function hasAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); - return $key ? array_key_exists($key, $r[$name] ?? []) : array_key_exists($name, $r); + if ($key) { + return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]); + } else { + return array_key_exists($name, $r); + } } /** @@ -209,12 +212,11 @@ public function hasAnnotation($name, $key= null) { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { - if (array_key_exists($key, $r[$name] ?? [])) return $r[$name][$key]; + if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key]; } else { if (array_key_exists($name, $r)) return $r[$name]; } @@ -228,8 +230,7 @@ public function getAnnotation($name, $key= null) { * @return bool */ public function hasAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return !empty($r); } @@ -240,8 +241,7 @@ public function hasAnnotations() { * @return var[] annotations */ public function getAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return $r; } diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php index 6d9324da23..b78df543b2 100755 --- a/src/main/php/lang/reflect/Routine.class.php +++ b/src/main/php/lang/reflect/Routine.class.php @@ -259,12 +259,13 @@ public function getComment() { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - return is_array($a) && array_key_exists($key, $a); + return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]); } else { - return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); + return array_key_exists($name, $r); } } @@ -277,12 +278,13 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; + if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key]; } else { - if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; + if (array_key_exists($name, $r)) return $r[$name]; } throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); @@ -290,8 +292,9 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether a method has annotations */ public function hasAnnotations(): bool { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? !empty($details[DETAIL_ANNOTATIONS]) : false; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return !empty($r); } /** @@ -300,8 +303,9 @@ public function hasAnnotations(): bool { * @return [:var] annotations */ public function getAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? $details[DETAIL_ANNOTATIONS] : []; + $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + XPClass::mergeAttributes($r, $this->_reflect); + return $r; } /** From d6a7d58bec3c81952ce471d6d5d906964a49612b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 24 May 2020 18:50:15 +0200 Subject: [PATCH 09/15] Fix segfault with PHP 7.4 --- src/main/php/lang/XPClass.class.php | 12 ++++++++---- src/main/php/lang/reflect/Field.class.php | 12 ++++++++---- src/main/php/lang/reflect/Parameter.class.php | 12 ++++++++---- src/main/php/lang/reflect/Routine.class.php | 12 ++++++++---- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 5f53b6ea4e..7e1a1577d3 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -577,7 +577,8 @@ public function getModifiers(): int { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + $details= self::detailsForClass($this->reflect()); + $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); if ($key) { @@ -596,7 +597,8 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + $details= self::detailsForClass($this->reflect()); + $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); if ($key) { @@ -610,7 +612,8 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether a method has annotations */ public function hasAnnotations(): bool { - $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + $details= self::detailsForClass($this->reflect()); + $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); return !empty($r); @@ -622,7 +625,8 @@ public function hasAnnotations(): bool { * @return array annotations */ public function getAnnotations() { - $r= self::detailsForClass($this->reflect())['class'][DETAIL_ANNOTATIONS] ?? []; + $details= self::detailsForClass($this->reflect()); + $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); return $r; diff --git a/src/main/php/lang/reflect/Field.class.php b/src/main/php/lang/reflect/Field.class.php index 01614e0167..114c2d819b 100755 --- a/src/main/php/lang/reflect/Field.class.php +++ b/src/main/php/lang/reflect/Field.class.php @@ -90,7 +90,8 @@ public function getTypeName(): string { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -109,7 +110,8 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -123,7 +125,8 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether this field has annotations */ public function hasAnnotations(): bool { - $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return !empty($r); } @@ -134,7 +137,8 @@ public function hasAnnotations(): bool { * @return [:var] annotations */ public function getAnnotations() { - $r= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return $r; } diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index 60ab495c9e..c64b1ba925 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -193,7 +193,8 @@ public function getDefaultValue() { * @return bool */ public function hasAnnotation($name, $key= null) { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -212,7 +213,8 @@ public function hasAnnotation($name, $key= null) { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -230,7 +232,8 @@ public function getAnnotation($name, $key= null) { * @return bool */ public function hasAnnotations() { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return !empty($r); } @@ -241,7 +244,8 @@ public function hasAnnotations() { * @return var[] annotations */ public function getAnnotations() { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return $r; } diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php index b78df543b2..4b9c652a3a 100755 --- a/src/main/php/lang/reflect/Routine.class.php +++ b/src/main/php/lang/reflect/Routine.class.php @@ -259,7 +259,8 @@ public function getComment() { * @return bool */ public function hasAnnotation($name, $key= null): bool { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -278,7 +279,8 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); if ($key) { @@ -292,7 +294,8 @@ public function getAnnotation($name, $key= null) { /** Retrieve whether a method has annotations */ public function hasAnnotations(): bool { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return !empty($r); } @@ -303,7 +306,8 @@ public function hasAnnotations(): bool { * @return [:var] annotations */ public function getAnnotations() { - $r= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName())[DETAIL_ANNOTATIONS] ?? []; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); + $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); return $r; } From cffa8088496de364922a0235968b951e9e49be9b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 31 May 2020 16:24:33 +0200 Subject: [PATCH 10/15] Merge PHP attributes w/o value with XP annotations --- src/main/php/lang/XPClass.class.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 7e1a1577d3..f7c1e3ca64 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -671,13 +671,11 @@ public static function mergeAttributes(&$details, $reflect) { $value= $args; } + // Only resolve uppercase attributes $name= $attribute->getName(); $p= strrpos($name, '\\'); - if (false === $p || $name[$p + 1] <= 'Z') { - $details[$name]= $value; - } else { - $details[substr($name, $p + 1)]= $value; - } + $l= false === $p || $name[$p + 1] <= 'Z' ? $name : substr($name, $p + 1); + $details[$l] ?? $details[$l]= $value; } } From 4e363a03f13822060eccdcdd7cee1f37b96eee47 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 31 May 2020 16:25:12 +0200 Subject: [PATCH 11/15] Parse inline value notation inside PHP attributes See https://github.com/xp-framework/core/pull/243#issuecomment-636453872 --- .../php/lang/reflect/ClassParser.class.php | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index f92cfbd3ed..f1a7a62d1f 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -151,7 +151,11 @@ protected function valueOf($tokens, &$i, $context, $imports) { } else if (T_FN === $token || T_STRING === $token && 'fn' === $tokens[$i][1]) { $s= sizeof($tokens); $b= 0; - $code= 'function'; + $code= ''; + foreach ($imports as $name => $qualified) { + $code.= 'use '.strtr($qualified, '.', '\\').' as '.$name.';'; + } + $code.= 'return function'; for ($i++; $i < $s; $i++) { if ('(' === $tokens[$i]) { $b++; @@ -191,10 +195,10 @@ protected function valueOf($tokens, &$i, $context, $imports) { } } $i--; - $code.= '; }'; + $code.= '; };'; try { - $func= eval('return '.$code.';'); + $func= eval($code); } catch (\ParseError $e) { throw new IllegalStateException('In `'.$code.'`: '.$e->getMessage()); } @@ -238,7 +242,11 @@ protected function valueOf($tokens, &$i, $context, $imports) { return $class->newInstance(...$args); } else if (T_FUNCTION === $token) { $b= 0; - $code= 'function'; + $code= ''; + foreach ($imports as $name => $qualified) { + $code.= 'use '.strtr($qualified, '.', '\\').' as '.$name.';'; + } + $code.= 'return function'; for ($i++, $s= sizeof($tokens); $i < $s; $i++) { if ('{' === $tokens[$i]) { $b++; @@ -252,7 +260,7 @@ protected function valueOf($tokens, &$i, $context, $imports) { } } try { - $func= eval('return '.$code.';'); + $func= eval($code.';'); } catch (\ParseError $e) { throw new IllegalStateException('In `'.$code.'`: '.$e->getMessage()); } @@ -285,7 +293,7 @@ protected function valueOf($tokens, &$i, $context, $imports) { * @return [:var] * @throws lang.ClassFormatException */ - public function parseAnnotations($bytes, $context, $imports= [], $line= -1) { + public function parseAnnotations($bytes, $context, $imports= [], $line= -1, $state= 0) { static $states= [ 'annotation', 'annotation name', 'annotation value', 'annotation map key', 'annotation map value', @@ -298,7 +306,7 @@ public function parseAnnotations($bytes, $context, $imports= [], $line= -1) { // Parse tokens try { - for ($state= 0, $i= 1, $s= sizeof($tokens); $i < $s; $i++) { + for ($i= 1, $s= sizeof($tokens); $i < $s; $i++) { if (T_WHITESPACE === $tokens[$i][0]) { continue; } else if (0 === $state) { // Initial state, expecting @attr or @$param: attr @@ -483,9 +491,38 @@ public function parseDetails($bytes, $context= '') { $comment= $tokens[$i][1]; break; + case T_SL: + if (T_NS_SEPARATOR === $tokens[$i + 1][0]) { + $attribute= ''; + } else { + $attribute= $tokens[++$i][1]; + } + while (T_NS_SEPARATOR === $tokens[++$i][0]) { + $attribute.= '\\'.$tokens[++$i][1]; + } + + if ('\\' === $attribute[0]) { + $attribute= substr($attribute, 1); + } else if (isset($imports[$attribute])) { + $attribute= strtr($imports[$attribute], '.', '\\'); + } else if ($namespace) { + $attribute= $namespace.'\\'.$attribute; + } + $parsed= ''; + break; + + case T_SR: + if ('' !== $parsed) { + $j= 1; + $annotations[0][$attribute]= $this->valueOf(token_get_all(' Date: Sun, 31 May 2020 16:25:46 +0200 Subject: [PATCH 12/15] Add test for inline notation in PHP attributes See https://github.com/xp-framework/core/pull/243#issuecomment-636453872 --- .../annotations/AttributesTest.class.php | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php index a0994e7fa1..f72f86d3a4 100755 --- a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php +++ b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php @@ -1,11 +1,17 @@ =8.0.0-dev'))] class AttributesTest extends TestCase { + private $cl; + + /** @return void */ + public function setUp() { + $this->cl= DynamicClassLoader::instanceFor(self::class); + } /** * Declares a type from source @@ -19,11 +25,12 @@ private function type($source, $namespace= null) { $name= '__A'.(++$u); if (null === $namespace) { - eval(sprintf($source, $name)); - return new XPClass($name); + $this->cl->setClassBytes($name, sprintf($source, $name)); + return $this->cl->loadClass($name); } else { - eval('namespace '.$namespace.';'.sprintf($source, $name)); - return new XPClass($namespace.'\\'.$name); + $qualified= $namespace.'.'.$name; + $this->cl->setClassBytes($qualified, 'namespace '.$namespace.';'.sprintf($source, $name)); + return $this->cl->loadClass($qualified); } } @@ -115,6 +122,18 @@ public function resolved_against_imports_inside_namespace() { $this->assertAnnotations(['unittest\\Test' => null], $t); } + #[@test] + public function qualified() { + $t= $this->type('<> class %s { }', 'unittest'); + $this->assertAnnotations(['unittest\\annotations\\Test' => null], $t); + } + + #[@test] + public function fully_qualified() { + $t= $this->type('<<\\unittest\\annotations\\Test>> class %s { }', 'com\\example'); + $this->assertAnnotations(['unittest\\annotations\\Test' => null], $t); + } + #[@test] public function lowercase_annotations_are_not_resolved() { $t= $this->type('<> class %s { }', 'com\\example'); @@ -132,4 +151,39 @@ public function with_values() { $t= $this->type('<> class %s { }'); $this->assertAnnotations(['Product' => ['PHP', '8.0.0']], $t); } + + #[@test, @values([ + # 'Values', + # 'unittest\\Values', + # '\\unittest\\Values', + #])] + public function with_non_static_value_inline($annotation) { + $t= $this->type(' + use net\\xp_framework\\unittest\\annotations\\Name; + use unittest\\{Test, Values, Expect}; + use lang\\ElementNotFoundException; + + class %s { + + <> + <<'.$annotation.'( + #[ + # new Name("A"), + # new Name("B"), + #] + )>> + < $e instanceof ElementNotFoundException && strstr($e->getMessage(), "fail"); + )>> + public function fixture($value) { + // TBI + } + } + '); + + $m= $t->getMethod('fixture'); + $this->assertNull($m->getAnnotation('unittest\\Test')); + $this->assertEquals([new Name('A'), new Name('B')], $m->getAnnotation('unittest\\Values')); + $this->assertTrue($m->getAnnotation('unittest\\Expect')(new ElementNotFoundException('Test failed'))); + } } From c1ff73418c298f9ce3ecb51f0f81d69aa1859177 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 31 May 2020 16:28:59 +0200 Subject: [PATCH 13/15] Allow dotted notation inside hasAnnotation() / getAnnotation() --- src/main/php/lang/XPClass.class.php | 2 ++ src/main/php/lang/reflect/Field.class.php | 2 ++ src/main/php/lang/reflect/Parameter.class.php | 2 ++ src/main/php/lang/reflect/Routine.class.php | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index f7c1e3ca64..3e7e47965c 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -577,6 +577,7 @@ public function getModifiers(): int { * @return bool */ public function hasAnnotation($name, $key= null): bool { + $name= strtr($name, '.', '\\'); $details= self::detailsForClass($this->reflect()); $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); @@ -597,6 +598,7 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { + $name= strtr($name, '.', '\\'); $details= self::detailsForClass($this->reflect()); $r= $details['class'][DETAIL_ANNOTATIONS] ?? []; self::mergeAttributes($r, $this->_reflect); diff --git a/src/main/php/lang/reflect/Field.class.php b/src/main/php/lang/reflect/Field.class.php index 114c2d819b..54ec89b03b 100755 --- a/src/main/php/lang/reflect/Field.class.php +++ b/src/main/php/lang/reflect/Field.class.php @@ -90,6 +90,7 @@ public function getTypeName(): string { * @return bool */ public function hasAnnotation($name, $key= null): bool { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); @@ -110,6 +111,7 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index c64b1ba925..4352496737 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -193,6 +193,7 @@ public function getDefaultValue() { * @return bool */ public function hasAnnotation($name, $key= null) { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); @@ -213,6 +214,7 @@ public function hasAnnotation($name, $key= null) { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; XPClass::mergeAttributes($r, $this->_reflect); diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php index 4b9c652a3a..24fa90d798 100755 --- a/src/main/php/lang/reflect/Routine.class.php +++ b/src/main/php/lang/reflect/Routine.class.php @@ -259,6 +259,7 @@ public function getComment() { * @return bool */ public function hasAnnotation($name, $key= null): bool { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); @@ -279,6 +280,7 @@ public function hasAnnotation($name, $key= null): bool { * @throws lang.ElementNotFoundException */ public function getAnnotation($name, $key= null) { + $name= strtr($name, '.', '\\'); $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); $r= $details[DETAIL_ANNOTATIONS] ?? []; XPClass::mergeAttributes($r, $this->_reflect); From 9a5276c1dc0225f8d74944ed3a6ce0d8ba32a42f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 31 May 2020 16:29:12 +0200 Subject: [PATCH 14/15] Use dotted notation --- .../unittest/annotations/AttributesTest.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php index f72f86d3a4..6616b6bf42 100755 --- a/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php +++ b/src/test/php/net/xp_framework/unittest/annotations/AttributesTest.class.php @@ -182,8 +182,8 @@ public function fixture($value) { '); $m= $t->getMethod('fixture'); - $this->assertNull($m->getAnnotation('unittest\\Test')); - $this->assertEquals([new Name('A'), new Name('B')], $m->getAnnotation('unittest\\Values')); - $this->assertTrue($m->getAnnotation('unittest\\Expect')(new ElementNotFoundException('Test failed'))); + $this->assertNull($m->getAnnotation('unittest.Test')); + $this->assertEquals([new Name('A'), new Name('B')], $m->getAnnotation('unittest.Values')); + $this->assertTrue($m->getAnnotation('unittest.Expect')(new ElementNotFoundException('Test failed'))); } } From 9943bcc66aa9650f4f56f7809ea46b0d0242cbb6 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 31 May 2020 16:57:31 +0200 Subject: [PATCH 15/15] Revert adding optional state argument to parseAnnotations() --- src/main/php/lang/reflect/ClassParser.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index f1a7a62d1f..625b6ae6f4 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -293,7 +293,7 @@ protected function valueOf($tokens, &$i, $context, $imports) { * @return [:var] * @throws lang.ClassFormatException */ - public function parseAnnotations($bytes, $context, $imports= [], $line= -1, $state= 0) { + public function parseAnnotations($bytes, $context, $imports= [], $line= -1) { static $states= [ 'annotation', 'annotation name', 'annotation value', 'annotation map key', 'annotation map value', @@ -306,7 +306,7 @@ public function parseAnnotations($bytes, $context, $imports= [], $line= -1, $sta // Parse tokens try { - for ($i= 1, $s= sizeof($tokens); $i < $s; $i++) { + for ($state= 0, $i= 1, $s= sizeof($tokens); $i < $s; $i++) { if (T_WHITESPACE === $tokens[$i][0]) { continue; } else if (0 === $state) { // Initial state, expecting @attr or @$param: attr