From 8ad4c4e02484b532724d39a75c83f36372cc74b8 Mon Sep 17 00:00:00 2001 From: Garion Herman Date: Wed, 30 Sep 2020 13:05:15 +1300 Subject: [PATCH] FIX Fix namespace parsing under PHP 8, tweak readability of parser $hadNamespace was ambiguously named, so the original PHP 8 support update marked it true when it was strictly meant to indicate that a namespace separator token had been encountered, resulting in bungled parsing of complex class specs like Class(["arg" => true]). --- src/Core/ClassInfo.php | 14 ++++++------ tests/php/Core/ClassInfoTest.php | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/Core/ClassInfo.php b/src/Core/ClassInfo.php index dc64b9324..377320d80 100644 --- a/src/Core/ClassInfo.php +++ b/src/Core/ClassInfo.php @@ -430,7 +430,7 @@ class ClassInfo // Keep track of the current bucket that we're putting data into $bucket = &$args; $bucketStack = []; - $hadNamespace = false; + $lastTokenWasNSSeparator = false; $currentKey = null; foreach ($tokens as $token) { @@ -443,20 +443,20 @@ class ClassInfo ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) ) { // PHP 8 exposes the FQCN as a single T_NAME_QUALIFIED or T_NAME_FULLY_QUALIFIED token - $class .= $token[1]; - $hadNamespace = true; + $class = $token[1]; } elseif ($class === null && is_array($token) && $token[0] === T_STRING) { $class = $token[1]; } elseif (is_array($token) && $token[0] === T_NS_SEPARATOR) { $class .= $token[1]; - $hadNamespace = true; + $lastTokenWasNSSeparator = true; } elseif ($token === '.') { // Treat service name separator as NS separator $class .= '.'; - $hadNamespace = true; - } elseif ($hadNamespace && is_array($token) && $token[0] === T_STRING) { + $lastTokenWasNSSeparator = true; + } elseif ($lastTokenWasNSSeparator && is_array($token) && $token[0] === T_STRING) { + // Found another section of a namespaced class $class .= $token[1]; - $hadNamespace = false; + $lastTokenWasNSSeparator = false; // Get arguments } elseif (is_array($token)) { switch ($token[0]) { diff --git a/tests/php/Core/ClassInfoTest.php b/tests/php/Core/ClassInfoTest.php index e730ff0a0..76050e34b 100644 --- a/tests/php/Core/ClassInfoTest.php +++ b/tests/php/Core/ClassInfoTest.php @@ -265,4 +265,41 @@ class ClassInfoTest extends SapphireTest 'ClassInfo::testClassesWithExtension() returns no classes after an extension being removed' ); } + + /** + * @dataProvider provideClassSpecCases + */ + public function testParseClassSpec($input, $output) + { + $this->assertEquals( + $output, + ClassInfo::parse_class_spec($input) + ); + } + + public function provideClassSpecCases() + { + return [ + 'Standard class' => [ + 'SimpleClass', + ['SimpleClass', []], + ], + 'Namespaced class' => [ + 'Foo\\Bar\\NamespacedClass', + ['Foo\\Bar\\NamespacedClass', []], + ], + 'Namespaced class with service name' => [ + 'Foo\\Bar\\NamespacedClass.withservicename', + ['Foo\\Bar\\NamespacedClass.withservicename', []], + ], + 'Namespaced class with argument' => [ + 'Foo\\Bar\\NamespacedClass(["with-arg" => true])', + ['Foo\\Bar\\NamespacedClass', [["with-arg" => true]]], + ], + 'Namespaced class with service name and argument' => [ + 'Foo\\Bar\\NamespacedClass.withmodifier(["and-arg" => true])', + ['Foo\\Bar\\NamespacedClass.withmodifier', [["and-arg" => true]]], + ], + ]; + } }