Skip to content

Commit 8835461

Browse files
Fix exporting default values on PHP 8.2/8.1
1 parent 5ca79bc commit 8835461

9 files changed

+74
-9
lines changed

.github/workflows/phpunit.yml

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
pull_request:
55
push:
66

7+
defaults:
8+
run:
9+
shell: bash
10+
711
jobs:
812
phpunit:
913
name: "PHPUnit tests"
@@ -19,8 +23,10 @@ jobs:
1923
- "7.4"
2024
- "8.0"
2125
- "8.1"
26+
- "8.2"
2227
operating-system:
2328
- "ubuntu-latest"
29+
fail-fast: false
2430

2531
steps:
2632
- name: "Checkout"
@@ -44,7 +50,10 @@ jobs:
4450
restore-keys: "php-${{ matrix.php-version }}"
4551

4652
- name: "Test with lowest dependencies"
47-
run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest && vendor/bin/simple-phpunit"
53+
if: "matrix.php-version != '8.2'"
54+
run: |
55+
composer update --prefer-lowest --no-interaction --no-progress --no-suggest && vendor/bin/simple-phpunit
4856
4957
- name: "Test with highest dependencies"
50-
run: "composer update --no-interaction --no-progress --no-suggest && vendor/bin/simple-phpunit"
58+
run: |
59+
composer update --no-interaction --no-progress --no-suggest $([[ "${{ matrix.php-version }}" = "8.2" ]] && echo ' --ignore-platform-req=php+') && vendor/bin/simple-phpunit

src/ProxyManager/Generator/ValueGenerator.php

+52-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
use function explode;
1212
use function implode;
13+
use function in_array;
1314
use function preg_replace;
1415
use function preg_split;
1516
use function rtrim;
1617
use function substr;
1718
use function var_export;
1819

1920
use const PREG_SPLIT_DELIM_CAPTURE;
21+
use const PREG_SPLIT_NO_EMPTY;
2022

2123
/**
2224
* @internal do not use this in your code: it is only here for internal use
@@ -50,16 +52,20 @@ public function generate(): string
5052
return parent::generate();
5153
} catch (RuntimeException $e) {
5254
if ($this->reflection) {
53-
$value = rtrim(substr(explode('$' . $this->reflection->getName() . ' = ', (string) $this->reflection, 2)[1], 0, -2));
55+
$value = self::exportDefault($this->reflection);
5456
} else {
5557
$value = var_export($this->value, true);
58+
59+
if (\PHP_VERSION_ID < 80200) {
60+
return self::fixVarExport($value);
61+
}
5662
}
5763

58-
return self::fixExport($value);
64+
return $value;
5965
}
6066
}
6167

62-
private static function fixExport(string $value): string
68+
private static function fixVarExport(string $value): string
6369
{
6470
$parts = preg_split('{(\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')}', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
6571

@@ -73,4 +79,47 @@ private static function fixExport(string $value): string
7379

7480
return implode('', $parts);
7581
}
82+
83+
private static function exportDefault(\ReflectionParameter $param): string
84+
{
85+
$default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
86+
87+
if (in_array($default, ['<default>', 'NULL'], true)) {
88+
return 'null';
89+
}
90+
if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) {
91+
return var_export($param->getDefaultValue(), true);
92+
}
93+
94+
$regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
95+
$parts = preg_split($regexp, $default, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
96+
97+
$regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(?!: )/';
98+
$callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass())
99+
? function ($m) use ($class) {
100+
switch ($m[2]) {
101+
case 'new': case 'false': case 'true': case 'null': return $m[1].$m[2];
102+
case 'NULL': return $m[1].'null';
103+
case 'self': return $m[1].'\\'.$class->name;
104+
case 'namespace\\parent':
105+
case 'parent': $m[1].(($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent');
106+
default: return $m[1].'\\'.$m[2];
107+
}
108+
}
109+
: function ($m) {
110+
switch ($m[2]) {
111+
case 'new': case 'false': case 'true': case 'null': return $m[1].$m[2];
112+
case 'NULL': return $m[1].'null';
113+
default: return $m[1].'\\'.$m[2];
114+
}
115+
};
116+
117+
return implode('', array_map(function ($part) use ($regexp, $callback) {
118+
switch ($part[0]) {
119+
case '"': return $part; // for internal classes only
120+
case "'": return false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part;
121+
default: return preg_replace_callback($regexp, $callback, $part);
122+
}
123+
}, $parts));
124+
}
76125
}

tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-magic-property-read.phpt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Verifies that generated lazy loading ghost objects disallow reading non-existing
55

66
require_once __DIR__ . '/init.php';
77

8+
#[AllowDynamicProperties]
89
class Kitchen
910
{
1011
private $sweets;
@@ -22,4 +23,4 @@ $proxy = $factory->createProxy(Kitchen::class, function () {});
2223
echo $proxy->nonExisting;
2324
?>
2425
--EXPECTF--
25-
nonExisting
26+
nonExisting

tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-property-write.phpt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Verifies that generated lazy loading ghost objects disallow reading non-existing
55

66
require_once __DIR__ . '/init.php';
77

8+
#[AllowDynamicProperties]
89
class Kitchen
910
{
1011
private $sweets;
@@ -18,4 +19,4 @@ $proxy->nonExisting = 'I do not exist';
1819
echo $proxy->nonExisting;
1920
?>
2021
--EXPECTF--
21-
I do not exist
22+
I do not exist

tests/language-feature-scripts/lazy-loading-ghost-denies-inexisting-property-read.phpt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Verifies that generated lazy loading ghost objects disallow reading non-existing
55

66
require_once __DIR__ . '/init.php';
77

8+
#[AllowDynamicProperties]
89
class Kitchen
910
{
1011
private $sweets;
@@ -17,4 +18,4 @@ $proxy = $factory->createProxy(Kitchen::class, function () {});
1718
$proxy->nonExisting;
1819
?>
1920
--EXPECTF--
20-
%SNotice: Undefined property: Kitchen::$nonExisting in %a
21+
%SNotice: Undefined property: Kitchen::$nonExisting in %a

tests/language-feature-scripts/lazy-loading-ghost-skip-destructor.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Verifies that generated lazy loading ghost objects can skip calling the proxied
55

66
require_once __DIR__ . '/init.php';
77

8+
#[AllowDynamicProperties]
89
class Destructable
910
{
1011
public function __destruct()

tests/language-feature-scripts/lazy-loading-value-holder-denies-interface-property-read.phpt

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ $factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory($configuratio
1414
$proxy = $factory
1515
->createProxy(MyInterface::class, function (& $wrapped, $proxy, $method, array $parameters, & $initializer) : bool {
1616
$initializer = null;
17-
$wrapped = new class implements MyInterface {
17+
$wrapped = new #[AllowDynamicProperties]
18+
class implements MyInterface {
1819
};
1920

2021
return true;

tests/language-feature-scripts/lazy-loading-value-holder-interface-proxy.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface MyInterface
1010
public function do();
1111
}
1212

13+
#[AllowDynamicProperties]
1314
class MyClass implements MyInterface
1415
{
1516
public function do()

tests/language-feature-scripts/lazy-loading-value-holder-skip-destructor.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Verifies that generated lazy loading value holders can skip calling the proxied
55

66
require_once __DIR__ . '/init.php';
77

8+
#[AllowDynamicProperties]
89
class Destructable
910
{
1011
public function __destruct()

0 commit comments

Comments
 (0)