From cf3ddd06fa620c13281d2bc556f5cf6727bfcc12 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:30:02 +0200 Subject: [PATCH 01/38] Add fix for typeof() raising warnings in PHP 7.4 --- ChangeLog.md | 4 ++++ src/main/php/lang.base.php | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0eb5ed31ff..fed2246b8e 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,10 @@ XP Framework Core ChangeLog ## ?.?.? / ????-??-?? +### Bugfixes + +* Added fix for typeof() raising warnings in PHP 7.4 - @thekid + ## 9.8.2 / 2019-01-01 ### Bugfixes diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index ef880191c1..e7bddbfea7 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -378,9 +378,18 @@ function typeof($arg) { $signature= []; foreach ($r->getParameters() as $param) { if ($param->isVariadic()) break; - $signature[]= \lang\Type::forName((string)$param->getType() ?: 'var'); + if ($t= $param->getType()) { + $signature[]= \lang\Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); + } else { + $signature[]= \lang\Type::$VAR; + } + } + if ($t= $r->getReturnType()) { + $return= \lang\Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); + } else { + $return= \lang\Type::$VAR; } - return new \lang\FunctionType($signature, \lang\Type::forName((string)$r->getReturnType() ?: 'var')); + return new \lang\FunctionType($signature, $return); } else if (is_object($arg)) { return new \lang\XPClass($arg); } else if (is_array($arg)) { From 47f6e38d55ae72c66d5dee079f91fbc35a681e21 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:39:38 +0200 Subject: [PATCH 02/38] Backport defensive code for all PHP 7.0 - 8.0 versions --- src/main/php/lang/reflect/Field.class.php | 27 ++++++------- src/main/php/lang/reflect/Parameter.class.php | 39 +++++++------------ src/main/php/lang/reflect/Routine.class.php | 32 +++++++-------- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/main/php/lang/reflect/Field.class.php b/src/main/php/lang/reflect/Field.class.php index c65389ead2..3549b76c44 100755 --- a/src/main/php/lang/reflect/Field.class.php +++ b/src/main/php/lang/reflect/Field.class.php @@ -110,11 +110,12 @@ public function getTypeName(): string { */ public function hasAnnotation($name, $key= null): bool { $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - - return $details && ($key - ? array_key_exists($key, (array)@$details[DETAIL_ANNOTATIONS][$name]) - : array_key_exists($name, (array)@$details[DETAIL_ANNOTATIONS]) - ); + if ($key) { + $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; + return is_array($a) && array_key_exists($key, $a); + } else { + return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); + } } /** @@ -127,18 +128,14 @@ public function hasAnnotation($name, $key= null): bool { */ public function getAnnotation($name, $key= null) { $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - - if (!$details || !($key - ? array_key_exists($key, @$details[DETAIL_ANNOTATIONS][$name]) - : array_key_exists($name, @$details[DETAIL_ANNOTATIONS]) - )) { - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); + if ($key) { + $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; + if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; + } else { + if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; } - return ($key - ? $details[DETAIL_ANNOTATIONS][$name][$key] - : $details[DETAIL_ANNOTATIONS][$name] - ); + throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); } /** Retrieve whether this field has annotations */ diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index bcd465f47d..44e52b6a56 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -69,7 +69,7 @@ public function getType() { // this the other way around is that we have "richer" information, e.g. "string[]", // where PHP simply knows about "arrays" (of whatever). if ($t= $this->_reflect->getType()) { - return Type::forName((string)$t); + return Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return Type::$VAR; } @@ -103,7 +103,7 @@ public function getTypeName() { } if ($t= $this->_reflect->getType()) { - return str_replace('HH\\', '', $t); + return str_replace('HH\\', '', PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return 'var'; } @@ -188,17 +188,13 @@ public function getDefaultValue() { */ public function hasAnnotation($name, $key= null) { $n= '$'.$this->_reflect->getName(); - if ( - !($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])) || - !isset($details[DETAIL_TARGET_ANNO][$n]) - ) { // Unknown or unparseable - return false; + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + if ($key) { + $a= $details[DETAIL_TARGET_ANNO][$n][$name] ?? null; + return is_array($a) && array_key_exists($key, $a); + } else { + return array_key_exists($name, $details[DETAIL_TARGET_ANNO][$n] ?? []); } - - return $details && ($key - ? array_key_exists($key, (array)@$details[DETAIL_TARGET_ANNO][$n][$name]) - : array_key_exists($name, (array)@$details[DETAIL_TARGET_ANNO][$n]) - ); } /** @@ -211,20 +207,15 @@ public function hasAnnotation($name, $key= null) { */ public function getAnnotation($name, $key= null) { $n= '$'.$this->_reflect->getName(); - if ( - !($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])) || - !isset($details[DETAIL_TARGET_ANNO][$n]) || !($key - ? array_key_exists($key, (array)@$details[DETAIL_TARGET_ANNO][$n][$name]) - : array_key_exists($name, (array)@$details[DETAIL_TARGET_ANNO][$n]) - ) - ) { - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); + $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); + if ($key) { + $a= $details[DETAIL_TARGET_ANNO][$n][$name] ?? null; + if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; + } else { + if (array_key_exists($name, $details[DETAIL_TARGET_ANNO][$n] ?? [])) return $details[DETAIL_TARGET_ANNO][$n][$name]; } - return ($key - ? $details[DETAIL_TARGET_ANNO][$n][$name][$key] - : $details[DETAIL_TARGET_ANNO][$n][$name] - ); + throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); } /** diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php index b3d9fce33c..de1a94d610 100755 --- a/src/main/php/lang/reflect/Routine.class.php +++ b/src/main/php/lang/reflect/Routine.class.php @@ -123,7 +123,7 @@ public function getReturnType(): Type { return Type::forName($t); } } else if ($t= $this->_reflect->getReturnType()) { - return Type::forName((string)$t); + return Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return Type::$VAR; } @@ -137,7 +137,7 @@ public function getReturnTypeName(): string { ) { return ltrim($details[DETAIL_RETURNS], '&'); } else if ($t= $this->_reflect->getReturnType()) { - return str_replace('HH\\', '', $t); + return str_replace('HH\\', '', PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else if (defined('HHVM_VERSION')) { return str_replace('HH\\', '', $this->_reflect->getReturnTypeText() ?: 'var'); } else { @@ -191,7 +191,7 @@ public function getComment() { if (!($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()))) return null; return $details[DETAIL_COMMENT]; } - + /** * Check whether an annotation exists * @@ -201,11 +201,12 @@ public function getComment() { */ public function hasAnnotation($name, $key= null): bool { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - - return $details && ($key - ? array_key_exists($key, (array)@$details[DETAIL_ANNOTATIONS][$name]) - : array_key_exists($name, (array)@$details[DETAIL_ANNOTATIONS]) - ); + if ($key) { + $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; + return is_array($a) && array_key_exists($key, $a); + } else { + return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); + } } /** @@ -218,17 +219,14 @@ public function hasAnnotation($name, $key= null): bool { */ public function getAnnotation($name, $key= null) { $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if (!$details || !($key - ? array_key_exists($key, @$details[DETAIL_ANNOTATIONS][$name]) - : array_key_exists($name, @$details[DETAIL_ANNOTATIONS]) - )) { - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); + if ($key) { + $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; + if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; + } else { + if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; } - return ($key - ? $details[DETAIL_ANNOTATIONS][$name][$key] - : $details[DETAIL_ANNOTATIONS][$name] - ); + throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); } /** Retrieve whether a method has annotations */ From 666e36434f6f11e8f1feb0d301fa410877047a7a Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:41:29 +0200 Subject: [PATCH 03/38] Backport PHP 7.4 handling casting of objects --- .../xp_framework/unittest/core/ErrorsTest.class.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php b/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php index a29aedfa97..7e00e2c862 100644 --- a/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php +++ b/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php @@ -9,8 +9,8 @@ Value, XPException }; -use unittest\actions\RuntimeVersion; use net\xp_framework\unittest\IgnoredOnHHVM; +use unittest\actions\RuntimeVersion; /** * Test the XP error handling semantics @@ -129,12 +129,18 @@ public function missing_argument_mismatch_yield_error() { $f(); } - #[@test, @expect(ClassCastException::class)] + #[@test, @expect(ClassCastException::class), @action(new RuntimeVersion('<7.4.0-dev'))] public function cannot_convert_object_to_string_yields_cce() { $object= new class() { }; $object.'String'; } + #[@test, @expect(Error::class), @action(new RuntimeVersion('>=7.4.0'))] + public function cannot_convert_object_to_string_yields_error() { + $object= new class() { }; + $object.'String'; + } + #[@test, @expect(ClassCastException::class)] public function cannot_convert_array_to_string_yields_cce() { $array= []; From bfc5916d1449075739949111a6796941a6a1bd43 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:44:26 +0200 Subject: [PATCH 04/38] Remove HHVM, add PHP 7.4 --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ad25ab027..f878a71625 100755 --- a/.travis.yml +++ b/.travis.yml @@ -7,17 +7,14 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot - nightly - - hhvm - - hhvm-nightly matrix: allow_failures: - - php: hhvm - - php: hhvm-nightly + - php: nightly before_script: - - if [[ $TRAVIS_PHP_VERSION = "hhvm"* ]]; then echo hhvm.hack_compiler_default=false > /etc/hhvm/php.ini; fi - sh .travis.sh install script: From 51d00dcad7dc3f0972988ca8e6a0ff7e314e9477 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:45:38 +0200 Subject: [PATCH 05/38] Document fix for reflection code raising warnings in PHP 7.4 [skip ci] --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index fed2246b8e..f1ef213842 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ XP Framework Core ChangeLog ### Bugfixes +* Added fix for reflection code raising warnings in PHP 7.4 - @thekid * Added fix for typeof() raising warnings in PHP 7.4 - @thekid ## 9.8.2 / 2019-01-01 From 7de362c3965ff4eff8b0e747e05d8ee91f330875 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:48:35 +0200 Subject: [PATCH 06/38] Backport rewriting error suppression operator --- src/main/php/io/Path.class.php | 3 ++- src/main/php/lang/FileSystemClassLoader.class.php | 4 +++- src/main/php/lang/XPClass.class.php | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/php/io/Path.class.php b/src/main/php/io/Path.class.php index e941954792..494b335fda 100755 --- a/src/main/php/io/Path.class.php +++ b/src/main/php/io/Path.class.php @@ -198,8 +198,9 @@ private static function real0($path, $wd) { } else { $normalized.= DIRECTORY_SEPARATOR.$component; if ($check) { - $stat= @lstat($normalized); + $stat= lstat($normalized); if (false === $stat) { + \xp::gc(__FILE__); $check= false; } else if (0120000 === ($stat[2] & 0120000)) { $normalized= readlink($normalized); diff --git a/src/main/php/lang/FileSystemClassLoader.class.php b/src/main/php/lang/FileSystemClassLoader.class.php index 98ca7fa640..574f8f0b13 100755 --- a/src/main/php/lang/FileSystemClassLoader.class.php +++ b/src/main/php/lang/FileSystemClassLoader.class.php @@ -153,7 +153,9 @@ public static function instanceFor($path, $expand= true) { */ public function packageContents($package) { $contents= []; - if ($d= @dir($this->path.strtr($package, '.', DIRECTORY_SEPARATOR))) { + $path= $this->path.strtr($package, '.', DIRECTORY_SEPARATOR); + if (is_dir($path)) { + $d= dir($path); while ($e= $d->read()) { if ('.' != $e{0}) $contents[]= $e.(is_dir($d->path.DIRECTORY_SEPARATOR.$e) ? '/' : ''); } diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 72c9adbc48..11d38f9c66 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -587,8 +587,8 @@ public function hasAnnotation($name, $key= null): bool { $details= self::detailsForClass($this->name); return $details && ($key - ? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) - : @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) + ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) + : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) ); } @@ -603,8 +603,8 @@ public function hasAnnotation($name, $key= null): bool { public function getAnnotation($name, $key= null) { $details= self::detailsForClass($this->name); if (!$details || !($key - ? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) - : @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) + ? 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'); } From 84ee3874cd4499b594bc8afa0955c0f6b0128615 Mon Sep 17 00:00:00 2001 From: "U-UNITED\\friebe" Date: Fri, 14 Jun 2019 19:53:57 +0200 Subject: [PATCH 07/38] Prepare 9.8.3 --- ChangeLog.md | 2 ++ README.md | 4 ++-- src/main/resources/VERSION | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f1ef213842..24b47599f6 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ XP Framework Core ChangeLog ## ?.?.? / ????-??-?? +## 9.8.3 / 2019-06-14 + ### Bugfixes * Added fix for reflection code raising warnings in PHP 7.4 - @thekid diff --git a/README.md b/README.md index b33fe8fba9..b6b70777fa 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Finally, start `xp -v` to see it working: ```sh $ xp -v -XP 9.8.3-dev { PHP 7.3.0 & ZE 3.3.0 } @ Windows NT SLATE 10.0 build 17134 (Windows 10) AMD64 +XP 9.8.3 { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 Copyright (c) 2001-2019 the XP group FileSystemCL<./src/main/php> FileSystemCL<./src/test/php> @@ -52,7 +52,7 @@ The XP Framework runs scripts or classes. Save the following sourcecode to a file called `ageindays.script.php`: ```php - Date: Fri, 14 Jun 2019 19:58:52 +0200 Subject: [PATCH 08/38] Bump versions after release --- README.md | 2 +- src/main/resources/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6b70777fa..f19a9d8e4a 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Finally, start `xp -v` to see it working: ```sh $ xp -v -XP 9.8.3 { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 +XP 9.8.4-dev { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 Copyright (c) 2001-2019 the XP group FileSystemCL<./src/main/php> FileSystemCL<./src/test/php> diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 37f8c696b7..2a53cc7b07 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.8.3 \ No newline at end of file +9.8.4-dev \ No newline at end of file From 479193ccab431559511f799c4245a2d2716e8b67 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 29 Jul 2019 21:45:24 +0200 Subject: [PATCH 09/38] Prevent "include(): lang\DynamicClassLoader::stream_set_option is not implemented!" warnings --- src/main/php/lang/DynamicClassLoader.class.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/php/lang/DynamicClassLoader.class.php b/src/main/php/lang/DynamicClassLoader.class.php index d7fafb47a4..1393347a28 100755 --- a/src/main/php/lang/DynamicClassLoader.class.php +++ b/src/main/php/lang/DynamicClassLoader.class.php @@ -253,7 +253,19 @@ public function stream_flush() { public function stream_close() { return true; } - + + /** + * Stream wrapper method stream_set_option + * + * @param int $option + * @param int $arg1 + * @param int $arg2 + * @return bool + */ + public function stream_set_option($option, $arg1, $arg2) { + return true; + } + /** * Stream wrapper method url_stat * From bbd46692c42e5413d5e6734ccc48007125805491 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Thu, 8 Aug 2019 15:16:10 +0200 Subject: [PATCH 10/38] Implement stream_set_option() --- src/main/php/xp/runtime/Script.class.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/php/xp/runtime/Script.class.php b/src/main/php/xp/runtime/Script.class.php index 5401dde0e2..fd2b46d931 100755 --- a/src/main/php/xp/runtime/Script.class.php +++ b/src/main/php/xp/runtime/Script.class.php @@ -49,4 +49,9 @@ public function stream_eof() { public function stream_close() { // NOOP } + + /** @return void */ + public function stream_set_option($option, $arg1, $arg2) { + // NOOP + } } \ No newline at end of file From 2a004889270457d99a61640d4c85749801d57e25 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 28 Jul 2019 11:51:52 +0200 Subject: [PATCH 11/38] Rewrite all other occurrences using curly braces for offsets See https://github.com/xp-framework/core/issues/220 --- src/main/php/io/Path.class.php | 10 +++++----- .../php/io/streams/GzCompressingOutputStream.class.php | 2 +- src/main/php/lang.base.php | 8 ++++---- src/main/php/util/Bytes.class.php | 2 +- src/main/php/util/Properties.class.php | 6 +++--- src/main/php/util/Random.class.php | 2 +- src/main/php/util/TimeSpan.class.php | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/php/io/Path.class.php b/src/main/php/io/Path.class.php index e941954792..8553799245 100755 --- a/src/main/php/io/Path.class.php +++ b/src/main/php/io/Path.class.php @@ -115,7 +115,7 @@ public function isEmpty() { return '' === $this->path; } /** Tests whether this path is absolute, e.g. `/usr` or `C:\Windows` */ public function isAbsolute(): bool { return '' !== $this->path && ( - DIRECTORY_SEPARATOR === $this->path{0} || + DIRECTORY_SEPARATOR === $this->path[0] || 2 === sscanf($this->path, '%c%[:]', $drive, $colon) ); } @@ -130,12 +130,12 @@ private static function norm0($path) { $l= strlen($path); if (0 === $l) { return ''; - } else if (DIRECTORY_SEPARATOR === $path{0}) { + } else if (DIRECTORY_SEPARATOR === $path[0]) { $components= explode(DIRECTORY_SEPARATOR, substr($path, 1)); $base= DIRECTORY_SEPARATOR; - } else if ($l > 1 && ':' === $path{1}) { + } else if ($l > 1 && ':' === $path[1]) { $components= explode(DIRECTORY_SEPARATOR, substr($path, 3)); - $base= strtoupper($path{0}).':'.DIRECTORY_SEPARATOR; + $base= strtoupper($path[0]).':'.DIRECTORY_SEPARATOR; } else { $components= explode(DIRECTORY_SEPARATOR, $path); $base= null; @@ -176,7 +176,7 @@ private static function norm0($path) { * @return string */ private static function real0($path, $wd) { - if (DIRECTORY_SEPARATOR === $path{0}) { + if (DIRECTORY_SEPARATOR === $path[0]) { $normalized= ''; $components= explode(DIRECTORY_SEPARATOR, substr($path, 1)); } else if (2 === sscanf($path, '%c%*[:]', $drive)) { diff --git a/src/main/php/io/streams/GzCompressingOutputStream.class.php b/src/main/php/io/streams/GzCompressingOutputStream.class.php index 279a8824b2..13e112663e 100755 --- a/src/main/php/io/streams/GzCompressingOutputStream.class.php +++ b/src/main/php/io/streams/GzCompressingOutputStream.class.php @@ -81,7 +81,7 @@ public function close() { // Write GZIP footer: // * CRC32 (CRC-32 checksum) // * ISIZE (Input size) - fwrite($this->out, pack('aaaaV', $final{3}, $final{2}, $final{1}, $final{0}, $this->length)); + fwrite($this->out, pack('aaaaV', $final[3], $final[2], $final[1], $final[0], $this->length)); fclose($this->out); $this->out= null; $this->md= null; diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index ef880191c1..c8d513b5e0 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -40,7 +40,7 @@ function loadClass0($class) { // We rely on paths having been expanded including a trailing directory separator // character inside bootstrap(). This way, we can save testing for whether the path // entry is a directory with file system stat() calls. - if (DIRECTORY_SEPARATOR === $path{strlen($path) - 1}) { + if (DIRECTORY_SEPARATOR === $path[strlen($path) - 1]) { $f= $path.strtr($class, '.', DIRECTORY_SEPARATOR).xp::CLASS_FILE_EXT; $cl= 'lang.FileSystemClassLoader'; } else { @@ -230,12 +230,12 @@ function literal($type) { } else if (false !== ($p= strpos($type, '<'))) { $l= literal(substr($type, 0, $p))."\xb7\xb7"; for ($args= substr($type, $p+ 1, -1).',', $o= 0, $brackets= 0, $i= 0, $s= strlen($args); $i < $s; $i++) { - if (',' === $args{$i} && 0 === $brackets) { + if (',' === $args[$i] && 0 === $brackets) { $l.= strtr(literal(ltrim(substr($args, $o, $i- $o)))."\xb8", '\\', "\xa6"); $o= $i+ 1; - } else if ('<' === $args{$i}) { + } else if ('<' === $args[$i]) { $brackets++; - } else if ('>' === $args{$i}) { + } else if ('>' === $args[$i]) { $brackets--; } } diff --git a/src/main/php/util/Bytes.class.php b/src/main/php/util/Bytes.class.php index 3981fec0ee..7c802a6ba6 100755 --- a/src/main/php/util/Bytes.class.php +++ b/src/main/php/util/Bytes.class.php @@ -17,7 +17,7 @@ class Bytes implements \lang\Value, \ArrayAccess, \IteratorAggregate { * @return string */ private function asByte($in) { - return is_int($in) ? chr($in) : $in{0}; + return is_int($in) ? chr($in) : $in[0]; } /** diff --git a/src/main/php/util/Properties.class.php b/src/main/php/util/Properties.class.php index 1bf5e7afd0..002b697df3 100755 --- a/src/main/php/util/Properties.class.php +++ b/src/main/php/util/Properties.class.php @@ -77,7 +77,7 @@ public function load($in, $charset= null): self { while (null !== ($t= $reader->readLine())) { $trimmedToken= trim($t); if ('' === $trimmedToken) continue; // Empty lines - $c= $trimmedToken{0}; + $c= $trimmedToken[0]; if (';' === $c || '#' === $c) { // One line comments continue; } else if ('[' === $c) { @@ -91,7 +91,7 @@ public function load($in, $charset= null): self { $value= ltrim(substr($t, $p+ 1)); if ('' === $value) { // OK - } else if ('"' === $value{0}) { // Quoted strings + } else if ('"' === $value[0]) { // Quoted strings $quoted= substr($value, 1); while (false === ($p= strrpos($quoted, '"'))) { if (null === ($line= $reader->readLine())) break; @@ -150,7 +150,7 @@ public function store(OutputStream $out) { foreach (array_keys($this->_data) as $section) { $out->write('['.$section."]\n"); foreach ($this->_data[$section] as $key => $val) { - if (';' == $key{0}) { + if (';' == $key[0]) { $out->write("\n; ".$val."\n"); } else if (is_array($val)) { if (empty($val)) { diff --git a/src/main/php/util/Random.class.php b/src/main/php/util/Random.class.php index 2bdd1a3b74..24eb95f614 100755 --- a/src/main/php/util/Random.class.php +++ b/src/main/php/util/Random.class.php @@ -148,7 +148,7 @@ private function random($min, $max) { do { for ($random= $this->bytes($bytes), $result= 0, $i= 0; $i < $bytes; $i++) { - $result |= ord($random{$i}) << ($i * 8); + $result |= ord($random[$i]) << ($i * 8); } // Wrap around if negative diff --git a/src/main/php/util/TimeSpan.class.php b/src/main/php/util/TimeSpan.class.php index 6fb2ffaefb..c594294f22 100755 --- a/src/main/php/util/TimeSpan.class.php +++ b/src/main/php/util/TimeSpan.class.php @@ -250,7 +250,7 @@ public function format($format) { while (false !== ($p= strcspn($format, '%', $o))) { $return.= substr($format, $o, $p); if (($o+= $p+ 2) <= $l) { - switch ($format{$o- 1}) { + switch ($format[$o - 1]) { case 's': $return.= $this->getSeconds(); break; From 432794ba876017f1d12f6ca8c3e441ab46320ad6 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 28 Jul 2019 11:45:13 +0200 Subject: [PATCH 12/38] Rewrite all occurrences in lang namespace using curly braces for offsets See https://github.com/xp-framework/core/issues/220 --- src/main/php/lang.base.php | 2 +- src/main/php/lang/ClassLoader.class.php | 10 +++++----- src/main/php/lang/CommandLine.class.php | 8 ++++---- .../php/lang/FileSystemClassLoader.class.php | 4 ++-- src/main/php/lang/GenericTypes.class.php | 2 +- src/main/php/lang/Primitive.class.php | 4 ++-- src/main/php/lang/Process.class.php | 4 ++-- src/main/php/lang/Runtime.class.php | 6 +++--- src/main/php/lang/RuntimeOptions.class.php | 6 +++--- src/main/php/lang/Type.class.php | 16 ++++++++-------- .../lang/archive/ArchiveClassLoader.class.php | 2 +- src/main/php/lang/reflect/ClassParser.class.php | 8 ++++---- src/main/php/xp/runtime/Code.class.php | 2 +- 13 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index c8d513b5e0..02ebbc1fde 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -276,7 +276,7 @@ function with(... $args) { function newinstance($spec, $args, $def= null) { static $u= 0; - if ('#' === $spec{0}) { + if ('#' === $spec[0]) { $p= strrpos($spec, ' '); $annotations= substr($spec, 0, $p).' '; $spec= substr($spec, $p+ 1); diff --git a/src/main/php/lang/ClassLoader.class.php b/src/main/php/lang/ClassLoader.class.php index 5707aba3f8..3aebf665a6 100755 --- a/src/main/php/lang/ClassLoader.class.php +++ b/src/main/php/lang/ClassLoader.class.php @@ -48,7 +48,7 @@ static function __static() { // Scan include-path, setting up classloaders for each element foreach (\xp::$classpath as $element) { - if (DIRECTORY_SEPARATOR === $element{strlen($element) - 1}) { + if (DIRECTORY_SEPARATOR === $element[strlen($element) - 1]) { $cl= FileSystemClassLoader::instanceFor($element, false); } else { $cl= ArchiveClassLoader::instanceFor($element, false); @@ -85,7 +85,7 @@ public static function getDefault() { * @throws lang.ElementNotFoundException if the path cannot be found */ public static function registerPath($element, $before= false) { - if (null === $before && '!' === $element{0}) { + if (null === $before && '!' === $element[0]) { $before= true; $element= substr($element, 1); } else { @@ -200,7 +200,7 @@ public static function getLoaders() { protected static function classLiteral($class) { if ($class instanceof XPClass) { return '\\'.$class->literal(); - } else if ('\\' === $class{0}) { + } else if ('\\' === $class[0]) { return $class; } else { return '\\'.XPClass::forName((string)$class)->literal(); @@ -266,7 +266,7 @@ protected static function defineForward($name, $func, $invoke) { * @return lang.XPClass */ public static function defineType($spec, $declaration, $def) { - if ('#' === $spec{0}) { + if ('#' === $spec[0]) { $p= strrpos($spec, ' '); $typeAnnotations= substr($spec, 0, $p)."\n"; $spec= substr($spec, $p+ 1); @@ -281,7 +281,7 @@ public static function defineType($spec, $declaration, $def) { $iface= 'interface' === $declaration['kind']; $bytes= ''; foreach ($def as $name => $member) { - if ('#' === $name{0}) { + if ('#' === $name[0]) { $p= strrpos($name, ' '); $memberAnnotations= substr($name, 0, $p)."\n"; $name= substr($name, $p+ 1); diff --git a/src/main/php/lang/CommandLine.class.php b/src/main/php/lang/CommandLine.class.php index c2cceeba8e..3b554edd94 100755 --- a/src/main/php/lang/CommandLine.class.php +++ b/src/main/php/lang/CommandLine.class.php @@ -34,10 +34,10 @@ public function parse($cmd) { $parts= []; $r= ''; for ($i= 0, $s= strlen($cmd); $i < $s; $i++) { - if (' ' === $cmd{$i}) { + if (' ' === $cmd[$i]) { $parts[]= $r; $r= ''; - } else if ('"' === $cmd{$i}) { + } else if ('"' === $cmd[$i]) { $q= $i+ 1; do { if (false === ($p= strpos($cmd, '"', $q))) { @@ -58,7 +58,7 @@ public function parse($cmd) { $r.= str_replace($triple, '"', substr($cmd, $i+ 1, $q- $i- 1)); $i= $q; } else { - $r.= $cmd{$i}; + $r.= $cmd[$i]; } } $parts[]= $r; @@ -101,7 +101,7 @@ public function parse($cmd) { } else { $o+= $p+ 1; } - if ('"' === $option{0} || "'" === $option{0}) $option= substr($option, 1, -1); + if ('"' === $option[0] || "'" === $option[0]) $option= substr($option, 1, -1); $parts[]= $option; } return $parts; diff --git a/src/main/php/lang/FileSystemClassLoader.class.php b/src/main/php/lang/FileSystemClassLoader.class.php index 98ca7fa640..60ad18f0e1 100755 --- a/src/main/php/lang/FileSystemClassLoader.class.php +++ b/src/main/php/lang/FileSystemClassLoader.class.php @@ -82,7 +82,7 @@ protected function classAtUri($uri) { if (0 !== substr_compare($uri, \xp::CLASS_FILE_EXT, -strlen(\xp::CLASS_FILE_EXT))) return null; // Resolve path if not absolute - if ((DIRECTORY_SEPARATOR === $uri{0} || (':' === $uri{1} && '\\' === $uri{2}))) { + if ((DIRECTORY_SEPARATOR === $uri[0] || (':' === $uri[1] && '\\' === $uri[2]))) { $absolute= realpath($uri); } else { $absolute= realpath($this->path.DIRECTORY_SEPARATOR.$uri); @@ -155,7 +155,7 @@ public function packageContents($package) { $contents= []; if ($d= @dir($this->path.strtr($package, '.', DIRECTORY_SEPARATOR))) { while ($e= $d->read()) { - if ('.' != $e{0}) $contents[]= $e.(is_dir($d->path.DIRECTORY_SEPARATOR.$e) ? '/' : ''); + if ('.' != $e[0]) $contents[]= $e.(is_dir($d->path.DIRECTORY_SEPARATOR.$e) ? '/' : ''); } $d->close(); } diff --git a/src/main/php/lang/GenericTypes.class.php b/src/main/php/lang/GenericTypes.class.php index 8f5057875f..78f447770c 100755 --- a/src/main/php/lang/GenericTypes.class.php +++ b/src/main/php/lang/GenericTypes.class.php @@ -298,7 +298,7 @@ public function newType0($base, $arguments) { $i++; } $i--; - '\\' === $rel{0} || $rel= isset($imports[$rel]) ? $imports[$rel] : $namespace.'\\'.$rel; + '\\' === $rel[0] || $rel= isset($imports[$rel]) ? $imports[$rel] : $namespace.'\\'.$rel; if (isset($annotation[$counter])) { $iargs= []; foreach (explode(',', $annotation[$counter]) as $j => $placeholder) { diff --git a/src/main/php/lang/Primitive.class.php b/src/main/php/lang/Primitive.class.php index 6ce51f15b6..b59a379711 100755 --- a/src/main/php/lang/Primitive.class.php +++ b/src/main/php/lang/Primitive.class.php @@ -69,9 +69,9 @@ protected function coerce($value, $default) { case self::$INT: if (strlen($value) <= 1) { return (int)$value; - } else if ('x' === $value{1}) { + } else if ('x' === $value[1]) { return hexdec($value); - } else if ('0' === $value{0}) { + } else if ('0' === $value[0]) { return octdec($value); } else { return (int)$value; diff --git a/src/main/php/lang/Process.class.php b/src/main/php/lang/Process.class.php index 69e19b4be0..b83e7de632 100755 --- a/src/main/php/lang/Process.class.php +++ b/src/main/php/lang/Process.class.php @@ -122,8 +122,8 @@ public static function resolve(string $command): string { // If the command is in fully qualified form and refers to a file // that does not exist (e.g. "C:\DoesNotExist.exe", "\DoesNotExist.com" // or /usr/bin/doesnotexist), do not attempt to search for it. - if ((DIRECTORY_SEPARATOR === $command{0}) || ((strncasecmp(PHP_OS, 'Win', 3) === 0) && - strlen($command) > 1 && (':' === $command{1} || '/' === $command{0}) + if ((DIRECTORY_SEPARATOR === $command[0]) || ((strncasecmp(PHP_OS, 'Win', 3) === 0) && + strlen($command) > 1 && (':' === $command[1] || '/' === $command[0]) )) { foreach ($extensions as $ext) { $q= $command.$ext; diff --git a/src/main/php/lang/Runtime.class.php b/src/main/php/lang/Runtime.class.php index 7b3621477d..c141285f3d 100755 --- a/src/main/php/lang/Runtime.class.php +++ b/src/main/php/lang/Runtime.class.php @@ -153,18 +153,18 @@ public static function parseArguments($arguments) { while (null !== ($argument= array_shift($arguments))) { if ('' === $argument) { continue; - } else if ('-' !== $argument{0}) { + } else if ('-' !== $argument[0]) { $return['bootstrap']= trim($argument, '"\'');; break; } else if ('--' === $argument) { $return['bootstrap']= trim(array_shift($arguments), '"\''); break; } - switch ($argument{1}) { + switch ($argument[1]) { case 'q': // quiet case 'n': // No php.ini file will be used case 'C': { // [cgi] Do not chdir to the script's directory - $return['options']->withSwitch($argument{1}); + $return['options']->withSwitch($argument[1]); break; } diff --git a/src/main/php/lang/RuntimeOptions.class.php b/src/main/php/lang/RuntimeOptions.class.php index 148f27c457..806e3fe1ab 100755 --- a/src/main/php/lang/RuntimeOptions.class.php +++ b/src/main/php/lang/RuntimeOptions.class.php @@ -110,11 +110,11 @@ public function getClassPath() { public function asArguments() { $s= defined('HHVM_VERSION') ? ['--php'] : []; foreach ($this->backing as $key => $value) { - if ("\1" === $key{0}) { + if ("\1" === $key[0]) { $s[]= '-'.substr($key, 1); - } else if ("\0" !== $key{0}) { + } else if ("\0" !== $key[0]) { foreach ($value as $v) { - $s[]= '-'.$key{0}; + $s[]= '-'.$key[0]; $s[]= substr($key, 1).'='.$v; } } diff --git a/src/main/php/lang/Type.class.php b/src/main/php/lang/Type.class.php index 34911e815a..7eedf3d2d7 100755 --- a/src/main/php/lang/Type.class.php +++ b/src/main/php/lang/Type.class.php @@ -137,7 +137,7 @@ public static function forNames($names) { */ private static function matching(&$string, $chars, $offset= 0) { for ($b= 1, $o= $offset, $s= 1, $l= strlen($string); $b && (($o+= $s) < $l); $o++, $s= strcspn($string, $chars, $o)) { - if ($chars{0} === $string{$o}) $b++; else if ($chars{1} === $string{$o}) $b--; + if ($chars[0] === $string[$o]) $b++; else if ($chars[1] === $string[$o]) $b--; } if (0 === $b) { @@ -158,7 +158,7 @@ private static function matching(&$string, $chars, $offset= 0) { */ private static function split($string, $char) { for ($i= 0, $l= strlen($string); $i < $l; $i++) { - if ('(' === $string{$i}) { + if ('(' === $string[$i]) { yield self::matching($string, '()', $i); $i= 0; $l= strlen($string); @@ -166,7 +166,7 @@ private static function split($string, $char) { $s= strcspn($string, $char.'<>', $i); $t= trim(substr($string, $i, $s)); $n= $i + $s; - if ($n < $l && '<' === $string{$n}) { + if ($n < $l && '<' === $string[$n]) { yield $t.'<'.self::matching($string, '<>', $n).'>'; $i= 0; $l= strlen($string); @@ -233,7 +233,7 @@ public static function forName($type) { return new TypeUnion([Primitive::$INT, Primitive::$FLOAT]); } else if ('HH\arraykey' === $type) { return new TypeUnion([Primitive::$INT, Primitive::$STRING]); - } else if ('?' === $type{0} || '@' === $type{0}) { + } else if ('?' === $type[0] || '@' === $type[0]) { return self::forName(substr($type, 1)); } @@ -246,7 +246,7 @@ public static function forName($type) { $p= strcspn($type, '<|[*('); if ($p >= $l) { return XPClass::forName($type); - } else if ('(' === $type{0}) { + } else if ('(' === $type[0]) { $t= self::forName(self::matching($type, '()', 0)); } else if (0 === substr_compare($type, '[:', 0, 2)) { $t= new MapType(self::forName(self::matching($type, '[]', 1))); @@ -266,7 +266,7 @@ public static function forName($type) { $type= ''; } $t= new FunctionType($args, $return); - } else if ('<' === $type{$p}) { + } else if ('<' === $type[$p]) { $base= substr($type, 0, $p); $components= []; $wildcard= false; @@ -294,10 +294,10 @@ public static function forName($type) { // Suffixes and unions `T[]` is an array, `T*` is a vararg, `A|B|C` is a union while ($type) { - if ('*' === $type{0}) { + if ('*' === $type[0]) { $t= new ArrayType($t); $type= trim(substr($type, 1)); - } else if ('|' === $type{0}) { + } else if ('|' === $type[0]) { $components= [$t]; foreach (self::split(substr($type, 1), '|') as $arg) { $components[]= self::forName($arg); diff --git a/src/main/php/lang/archive/ArchiveClassLoader.class.php b/src/main/php/lang/archive/ArchiveClassLoader.class.php index e3fcd0310c..44b390eb6f 100755 --- a/src/main/php/lang/archive/ArchiveClassLoader.class.php +++ b/src/main/php/lang/archive/ArchiveClassLoader.class.php @@ -71,7 +71,7 @@ protected function classAtUri($uri) { if (0 !== substr_compare($uri, \xp::CLASS_FILE_EXT, -strlen(\xp::CLASS_FILE_EXT))) return null; // Absolute URIs have the form "xar://containing.xar?the/classes/Name.class.php" - if ((DIRECTORY_SEPARATOR === $uri{0} || (':' === $uri{1} && '\\' === $uri{2}))) { + if ((DIRECTORY_SEPARATOR === $uri[0] || (':' === $uri[1] && '\\' === $uri[2]))) { return null; } else if (false !== ($p= strpos($uri, '?'))) { $archive= substr($uri, 0, $p + 1); diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index 9e5661163c..3428d39765 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -313,9 +313,9 @@ public function parseAnnotations($bytes, $context, $imports= [], $line= -1) { */ protected static function matching($text, $open, $close) { for ($braces= $open.$close, $i= 0, $b= 0, $s= strlen($text); $i < $s; $i+= strcspn($text, $braces, $i)) { - if ($text{$i} === $open) { + if ($text[$i] === $open) { $b++; - } else if ($text{$i} === $close) { + } else if ($text[$i] === $close) { if (0 === --$b) return $i + 1; } $i++; @@ -338,7 +338,7 @@ public static function typeIn($text, $imports) { } else if (0 === strncmp($text, '(function(', 10)) { $p= self::matching($text, '(', ')'); return substr($text, 0, $p).self::typeIn(substr($text, $p), $imports); - } else if ('[' === $text{0}) { + } else if ('[' === $text[0]) { $p= self::matching($text, '[', ']'); return substr($text, 0, $p); } else if (strstr($text, '<')) { @@ -348,7 +348,7 @@ public static function typeIn($text, $imports) { $type= substr($text, 0, strcspn($text, ' ')); } - if ('\\' === $type{0}) { + if ('\\' === $type[0]) { return strtr(substr($type, 1), '\\', '.'); } else if (isset($imports[$type])) { return $imports[$type]; diff --git a/src/main/php/xp/runtime/Code.class.php b/src/main/php/xp/runtime/Code.class.php index 5f9becfa93..0f277d4963 100755 --- a/src/main/php/xp/runtime/Code.class.php +++ b/src/main/php/xp/runtime/Code.class.php @@ -117,7 +117,7 @@ private function importsIn($use) { $name= strrpos($use, '\\') + 1; $used= []; - if ('{' === $use{$name}) { + if ('{' === $use[$name]) { $namespace= substr($use, 0, $name); foreach (explode(',', substr($use, $name + 1, -1)) as $type) { $used[$namespace.trim($type)]= $module; From c40ec0c9ef0b0a61b1ba2e01c6646482b31b7095 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 28 Jul 2019 14:00:45 +0200 Subject: [PATCH 13/38] Fix classLiteral() raising warnings when given empty class names See https://github.com/xp-framework/core/issues/220#issuecomment-515756477 --- src/main/php/lang/ClassLoader.class.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/php/lang/ClassLoader.class.php b/src/main/php/lang/ClassLoader.class.php index 3aebf665a6..1645ac5784 100755 --- a/src/main/php/lang/ClassLoader.class.php +++ b/src/main/php/lang/ClassLoader.class.php @@ -200,10 +200,15 @@ public static function getLoaders() { protected static function classLiteral($class) { if ($class instanceof XPClass) { return '\\'.$class->literal(); - } else if ('\\' === $class[0]) { + } + + $name= (string)$class; + if ('' === $name) { + throw new ClassNotFoundException('Empty class name given'); + } else if ('\\' === $name[0]) { return $class; } else { - return '\\'.XPClass::forName((string)$class)->literal(); + return '\\'.XPClass::forName($name)->literal(); } } From f60fc3c41d19c0366cf183d4d85b55340e77434d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 8 Mar 2019 12:36:19 +0100 Subject: [PATCH 14/38] Remove third & optional "nullsafe" argument to cast(), replaced by nullable types --- src/main/php/lang.base.php | 5 +++-- .../net/xp_framework/unittest/core/CastingTest.class.php | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index 02ebbc1fde..1fc9152831 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -163,8 +163,9 @@ function __error($code, $msg, $file, $line) { // {{{ proto var cast (var arg, var type) // Casts an arg NULL-safe -function cast($arg, $type, $nullsafe= true) { - if (null === $arg && $nullsafe && 0 !== strncmp($type, '?', 1)) { +function cast($arg, $type) { + if (null === $arg) { + if (0 === strncmp($type, '?', 1)) return null; throw new \lang\ClassCastException('Cannot cast NULL to '.$type); } else if ($type instanceof \lang\Type) { return $type->cast($arg); diff --git a/src/test/php/net/xp_framework/unittest/core/CastingTest.class.php b/src/test/php/net/xp_framework/unittest/core/CastingTest.class.php index 091651b013..dd0c725c3c 100644 --- a/src/test/php/net/xp_framework/unittest/core/CastingTest.class.php +++ b/src/test/php/net/xp_framework/unittest/core/CastingTest.class.php @@ -1,7 +1,7 @@ run(); } - /** @deprecated See https://github.com/xp-framework/rfc/issues/326 */ - #[@test] - public function passing_null_allowed_when_nullsafe_set_to_false() { - $this->assertNull(cast(null, Value::class, false)); - } - #[@test] public function thisClass() { $this->assertTrue($this === cast($this, typeof($this))); From 3bd2e244b820646008ea481c4029a9fb4ec7040d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 11 Jun 2019 20:10:55 +0200 Subject: [PATCH 15/38] Refrain from using deprecated ReflectionType::__toString See https://www.php.net/manual/en/reflectiontype.tostring.php Use __toString() explicitely for PHP 7.0, which does not have getName() --- src/main/php/lang.base.php | 13 +++++++++++-- src/main/php/lang/reflect/Parameter.class.php | 4 ++-- src/main/php/lang/reflect/Routine.class.php | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index 1fc9152831..c8a09dc046 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -379,9 +379,18 @@ function typeof($arg) { $signature= []; foreach ($r->getParameters() as $param) { if ($param->isVariadic()) break; - $signature[]= \lang\Type::forName((string)$param->getType() ?: 'var'); + if ($t= $param->getType()) { + $signature[]= \lang\Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); + } else { + $signature[]= \lang\Type::$VAR; + } + } + if ($t= $r->getReturnType()) { + $return= \lang\Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); + } else { + $return= \lang\Type::$VAR; } - return new \lang\FunctionType($signature, \lang\Type::forName((string)$r->getReturnType() ?: 'var')); + return new \lang\FunctionType($signature, $return); } else if (is_object($arg)) { return new \lang\XPClass($arg); } else if (is_array($arg)) { diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php index bcd465f47d..ed2c18a2f6 100755 --- a/src/main/php/lang/reflect/Parameter.class.php +++ b/src/main/php/lang/reflect/Parameter.class.php @@ -69,7 +69,7 @@ public function getType() { // this the other way around is that we have "richer" information, e.g. "string[]", // where PHP simply knows about "arrays" (of whatever). if ($t= $this->_reflect->getType()) { - return Type::forName((string)$t); + return Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return Type::$VAR; } @@ -103,7 +103,7 @@ public function getTypeName() { } if ($t= $this->_reflect->getType()) { - return str_replace('HH\\', '', $t); + return str_replace('HH\\', '', PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return 'var'; } diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php index b3d9fce33c..f62ddbcc39 100755 --- a/src/main/php/lang/reflect/Routine.class.php +++ b/src/main/php/lang/reflect/Routine.class.php @@ -123,7 +123,7 @@ public function getReturnType(): Type { return Type::forName($t); } } else if ($t= $this->_reflect->getReturnType()) { - return Type::forName((string)$t); + return Type::forName(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else { return Type::$VAR; } @@ -137,7 +137,7 @@ public function getReturnTypeName(): string { ) { return ltrim($details[DETAIL_RETURNS], '&'); } else if ($t= $this->_reflect->getReturnType()) { - return str_replace('HH\\', '', $t); + return str_replace('HH\\', '', PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } else if (defined('HHVM_VERSION')) { return str_replace('HH\\', '', $this->_reflect->getReturnTypeText() ?: 'var'); } else { From eb1be2aca224369513c73d0dd45edb9970437831 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 28 Jul 2019 13:56:02 +0200 Subject: [PATCH 16/38] Fix "Trying to access array offset on value of type null" See https://github.com/xp-framework/core/issues/220#issuecomment-515756477 --- src/main/php/lang/Primitive.class.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/php/lang/Primitive.class.php b/src/main/php/lang/Primitive.class.php index b59a379711..520f5b8730 100755 --- a/src/main/php/lang/Primitive.class.php +++ b/src/main/php/lang/Primitive.class.php @@ -67,15 +67,16 @@ protected function coerce($value, $default) { case self::$FLOAT: return (float)$value; case self::$BOOL: return (bool)$value; case self::$INT: - if (strlen($value) <= 1) { - return (int)$value; - } else if ('x' === $value[1]) { - return hexdec($value); - } else if ('0' === $value[0]) { - return octdec($value); - } else { - return (int)$value; + if (is_string($value)) { + if (strlen($value) <= 1) { + return (int)$value; + } else if ('x' === $value[1]) { + return hexdec($value); + } else if ('0' === $value[0]) { + return octdec($value); + } } + return (int)$value; } return $default($value); From 11b67903eb336f4c67e457d38d7fff714836eabc Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 11 Jun 2019 20:19:57 +0200 Subject: [PATCH 17/38] Restore compatibility with latest snapshot of PHP 7.4 --- .../net/xp_framework/unittest/core/ErrorsTest.class.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php b/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php index a29aedfa97..54bd469002 100644 --- a/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php +++ b/src/test/php/net/xp_framework/unittest/core/ErrorsTest.class.php @@ -129,12 +129,18 @@ public function missing_argument_mismatch_yield_error() { $f(); } - #[@test, @expect(ClassCastException::class)] + #[@test, @expect(ClassCastException::class), @action(new RuntimeVersion('<7.4.0-dev'))] public function cannot_convert_object_to_string_yields_cce() { $object= new class() { }; $object.'String'; } + #[@test, @expect(Error::class), @action(new RuntimeVersion('>=7.4.0-dev'))] + public function cannot_convert_object_to_string_yields_error() { + $object= new class() { }; + $object.'String'; + } + #[@test, @expect(ClassCastException::class)] public function cannot_convert_array_to_string_yields_cce() { $array= []; From 545b9d2091ca87b3fdb5440179eb1bf0117f68b0 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 11:03:02 +0200 Subject: [PATCH 18/38] Use XP runners compatible with PHP 7.4 --- .travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.sh b/.travis.sh index c63ddc2d5d..2e66442a2f 100755 --- a/.travis.sh +++ b/.travis.sh @@ -1,6 +1,6 @@ #!/bin/sh -XP_RUNNERS_URL=https://dl.bintray.com/xp-runners/generic/xp-run-master.sh +XP_RUNNERS_URL=https://dl.bintray.com/xp-runners/generic/xp-run-8.1.7.sh case $1 in install) From a19b8a1e8ec8e211fd72446d0eb912d5133bc959 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Mon, 29 Jul 2019 22:54:47 +0200 Subject: [PATCH 19/38] Fix more occurrences of curly braces used for array offsets ...which is deprecated and raises warnings in PHP 7.4+, see https://travis-ci.org/xp-framework/core/jobs/565153155 --- src/main/php/io/Folder.class.php | 2 +- src/main/php/util/Bytes.class.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/php/io/Folder.class.php b/src/main/php/io/Folder.class.php index edefd17424..95f764d042 100755 --- a/src/main/php/io/Folder.class.php +++ b/src/main/php/io/Folder.class.php @@ -75,7 +75,7 @@ public function setURI($uri) { $i= 1; if ('' === $components[0]) { $this->uri= rtrim(realpath(DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR); - } else if ((strncasecmp(PHP_OS, 'Win', 3) === 0) && strlen($components[0]) > 1 && ':' === $components[0]{1}) { + } else if ((strncasecmp(PHP_OS, 'Win', 3) === 0) && strlen($components[0]) > 1 && ':' === $components[0][1]) { $this->uri= rtrim(realpath($components[0]), DIRECTORY_SEPARATOR); } else if ('..' === $components[0]) { $this->uri= rtrim(realpath('..'), DIRECTORY_SEPARATOR); diff --git a/src/main/php/util/Bytes.class.php b/src/main/php/util/Bytes.class.php index 7c802a6ba6..7e49b29de1 100755 --- a/src/main/php/util/Bytes.class.php +++ b/src/main/php/util/Bytes.class.php @@ -42,7 +42,7 @@ public function __construct($initial= null) { /** Returns an iterator for use in foreach() */ public function getIterator(): \Traversable { for ($offset= 0; $offset < $this->size; $offset++) { - $n= ord($this->buffer{$offset}); + $n= ord($this->buffer[$offset]); yield $n < 128 ? $n : $n - 256; } } @@ -58,7 +58,7 @@ public function offsetGet($offset) { if ($offset >= $this->size || $offset < 0) { throw new IndexOutOfBoundsException('Offset '.$offset.' out of bounds'); } - $n= ord($this->buffer{$offset}); + $n= ord($this->buffer[$offset]); return $n < 128 ? $n : $n - 256; } @@ -77,7 +77,7 @@ public function offsetSet($offset, $value) { } else if ($offset >= $this->size || $offset < 0) { throw new IndexOutOfBoundsException('Offset '.$offset.' out of bounds'); } else { - $this->buffer{$offset}= $this->asByte($value); + $this->buffer[$offset]= $this->asByte($value); } } From 8c14977c19f87db3614fcb45ecdd87c8e1f2aff0 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 16:38:13 +0200 Subject: [PATCH 20/38] Prepare 9.9.0-RELEASE --- ChangeLog.md | 11 +++++++++++ src/main/resources/VERSION | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 24b47599f6..722b720222 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,17 @@ XP Framework Core ChangeLog ## ?.?.? / ????-??-?? +## 9.9.0 / 2019-08-09 + +### Bugfixes + +* Backported from XP 10 various compatibility fixes with PHP 7.4: + - Fix "Trying to access array offset on value of type null" + - Refrain from using curly braces used for array offsets + - Refrain from using deprecated `ReflectionType::__toString` + Prevent "stream_set_option is not implemented!" warnings + (@thekid) + ## 9.8.3 / 2019-06-14 ### Bugfixes diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 2a53cc7b07..0683dd70ba 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.8.4-dev \ No newline at end of file +9.9.0 \ No newline at end of file From 40717b140eaeb09e471f5cc32195f5d2add4e8b2 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 16:40:56 +0200 Subject: [PATCH 21/38] Bump versions after release [skip ci] --- README.md | 2 +- src/main/resources/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f19a9d8e4a..eeb51eb60d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Finally, start `xp -v` to see it working: ```sh $ xp -v -XP 9.8.4-dev { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 +XP 9.9.1-dev { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 Copyright (c) 2001-2019 the XP group FileSystemCL<./src/main/php> FileSystemCL<./src/test/php> diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 0683dd70ba..a70ecbe7d9 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.0 \ No newline at end of file +9.9.1-dev \ No newline at end of file From 1c531d9d9f750ff137c7f1cd931e28df244ba4f9 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 16:48:27 +0200 Subject: [PATCH 22/38] Rewrite curly braces to square brackets --- src/main/php/io/streams/StringReader.class.php | 2 +- src/main/php/io/streams/TextReader.class.php | 2 +- src/main/php/io/sys/ShmSegment.class.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/php/io/streams/StringReader.class.php b/src/main/php/io/streams/StringReader.class.php index 30e6d7c53f..247655c20c 100755 --- a/src/main/php/io/streams/StringReader.class.php +++ b/src/main/php/io/streams/StringReader.class.php @@ -62,7 +62,7 @@ public function readLine() { continue; } - $o= "\r" === $this->buf{$p} && $p < $l - 1 && "\n" === $this->buf{$p + 1} ? 2 : 1; + $o= "\r" === $this->buf[$p] && $p < $l - 1 && "\n" === $this->buf[$p + 1] ? 2 : 1; $bytes= substr($this->buf, 0, $p); $this->buf= substr($this->buf, $p + $o); break; diff --git a/src/main/php/io/streams/TextReader.class.php b/src/main/php/io/streams/TextReader.class.php index 974149c523..df9db92725 100755 --- a/src/main/php/io/streams/TextReader.class.php +++ b/src/main/php/io/streams/TextReader.class.php @@ -129,7 +129,7 @@ public function readLine() { continue; } - $o= ("\r" === $this->buf{$p} && "\n" === $this->buf{$p + $this->cl}) ? $this->cl * 2 : $this->cl; + $o= ("\r" === $this->buf[$p] && "\n" === $this->buf[$p + $this->cl]) ? $this->cl * 2 : $this->cl; $p-= $this->of; $bytes= substr($this->buf, 0, $p); $this->buf= substr($this->buf, $p + $o); diff --git a/src/main/php/io/sys/ShmSegment.class.php b/src/main/php/io/sys/ShmSegment.class.php index 74410c3215..2b7b2cf95f 100755 --- a/src/main/php/io/sys/ShmSegment.class.php +++ b/src/main/php/io/sys/ShmSegment.class.php @@ -33,7 +33,7 @@ public function __construct($name) { $str= str_pad($name, 4, 'Z'); $this->spot= ''; for ($i= 0; $i < 4; $i++) { - $this->spot.= dechex(ord($str{$i})); + $this->spot.= dechex(ord($str[$i])); } $this->spot= hexdec('0x'.$this->spot); From 52c914fe7f3a4d6a843c7e3072875a94aae8ccdc Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 16:48:55 +0200 Subject: [PATCH 23/38] Re-release v9.9.0 --- src/main/resources/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index a70ecbe7d9..0683dd70ba 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.1-dev \ No newline at end of file +9.9.0 \ No newline at end of file From 9bce701a310a5d93cb80c02fc146d4b3b76616dc Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 9 Aug 2019 16:49:23 +0200 Subject: [PATCH 24/38] Bump versions after release [skip ci] --- src/main/resources/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 0683dd70ba..a70ecbe7d9 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.0 \ No newline at end of file +9.9.1-dev \ No newline at end of file From c05d97fdcf30a39c58a94c40a6116a4ecf269f6d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 20 Aug 2019 01:13:59 +0200 Subject: [PATCH 25/38] Fix "Function ReflectionType::__toString() is deprecated" warnings --- src/main/php/lang/ClassLoader.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/php/lang/ClassLoader.class.php b/src/main/php/lang/ClassLoader.class.php index 1645ac5784..caf9b9106d 100755 --- a/src/main/php/lang/ClassLoader.class.php +++ b/src/main/php/lang/ClassLoader.class.php @@ -230,7 +230,7 @@ protected static function defineForward($name, $func, $invoke) { } else if ($t->isBuiltin()) { $constraint= (string)$t; } else { - $constraint= '\\'.(string)$t; + $constraint= '\\'.(PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString()); } if ($param->isVariadic()) { From b279ccc6092ecb526d7ad89579a87c3863678a80 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 20 Aug 2019 01:15:58 +0200 Subject: [PATCH 26/38] Prepare 9.9.1-RELEASE --- ChangeLog.md | 7 +++++++ src/main/resources/VERSION | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 722b720222..529934ce1a 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,13 @@ XP Framework Core ChangeLog ## ?.?.? / ????-??-?? +## 9.9.1 / 2019-08-20 + +### Bugfixes + +* Fixed compatibility issue with PHP 7.4 in `ClassLoader::defineForward()` + (@thekid) + ## 9.9.0 / 2019-08-09 ### Bugfixes diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index a70ecbe7d9..ca82b8ef33 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.1-dev \ No newline at end of file +9.9.1 \ No newline at end of file From e4f3c559aedd77511af24bf8cd3aba24e6b8fbaa Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 20 Aug 2019 01:18:39 +0200 Subject: [PATCH 27/38] Bump versions after release [skip ci] --- README.md | 2 +- src/main/resources/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eeb51eb60d..188a933a24 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Finally, start `xp -v` to see it working: ```sh $ xp -v -XP 9.9.1-dev { PHP/7.3.6 & Zend/3.3.6 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 +XP 9.9.2-dev { PHP/7.3.8 & Zend/3.3.8 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 Copyright (c) 2001-2019 the XP group FileSystemCL<./src/main/php> FileSystemCL<./src/test/php> diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index ca82b8ef33..48456dee5b 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.1 \ No newline at end of file +9.9.2-dev \ No newline at end of file From 57945d639b863044744beb3a2ff901b967c9c488 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:01:38 +0200 Subject: [PATCH 28/38] Backport #227: PHP 7.4 arrow functions in annotations --- .../php/lang/reflect/ClassParser.class.php | 65 +++++++++++++++++++ .../reflection/ClassDetailsTest.class.php | 41 ++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index 3428d39765..371ab972c0 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -14,6 +14,10 @@ */ class ClassParser { + static function __static() { + defined('T_FN') || define('T_FN', -1); + } + /** * Resolves a type in a given context. Recognizes classes imported via * the `use` statement. @@ -144,6 +148,67 @@ protected function valueOf($tokens, &$i, $context, $imports) { $type.= '.'.$tokens[$i++][1]; } return $this->memberOf(XPClass::forName(substr($type, 1)), $tokens[$i], $context); + } else if (T_FN === $token || T_STRING === $token && 'fn' === $tokens[$i][1]) { + $s= sizeof($tokens); + $b= 0; + $code= 'function'; + for ($i++; $i < $s; $i++) { + if ('(' === $tokens[$i]) { + $b++; + $code.= '('; + } else if (')' === $tokens[$i]) { + $b--; + $code.= ')'; + if (0 === $b) break; + } else { + $code.= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; + } + } + + // Translates => to return statement + $code.= '{ return '; + while ($i < $s && T_DOUBLE_ARROW !== $tokens[$i][0]) $i++; + + // Parse expression + $b= $c= 0; + for ($i++; $i < $s; $i++) { + if ('(' === $tokens[$i]) { + $b++; + $code.= '('; + } else if (')' === $tokens[$i]) { + if (--$b < 0) break; + $code.= ')'; + } else if ('[' === $tokens[$i]) { + $c++; + $code.= '['; + } else if (']' === $tokens[$i]) { + if (--$c < 0) break; + $code.= ']'; + } else if (0 === $b && 0 === $c && ',' === $tokens[$i]) { + break; + } else { + $code.= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; + } + } + $i--; + $code.= '; }'; + + try { + $func= eval('return '.$code.';'); + } catch (\ParseError $e) { + throw new IllegalStateException('In `'.$code.'`: '.$e->getMessage()); + } + if (!($func instanceof \Closure)) { + if ($error= error_get_last()) { + set_error_handler('__error', 0); + trigger_error('clear_last_error'); + restore_error_handler(); + } else { + $error= ['message' => 'Syntax error']; + } + throw new IllegalStateException('In `'.$code.'`: '.ucfirst($error['message'])); + } + return $func; } else if (T_STRING === $token) { // constant vs. class::constant if (T_DOUBLE_COLON === $tokens[$i + 1][0]) { $i+= 2; diff --git a/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php index d3a3979463..89e4efdf73 100644 --- a/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php @@ -417,6 +417,47 @@ public function fixture() { } ); } + #[@test, @values([ + # 'function() { return "Test"; }', + # 'fn() => "Test"', + #])] + public function closures_inside_annotations($declaration) { + $details= (new ClassParser())->parseDetails('assertEquals('Test', $details[1]['fixture'][DETAIL_ANNOTATIONS]['call']()); + } + + #[@test] + public function fn_braced_expression() { + $details= (new ClassParser())->parseDetails(' ord(chr(42)))] + public abstract function fixture(); + } + '); + $this->assertEquals(42, $details[1]['fixture'][DETAIL_ANNOTATIONS]['call']()); + } + + #[@test, @values([ + # '[fn() => [1, 2, 3]]', + # '[fn() => [1, 2, 3], ]', + # '[fn() => [1, 2, 3], 1]', + # '[fn() => [[1, [2][0], 3]][0]]', + #])] + public function fn_with_arrays($declaration) { + $details= (new ClassParser())->parseDetails('assertEquals([1, 2, 3], $details[1]['fixture'][DETAIL_ANNOTATIONS]['call'][0]()); + } + #[@test] public function field_initializer_with_class_keyword() { $details= (new ClassParser())->parseDetails(' Date: Wed, 2 Oct 2019 17:07:16 +0200 Subject: [PATCH 29/38] Backport #217: New io.Files class replacing the ill-named FileUtil --- src/main/php/io/FileUtil.class.php | 1 + src/test/config/unittest/io.ini | 3 +++ .../php/net/xp_framework/unittest/io/FileUtilTest.class.php | 1 + 3 files changed, 5 insertions(+) diff --git a/src/main/php/io/FileUtil.class.php b/src/main/php/io/FileUtil.class.php index e0a01d11d8..27e17eb0c1 100755 --- a/src/main/php/io/FileUtil.class.php +++ b/src/main/php/io/FileUtil.class.php @@ -3,6 +3,7 @@ /** * File utility functions * + * @deprecated Use io.Files instead * @see xp://io.File * @test xp://net.xp_framework.unittest.io.FileUtilTest */ diff --git a/src/test/config/unittest/io.ini b/src/test/config/unittest/io.ini index 9314d2d573..fc13829a1b 100644 --- a/src/test/config/unittest/io.ini +++ b/src/test/config/unittest/io.ini @@ -12,6 +12,9 @@ class="net.xp_framework.unittest.io.FileTest" [tempfile] class="net.xp_framework.unittest.io.TempFileTest" +[files] +class="net.xp_framework.unittest.io.FilesTest" + [fileutil] class="net.xp_framework.unittest.io.FileUtilTest" diff --git a/src/test/php/net/xp_framework/unittest/io/FileUtilTest.class.php b/src/test/php/net/xp_framework/unittest/io/FileUtilTest.class.php index a444fbff36..3e6d8cdeff 100755 --- a/src/test/php/net/xp_framework/unittest/io/FileUtilTest.class.php +++ b/src/test/php/net/xp_framework/unittest/io/FileUtilTest.class.php @@ -7,6 +7,7 @@ /** * TestCase * + * @deprecated Use io.Files instead * @see xp://io.FileUtil * @see https://github.com/xp-framework/xp-framework/pull/220 */ From 324bc99670852191eb08649a8fe57c165f93b3f3 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:09:46 +0200 Subject: [PATCH 30/38] Add Files class and tests --- src/main/php/io/Files.class.php | 96 +++++++++++++++++++ .../unittest/io/FilesTest.class.php | 91 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 src/main/php/io/Files.class.php create mode 100644 src/test/php/net/xp_framework/unittest/io/FilesTest.class.php diff --git a/src/main/php/io/Files.class.php b/src/main/php/io/Files.class.php new file mode 100644 index 0000000000..0ae8fccd43 --- /dev/null +++ b/src/main/php/io/Files.class.php @@ -0,0 +1,96 @@ +isOpen()) { + $f->seek(0, SEEK_SET); + $bytes= ''; + do { + $bytes.= $f->read(); + } while (!$f->eof()); + } else { + clearstatcache(); + $f->open(File::READ); + $size= $f->size(); + + // Read until EOF. Best case scenario is that this will run exactly once. + $bytes= ''; + do { + $l= $size - strlen($bytes); + $bytes.= $f->read($l); + } while ($l > 0 && !$f->eof()); + $f->close(); + } + return $bytes; + } + + /** + * Set file contents. If the file was previously open, it is not closed + * after the bytes have been written. + * + * ```php + * $written= Files::write(new File('myfile'), 'Hello world'); + * ``` + * + * @param string|io.File $file + * @param string $bytes + * @return int + * @throws io.IOException + */ + public static function write($file, $bytes) { + $f= $file instanceof File ? $file : new File($file); + if ($f->isOpen()) { + $f->seek(0, SEEK_SET); + $written= $f->write($bytes); + $f->truncate($written); + } else { + $f->open(File::WRITE); + $written= $f->write($bytes); + $f->close(); + } + return $written; + } + + /** + * Append file contents. If the file was previously open, it is not closed + * after the bytes have been written. + * + * @param string|io.File $file + * @param string $bytes + * @return int + * @throws io.IOException + */ + public static function append($file, $bytes) { + $f= $file instanceof File ? $file : new File($file); + if ($f->isOpen()) { + $written= $f->write($bytes); + } else { + $f->open(File::APPEND); + $written= $f->write($bytes); + $f->close(); + } + return $written; + } +} diff --git a/src/test/php/net/xp_framework/unittest/io/FilesTest.class.php b/src/test/php/net/xp_framework/unittest/io/FilesTest.class.php new file mode 100644 index 0000000000..50958ce7c4 --- /dev/null +++ b/src/test/php/net/xp_framework/unittest/io/FilesTest.class.php @@ -0,0 +1,91 @@ +assertEquals('Test', Files::read($f)); + } + + #[@test] + public function read_from_uri() { + $in= new MemoryInputStream('Test'); + $this->assertEquals('Test', Files::read(Streams::readableUri($in))); + } + + #[@test] + public function write_returns_number_of_written_bytes() { + $f= new File(Streams::writeableFd(new MemoryOutputStream())); + $this->assertEquals(4, Files::write($f, 'Test')); + } + + #[@test] + public function write_bytes() { + $out= new MemoryOutputStream(); + Files::write(new File(Streams::writeableFd($out)), 'Test'); + $this->assertEquals('Test', $out->bytes()); + } + + #[@test] + public function write_bytes_to_uri() { + $out= new MemoryOutputStream(); + Files::write(Streams::writeableUri($out), 'Test'); + $this->assertEquals('Test', $out->bytes()); + } + + #[@test] + public function overwrite_bytes() { + $out= new MemoryOutputStream('Existing'); + Files::write(new File(Streams::writeableFd($out)), 'Test'); + $this->assertEquals('Test', $out->bytes()); + } + + #[@test] + public function append_bytes() { + $out= new MemoryOutputStream(); + Files::append(new File(Streams::writeableFd($out)), 'Test'); + $this->assertEquals('Test', $out->bytes()); + } + + #[@test] + public function append_bytes_to_uri() { + $out= new MemoryOutputStream(); + Files::append(Streams::writeableUri($out), 'Test'); + $this->assertEquals('Test', $out->bytes()); + } + + #[@test] + public function append_bytes_to_existing() { + $out= new MemoryOutputStream('Existing'); + Files::append(new File(Streams::writeableFd($out)), 'Test'); + $this->assertEquals('ExistingTest', $out->bytes()); + } + + #[@test] + public function append_bytes_to_existing_uri() { + $out= new MemoryOutputStream('Existing'); + Files::append(Streams::writeableUri($out), 'Test'); + $this->assertEquals('ExistingTest', $out->bytes()); + } + + #[@test] + public function read_returns_less_than_size() { + $f= new File(Streams::readableFd(new class('Test') extends MemoryInputStream { + public function read($size= 4096) { return parent::read(min(1, $size)); } + })); + $this->assertEquals('Test', Files::read($f)); + } + + #[@test] + public function methods_can_be_used_on_instance() { + $f= new File(Streams::readableFd(new MemoryInputStream('Test'))); + + $files= new Files(); + $this->assertEquals('Test', $files->read($f)); + } +} From f84a6d5a734f0feb2fcf6aeea2841a1931e3ff89 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:11:06 +0200 Subject: [PATCH 31/38] Backport #218: Add new util.Dates class superseding util.DateUtil --- src/main/php/util/Dates.class.php | 122 ++++++++++++ src/test/config/unittest/date.ini | 3 + .../unittest/util/DatesTest.class.php | 174 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 src/main/php/util/Dates.class.php create mode 100644 src/test/php/net/xp_framework/unittest/util/DatesTest.class.php diff --git a/src/main/php/util/Dates.class.php b/src/main/php/util/Dates.class.php new file mode 100644 index 0000000000..93678f4272 --- /dev/null +++ b/src/main/php/util/Dates.class.php @@ -0,0 +1,122 @@ +getTime() + $span->getSeconds()); + } else if (is_numeric($span)) { + return new Date($date->getTime() + $span); + } else if ('P' === $span{0}) { + return new Date($date->getHandle()->add(new \DateInterval($span))); + } else { + return new Date($date->getHandle()->add(\DateInterval::createFromDateString($span))); + } + } + + /** + * Subtracts a time span from a date + * + * @param util.Date $date + * @param util.TimeSpan|int|string $span + * @return util.Date + */ + public static function subtract(Date $date, $span) { + if ($span instanceof TimeSpan) { + return new Date($date->getTime() - $span->getSeconds()); + } else if (is_numeric($span)) { + return new Date($date->getTime() - $span); + } else if ('P' === $span{0}) { + return new Date($date->getHandle()->sub(new \DateInterval($span))); + } else { + return new Date($date->getHandle()->sub(\DateInterval::createFromDateString($span))); + } + } + + /** + * Truncates a date, leaving the field specified as the most significant field. + * + * @param util.Date $date + * @param util.TimeInterval $interval + * @return util.Date + */ + public static function truncate(Date $date, TimeInterval $interval) { + $h= $date->getHandle(); + switch ($interval) { + case TimeInterval::$YEAR: $h->setDate($date->getYear(), 1, 1); $h->setTime(0, 0, 0); break; + case TimeInterval::$MONTH: $h->setDate($date->getYear(), $date->getMonth(), 1); $h->setTime(0, 0, 0); break; + case TimeInterval::$DAY: $h->setTime(0, 0, 0); break; + case TimeInterval::$HOURS: $h->setTime($date->getHours(), 0, 0); break; + case TimeInterval::$MINUTES: $h->setTime($date->getHours(), $date->getMinutes(), 0); break; + case TimeInterval::$SECONDS: $h->setTime($date->getHours(), $date->getMinutes(), (int)$date->getSeconds()); break; + } + return new Date($h); + } + + /** + * Gets a date ceiling, leaving the field specified as the most significant field. + * + * @param util.Date $date + * @param util.TimeInterval $interval + * @return util.Date + */ + public static function ceiling(Date $date, TimeInterval $interval) { + $h= $date->getHandle(); + switch ($interval) { + case TimeInterval::$YEAR: $h->setDate($date->getYear() + 1, 1, 1); $h->setTime(0, 0, 0); break; + case TimeInterval::$MONTH: $h->setDate($date->getYear(), $date->getMonth() + 1, 1); $h->setTime(0, 0, 0); break; + case TimeInterval::$DAY: $h->setDate($date->getYear(), $date->getMonth(), $date->getDay() + 1); $h->setTime(0, 0, 0); break; + case TimeInterval::$HOURS: $h->setTime($date->getHours() + 1, 0, 0); break; + case TimeInterval::$MINUTES: $h->setTime($date->getHours(), $date->getMinutes() + 1, 0); break; + case TimeInterval::$SECONDS: $h->setTime($date->getHours(), $date->getMinutes(), (int)$date->getSeconds() + 1); break; + } + return new Date($h); + } + + /** + * Returns a TimeSpan representing the difference + * between the two given Date objects + * + * @param util.Date $a + * @param util.Date $b + * @return util.TimeSpan + */ + public static function diff(Date $a, Date $b) { + return new TimeSpan($a->getTime() - $b->getTime()); + } + + /** + * Comparator method for two Date objects + * + * Returns a negative number if a < b, a positive number if a > b + * and 0 if both dates are equal + * + * Example usage with usort(): + * ```php + * usort($datelist, [Dates::class, 'compare']) + * ``` + * + * @param util.Date $a + * @param util.Date $b + * @return int + */ + public static function compare(Date $a, Date $b) { + return $b->compareTo($a); + } +} diff --git a/src/test/config/unittest/date.ini b/src/test/config/unittest/date.ini index 57f076f20e..e9cb16f0a3 100644 --- a/src/test/config/unittest/date.ini +++ b/src/test/config/unittest/date.ini @@ -18,6 +18,9 @@ class="net.xp_framework.unittest.util.TimeSpanTest" [dateutil] class="net.xp_framework.unittest.util.DateUtilTest" +[dates] +class="net.xp_framework.unittest.util.DatesTest" + [calendar] class="net.xp_framework.unittest.util.CalendarTest" diff --git a/src/test/php/net/xp_framework/unittest/util/DatesTest.class.php b/src/test/php/net/xp_framework/unittest/util/DatesTest.class.php new file mode 100644 index 0000000000..a4b74011f3 --- /dev/null +++ b/src/test/php/net/xp_framework/unittest/util/DatesTest.class.php @@ -0,0 +1,174 @@ +assertEquals( + Date::create(2019, 6, 13, 12, 0, 10), + Dates::add(Date::create(2019, 6, 13, 12, 0, 0), TimeSpan::seconds(10)) + ); + } + + #[@test] + public function add_period() { + $this->assertEquals( + Date::create(2020, 6, 13, 12, 0, 0), + Dates::add(Date::create(2019, 6, 13, 12, 0, 0), 'P1Y') + ); + } + + #[@test] + public function add_int() { + $this->assertEquals( + Date::create(2019, 6, 14, 12, 0, 0), + Dates::add(Date::create(2019, 6, 13, 12, 0, 0), 86400) + ); + } + + #[@test] + public function add_string() { + $this->assertEquals( + Date::create(2019, 7, 13, 12, 0, 0), + Dates::add(Date::create(2019, 6, 13, 12, 0, 0), '1 month') + ); + } + + #[@test] + public function subtract_timespan() { + $this->assertEquals( + Date::create(2019, 6, 13, 11, 59, 50), + Dates::subtract(Date::create(2019, 6, 13, 12, 0, 0), TimeSpan::seconds(10)) + ); + } + + #[@test] + public function subtract_period() { + $this->assertEquals( + Date::create(2018, 6, 13, 12, 0, 0), + Dates::subtract(Date::create(2019, 6, 13, 12, 0, 0), 'P1Y') + ); + } + + #[@test] + public function subtract_int() { + $this->assertEquals( + Date::create(2019, 6, 12, 12, 0, 0), + Dates::subtract(Date::create(2019, 6, 13, 12, 0, 0), 86400) + ); + } + + #[@test] + public function subtract_string() { + $this->assertEquals( + Date::create(2019, 5, 13, 12, 0, 0), + Dates::subtract(Date::create(2019, 6, 13, 12, 0, 0), '1 month') + ); + } + + #[@test] + public function truncate_minutes() { + $this->assertEquals( + Date::create(2019, 6, 13, 12, 39, 0), + Dates::truncate(Date::create(2019, 6, 13, 12, 39, 11), TimeInterval::$MINUTES) + ); + } + + #[@test] + public function truncate_hours() { + $this->assertEquals( + Date::create(2019, 6, 13, 12, 0, 0), + Dates::truncate(Date::create(2019, 6, 13, 12, 39, 11), TimeInterval::$HOURS) + ); + } + + #[@test] + public function truncate_day() { + $this->assertEquals( + Date::create(2019, 6, 13, 0, 0, 0), + Dates::truncate(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$DAY) + ); + } + + #[@test] + public function truncate_month() { + $this->assertEquals( + Date::create(2019, 6, 1, 0, 0, 0), + Dates::truncate(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$MONTH) + ); + } + + #[@test] + public function truncate_year() { + $this->assertEquals( + Date::create(2019, 1, 1, 0, 0, 0), + Dates::truncate(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$YEAR) + ); + } + + #[@test] + public function ceiling_of_minutes() { + $this->assertEquals( + Date::create(2019, 6, 13, 12, 40, 0), + Dates::ceiling(Date::create(2019, 6, 13, 12, 39, 11), TimeInterval::$MINUTES) + ); + } + + #[@test] + public function ceiling_of_hours() { + $this->assertEquals( + Date::create(2019, 6, 13, 13, 0, 0), + Dates::ceiling(Date::create(2019, 6, 13, 12, 39, 11), TimeInterval::$HOURS) + ); + } + + #[@test] + public function ceiling_of_day() { + $this->assertEquals( + Date::create(2019, 6, 14, 0, 0, 0), + Dates::ceiling(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$DAY) + ); + } + + #[@test] + public function ceiling_of_month() { + $this->assertEquals( + Date::create(2019, 7, 1, 0, 0, 0), + Dates::ceiling(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$MONTH) + ); + } + + #[@test] + public function ceiling_of_year() { + $this->assertEquals( + Date::create(2020, 1, 1, 0, 0, 0), + Dates::ceiling(Date::create(2019, 6, 13, 12, 0, 0), TimeInterval::$YEAR) + ); + } + + #[@test] + public function diff() { + $this->assertEquals( + TimeSpan::hours(1), + Dates::diff(Date::create(2019, 6, 13, 12, 39, 1), Date::create(2019, 6, 13, 13, 39, 1)) + ); + } + + #[@test] + public function compare_a_less_than_b() { + $this->assertTrue(Dates::compare(new Date('1977-12-14'), new Date('1980-05-28')) < 0, 'a < b'); + } + + #[@test] + public function compare_a_greater_than_b() { + $this->assertTrue(Dates::compare(new Date('1980-05-28'), new Date('1977-12-14')) > 0, 'a > b'); + } + + #[@test] + public function compare_a_equal_to_b() { + $this->assertEquals(0, Dates::compare(new Date('1980-05-28'), new Date('1980-05-28'))); + } +} \ No newline at end of file From 8fbfff39b89ee0ee978a4c7d728f902c943e9a78 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:19:45 +0200 Subject: [PATCH 32/38] Fix "Array and string offset access syntax with curly braces is deprecated" --- src/main/php/util/Dates.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/php/util/Dates.class.php b/src/main/php/util/Dates.class.php index 93678f4272..cfb2ace306 100644 --- a/src/main/php/util/Dates.class.php +++ b/src/main/php/util/Dates.class.php @@ -23,7 +23,7 @@ public static function add(Date $date, $span) { return new Date($date->getTime() + $span->getSeconds()); } else if (is_numeric($span)) { return new Date($date->getTime() + $span); - } else if ('P' === $span{0}) { + } else if ('P' === $span[0]) { return new Date($date->getHandle()->add(new \DateInterval($span))); } else { return new Date($date->getHandle()->add(\DateInterval::createFromDateString($span))); @@ -42,7 +42,7 @@ public static function subtract(Date $date, $span) { return new Date($date->getTime() - $span->getSeconds()); } else if (is_numeric($span)) { return new Date($date->getTime() - $span); - } else if ('P' === $span{0}) { + } else if ('P' === $span[0]) { return new Date($date->getHandle()->sub(new \DateInterval($span))); } else { return new Date($date->getHandle()->sub(\DateInterval::createFromDateString($span))); From c1acd979250ffec74f1b85815c954e63dddfffbb Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:39:13 +0200 Subject: [PATCH 33/38] Backport #219: Add util.Date::getMicroSeconds() --- src/main/php/util/Date.class.php | 19 ++++++++++++++----- .../unittest/util/DateTest.class.php | 14 +++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/php/util/Date.class.php b/src/main/php/util/Date.class.php index 87e24dece8..201b013e95 100755 --- a/src/main/php/util/Date.class.php +++ b/src/main/php/util/Date.class.php @@ -92,11 +92,15 @@ public static function create($year, $month, $day, $hour, $minute, $second, Time if ($tz) { date_timezone_set($date, $tz->getHandle()); } - - if (false === @date_date_set($date, $year, $month, $day) || - false === @date_time_set($date, $hour, $minute, $second) - ) { - throw new IllegalArgumentException(sprintf( + + try { + $r= date_date_set($date, $year, $month, $day) && date_time_set($date, $hour, $minute, $second); + } catch (\Throwable $e) { + $r= false; + } + + if (!$r) { + $e= new IllegalArgumentException(sprintf( 'One or more given arguments are not valid: $year=%s, $month=%s, $day= %s, $hour=%s, $minute=%s, $second=%s', $year, $month, @@ -105,6 +109,8 @@ public static function create($year, $month, $day, $hour, $minute, $second, Time $minute, $second )); + \xp::gc(__FILE__); + throw $e; } return new self($date); @@ -138,6 +144,9 @@ public function isAfter(Date $date): bool { /** Retrieve Unix-Timestamp for this date */ public function getTime(): int { return date_timestamp_get($this->handle); } + /** Get microseconds */ + public function getMicroSeconds(): int { return $this->handle->format('u'); } + /** Get seconds */ public function getSeconds(): int { return $this->handle->format('s'); } diff --git a/src/test/php/net/xp_framework/unittest/util/DateTest.class.php b/src/test/php/net/xp_framework/unittest/util/DateTest.class.php index 0b5b2a1287..4218bcc46e 100644 --- a/src/test/php/net/xp_framework/unittest/util/DateTest.class.php +++ b/src/test/php/net/xp_framework/unittest/util/DateTest.class.php @@ -1,10 +1,9 @@ assertEquals(393313, (new Date('2019-07-03 15:18:10.393313'))->getMicroSeconds()); + } } From d2b3af59ab3769f9e74d231a8889929fb78ce4f2 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Wed, 2 Oct 2019 17:46:25 +0200 Subject: [PATCH 34/38] Backport #226: Annotations from anonymous classes --- src/main/php/lang.base.php | 8 +- .../php/lang/AnonymousClassLoader.class.php | 126 ++++++++++++++++++ src/main/php/lang/XPClass.class.php | 1 + .../php/lang/reflect/ClassParser.class.php | 21 +++ .../unittest/core/AnnotationTest.class.php | 13 +- .../reflection/ClassDetailsTest.class.php | 44 ++++++ .../reflection/ModuleLoadingTest.class.php | 8 +- .../unittest/reflection/ProxyTest.class.php | 13 +- .../unittest/reflection/XPClassTest.class.php | 10 +- 9 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 src/main/php/lang/AnonymousClassLoader.class.php diff --git a/src/main/php/lang.base.php b/src/main/php/lang.base.php index c8a09dc046..a6be869081 100755 --- a/src/main/php/lang.base.php +++ b/src/main/php/lang.base.php @@ -392,7 +392,13 @@ function typeof($arg) { } return new \lang\FunctionType($signature, $return); } else if (is_object($arg)) { - return new \lang\XPClass($arg); + $class= get_class($arg); + if (0 === strncmp($class, 'class@anonymous', 15)) { + $r= new \ReflectionObject($arg); + \xp::$cl[strtr($class, '\\', '.')]= 'lang.AnonymousClassLoader://'.$r->getStartLine().':'.$r->getEndLine().'@'.$r->getFileName(); + return new \lang\XPClass($r); + } + return new \lang\XPClass($class); } else if (is_array($arg)) { return 0 === key($arg) ? \lang\ArrayType::forName('var[]') : \lang\MapType::forName('[:var]'); } else { diff --git a/src/main/php/lang/AnonymousClassLoader.class.php b/src/main/php/lang/AnonymousClassLoader.class.php new file mode 100644 index 0000000000..9f0d345317 --- /dev/null +++ b/src/main/php/lang/AnonymousClassLoader.class.php @@ -0,0 +1,126 @@ +file= $file; + $this->start= $start; + $this->end= $end; + } + + /** + * Checks whether this loader can provide the requested class + * + * @param string class + * @return bool + */ + public function providesClass($class) { + return false; + } + + /** + * Checks whether this loader can provide the requested resource + * + * @param string filename + * @return bool + */ + public function providesResource($filename) { + return false; + } + + /** + * Checks whether this loader can provide the requested package + * + * @param string package + * @return bool + */ + public function providesPackage($package) { + return false; + } + + /** + * Load class bytes + * + * @param string name fully qualified class name + * @return string + */ + public function loadClassBytes($name) { + $fd= fopen($this->file, 'rb'); + $i= 0; + $bytes= ''; + while ($line= fgets($fd, 8192)) { + $i++; + if ($i > $this->end) { + break; + } else if ($i >= $this->start) { + $bytes.= $line; + } + } + fclose($fd); + return '_class= $ref->getName(); + $this->_reflect= $ref; } else if ($ref instanceof \__PHP_Incomplete_Class) { throw new ClassCastException('Cannot use incomplete classes in reflection'); } else if (is_object($ref)) { diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index 3428d39765..897d3a467d 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -427,6 +427,27 @@ public function parseDetails($bytes, $context= '') { } break; + case T_NEW: // Anonymous class + $details['class']= [ + DETAIL_COMMENT => null, + DETAIL_ANNOTATIONS => [], + DETAIL_ARGUMENTS => null + ]; + $annotations= [0 => [], 1 => []]; + $comment= ''; + + $b= 0; + while (++$i < $s) { + if ('(' === $tokens[$i][0]) { + $b++; + } else if (')' === $tokens[$i][0]) { + if (0 === --$b) break; + } else if (0 === $b && ';' === $tokens[$i][0]) { + break; // Abstract or interface method + } + } + break; + case T_CLASS: if (isset($details['class'])) break; // Inside class, e.g. $lookup= ['self' => self::class] diff --git a/src/test/php/net/xp_framework/unittest/core/AnnotationTest.class.php b/src/test/php/net/xp_framework/unittest/core/AnnotationTest.class.php index 6b20e28c24..8c5b0e4e13 100644 --- a/src/test/php/net/xp_framework/unittest/core/AnnotationTest.class.php +++ b/src/test/php/net/xp_framework/unittest/core/AnnotationTest.class.php @@ -1,7 +1,7 @@ class->getMethod('testMethod')->getAnnotation('limit') ); } + + #[@test] + public function on_anonymous_class() { + $c= new class() { + + #[@test] + public function fixture() { } + }; + + $this->assertEquals(['test' => null], typeof($c)->getMethod('fixture')->getAnnotations()); + } } diff --git a/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php index d3a3979463..63ffd00f12 100644 --- a/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ClassDetailsTest.class.php @@ -449,4 +449,48 @@ public abstract function fixture(); '); $this->assertEquals(['test' => $value], $details[1]['fixture'][DETAIL_ANNOTATIONS]); } + + #[@test] + public function anonymous_class() { + $details= (new ClassParser())->parseDetails('assertEquals( + [DETAIL_COMMENT => null, DETAIL_ANNOTATIONS => [], DETAIL_ARGUMENTS => null], + $details['class'] + ); + } + + #[@test] + public function anonymous_class_with_arguments() { + $details= (new ClassParser())->parseDetails('assertEquals( + [DETAIL_COMMENT => null, DETAIL_ANNOTATIONS => [], DETAIL_ARGUMENTS => null], + $details['class'] + ); + } + + #[@test] + public function anonymous_class_member() { + $details= (new ClassParser())->parseDetails('assertEquals(['test' => null], $details[0]['fixture'][DETAIL_ANNOTATIONS]); + } + + #[@test] + public function anonymous_class_method() { + $details= (new ClassParser())->parseDetails('assertEquals(['test' => null], $details[1]['fixture'][DETAIL_ANNOTATIONS]); + } } \ No newline at end of file diff --git a/src/test/php/net/xp_framework/unittest/reflection/ModuleLoadingTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ModuleLoadingTest.class.php index 6df2d7b6f8..ef38c9f580 100755 --- a/src/test/php/net/xp_framework/unittest/reflection/ModuleLoadingTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ModuleLoadingTest.class.php @@ -1,8 +1,8 @@ register(new LoaderProviding([ 'module.xp' => 'assertTrue(in_array($cl, typeof(Module::forName('xp-framework/impl'))->getInterfaces())); + $interfaces= typeof(Module::forName('xp-framework/impl'))->getInterfaces(); + foreach ($interfaces as $interface) { + if ($cl->equals($interface)) return; + } + $this->fail($cl->getName().' not included', $interfaces, [$cl]); } #[@test] diff --git a/src/test/php/net/xp_framework/unittest/reflection/ProxyTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/ProxyTest.class.php index 15666e3f5b..3e2f3fe342 100644 --- a/src/test/php/net/xp_framework/unittest/reflection/ProxyTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/ProxyTest.class.php @@ -1,9 +1,9 @@ proxyClassFor([$this->iteratorClass]); - $interfaces= $class->getInterfaces(); - $this->assertEquals(1, sizeof($interfaces)); - $this->assertTrue(in_array($this->iteratorClass, $interfaces)); + $this->assertEquals([$this->iteratorClass], $class->getInterfaces()); } #[@test] public function allInterfacesAreImplemented() { $class= $this->proxyClassFor([$this->iteratorClass, $this->observerClass]); - $interfaces= $class->getInterfaces(); - $this->assertEquals(2, sizeof($interfaces)); - $this->assertTrue(in_array($this->iteratorClass, $interfaces)); - $this->assertTrue(in_array($this->observerClass, $interfaces)); + $this->assertEquals([$this->iteratorClass, $this->observerClass], $class->getInterfaces()); } #[@test] diff --git a/src/test/php/net/xp_framework/unittest/reflection/XPClassTest.class.php b/src/test/php/net/xp_framework/unittest/reflection/XPClassTest.class.php index 2027a0f923..aceee22fb1 100644 --- a/src/test/php/net/xp_framework/unittest/reflection/XPClassTest.class.php +++ b/src/test/php/net/xp_framework/unittest/reflection/XPClassTest.class.php @@ -1,5 +1,6 @@ assertTrue(in_array( - XPClass::forName('lang.Runnable'), + public function getInterfaces_consist_of_declared_interface() { + $this->assertEquals( + [XPClass::forName('lang.Runnable')], $this->fixture->getInterfaces() - )); + ); } #[@test] From 08a9ec7726406b17f8b978071dbbaf0b5bbe16f5 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 4 Oct 2019 11:00:02 +0200 Subject: [PATCH 35/38] Bump versions --- README.md | 2 +- src/main/resources/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 188a933a24..5b4a8144d5 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Finally, start `xp -v` to see it working: ```sh $ xp -v -XP 9.9.2-dev { PHP/7.3.8 & Zend/3.3.8 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 +XP 9.10.0-dev { PHP/7.3.10 & Zend/3.3.10 } @ Windows NT SLATE 10.0 build 18362 (Windows 10) AMD64 Copyright (c) 2001-2019 the XP group FileSystemCL<./src/main/php> FileSystemCL<./src/test/php> diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 48456dee5b..e242283c15 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.9.2-dev \ No newline at end of file +9.10.0-dev \ No newline at end of file From db38ac4d981879b1813d9e6f7c0320f2f808d0ec Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 4 Oct 2019 11:00:42 +0200 Subject: [PATCH 36/38] Add changelog for backported features See https://github.com/xp-framework/core/projects/1#card-27242937 --- ChangeLog.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 529934ce1a..74fb21c867 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,19 @@ XP Framework Core ChangeLog ## ?.?.? / ????-??-?? +## 9.10.0 / 2019-10-04 + +### Features + +* Backported XP 10 features for easier adoption: + - PHP 7.4 arrow functions in annotations (#227) + - Annotations from anonymous classes (#226) + - New `util.Date::getMicroSeconds()` (#219) + - New `util.Dates` class superseding util.DateUtil (#218) + - New `io.Files` class replacing the ill-named *FileUtil* (#217) + https://github.com/xp-framework/core/projects/1 + (@thekid) + ## 9.9.1 / 2019-08-20 ### Bugfixes From ff3d06223e16a8b72fe1c0523092775b43ebf18d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 4 Oct 2019 11:02:22 +0200 Subject: [PATCH 37/38] Advertise newest XP runners [skip ci] --- README.md | 2 +- src/main/resources/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b4a8144d5..829e5aeeb8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ the following one-liner: ```sh $ cd ~/bin -$ curl -sSL https://bintray.com/artifact/download/xp-runners/generic/setup-8.1.3.sh | sh +$ curl -sSL https://bintray.com/artifact/download/xp-runners/generic/setup-8.1.7.sh | sh ``` ### Using it diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index e242283c15..492932e133 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.10.0-dev \ No newline at end of file +9.10.0 \ No newline at end of file From 74fcd02a8840297a6a211cd0c8f378d77947b00d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 4 Oct 2019 11:05:41 +0200 Subject: [PATCH 38/38] Bump version after release --- src/main/resources/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION index 492932e133..6841954b33 100644 --- a/src/main/resources/VERSION +++ b/src/main/resources/VERSION @@ -1 +1 @@ -9.10.0 \ No newline at end of file +9.10.1-dev \ No newline at end of file