core icon indicating copy to clipboard operation
core copied to clipboard

PHP 8.5 adoption

Open thekid opened this issue 9 months ago • 12 comments

Added to test suite in xp-framework/core@009498040

Changes in PHP 8.5

See https://wiki.php.net/rfc#php_85

Initial results

See https://github.com/xp-framework/core/actions/runs/14570004500/job/40865400220:

  • [x] realpath(): Argument 1 ($path) must not contain any null bytes)
  • [x] Handling of self and parent

thekid avatar Apr 21 '25 08:04 thekid

Handling of self and parent

$r= new ReflectionClass(new class() {
  public function instance(): self { return $this; }
});

$type= $r->getMethod('instance')->getReturnType();
var_dump($type->getName()); // PHP 8.4: "self", PHP 8.5: "class@anonymous"

The problem is not per se that the name is resolved by PHP, the problem is that "class@anonymous" is not the full name of the class, which is more like "class@anonymous\000Command line code:1$0". The NUL character in the string truncates the name in PHP 8.5, leading the problems when trying to create ReflectionClass instances from the type name!

See https://github.com/php/php-src/issues/18373


Extended example

...showing an abbreviated version of how the XP reflection API works:

<?php

class ReflectionPrimitive {
  public string $name;
  public function __construct(ReflectionNamedType $type) {
    $this->name= $type->getName();
  }
}

$reflect= new ReflectionClass(new class() {
  public function name(): string { return 'Test'; }
  public function date(): DateTime { return new DateTime(); }
  public function with(): self { return $this; }
});

foreach (['name', 'date', 'with'] as $method) {
  echo "== {$method} ==\n";
  $type= $reflect->getMethod($method)->getReturnType();
  var_dump($type->getName());

  // Shortened for brevity: Handling of union and intersecton types

  if ($type->isBuiltin()) {
    $return= new ReflectionPrimitive($type);
  } else {
    $return= match ($type->getName()) {
      'self'   => $reflect,
      'static' => $reflect,
      'parent' => $reflect->getParentClass(),
      default  => new ReflectionClass($type->getName())
    };
  }
  var_dump($return);
}

PHP 8.4 output:

== name ==
string(6) "string"
object(ReflectionPrimitive)#2 (1) {
  ["name"]=>
  string(6) "string"
}
== date ==
string(8) "DateTime"
object(ReflectionClass)#3 (1) {
  ["name"]=>
  string(8) "DateTime"
}
== with ==
string(4) "self"
object(ReflectionClass)#1 (1) {
  ["name"]=>
  string(73) "class@anonymousC:\Tools\Cygwin\home\timmf\devel\xp\core\reflect.php:10$0"
}

PHP 8.5 output:

== name ==
string(6) "string"
object(ReflectionPrimitive)#2 (1) {
  ["name"]=>
  string(6) "string"
}
== date ==
string(8) "DateTime"
object(ReflectionClass)#3 (1) {
  ["name"]=>
  string(8) "DateTime"
}
== with ==
string(15) "class@anonymous"

Fatal error: Uncaught ReflectionException: Class "class@anonymous" does not exist in C:\Tools\Cygwin\home\timmf\devel\xp\core\reflect.php:30
Stack trace:
#0 C:\Tools\Cygwin\home\timmf\devel\xp\core\reflect.php(30): ReflectionClass->__construct('class@anonymous')
#1 {main}
  thrown in C:\Tools\Cygwin\home\timmf\devel\xp\core\reflect.php on line 30

thekid avatar Apr 21 '25 09:04 thekid

the problem is that "class@anonymous" is not the full name of the class

This issue might have already been fixed in PHP in the meantime, see https://github.com/php/php-src/issues/18373#issuecomment-2818059577

However, the names being resolved still needs a minor patch to work:

diff --git a/src/main/php/lang/Type.class.php b/src/main/php/lang/Type.class.php
index e8fd37486..dff5d0b52 100755
--- a/src/main/php/lang/Type.class.php
+++ b/src/main/php/lang/Type.class.php
@@ -287,7 +287,7 @@ class Type implements Value {
     //   card type.
     // * Anything else is a qualified or unqualified class name
     $l= strlen($name);
-    $p= strcspn($name, '<&|[*(');
+    $p= strcspn($name, '<&|[*(@');
     if ($p === $l) {
       return isset($context[$name]) ? $context[$name]() : ((isset($context['*']) && strcspn($name, '.\\') === $l)
         ? $context['*']($name)
@@ -334,6 +334,8 @@ class Type implements Value {
       } else {
         $t= self::named($base, $context)->newGenericType($components);
       }
+    } else if ('@' === $name[$p]) {
+      return new XPClass($name);
     } else {
       $t= self::named(trim(substr($name, 0, $p)), $context);
       $name= substr($name, $p);

thekid avatar Apr 21 '25 09:04 thekid

With this patch, there is a handful of tests that actually test for self and parent failing, see https://github.com/xp-framework/core/actions/runs/14571525578/job/40869557458. We will need to rewrite them to handle this.

thekid avatar Apr 21 '25 10:04 thekid

Now only PHP 8.5 on Windows is failing, see https://github.com/xp-framework/core/actions/runs/14572384660, a newer build might fix this, see https://github.com/php/php-src/issues/18373#issuecomment-2818059577

thekid avatar Apr 21 '25 11:04 thekid

Fix for Non-canonical cast (double) is deprecated, use the (float) cast instead (see here):

  • [x] https://github.com/xp-forge/json/releases/tag/v6.0.1
  • [x] https://github.com/xp-framework/ftp/releases/tag/v12.0.0
  • [x] https://github.com/xp-framework/rdbms/releases/tag/v13.4.0
  • [x] https://github.com/xp-forge/json-patch/releases/tag/v2.1.1

thekid avatar Aug 14 '25 19:08 thekid

Another small change to chr(), see here:

⨯ util.unittest.BytesTest::byteArrayToBytes
  Succeeded but raised 1 warning(s)
    at <main>::@error() [line 27 of Bytes.class.php] E_DEPRECATED: chr(): Providing a value not in-between 0 and 255 is deprecated, this is because a byte value must be in the [0, 255] interval. The value used will be constrained using % 256
    at test.execution.TestCase::run() [line 32 of RunTest.class.php] 
    at test.execution.RunTest::run() [line 116 of Runner.class.php] 
    at xp.test.Runner::main() [line 389 of class-main.php]

thekid avatar Aug 16 '25 18:08 thekid

Released https://github.com/xp-framework/core/releases/tag/v12.6.1 w/ fixes for https://wiki.php.net/rfc/deprecations_php_8_5

thekid avatar Aug 16 '25 18:08 thekid

The Ignore annotation on the lang.unittest.TypeUnionTest::php8_native_union_with_self test can be removed once the Windows PHP builds on GitHub actions are up-to-date again now that php/php-src@6497c6c455ef0cb2debe1891d6c6d00ece95a3c2 has been committed.

thekid avatar Aug 27 '25 18:08 thekid

E_DEPRECATED: Using null as an array offset is deprecated, use an empty string instead

  • [x] line 16 of TypeDefinition.class.php
  • [x] line 31 of TypeDefinition.class.php
  • [x] line 48 of DynamicClassLoader.class.php
  • [x] line 103of DynamicClassLoader.class.php
  • [x] line 127 of Properties.class.php
  • [x] line 256 of Properties.class.php
  • [x] line 118 of Properties.class.php
  • [x] line 119 of Properties.class.php
  • [x] line 122 of Properties.class.php
  • [x] line 124 of Properties.class.php
  • [x] line 127 of Properties.class.php
  • [x] line 236 of Properties.class.php
  • [x] line 272 of Properties.class.php
  • [x] line 296 of Properties.class.php

See https://github.com/xp-framework/core/actions/runs/17506126128/job/49729895116 (before 💣) See https://github.com/xp-framework/core/actions/runs/17506360976/job/49730568189 (after ✅)

thekid avatar Sep 05 '25 22:09 thekid

E_DEPRECATED: Implicit conversion from float (...) to int loses precision

  • [x] line 157 of UUID.class.php
  • [x] line 158 of UUID.class.php
  • [x] line 159 of UUID.class.php

Update: These were caused by me using the x86 binary, reverted the changes

thekid avatar Sep 05 '25 23:09 thekid

Smaller adjustments were also necessary for the XP Compiler to fix Arrow functions on the right hand side of |> must be parenthesized errors:

- return "test" |> fn($x) => $x.": OK";
+ return "test" |> (fn($x) => $x.": OK");

See https://externals.io/message/128473#128554

thekid avatar Sep 06 '25 09:09 thekid

Released https://github.com/xp-framework/core/releases/tag/v12.6.2

thekid avatar Sep 06 '25 18:09 thekid