26 private static $rootJson;
31 private static $dependencies;
36 private static $objectManager;
41 private static $rootComposerModuleBlacklist = [];
46 private static $moduleNameBlacklist;
51 self::$rootJson = json_decode(
file_get_contents(self::$root .
'/composer.json'),
true);
52 self::$dependencies = [];
56 __DIR__ .
'/_files/blacklist/composer_root_modules*.txt' 71 $blacklist = array_merge($blacklist, file($list, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
78 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
84 function ($dir, $packageType) {
85 $file = $dir .
'/composer.json';
86 $this->assertFileExists($file);
87 $this->validateComposerJsonFile($dir);
91 $this->assertMagentoConventions($dir, $packageType, $json);
106 $result[$dir] = [$dir,
'magento2-module'];
109 $result[$dir] = [$dir,
'magento2-language'];
112 $result[$dir] = [$dir,
'magento2-theme'];
115 $result[$dir] = [$dir,
'magento2-library'];
117 $result[$root] = [$root,
'project'];
127 private function validateComposerJsonFile(
$path)
131 $app = $appFactory->create();
134 $app->runComposerCommand([
'command' =>
'validate'],
$path);
136 $this->fail($exception->getMessage());
145 private function assertCodingStyle(
$contents)
147 $this->assertNotRegExp(
'/" :\s*["{]/',
$contents,
'Coding style: there should be no space before colon.');
148 $this->assertNotRegExp(
'/":["{]/',
$contents,
'Coding style: a space is necessary after colon.');
159 private function assertMagentoConventions($dir, $packageType, \StdClass $json)
161 $this->assertObjectHasAttribute(
'name', $json);
162 $this->assertObjectHasAttribute(
'license', $json);
163 $this->assertObjectHasAttribute(
'type', $json);
164 $this->assertObjectHasAttribute(
'require', $json);
165 $this->assertEquals($packageType, $json->type);
166 if ($packageType !==
'project') {
167 self::$dependencies[] = $json->name;
168 $this->assertAutoloadRegistrar($json, $dir);
169 $this->assertNoMap($json);
171 switch ($packageType) {
172 case 'magento2-module':
173 $xml = simplexml_load_file(
"$dir/etc/module.xml");
174 if ($this->isVendorMagento($json->name)) {
175 $this->assertConsistentModuleName($xml, $json->name);
177 $this->assertDependsOnPhp($json->require);
178 $this->assertPhpVersionInSync($json->name, $json->require->php);
179 $this->assertDependsOnFramework($json->require);
180 $this->assertRequireInSync($json);
181 $this->assertAutoload($json);
182 $this->assertNoVersionSpecified($json);
184 case 'magento2-language':
185 $this->assertRegExp(
'/^magento\/language\-[a-z]{2}_([a-z]{4}_)?[a-z]{2}$/', $json->name);
186 $this->assertDependsOnFramework($json->require);
187 $this->assertRequireInSync($json);
188 $this->assertNoVersionSpecified($json);
190 case 'magento2-theme':
191 $this->assertRegExp(
'/^magento\/theme-(?:adminhtml|frontend)(\-[a-z0-9_]+)+$/', $json->name);
192 $this->assertDependsOnPhp($json->require);
193 $this->assertPhpVersionInSync($json->name, $json->require->php);
194 $this->assertDependsOnFramework($json->require);
195 $this->assertRequireInSync($json);
196 $this->assertNoVersionSpecified($json);
198 case 'magento2-library':
199 $this->assertDependsOnPhp($json->require);
200 $this->assertRegExp(
'/^magento\/framework*/', $json->name);
201 $this->assertPhpVersionInSync($json->name, $json->require->php);
202 $this->assertRequireInSync($json);
203 $this->assertAutoload($json);
204 $this->assertNoVersionSpecified($json);
207 $this->checkProject();
208 $this->assertNoVersionSpecified($json);
211 throw new \InvalidArgumentException(
"Unknown package type {$packageType}");
221 private function isVendorMagento(
string $packageName): bool
223 return strpos($packageName,
'magento/') === 0;
232 private function assertAutoloadRegistrar(\StdClass $json, $dir)
234 $error =
'There must be an "autoload->files" node in composer.json of each Magento component.';
235 $this->assertObjectHasAttribute(
'autoload', $json, $error);
236 $this->assertObjectHasAttribute(
'files', $json->autoload, $error);
237 $this->assertTrue(in_array(
"registration.php", $json->autoload->files), $error);
238 $this->assertFileExists(
"$dir/registration.php");
248 private function assertNoVersionSpecified(\StdClass $json)
250 $errorMessage =
'Version must not be specified in the root and package composer JSON files in Git';
251 $this->assertObjectNotHasAttribute(
'version', $json, $errorMessage);
259 private function assertAutoload(\StdClass $json)
261 $errorMessage =
'There must be an "autoload->psr-4" section in composer.json of each Magento component.';
262 $this->assertObjectHasAttribute(
'autoload', $json, $errorMessage);
263 $this->assertObjectHasAttribute(
'psr-4', $json->autoload, $errorMessage);
271 private function assertNoMap(\StdClass $json)
273 $error =
'There is no "extra->map" node in composer.json of each Magento component.';
274 $this->assertObjectNotHasAttribute(
'extra', $json, $error);
283 private function assertConsistentModuleName(\SimpleXMLElement $xml, $packageName)
285 if (!in_array($packageName, self::$moduleNameBlacklist)) {
286 $moduleName = (string)$xml->module->attributes()->name;
287 $expectedPackageName = $this->convertModuleToPackageName($moduleName);
289 $expectedPackageName,
291 "For the module '{$moduleName}', the expected package name is '{$expectedPackageName}'" 301 private function assertDependsOnPhp(\StdClass $json)
303 $this->assertObjectHasAttribute(
'php', $json,
'This component is expected to depend on certain PHP version(s)');
311 private function assertDependsOnFramework(\StdClass $json)
313 $this->assertObjectHasAttribute(
316 'This component is expected to depend on magento/framework' 326 private function assertPhpVersionInSync(
$name, $phpVersion)
328 if (isset(self::$rootJson[
'require'][
'php'])) {
329 if ($this->isVendorMagento(
$name)) {
331 self::$rootJson[
'require'][
'php'],
333 "PHP version {$phpVersion} in component {$name} is inconsistent with version " 334 . self::$rootJson[
'require'][
'php'] .
' in root composer.json' 337 $composerVersionsPattern =
'{\s*\|\|?\s*}';
338 $rootPhpVersions = preg_split($composerVersionsPattern, self::$rootJson[
'require'][
'php']);
339 $modulePhpVersions = preg_split($composerVersionsPattern, $phpVersion);
342 array_diff($rootPhpVersions, $modulePhpVersions),
343 "PHP version {$phpVersion} in component {$name} is inconsistent with version " 344 . self::$rootJson[
'require'][
'php'] .
' in root composer.json' 356 private function assertRequireInSync(\StdClass $json)
358 if (preg_match(
'/magento\/project-*/', self::$rootJson[
'name']) == 1) {
361 if (!in_array($json->name, self::$rootComposerModuleBlacklist) && isset($json->require)) {
362 $this->checkPackageInRootComposer($json);
372 private function checkPackageInRootComposer(\StdClass $json)
376 foreach (array_keys((array)$json->require) as $depName) {
377 if ($depName ==
'magento/magento-composer-installer') {
381 if (!isset(self::$rootJson[
'require-dev'][$depName]) && !isset(self::$rootJson[
'require'][$depName])
382 && !isset(self::$rootJson[
'replace'][$depName])) {
383 $errors[] =
"'$name' depends on '$depName'";
388 "The following dependencies are missing in root 'composer.json'," 389 .
" while declared in child components.\n" 390 .
"Consider adding them to 'require-dev' section (if needed for child components only)," 391 .
" to 'replace' section (if they are present in the project)," 392 .
" to 'require' section (if needed for the skeleton).\n" 404 private function convertModuleToPackageName($moduleName)
406 list($vendor,
$name) = explode(
'_', $moduleName, 2);
408 foreach (preg_split(
'/([A-Z\d][a-z]*)/',
$name, -1, PREG_SPLIT_DELIM_CAPTURE) as $chunk) {
409 $package .= $chunk ?
"-{$chunk}" :
'';
411 return strtolower(
"{$vendor}/{$package}");
416 if (!isset(self::$rootJson[
'extra']) || !isset(self::$rootJson[
'extra'][
'component_paths'])) {
417 $this->markTestSkipped(
"The root composer.json file doesn't mention any extra component paths information");
419 $this->assertArrayHasKey(
422 "If there are any component paths specified, then they must be reflected in 'replace' section" 424 $flat = $this->getFlatPathsInfo(self::$rootJson[
'extra'][
'component_paths']);
425 foreach ($flat as
$item) {
427 $this->assertFileExists(
428 self::$root .
'/' .
$path,
429 "Missing or invalid component path: {$component} -> {$path}" 431 $this->assertArrayHasKey(
433 self::$rootJson[
'replace'],
434 "The {$component} is specified in 'extra->component_paths', but missing in 'replace' section" 437 foreach (array_keys(self::$rootJson[
'replace']) as $replace) {
439 $this->assertArrayHasKey(
441 self::$rootJson[
'extra'][
'component_paths'],
442 "The {$replace} is specified in 'replace', but missing in 'extra->component_paths' section" 453 private function getFlatPathsInfo(array
$info)
461 $flat[] = [$key,
$path];
464 throw new \Exception(
"Unexpected element 'in extra->component_paths' section");
474 private function checkProject()
476 sort(self::$dependencies);
477 $dependenciesListed = [];
478 if (strpos(self::$rootJson[
'name'],
'magento/project-') !== 0) {
479 $this->assertArrayHasKey(
481 (array)self::$rootJson,
482 'No "replace" section found in root composer.json' 484 foreach (array_keys((array)self::$rootJson[
'replace']) as $key) {
486 $dependenciesListed[] = $key;
489 sort($dependenciesListed);
490 $nonDeclaredDependencies = array_diff(
493 self::$rootComposerModuleBlacklist
495 $nonexistentDependencies = array_diff($dependenciesListed, self::$dependencies);
497 $nonDeclaredDependencies,
498 'Following dependencies are not declared in the root composer.json: ' 499 . join(
', ', $nonDeclaredDependencies)
502 $nonexistentDependencies,
503 'Following dependencies declared in the root composer.json do not exist: ' 504 . join(
', ', $nonexistentDependencies)
static matchMagentoComponent($key)
static create($rootDir, array $initParams, ObjectManagerFactory $factory=null)
elseif(isset( $params[ 'redirect_parent']))
defined('TESTS_BP')||define('TESTS_BP' __DIR__
testComponentPathsInRoot()
static getBlacklist(string $pattern)
static setUpBeforeClass()
validateComposerJsonDataProvider()
foreach( $_productCollection as $_product)() ?>" class $info
if(!isset($_GET['name'])) $name