magento2-docker/dev/tests/static/testsuite/Magento/Test/Integrity/Di/CompilerTest.php

419 lines
15 KiB
PHP
Executable File

<?php
/**
* Compiler test. Check compilation of DI definitions and code generation
*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Test\Integrity\Di;
use Magento\Framework\Api\Code\Generator\Mapper;
use Magento\Framework\Api\Code\Generator\SearchResults;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Interception\Code\InterfaceValidator;
use Magento\Framework\ObjectManager\Code\Generator\Converter;
use Magento\Framework\ObjectManager\Code\Generator\Factory;
use Magento\Framework\ObjectManager\Code\Generator\Repository;
use Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator;
use Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator;
use Magento\Framework\App\Utility\Files;
use Magento\TestFramework\Integrity\PluginValidator;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CompilerTest extends \PHPUnit\Framework\TestCase
{
/**
* @var string
*/
protected $_command;
/**
* @var \Magento\Framework\Shell
*/
protected $_shell;
/**
* @var string
*/
protected $_generationDir;
/**
* @var string
*/
protected $_compilationDir;
/**
* @var \Magento\Framework\ObjectManager\Config\Mapper\Dom()
*/
protected $_mapper;
/**
* @var \Magento\Framework\Code\Validator
*/
protected $_validator;
/**
* Class arguments reader
*
* @var PluginValidator
*/
protected $pluginValidator;
/**
* @var string[]|null
*/
private $pluginBlacklist;
protected function setUp(): void
{
$this->_shell = new \Magento\Framework\Shell(new \Magento\Framework\Shell\CommandRenderer());
$basePath = BP;
$basePath = str_replace('\\', '/', $basePath);
$directoryList = new DirectoryList($basePath);
$this->_generationDir = $directoryList->getPath(DirectoryList::GENERATED_CODE);
$this->_compilationDir = $directoryList->getPath(DirectoryList::GENERATED_METADATA);
$this->_command = 'php ' . $basePath . '/bin/magento setup:di:compile';
$booleanUtils = new \Magento\Framework\Stdlib\BooleanUtils();
$constInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Constant();
$argumentInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Composite(
[
'boolean' => new \Magento\Framework\Data\Argument\Interpreter\Boolean($booleanUtils),
'string' => new \Magento\Framework\Data\Argument\Interpreter\BaseStringUtils($booleanUtils),
'number' => new \Magento\Framework\Data\Argument\Interpreter\Number(),
'null' => new \Magento\Framework\Data\Argument\Interpreter\NullType(),
'object' => new \Magento\Framework\Data\Argument\Interpreter\DataObject($booleanUtils),
'const' => $constInterpreter,
'init_parameter' => new \Magento\Framework\App\Arguments\ArgumentInterpreter($constInterpreter),
],
\Magento\Framework\ObjectManager\Config\Reader\Dom::TYPE_ATTRIBUTE
);
// Add interpreters that reference the composite
$argumentInterpreter->addInterpreter(
'array',
new \Magento\Framework\Data\Argument\Interpreter\ArrayType($argumentInterpreter)
);
$this->_mapper = new \Magento\Framework\ObjectManager\Config\Mapper\Dom(
$argumentInterpreter,
$booleanUtils,
new \Magento\Framework\ObjectManager\Config\Mapper\ArgumentParser()
);
$this->_validator = new \Magento\Framework\Code\Validator();
$this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorIntegrity());
$this->_validator->add(new \Magento\Framework\Code\Validator\TypeDuplication());
$this->_validator->add(new \Magento\Framework\Code\Validator\ArgumentSequence());
$this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorArgumentTypes());
$this->pluginValidator = new PluginValidator(new InterfaceValidator());
}
/**
* Return plugin blacklist class names
*
* @return string[]
*/
private function getPluginBlacklist(): array
{
if ($this->pluginBlacklist === null) {
$blacklistFiles = str_replace(
'\\',
'/',
realpath(__DIR__) . '/../_files/blacklist/compiler_plugins*.txt'
);
$blacklistItems = [];
foreach (glob($blacklistFiles) as $fileName) {
$blacklistItems[] = file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
}
$blacklistItems = array_merge([], ...$blacklistItems);
$this->pluginBlacklist = $blacklistItems;
}
return $this->pluginBlacklist;
}
/**
* Validate DI config file
*
* @param string $file
*/
protected function _validateFile($file)
{
$dom = new \DOMDocument();
$dom->load($file);
$data = $this->_mapper->convert($dom);
foreach ($data as $instanceName => $parameters) {
if (!isset($parameters['parameters']) || empty($parameters['parameters'])) {
continue;
}
if (\Magento\Framework\App\Utility\Classes::isVirtual($instanceName)) {
$instanceName = \Magento\Framework\App\Utility\Classes::resolveVirtualType($instanceName);
}
if (!$this->_classExistsAsReal($instanceName)) {
continue;
}
$reflectionClass = new \ReflectionClass($instanceName);
$constructor = $reflectionClass->getConstructor();
if (!$constructor) {
$this->fail('Class ' . $instanceName . ' does not have __constructor');
}
$parameters = $parameters['parameters'];
$classParameters = $constructor->getParameters();
foreach ($classParameters as $classParameter) {
$parameterName = $classParameter->getName();
if (array_key_exists($parameterName, $parameters)) {
unset($parameters[$parameterName]);
}
}
$message = 'Configuration of ' . $instanceName . ' contains data for non-existed parameters: ' . implode(
', ',
array_keys($parameters)
);
$this->assertEmpty($parameters, $message);
}
}
/**
* Checks if class is a real one or generated Factory
* @param string $instanceName class name
* @throws \PHPUnit\Framework\AssertionFailedError
* @return bool
*/
protected function _classExistsAsReal($instanceName)
{
if (class_exists($instanceName)) {
return true;
}
// check for generated factory
if (substr($instanceName, -7) == 'Factory' && class_exists(substr($instanceName, 0, -7))) {
return false;
}
$this->fail('Detected configuration of non existed class: ' . $instanceName);
}
/**
* Get php classes list
*
* @return array
*/
protected function _phpClassesDataProvider()
{
$generationPath = str_replace('/', '\\', $this->_generationDir);
$files = Files::init()->getPhpFiles(Files::INCLUDE_APP_CODE | Files::INCLUDE_LIBS);
$patterns = ['/' . preg_quote($generationPath) . '/',];
$replacements = [''];
$componentRegistrar = new ComponentRegistrar();
foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
$patterns[] = '/' . preg_quote(str_replace('/', '\\', $modulePath)) . '/';
$replacements[] = '\\' . str_replace('_', '\\', $moduleName);
}
foreach ($componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libPath) {
$patterns[] = '/' . preg_quote(str_replace('/', '\\', $libPath)) . '/';
$replacements[] = '\\Magento\\Framework';
}
/** Convert file names into class name format */
$classes = [];
foreach ($files as $file) {
$file = str_replace('/', '\\', $file);
$filePath = preg_replace($patterns, $replacements, $file);
$className = substr($filePath, 0, -4);
if (class_exists($className, false)) {
$file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
$classes[$file] = $className;
}
}
/** Build class inheritance hierarchy */
$output = [];
$allowedFiles = array_keys($classes);
foreach ($classes as $class) {
if (!in_array($class, $output)) {
$output[] = $this->_buildInheritanceHierarchyTree($class, $allowedFiles);
}
}
$output = array_unique(array_merge([], ...$output));
/** Convert data into data provider format */
$outputClasses = [];
foreach ($output as $className) {
$outputClasses[] = [$className];
}
return $outputClasses;
}
/**
* Build inheritance hierarchy tree
*
* @param string $className
* @param array $allowedFiles
* @return array
*/
protected function _buildInheritanceHierarchyTree($className, array $allowedFiles)
{
$output = [];
if (0 !== strpos($className, '\\')) {
$className = '\\' . $className;
}
$class = new \ReflectionClass($className);
$parent = $class->getParentClass();
$file = false;
if ($parent) {
$file = str_replace('\\', DIRECTORY_SEPARATOR, $parent->getFileName());
}
/** Prevent analysis of non Magento classes */
if ($parent && in_array($file, $allowedFiles)) {
$output = array_merge(
$this->_buildInheritanceHierarchyTree($parent->getName(), $allowedFiles),
[$className],
$output
);
} else {
$output[] = $className;
}
return array_unique($output);
}
/**
* Validate class
*
* @param string $className
*/
protected function _validateClass($className)
{
try {
$this->_validator->validate($className);
} catch (\Magento\Framework\Exception\ValidatorException $exceptions) {
$this->fail($exceptions->getMessage());
} catch (\ReflectionException $exceptions) {
$this->fail($exceptions->getMessage());
}
}
/**
* Validate DI configuration
*/
public function testConfigurationOfInstanceParameters()
{
$invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
$invoker(
function ($file) {
$this->_validateFile($file);
},
Files::init()->getDiConfigs(true)
);
}
/**
* Validate constructor integrity
*/
public function testConstructorIntegrity()
{
$generatorIo = new \Magento\Framework\Code\Generator\Io(
new \Magento\Framework\Filesystem\Driver\File(),
$this->_generationDir
);
$generator = new \Magento\Framework\Code\Generator(
$generatorIo,
[
Factory::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Factory::class,
Repository::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Repository::class,
Converter::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Converter::class,
Mapper::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\Mapper::class,
SearchResults::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\SearchResults::class,
ExtensionAttributesInterfaceGenerator::ENTITY_TYPE =>
\Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::class,
ExtensionAttributesGenerator::ENTITY_TYPE =>
\Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::class
]
);
$generationAutoloader = new \Magento\Framework\Code\Generator\Autoloader($generator);
spl_autoload_register([$generationAutoloader, 'load']);
$invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
$invoker(
function ($className) {
$this->_validateClass($className);
},
$this->_phpClassesDataProvider()
);
spl_autoload_unregister([$generationAutoloader, 'load']);
}
/**
* Test consistency of plugin interfaces
*/
public function testPluginInterfaces()
{
$invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
$invoker(
function ($plugin, $type) {
$this->validatePlugins($plugin, $type);
},
$this->pluginDataProvider()
);
}
/**
* Validate plugin interface
*
* @param string $plugin
* @param string $type
*/
protected function validatePlugins($plugin, $type)
{
try {
$module = \Magento\Framework\App\Utility\Classes::getClassModuleName($type);
if (Files::init()->isModuleExists($module)) {
$this->pluginValidator->validate($plugin, $type);
}
} catch (\Magento\Framework\Exception\ValidatorException $exception) {
$this->fail($exception->getMessage());
}
}
/**
* Get application plugins
*
* @return array
* @throws \Exception
*/
protected function pluginDataProvider()
{
$files = Files::init()->getDiConfigs();
$plugins = [];
foreach ($files as $file) {
$dom = new \DOMDocument();
$dom->load($file);
$xpath = new \DOMXPath($dom);
$pluginList = $xpath->query('//config/type/plugin');
foreach ($pluginList as $node) {
/** @var $node \DOMNode */
$type = $node->parentNode->attributes->getNamedItem('name')->nodeValue;
$type = \Magento\Framework\App\Utility\Classes::resolveVirtualType($type);
if ($node->attributes->getNamedItem('type')) {
$plugin = $node->attributes->getNamedItem('type')->nodeValue;
if (!in_array($plugin, $this->getPluginBlacklist())) {
$plugin = \Magento\Framework\App\Utility\Classes::resolveVirtualType($plugin);
$plugins[] = [$plugin, $type];
}
}
}
}
return $plugins;
}
}