Skip to content

Fix namespaces for PHP 8.4 #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .styleci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ php:
finder:
not-name:
- Php84Test.php
- ReflectionClosureBracedNamespaceTest.php
- ReflectionClosureNonBracedNamespaceTest.php
- ReflectionClosurePhp80Test.php
- ReflectionClosurePhp81Test.php
- SerializerPhp81Test.php
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<file>tests/ReflectionClosure3Test.php</file>
<file>tests/ReflectionClosure4Test.php</file>
<file>tests/ReflectionClosure5Test.php</file>
<file>tests/ReflectionClosureBracedNamespaceTest.php</file>
<file>tests/ReflectionClosureNonBracedNamespaceTest.php</file>
<file>tests/SerializerTest.php</file>
<file>tests/SignedSerializerTest.php</file>
</testsuite>
Expand Down
105 changes: 82 additions & 23 deletions src/Support/ReflectionClosure.php
Original file line number Diff line number Diff line change
Expand Up @@ -865,13 +865,18 @@ protected function getTokens()
*/
protected function getClasses()
{
$key = $this->getHashedFileName();

if (! isset(static::$classes[$key])) {
$this->fetchItems();
$line = $this->getStartLine();

foreach ($this->getStructures() as $struct) {
if ($struct['type'] === 'namespace' &&
$struct['start'] <= $line &&
$struct['end'] >= $line
) {
return $struct['classes'];
}
}

return static::$classes[$key];
return [];
}

/**
Expand Down Expand Up @@ -945,14 +950,36 @@ protected function fetchItems()
$alias = '';
$isFunc = $isConst = false;

$startLine = $endLine = 0;
$startLine = $lastKnownLine = 0;
$structType = $structName = '';
$structIgnore = false;

$namespace = '';
$namespaceStartLine = 0;
$namespaceBraced = false;
$namespaceClasses = [];

foreach ($tokens as $token) {
if (is_array($token)) {
$lastKnownLine = $token[2];
}

switch ($state) {
case 'start':
switch ($token[0]) {
case T_NAMESPACE:
$structures[] = [
'type' => 'namespace',
'name' => $namespace,
'start' => $namespaceStartLine,
'end' => $token[2] - 1,
'classes' => $namespaceClasses,
];
$namespace = '';
$namespaceClasses = [];
$state = 'namespace';
$namespaceStartLine = $token[2];
break;
case T_CLASS:
case T_INTERFACE:
case T_TRAIT:
Expand All @@ -978,6 +1005,33 @@ protected function fetchItems()
case T_DOUBLE_COLON:
$state = 'invoke';
break;
case '}':
if ($namespaceBraced) {
$structures[] = [
'type' => 'namespace',
'name' => $namespace,
'start' => $namespaceStartLine,
'end' => $lastKnownLine,
'classes' => $namespaceClasses,
];
$namespaceBraced = false;
$namespace = '';
$namespaceClasses = [];
}
break;
}
break;
case 'namespace':
switch ($token[0]) {
case T_STRING:
case T_NAME_QUALIFIED:
$namespace = $token[1];
break;
case ';':
case '{':
$state = 'start';
$namespaceBraced = $token[0] === '{';
break;
}
break;
case 'use':
Expand Down Expand Up @@ -1022,6 +1076,7 @@ protected function fetchItems()
$constants[$alias] = $name;
} else {
$classes[strtolower($alias)] = $name;
$namespaceClasses[strtolower($alias)] = $name;
}
}
$name = $alias = '';
Expand Down Expand Up @@ -1061,6 +1116,7 @@ protected function fetchItems()
$constants[$alias] = $prefix.$name;
} else {
$classes[strtolower($alias)] = $prefix.$name;
$namespaceClasses[strtolower($alias)] = $prefix.$name;
}
}
$name = $alias = '';
Expand Down Expand Up @@ -1118,22 +1174,26 @@ protected function fetchItems()
'type' => $structType,
'name' => $structName,
'start' => $startLine,
'end' => $endLine,
'end' => $lastKnownLine,
];
}
$structIgnore = false;
$state = 'start';
}
break;
default:
if (is_array($token)) {
$endLine = $token[2];
}
}
break;
}
}

$structures[] = [
'type' => 'namespace',
'name' => $namespace,
'start' => $namespaceStartLine,
'end' => PHP_INT_MAX,
'classes' => $namespaceClasses,
];

static::$classes[$key] = $classes;
static::$functions[$key] = $functions;
static::$constants[$key] = $constants;
Expand All @@ -1147,20 +1207,19 @@ protected function fetchItems()
*/
protected function getClosureNamespaceName()
{
$ns = $this->getNamespaceName();

$name = $this->getName();

// First class callables...
if ($name !== '{closure}'
&& ! str_contains($name, '{closure:/')
&& ! str_contains($name, '{closure:\\')
&& empty($ns)
&& ! is_null($this->getClosureScopeClass())) {
$ns = $this->getClosureScopeClass()->getNamespaceName();
$startLine = $this->getStartLine();
$endLine = $this->getEndLine();

foreach ($this->getStructures() as $struct) {
if ($struct['type'] === 'namespace' &&
$struct['start'] <= $startLine &&
$struct['end'] >= $endLine
) {
return $struct['name'];
}
}

return $ns;
return '';
}

/**
Expand Down
50 changes: 50 additions & 0 deletions tests/ReflectionClosureBracedNamespaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Space {
test('relative namespace (braced)', function () {
$f1 = fn (Foo $foo): Foo => new Foo();
$e1 = 'fn (\Space\Foo $foo): \Space\Foo => new \Space\Foo()';

$f2 = fn (Foo\Bar $fooBar): Foo\Bar => new Foo\Bar();
$e2 = 'fn (\Space\Foo\Bar $fooBar): \Space\Foo\Bar => new \Space\Foo\Bar()';

expect($f1)->toBeCode($e1);
expect($f2)->toBeCode($e2);
});
}

namespace Irrelevant {}

namespace Sub\Space {
test('relative other namespace (braced)', function () {
$f1 = fn (Foo $foo): Foo => new Foo();
$e1 = 'fn (\Sub\Space\Foo $foo): \Sub\Space\Foo => new \Sub\Space\Foo()';

expect($f1)->toBeCode($e1);
});
}

namespace {
test('back to global namespace (braced)', function () {
$f1 = fn (Foo $foo): Foo => new Foo();
$e1 = 'fn (\Foo $foo): \Foo => new \Foo()';
expect($f1)->toBeCode($e1);
});
}

namespace Irrelevant {
// Shouldn't be used below, as not in the same namespace
use Wrong as Qux;
}

namespace Space {
test('no using use from other namespace', function () {
$f1 = fn (Qux $qux) => true;
$e1 = 'fn (\Space\Qux $qux) => true';

expect($f1)->toBeCode($e1);
});

// Shouldn't be used above, as declared after usage. Not currently supported though.
// use AlsoWrong as Qux;
}
41 changes: 41 additions & 0 deletions tests/ReflectionClosureNonBracedNamespaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Space;

test('relative namespace (non-braced)', function () {
$f1 = fn (Foo $foo): Foo => new Foo();
$e1 = 'fn (\Space\Foo $foo): \Space\Foo => new \Space\Foo()';

$f2 = fn (Foo\Bar $fooBar): Foo\Bar => new Foo\Bar();
$e2 = 'fn (\Space\Foo\Bar $fooBar): \Space\Foo\Bar => new \Space\Foo\Bar()';

expect($f1)->toBeCode($e1);
expect($f2)->toBeCode($e2);
});

namespace Irrelevant;
namespace Sub\Space;

test('relative other namespace (non-braced)', function () {
$f1 = fn (Foo $foo): Foo => new Foo();
$e1 = 'fn (\Sub\Space\Foo $foo): \Sub\Space\Foo => new \Sub\Space\Foo()';

expect($f1)->toBeCode($e1);
});

namespace Irrelevant;

// Shouldn't be used below, as not in the same namespace
use Wrong as Qux;

namespace Space;

test('not using use from other namespace', function () {
$f1 = fn (Qux $qux) => true;
$e1 = 'fn (\Space\Qux $qux) => true';

expect($f1)->toBeCode($e1);
});

// Shouldn't be used above, as declared after usage. Not currently supported though.
// use AlsoWrong as Qux;