419 lines
15 KiB
PHP
Executable File
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;
|
|
}
|
|
}
|