diff --git a/composer.json b/composer.json index c36f93c7f75f6..c027a6d1e2a5f 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "elasticsearch/elasticsearch": "~7.13.1", "guzzlehttp/guzzle": "^6.3.3", "laminas/laminas-captcha": "^2.10", - "laminas/laminas-code": "^3.5.1", + "laminas/laminas-code": "~4.4.2", "laminas/laminas-db": "^2.12.0", "laminas/laminas-dependency-plugin": "^2.1.0", "laminas/laminas-di": "^3.2.0", diff --git a/composer.lock b/composer.lock index bf3fd98bbd49e..939d9b519dbe2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fb578a81f7a819082165f73582c28dc2", + "content-hash": "35b745645c685e44f50cf40d33295f3e", "packages": [ { "name": "aws/aws-sdk-php", @@ -1456,39 +1456,37 @@ }, { "name": "laminas/laminas-code", - "version": "3.5.1", + "version": "4.4.2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "b549b70c0bb6e935d497f84f750c82653326ac77" + "reference": "54251ab2b16c41c6980387839496b235f5f6e10b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/b549b70c0bb6e935d497f84f750c82653326ac77", - "reference": "b549b70c0bb6e935d497f84f750c82653326ac77", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/54251ab2b16c41c6980387839496b235f5f6e10b", + "reference": "54251ab2b16c41c6980387839496b235f5f6e10b", "shasum": "" }, "require": { - "laminas/laminas-eventmanager": "^3.3", - "laminas/laminas-zendframework-bridge": "^1.1", - "php": "^7.3 || ~8.0.0" + "php": "^7.4 || ~8.0.0" }, "conflict": { "phpspec/prophecy": "<1.9.0" }, - "replace": { - "zendframework/zend-code": "^3.4.1" - }, "require-dev": { "doctrine/annotations": "^1.10.4", "ext-phar": "*", - "laminas/laminas-coding-standard": "^1.0.0", + "laminas/laminas-coding-standard": "^2.1.4", "laminas/laminas-stdlib": "^3.3.0", - "phpunit/phpunit": "^9.4.2" + "phpunit/phpunit": "^9.4.2", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3.1" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" + "laminas/laminas-stdlib": "Laminas\\Stdlib component", + "laminas/laminas-zendframework-bridge": "A bridge with Zend Framework" }, "type": "library", "autoload": { @@ -1504,7 +1502,8 @@ "homepage": "https://laminas.dev", "keywords": [ "code", - "laminas" + "laminas", + "laminasframework" ], "support": { "chat": "https://laminas.dev/chat", @@ -1520,7 +1519,7 @@ "type": "community_bridge" } ], - "time": "2020-11-30T20:16:31+00:00" + "time": "2021-07-09T11:58:40+00:00" }, { "name": "laminas/laminas-config", diff --git a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php index 3a47f4547d4cb..dabb176dbb9b0 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php +++ b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php @@ -6,8 +6,6 @@ namespace Magento\TestFramework\Integrity\Library; use Laminas\Code\Reflection\ClassReflection; -use Laminas\Code\Reflection\FileReflection; -use Laminas\Code\Reflection\ParameterReflection; use ReflectionClass; use ReflectionException; use ReflectionParameter; @@ -18,40 +16,36 @@ class Injectable { /** - * @var \ReflectionException[] + * @var string[] */ protected $dependencies = []; /** * Get dependencies * - * @param FileReflection $fileReflection - * @return \ReflectionException[] - * @throws \ReflectionException + * @param ClassReflection $class + * + * @return string[] + * @throws ReflectionException */ - public function getDependencies(FileReflection $fileReflection) + public function getDependencies(ClassReflection $class): array { - foreach ($fileReflection->getClasses() as $class) { - /** @var ClassReflection $class */ - foreach ($class->getMethods() as $method) { - /** @var \Laminas\Code\Reflection\MethodReflection $method */ - if ($method->getDeclaringClass()->getName() != $class->getName()) { - continue; - } + foreach ($class->getMethods() as $method) { + if ($method->getDeclaringClass()->getName() !== $class->getName()) { + continue; + } - foreach ($method->getParameters() as $parameter) { - try { - /** @var ParameterReflection $parameter */ - $dependency = $this->getParameterClass($parameter); - if ($dependency instanceof ClassReflection) { - $this->dependencies[] = $dependency->getName(); - } - } catch (\ReflectionException $e) { - if (preg_match('#^Class ([A-Za-z0-9_\\\\]+) does not exist$#', $e->getMessage(), $result)) { - $this->dependencies[] = $result[1]; - } else { - throw $e; - } + foreach ($method->getParameters() as $parameter) { + try { + $dependency = $this->getParameterClass($parameter); + if ($dependency !== null) { + $this->dependencies[] = $dependency->getName(); + } + } catch (ReflectionException $e) { + if (preg_match('#^Class ([A-Za-z0-9_\\\\]+) does not exist$#', $e->getMessage(), $result)) { + $this->dependencies[] = $result[1]; + } else { + throw $e; } } } diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php deleted file mode 100644 index 1be796742c079..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php +++ /dev/null @@ -1,180 +0,0 @@ -injectable = new Injectable(); - $this->fileReflection = $this->getMockBuilder( - \Laminas\Code\Reflection\FileReflection::class - )->disableOriginalConstructor()->getMock(); - - $classReflection = $this->getMockBuilder( - \Laminas\Code\Reflection\ClassReflection::class - )->disableOriginalConstructor()->getMock(); - - $methodReflection = $this->getMockBuilder( - \Laminas\Code\Reflection\MethodReflection::class - )->disableOriginalConstructor()->getMock(); - - $this->parameterReflection = $this->getMockBuilder( - \Laminas\Code\Reflection\ParameterReflection::class - )->disableOriginalConstructor()->getMock(); - - $this->declaredClass = $this->getMockBuilder( - \Laminas\Code\Reflection\ClassReflection::class - )->disableOriginalConstructor()->getMock(); - - $methodReflection->expects( - $this->once() - )->method( - 'getDeclaringClass' - )->willReturn( - $this->declaredClass - ); - - $methodReflection->expects( - $this->any() - )->method( - 'getParameters' - )->willReturn( - [$this->parameterReflection] - ); - - $classReflection->expects( - $this->once() - )->method( - 'getMethods' - )->willReturn( - [$methodReflection] - ); - - $this->fileReflection->expects( - $this->once() - )->method( - 'getClasses' - )->willReturn( - [$classReflection] - ); - } - - /** - * Covered getDependencies - * - * @test - */ - public function testGetDependencies() - { - $classReflection = $this->getMockBuilder( - \Laminas\Code\Reflection\ClassReflection::class - )->disableOriginalConstructor()->getMock(); - - $classReflection->expects( - $this->once() - )->method( - 'getName' - )->willReturn( - \Magento\Framework\DataObject::class - ); - - $this->parameterReflection->expects( - $this->once() - )->method( - 'getClass' - )->willReturn( - $classReflection - ); - - $this->assertEquals( - [\Magento\Framework\DataObject::class], - $this->injectable->getDependencies($this->fileReflection) - ); - } - - /** - * Covered getDependencies - * - * @test - */ - public function testGetDependenciesWithException() - { - $this->parameterReflection->expects($this->once())->method('getClass')->willReturnCallback( - - function () { - throw new \ReflectionException('Class Magento\Framework\DataObject does not exist'); - } - - ); - - $this->assertEquals( - - [\Magento\Framework\DataObject::class], - $this->injectable->getDependencies($this->fileReflection) - ); - } - - /** - * Covered with some different exception method - * - * @test - */ - public function testGetDependenciesWithOtherException() - { - $this->expectException(\ReflectionException::class); - - $this->parameterReflection->expects($this->once())->method('getClass')->willReturnCallback( - - function () { - throw new \ReflectionException('Some message'); - } - - ); - - $this->injectable->getDependencies($this->fileReflection); - } - - /** - * Covered when method declared in parent class - * - * @test - */ - public function testGetDependenciesWhenMethodDeclaredInParentClass() - { - $this->declaredClass->expects($this->once())->method('getName')->willReturn('ParentClass'); - - $this->injectable->getDependencies($this->fileReflection); - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/InjectableTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/InjectableTest.php new file mode 100644 index 0000000000000..f557243d48683 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/InjectableTest.php @@ -0,0 +1,41 @@ +getDependencies($classReflection); + $expectedResult = [ + 'Magento\Framework\DataObject', + 'TestNamespace\Some\SomeTestClass', + 'TestNamespace\Other\Test', + ]; + + $this->assertEquals($expectedResult, $actualResult); + } +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ParserFactoryTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ParserFactoryTest.php similarity index 91% rename from dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ParserFactoryTest.php rename to dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ParserFactoryTest.php index 506ee830ec539..57885ec39c81a 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ParserFactoryTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ParserFactoryTest.php @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Test\Integrity\Library\PhpParser; - -use Magento\TestFramework\Integrity\Library\PhpParser\ParserFactory; +namespace Magento\TestFramework\Integrity\Library\PhpParser; /** */ diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/StaticCallsTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/StaticCallsTest.php similarity index 91% rename from dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/StaticCallsTest.php rename to dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/StaticCallsTest.php index aa1bfa8b3c8bb..eae57ca360a9b 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/StaticCallsTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/StaticCallsTest.php @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Test\Integrity\Library\PhpParser; - -use Magento\TestFramework\Integrity\Library\PhpParser\StaticCalls; +namespace Magento\TestFramework\Integrity\Library\PhpParser; /** */ @@ -46,27 +44,27 @@ public function testGetDependencies() ]; $this->tokens->expects($this->any())->method('getPreviousToken')->willReturnCallback( - + function ($k) use ($tokens) { return $tokens[$k - 1]; } - + ); $this->tokens->expects($this->any())->method('getTokenCodeByKey')->willReturnCallback( - + function ($k) use ($tokens) { return $tokens[$k][0]; } - + ); $this->tokens->expects($this->any())->method('getTokenValueByKey')->willReturnCallback( - + function ($k) use ($tokens) { return $tokens[$k][1]; } - + ); $throws = new StaticCalls($this->tokens); diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ThrowsTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ThrowsTest.php similarity index 92% rename from dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ThrowsTest.php rename to dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ThrowsTest.php index 6459161f24827..0c18b9a4e98fc 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/ThrowsTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/ThrowsTest.php @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Test\Integrity\Library\PhpParser; - -use Magento\TestFramework\Integrity\Library\PhpParser\Throws; +namespace Magento\TestFramework\Integrity\Library\PhpParser; /** */ @@ -49,19 +47,19 @@ public function testGetDependencies() ]; $this->tokens->expects($this->any())->method('getTokenCodeByKey')->willReturnCallback( - + function ($k) use ($tokens) { return $tokens[$k][0]; } - + ); $this->tokens->expects($this->any())->method('getTokenValueByKey')->willReturnCallback( - + function ($k) use ($tokens) { return $tokens[$k][1]; } - + ); $throws = new Throws($this->tokens); diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/TokensTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/TokensTest.php similarity index 96% rename from dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/TokensTest.php rename to dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/TokensTest.php index 102a1a69d9bcf..2f881d5585655 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/TokensTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/TokensTest.php @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Test\Integrity\Library\PhpParser; - -use Magento\TestFramework\Integrity\Library\PhpParser\Tokens; +namespace Magento\TestFramework\Integrity\Library\PhpParser; /** */ diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/UsesTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/UsesTest.php similarity index 97% rename from dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/UsesTest.php rename to dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/UsesTest.php index 6033e7e6dee98..32fa0afe956c2 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/PhpParser/UsesTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/PhpParser/UsesTest.php @@ -3,9 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Test\Integrity\Library\PhpParser; - -use Magento\TestFramework\Integrity\Library\PhpParser\Uses; +namespace Magento\TestFramework\Integrity\Library\PhpParser; /** */ diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/_files/DummyInjectableClass.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/_files/DummyInjectableClass.php new file mode 100644 index 0000000000000..bc54eeef5c9f8 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Integrity/Library/_files/DummyInjectableClass.php @@ -0,0 +1,22 @@ +errors = []; $componentRegistrar = new ComponentRegistrar(); - $fileReflection = new FileReflection($file); - $tokens = new Tokens($fileReflection->getContents(), new ParserFactory()); + $reflectedFilePath = $this->getFilePath($file); + $tokens = new Tokens(file_get_contents($reflectedFilePath), new ParserFactory()); $tokens->parseContent(); + $fileScanner = new FileClassScanner($file); + $className = $fileScanner->getClassName(); + if ($className) { // could be not a class but just a php-file + $class = new ClassReflection($className); + $classUses = (new Injectable())->getDependencies($class); + } else { + $classUses = []; + } + $dependencies = array_merge( - (new Injectable())->getDependencies($fileReflection), + $classUses, $tokens->getDependencies() ); $allowedNamespaces = str_replace('\\', '\\\\', implode('|', $this->getAllowedNamespaces())); @@ -83,7 +95,7 @@ function ($file) { foreach ($libraryPaths as $libraryPath) { $filePath = str_replace('\\', '/', $libraryPath . '/' . $dependencyPath . '.php'); if (preg_match($pattern, $dependency) && !file_exists($filePath)) { - $this->errors[$fileReflection->getFileName()][] = $dependency; + $this->errors[basename($reflectedFilePath)][] = $dependency; } } } @@ -96,6 +108,29 @@ function ($file) { ); } + /** + * copied from laminas-code 3.5.1 + * + * @param string $filename + * + * @return string + */ + private function getFilePath(string $filename): string + { + if (($fileRealPath = realpath($filename)) === false) { + $fileRealPath = stream_resolve_include_path($filename); + } + + if (! $fileRealPath) { + throw new InvalidArgumentException(sprintf( + 'No file for %s was found.', + $filename + )); + } + + return $fileRealPath; + } + /** * @inheritdoc */ diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php index fd87d02e2246a..d7ed6e2127f44 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php @@ -5,7 +5,9 @@ */ namespace Magento\Test\Integrity\Magento\Framework\Api; +use Laminas\Code\Reflection\ClassReflection; use Magento\Framework\App\Utility\Files; +use Magento\Setup\Module\Di\Code\Reader\FileClassScanner; /** * Check interfaces inherited from \Magento\Framework\Api\ExtensibleDataInterface. @@ -170,18 +172,18 @@ function ($filename) { if (preg_match('/' . $extensibleClassPattern . '/', $fileContent) && !preg_match('/' . $abstractExtensibleClassPattern . '/', $fileContent) ) { - $fileReflection = new \Laminas\Code\Reflection\FileReflection($filename, true); - foreach ($fileReflection->getClasses() as $classReflection) { - if ($classReflection->isSubclassOf(self::EXTENSIBLE_DATA_INTERFACE)) { - $methodsToCheck = ['setExtensionAttributes', 'getExtensionAttributes']; - foreach ($methodsToCheck as $methodName) { - try { - $classReflection->getMethod($methodName); - } catch (\ReflectionException $e) { - $className = $classReflection->getName(); - $errors[] = "'{$className}::{$methodName}()' must be declared or " - . "'{$className}' should not be inherited from extensible class."; - } + $fileClassScanner = new FileClassScanner($filename); + $classReflection = new ClassReflection($fileClassScanner->getClassName()); + + if ($classReflection->isSubclassOf(self::EXTENSIBLE_DATA_INTERFACE)) { + $methodsToCheck = ['setExtensionAttributes', 'getExtensionAttributes']; + foreach ($methodsToCheck as $methodName) { + try { + $classReflection->getMethod($methodName); + } catch (\ReflectionException $e) { + $className = $classReflection->getName(); + $errors[] = "'{$className}::{$methodName}()' must be declared or " + . "'{$className}' should not be inherited from extensible class."; } } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php index b5e61ed2ece8e..59183dc174682 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php @@ -5,7 +5,10 @@ */ namespace Magento\Test\Integrity; +use Exception; use Magento\Framework\App\Utility\Files; +use Magento\Setup\Module\Di\Code\Reader\FileClassScanner; +use PHPUnit\Framework\TestCase; use ReflectionClass; use ReflectionException; use ReflectionParameter; @@ -13,7 +16,7 @@ /** * Tests @api annotated code integrity */ -class PublicCodeTest extends \PHPUnit\Framework\TestCase +class PublicCodeTest extends TestCase { /** * List of simple return types that are used in docblocks. @@ -87,7 +90,7 @@ public function testAllBlocksReferencedInLayoutArePublic($layoutFile) * Find all layout update files in magento modules and themes. * * @return array - * @throws \Exception + * @throws Exception */ public function layoutFilesDataProvider() { @@ -141,40 +144,30 @@ public function testAllPHPClassesReferencedFromPublicClassesArePublic($class) /** * Retrieve list of all interfaces and classes in Magento codebase that are marked with @api annotation. + * * @return array - * @throws \Exception + * @throws Exception */ - public function publicPHPTypesDataProvider() + public function publicPHPTypesDataProvider(): array { $files = Files::init()->getPhpFiles(Files::INCLUDE_LIBS | Files::INCLUDE_APP_CODE); $result = []; foreach ($files as $file) { $fileContents = \file_get_contents($file); if (strpos($fileContents, '@api') !== false) { - foreach ($this->getDeclaredClassesAndInterfaces($file) as $class) { - if (!in_array($class->getName(), $this->getWhitelist()) - && (class_exists($class->getName()) || interface_exists($class->getName())) - ) { - $result[$class->getName()] = [$class->getName()]; - } + $fileClassScanner = new FileClassScanner($file); + $className = $fileClassScanner->getClassName(); + + if (!in_array($className, $this->getWhitelist()) + && (class_exists($className) || interface_exists($className)) + ) { + $result[$className] = [$className]; } } } return $result; } - /** - * Retrieve list of classes and interfaces declared in the file - * - * @param string $file - * @return \Laminas\Code\Scanner\ClassScanner[] - */ - private function getDeclaredClassesAndInterfaces($file) - { - $fileScanner = new \Magento\Setup\Module\Di\Code\Reader\FileScanner($file); - return $fileScanner->getClasses(); - } - /** * Check if a class is @api annotated * diff --git a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php index b3521698bf396..b55578a8fbbd3 100644 --- a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php +++ b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php @@ -6,6 +6,7 @@ namespace Magento\Framework\Code\Generator; +use InvalidArgumentException; use Laminas\Code\Generator\MethodGenerator; use Laminas\Code\Generator\PropertyGenerator; @@ -120,9 +121,10 @@ public function addMethods(array $methods) ) > 0 ) { $parametersArray = []; - foreach ($methodOptions['parameters'] as $parameterOptions) { + foreach ($methodOptions['parameters'] as $position => $parameterOptions) { $parameterObject = new \Laminas\Code\Generator\ParameterGenerator(); $this->_setDataToObject($parameterObject, $parameterOptions, $this->_parameterOptions); + $parameterObject->setPosition((int) $position); $parametersArray[] = $parameterObject; } @@ -151,12 +153,12 @@ public function addMethods(array $methods) * * @param MethodGenerator $method * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addMethodFromGenerator(MethodGenerator $method) { - if (!is_string($method->getName())) { - throw new \InvalidArgumentException('addMethodFromGenerator() expects string for name'); + if (empty($method->getName()) || !is_string($method->getName())) { + throw new InvalidArgumentException('addMethodFromGenerator() expects non-empty string for name'); } return parent::addMethodFromGenerator($method); @@ -167,7 +169,7 @@ public function addMethodFromGenerator(MethodGenerator $method) * * @param array $properties * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addProperties(array $properties) { @@ -196,12 +198,12 @@ public function addProperties(array $properties) * * @param PropertyGenerator $property * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addPropertyFromGenerator(PropertyGenerator $property) { - if (!is_string($property->getName())) { - throw new \InvalidArgumentException('addPropertyFromGenerator() expects string for name'); + if (empty($property->getName()) || !is_string($property->getName())) { + throw new InvalidArgumentException('addPropertyFromGenerator() expects non-empty string for name'); } return parent::addPropertyFromGenerator($property); diff --git a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php index 8c1192d855ee4..27d0643c5a421 100644 --- a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php +++ b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php @@ -257,9 +257,9 @@ protected function _validateData() $this->_addError('Source class ' . $sourceClassName . ' doesn\'t exist.'); return false; } elseif (/** - * If makeResultFileDirectory only fails because the file is already created, - * a competing process has generated the file, no exception should be thrown. - */ + * If makeResultFileDirectory only fails because the file is already created, + * a competing process has generated the file, no exception should be thrown. + */ !$this->_ioObject->makeResultFileDirectory($resultClassName) && !$this->_ioObject->fileExists($resultDir) ) { @@ -401,9 +401,12 @@ protected function _getMethodParameterInfo(\ReflectionParameter $parameter) { $parameterInfo = [ 'name' => $parameter->getName(), - 'passedByReference' => $parameter->isPassedByReference(), - 'variadic' => $parameter->isVariadic() + 'passedByReference' => $parameter->isPassedByReference() ]; + if ($parameter->isVariadic()) { + $parameterInfo['variadic'] = $parameter->isVariadic(); + } + if ($type = $this->extractParameterType($parameter)) { $parameterInfo['type'] = $type; } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php index a11e0dda16503..78981c6b35048 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php @@ -5,15 +5,15 @@ */ namespace Magento\Framework\Code\Test\Unit\Generator; +use Laminas\Code\Generator\AbstractMemberGenerator; use Laminas\Code\Generator\DocBlock\Tag; -use Laminas\Code\Generator\ParameterGenerator; -use Laminas\Code\Generator\ValueGenerator; -use PHPUnit\Framework\TestCase; -use Magento\Framework\Code\Generator\ClassGenerator; use Laminas\Code\Generator\DocBlockGenerator; -use Laminas\Code\Generator\AbstractMemberGenerator; use Laminas\Code\Generator\MethodGenerator; +use Laminas\Code\Generator\ParameterGenerator; use Laminas\Code\Generator\PropertyGenerator; +use Laminas\Code\Generator\ValueGenerator; +use Magento\Framework\Code\Generator\ClassGenerator; +use PHPUnit\Framework\TestCase; /** * Test for Magento\Framework\Code\Generator\ClassGenerator @@ -84,8 +84,7 @@ class ClassGeneratorTest extends TestCase 'name' => 'data', 'type' => 'array', 'defaultValue' => [], - 'passedByReference' => true, - 'variadic' => false + 'passedByReference' => true ], ], 'body' => 'return 1;', @@ -265,13 +264,10 @@ protected function _assertVisibility( $this->assertEquals($expectedVisibility, $actualObject->getVisibility()); } - /** - * Correct behaviour of addMethodFromGenerator is already tested in testAddMethods - */ - public function testAddMethodFromGenerator() + public function testAddMethodFromGenerator(): void { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('addMethodFromGenerator() expects string for name'); + $this->expectExceptionMessage('addMethodFromGenerator() expects non-empty string for name'); $invalidMethod = new MethodGenerator(); $this->_model->addMethodFromGenerator($invalidMethod); } @@ -312,13 +308,10 @@ public function testAddProperties() } } - /** - * Correct behaviour of addPropertyFromGenerator is already tested in testAddProperties - */ - public function testAddPropertyFromGenerator() + public function testAddPropertyFromGenerator(): void { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('addPropertyFromGenerator() expects string for name'); + $this->expectExceptionMessage('addPropertyFromGenerator() expects non-empty string for name'); $invalidProperty = new PropertyGenerator(); $this->_model->addPropertyFromGenerator($invalidProperty); } diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 4a3ed34810723..43e9d97dbd2d4 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -142,7 +142,7 @@ protected function _getParameterList(array $parameters) array_map( function ($item) { $output = ''; - if ($item['variadic']) { + if (!empty($item['variadic'])) { $output .= '... '; } diff --git a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php index 7de6fe9e97761..71d7fba5bb750 100644 --- a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php @@ -6,14 +6,15 @@ namespace Magento\Framework\Reflection; -use Magento\Framework\Exception\SerializationException; -use Magento\Framework\Phrase; use Laminas\Code\Reflection\ClassReflection; use Laminas\Code\Reflection\DocBlock\Tag\ParamTag; use Laminas\Code\Reflection\DocBlock\Tag\ReturnTag; use Laminas\Code\Reflection\DocBlockReflection; use Laminas\Code\Reflection\MethodReflection; use Laminas\Code\Reflection\ParameterReflection; +use Magento\Framework\Exception\SerializationException; +use Magento\Framework\Phrase; +use Magento\Setup\Module\Di\Code\Reader\FileScanner; /** * Type processor of config reader properties @@ -553,9 +554,9 @@ public function getParamType(ParameterReflection $param) */ public function getAliasMapping(ClassReflection $sourceClass): array { - $sourceFileName = $sourceClass->getDeclaringFile(); + $uses = (new FileScanner($sourceClass->getFileName()))->getUses(); $aliases = []; - foreach ($sourceFileName->getUses() as $use) { + foreach ($uses as $use) { if ($use['as'] !== null) { $aliases[$use['as']] = $use['use']; } else { diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index 49b04b058f565..28ceab73d1b87 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -26,7 +26,7 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.9 || ^2.0", "guzzlehttp/guzzle": "^6.3.3", - "laminas/laminas-code": "^3.5.1", + "laminas/laminas-code": "~4.4.2", "laminas/laminas-http": "^2.6.0", "laminas/laminas-mail": "^2.9.0", "laminas/laminas-mime": "^2.8.0", diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileClassScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileClassScanner.php index c5e5230d253cc..df70d64ed0f40 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileClassScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileClassScanner.php @@ -34,7 +34,7 @@ class FileClassScanner /** * The class name found in the file. * - * @var bool + * @var string|bool */ private $className = false; @@ -76,7 +76,7 @@ public function getFileContents() } /** - * Retrieves the first class found in a class file. + * Retrieves the first class found in a file. * * @return string */ @@ -98,6 +98,7 @@ public function getClassName(): string * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * * @return string */ private function extract(): string @@ -145,9 +146,10 @@ private function extract(): string $namespaceParts = []; $bracedNamespace = $this->isBracedNamespace($index); break; + case T_TRAIT: case T_CLASS: // Current loop contains the class keyword. Next loop will have the class name itself. - if ($braceLevel == 0 || ($bracedNamespace && $braceLevel == 1)) { + if ($braceLevel === 0 || ($bracedNamespace && $braceLevel === 1)) { $triggerClass = true; } break; @@ -155,7 +157,7 @@ private function extract(): string // We have a class name, let's concatenate and return it! if ($class !== '') { - $fqClassName = trim(join('', $namespaceParts)) . trim($class); + $fqClassName = trim(implode('', $namespaceParts)) . trim($class); return $fqClassName; } } diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php index 5b2667132bae4..f1e2abd5ad247 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php @@ -6,18 +6,61 @@ namespace Magento\Setup\Module\Di\Code\Reader; +use Laminas\Code\Exception; +use Laminas\Code\Exception\RuntimeException; + /** * FileScanner code reader * * @SuppressWarnings(PHPMD) */ -class FileScanner extends \Laminas\Code\Scanner\FileScanner +class FileScanner { + /** + * @var string + */ + protected $file; + + /** + * @var bool + */ + protected $isScanned = false; + + /** + * @var array + */ + protected $tokens = []; + + /** + * @var array + */ + protected $infos = []; + /** * @var int */ private $tokenType; + /** + * copied from laminas-code 3.5.1 + * + * @param string $file + * + * @throws Exception\InvalidArgumentException + */ + public function __construct(string $file) + { + $this->file = $file; + if (!file_exists($file)) { + throw new Exception\InvalidArgumentException(sprintf( + 'File "%s" not found', + $file + )); + } + $tokens = token_get_all(file_get_contents($file)); + $this->tokens = $tokens; + } + /** * @inheritDoc */ @@ -28,7 +71,7 @@ protected function scan() } if (!$this->tokens) { - throw new \Laminas\Code\Exception\RuntimeException('No tokens were provided'); + throw new RuntimeException('No tokens were provided'); } /** @@ -331,10 +374,10 @@ protected function scan() } if ($this->tokenType === null) { - if ($tokenContent == '{') { + if ($tokenContent === '{') { $classBraceCount++; } - if ($tokenContent == '}') { + if ($tokenContent === '}') { $classBraceCount--; if ($classBraceCount === 0) { goto SCANNER_CLASS_END; @@ -370,4 +413,55 @@ protected function scan() $this->isScanned = true; // phpcs:enable } + + /** + * copied from laminas-code 3.5.1 + * + * @param string|null $namespace + * + * @return array|null + */ + public function getUses(string $namespace = null): ?array + { + $this->scan(); + + return $this->getUsesNoScan($namespace); + } + + /** + * copied from laminas-code 3.5.1 + * + * @param string|null $namespace + * + * @return array|null + */ + protected function getUsesNoScan(string $namespace = null): ?array + { + $namespaces = []; + foreach ($this->infos as $info) { + if ($info['type'] === 'namespace') { + $namespaces[] = $info['namespace']; + } + } + + if ($namespace === null) { + $namespace = array_shift($namespaces); + } elseif (!in_array($namespace, $namespaces, true)) { + return null; + } + + $uses = []; + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + foreach ($info['statements'] as $statement) { + if ($info['namespace'] === $namespace) { + $uses[] = $statement; + } + } + } + + return $uses; + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/FileScannerTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/FileScannerTest.php index adaba7358423e..0f79e01ef9f7d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/FileScannerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/FileScannerTest.php @@ -10,31 +10,80 @@ use Magento\Setup\Module\Di\Code\Reader\FileScanner; use PHPUnit\Framework\TestCase; +/** + * Class FileScannerTest + */ class FileScannerTest extends TestCase { /** * @var FileScanner */ - private $object; + private $fileScanner; + /** + * @inheirtDoc + */ protected function setUp(): void { - $this->object = new FileScanner( - __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'classes.php' + $this->fileScanner = new FileScanner( + __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'TestClass.php' ); } - public function testGetClassesReturnsAllClassesAndInterfacesDeclaredInFile() + /** + * Check that all uses are found. + * + * @return void + */ + public function testGetUses(): void + { + $actualResult = $this->fileScanner->getUses(); + $expectedResult = $this->getExpectedResultForTestClass(); + + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * Check that all uses are found with correct namespace provided. + * + * @return void + */ + public function testGetUsesWithCorrectNamespace(): void + { + $actualResult = $this->fileScanner->getUses('Some\TestNamespace'); + $expectedResult = $this->getExpectedResultForTestClass(); + + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * Check that function returns null with wrong namespace provided. + * + * @return void + */ + public function testGetUsesWithAnotherNamespace(): void + { + $result = $this->fileScanner->getUses('Another\WrongNamespace'); + + $this->assertNull($result); + } + + /** + * Data provider for getUses test + * + * @return array + */ + private function getExpectedResultForTestClass(): array { - $classes = [ - 'My\NamespaceA\InterfaceA', - 'My\NamespaceA\ClassA', - 'My\NamespaceB\InterfaceB', - 'My\NamespaceB\ClassB', + return [ + [ + 'use' => 'Some\OtherNamespace\OtherClass', + 'as' => null + ], + [ + 'use' => 'Some\TestNamespace\TestInteface', + 'as' => 'TestAlias' + ] ]; - $this->assertCount(4, $this->object->getClasses()); - foreach ($this->object->getClasses() as $key => $class) { - $this->assertEquals($classes[$key], $class->getName()); - } } } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/_files/TestClass.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/_files/TestClass.php new file mode 100644 index 0000000000000..a8818a61ec7cb --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/_files/TestClass.php @@ -0,0 +1,15 @@ +