Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
CompilerTest.php
Go to the documentation of this file.
1 <?php
9 
22 
26 class CompilerTest extends \PHPUnit\Framework\TestCase
27 {
31  protected $_command;
32 
36  protected $_shell;
37 
41  protected $_generationDir;
42 
46  protected $_compilationDir;
47 
51  protected $_mapper;
52 
56  protected $_validator;
57 
63  protected $pluginValidator;
64 
68  private $pluginBlacklist;
69 
70  protected function setUp()
71  {
72  $this->_shell = new \Magento\Framework\Shell(new \Magento\Framework\Shell\CommandRenderer());
73  $basePath = BP;
74  $basePath = str_replace('\\', '/', $basePath);
75 
76  $directoryList = new DirectoryList($basePath);
77  $this->_generationDir = $directoryList->getPath(DirectoryList::GENERATED_CODE);
78  $this->_compilationDir = $directoryList->getPath(DirectoryList::GENERATED_METADATA);
79 
80  $this->_command = 'php ' . $basePath . '/bin/magento setup:di:compile';
81 
82  $booleanUtils = new \Magento\Framework\Stdlib\BooleanUtils();
83  $constInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Constant();
84  $argumentInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Composite(
85  [
86  'boolean' => new \Magento\Framework\Data\Argument\Interpreter\Boolean($booleanUtils),
87  'string' => new \Magento\Framework\Data\Argument\Interpreter\BaseStringUtils($booleanUtils),
88  'number' => new \Magento\Framework\Data\Argument\Interpreter\Number(),
89  'null' => new \Magento\Framework\Data\Argument\Interpreter\NullType(),
90  'object' => new \Magento\Framework\Data\Argument\Interpreter\DataObject($booleanUtils),
91  'const' => $constInterpreter,
92  'init_parameter' => new \Magento\Framework\App\Arguments\ArgumentInterpreter($constInterpreter),
93  ],
94  \Magento\Framework\ObjectManager\Config\Reader\Dom::TYPE_ATTRIBUTE
95  );
96  // Add interpreters that reference the composite
97  $argumentInterpreter->addInterpreter(
98  'array',
99  new \Magento\Framework\Data\Argument\Interpreter\ArrayType($argumentInterpreter)
100  );
101 
102  $this->_mapper = new \Magento\Framework\ObjectManager\Config\Mapper\Dom(
103  $argumentInterpreter,
104  $booleanUtils,
105  new \Magento\Framework\ObjectManager\Config\Mapper\ArgumentParser()
106  );
107  $this->_validator = new \Magento\Framework\Code\Validator();
108  $this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorIntegrity());
109  $this->_validator->add(new \Magento\Framework\Code\Validator\TypeDuplication());
110  $this->_validator->add(new \Magento\Framework\Code\Validator\ArgumentSequence());
111  $this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorArgumentTypes());
112  $this->pluginValidator = new PluginValidator(new InterfaceValidator());
113  }
114 
120  private function getPluginBlacklist(): array
121  {
122  if ($this->pluginBlacklist === null) {
123  $blacklistFiles = str_replace(
124  '\\',
125  '/',
126  realpath(__DIR__) . '/../_files/blacklist/compiler_plugins*.txt'
127  );
128  $blacklistItems = [];
129  foreach (glob($blacklistFiles) as $fileName) {
130  $blacklistItems = array_merge(
131  $blacklistItems,
132  file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)
133  );
134  }
135  $this->pluginBlacklist = $blacklistItems;
136  }
137  return $this->pluginBlacklist;
138  }
139 
145  protected function _validateFile($file)
146  {
147  $dom = new \DOMDocument();
148  $dom->load($file);
149  $data = $this->_mapper->convert($dom);
150 
151  foreach ($data as $instanceName => $parameters) {
152  if (!isset($parameters['parameters']) || empty($parameters['parameters'])) {
153  continue;
154  }
155  if (\Magento\Framework\App\Utility\Classes::isVirtual($instanceName)) {
156  $instanceName = \Magento\Framework\App\Utility\Classes::resolveVirtualType($instanceName);
157  }
158 
159  if (!$this->_classExistsAsReal($instanceName)) {
160  continue;
161  }
162 
163  $reflectionClass = new \ReflectionClass($instanceName);
164 
165  $constructor = $reflectionClass->getConstructor();
166  if (!$constructor) {
167  $this->fail('Class ' . $instanceName . ' does not have __constructor');
168  }
169 
170  $parameters = $parameters['parameters'];
171  $classParameters = $constructor->getParameters();
172  foreach ($classParameters as $classParameter) {
173  $parameterName = $classParameter->getName();
174  if (array_key_exists($parameterName, $parameters)) {
175  unset($parameters[$parameterName]);
176  }
177  }
178  $message = 'Configuration of ' . $instanceName . ' contains data for non-existed parameters: ' . implode(
179  ', ',
180  array_keys($parameters)
181  );
182  $this->assertEmpty($parameters, $message);
183  }
184  }
185 
192  protected function _classExistsAsReal($instanceName)
193  {
194  if (class_exists($instanceName)) {
195  return true;
196  }
197  // check for generated factory
198  if (substr($instanceName, -7) == 'Factory' && class_exists(substr($instanceName, 0, -7))) {
199  return false;
200  }
201  $this->fail('Detected configuration of non existed class: ' . $instanceName);
202  }
203 
209  protected function _phpClassesDataProvider()
210  {
211  $generationPath = str_replace('/', '\\', $this->_generationDir);
212 
214 
215  $patterns = ['/' . preg_quote($generationPath) . '/',];
216  $replacements = [''];
217 
219  foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
220  $patterns[] = '/' . preg_quote(str_replace('/', '\\', $modulePath)) . '/';
221  $replacements[] = '\\' . str_replace('_', '\\', $moduleName);
222  }
223 
225  $patterns[] = '/' . preg_quote(str_replace('/', '\\', $libPath)) . '/';
226  $replacements[] = '\\Magento\\Framework';
227  }
228 
230  $classes = [];
231  foreach ($files as $file) {
232  $file = str_replace('/', '\\', $file);
233  $filePath = preg_replace($patterns, $replacements, $file);
234  $className = substr($filePath, 0, -4);
235  if (class_exists($className, false)) {
236  $file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
237  $classes[$file] = $className;
238  }
239  }
240 
242  $output = [];
243  $allowedFiles = array_keys($classes);
244  foreach ($classes as $class) {
245  if (!in_array($class, $output)) {
246  $output = array_merge($output, $this->_buildInheritanceHierarchyTree($class, $allowedFiles));
247  $output = array_unique($output);
248  }
249  }
250 
252  $outputClasses = [];
253  foreach ($output as $className) {
254  $outputClasses[] = [$className];
255  }
256  return $outputClasses;
257  }
258 
266  protected function _buildInheritanceHierarchyTree($className, array $allowedFiles)
267  {
268  $output = [];
269  if (0 !== strpos($className, '\\')) {
270  $className = '\\' . $className;
271  }
272  $class = new \ReflectionClass($className);
273  $parent = $class->getParentClass();
274  $file = false;
275  if ($parent) {
276  $file = str_replace('\\', DIRECTORY_SEPARATOR, $parent->getFileName());
277  }
279  if ($parent && in_array($file, $allowedFiles)) {
280  $output = array_merge(
281  $this->_buildInheritanceHierarchyTree($parent->getName(), $allowedFiles),
282  [$className],
283  $output
284  );
285  } else {
286  $output[] = $className;
287  }
288  return array_unique($output);
289  }
290 
296  protected function _validateClass($className)
297  {
298  try {
299  $this->_validator->validate($className);
300  } catch (\Magento\Framework\Exception\ValidatorException $exceptions) {
301  $this->fail($exceptions->getMessage());
302  } catch (\ReflectionException $exceptions) {
303  $this->fail($exceptions->getMessage());
304  }
305  }
306 
311  {
312  $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
313  $invoker(
314  function ($file) {
315  $this->_validateFile($file);
316  },
317  Files::init()->getDiConfigs(true)
318  );
319  }
320 
324  public function testConstructorIntegrity()
325  {
326  $generatorIo = new \Magento\Framework\Code\Generator\Io(
327  new \Magento\Framework\Filesystem\Driver\File(),
328  $this->_generationDir
329  );
330  $generator = new \Magento\Framework\Code\Generator(
331  $generatorIo,
332  [
333  Factory::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Factory::class,
334  Repository::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Repository::class,
335  Converter::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Converter::class,
336  Mapper::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\Mapper::class,
337  SearchResults::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\SearchResults::class,
339  \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::class,
341  \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::class
342  ]
343  );
344  $generationAutoloader = new \Magento\Framework\Code\Generator\Autoloader($generator);
345  spl_autoload_register([$generationAutoloader, 'load']);
346 
347  $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
348  $invoker(
349  function ($className) {
350  $this->_validateClass($className);
351  },
352  $this->_phpClassesDataProvider()
353  );
354  spl_autoload_unregister([$generationAutoloader, 'load']);
355  }
356 
360  public function testPluginInterfaces()
361  {
362  $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
363  $invoker(
364  function ($plugin, $type) {
365  $this->validatePlugins($plugin, $type);
366  },
367  $this->pluginDataProvider()
368  );
369  }
370 
377  protected function validatePlugins($plugin, $type)
378  {
379  try {
381  if (Files::init()->isModuleExists($module)) {
382  $this->pluginValidator->validate($plugin, $type);
383  }
384  } catch (\Magento\Framework\Exception\ValidatorException $exception) {
385  $this->fail($exception->getMessage());
386  }
387  }
388 
395  protected function pluginDataProvider()
396  {
397  $files = Files::init()->getDiConfigs();
398  $plugins = [];
399  foreach ($files as $file) {
400  $dom = new \DOMDocument();
401  $dom->load($file);
402  $xpath = new \DOMXPath($dom);
403  $pluginList = $xpath->query('//config/type/plugin');
404  foreach ($pluginList as $node) {
406  $type = $node->parentNode->attributes->getNamedItem('name')->nodeValue;
408  if ($node->attributes->getNamedItem('type')) {
409  $plugin = $node->attributes->getNamedItem('type')->nodeValue;
410  if (!in_array($plugin, $this->getPluginBlacklist())) {
412  $plugins[] = ['plugin' => $plugin, 'intercepted type' => $type];
413  }
414  }
415  }
416  }
417 
418  return $plugins;
419  }
420 }
$componentRegistrar
Definition: bootstrap.php:23
static resolveVirtualType($className)
Definition: Classes.php:256
defined('TESTS_BP')||define('TESTS_BP' __DIR__
Definition: _bootstrap.php:60
$message
$type
Definition: item.phtml:13
_buildInheritanceHierarchyTree($className, array $allowedFiles)
$fileName
Definition: translate.phtml:15
$_option $_optionId $class
Definition: date.phtml:13
$reflectionClass
Definition: categories.php:25
const BP
Definition: autoload.php:14
$generatorIo
Definition: autoload.php:15
foreach($appDirs as $dir) $files
if($currentSelectedMethod==$_code) $className
Definition: form.phtml:31