Skip to content

Commit 6a5a1b6

Browse files
merge magento-commerce/2.4-develop into magento-l3/TANGO-PR-11-05-2020_24
2 parents 090afec + 884d148 commit 6a5a1b6

File tree

7 files changed

+560
-60
lines changed

7 files changed

+560
-60
lines changed

app/code/Magento/Backend/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"magento/module-backup": "*",
1111
"magento/module-catalog": "*",
1212
"magento/module-config": "*",
13+
"magento/module-cms": "*",
1314
"magento/module-customer": "*",
1415
"magento/module-developer": "*",
1516
"magento/module-directory": "*",

dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php

Lines changed: 161 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
namespace Magento\TestFramework\Dependency;
1111

1212
use Magento\Framework\App\Utility\Files;
13+
use Magento\Framework\Config\Reader\Filesystem as ConfigReader;
14+
use Magento\Framework\Exception\ConfigurationMismatchException;
1315
use Magento\Framework\Exception\LocalizedException;
1416
use Magento\Framework\UrlInterface;
1517
use Magento\TestFramework\Dependency\Reader\ClassScanner;
1618
use Magento\TestFramework\Dependency\Route\RouteMapper;
1719
use Magento\TestFramework\Exception\NoSuchActionException;
20+
use Magento\TestFramework\Inspection\Exception;
1821

1922
/**
2023
* Rule to check the dependencies between modules based on references, getUrl and layout blocks
@@ -58,6 +61,12 @@ class PhpRule implements RuleInterface
5861
*/
5962
protected $_mapLayoutBlocks = [];
6063

64+
/**
65+
* Used to retrieve information from WebApi urls
66+
* @var ConfigReader
67+
*/
68+
protected $configReader;
69+
6170
/**
6271
* Default modules list.
6372
*
@@ -85,28 +94,36 @@ class PhpRule implements RuleInterface
8594
*/
8695
private $classScanner;
8796

97+
/**
98+
* @var array
99+
*/
100+
private $serviceMethods;
101+
88102
/**
89103
* @param array $mapRouters
90104
* @param array $mapLayoutBlocks
105+
* @param ConfigReader $configReader
91106
* @param array $pluginMap
92107
* @param array $whitelists
93108
* @param ClassScanner|null $classScanner
94-
*
95-
* @throws LocalizedException
109+
* @param RouteMapper|null $routeMapper
96110
*/
97111
public function __construct(
98112
array $mapRouters,
99113
array $mapLayoutBlocks,
114+
ConfigReader $configReader,
100115
array $pluginMap = [],
101116
array $whitelists = [],
102-
ClassScanner $classScanner = null
117+
ClassScanner $classScanner = null,
118+
RouteMapper $routeMapper = null
103119
) {
104120
$this->_mapRouters = $mapRouters;
105121
$this->_mapLayoutBlocks = $mapLayoutBlocks;
122+
$this->configReader = $configReader;
106123
$this->pluginMap = $pluginMap ?: null;
107-
$this->routeMapper = new RouteMapper();
108124
$this->whitelists = $whitelists;
109125
$this->classScanner = $classScanner ?? new ClassScanner();
126+
$this->routeMapper = $routeMapper ?? new RouteMapper();
110127
}
111128

112129
/**
@@ -132,7 +149,7 @@ public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
132149
);
133150
$dependenciesInfo = $this->considerCaseDependencies(
134151
$dependenciesInfo,
135-
$this->_caseGetUrl($currentModule, $contents)
152+
$this->_caseGetUrl($currentModule, $contents, $file)
136153
);
137154
$dependenciesInfo = $this->considerCaseDependencies(
138155
$dependenciesInfo,
@@ -290,41 +307,29 @@ private function isPluginDependency($dependent, $dependency)
290307
*
291308
* @param string $currentModule
292309
* @param string $contents
310+
* @param string $file
293311
* @return array
294312
* @throws LocalizedException
295-
* @throws \Exception
296-
* @SuppressWarnings(PMD.CyclomaticComplexity)
297313
*/
298-
protected function _caseGetUrl(string $currentModule, string &$contents): array
314+
protected function _caseGetUrl(string $currentModule, string &$contents, string $file): array
299315
{
300-
$pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<route_id>[a-z0-9\-_]{3,}|\*)'
301-
.'(/(?<controller_name>[a-z0-9\-_]+|\*))?(/(?<action_name>[a-z0-9\-_]+|\*))?\3)#i';
302-
303316
$dependencies = [];
317+
$pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<path>[a-zA-Z0-9\-_*/]+)\3)\s*[,)]#';
304318
if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) {
305319
return $dependencies;
306320
}
307-
308321
try {
309322
foreach ($matches as $item) {
310-
$routeId = $item['route_id'];
311-
$controllerName = $item['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME;
312-
$actionName = $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME;
313-
314-
// skip rest
315-
if ($routeId === "rest") { //MC-19890
316-
continue;
323+
$path = $item['path'];
324+
$modules = [];
325+
if (strpos($path, '*') !== false) {
326+
$modules = $this->processWildcardUrl($path, $file);
327+
} elseif (preg_match('#rest(?<service>/V1/.+)#i', $path, $apiMatch)) {
328+
$modules = $this->processApiUrl($apiMatch['service']);
329+
} else {
330+
$modules = $this->processStandardUrl($path);
317331
}
318-
// skip wildcards
319-
if ($routeId === "*" || $controllerName === "*" || $actionName === "*") { //MC-19890
320-
continue;
321-
}
322-
$modules = $this->routeMapper->getDependencyByRoutePath(
323-
$routeId,
324-
$controllerName,
325-
$actionName
326-
);
327-
if (!in_array($currentModule, $modules)) {
332+
if ($modules && !in_array($currentModule, $modules)) {
328333
$dependencies[] = [
329334
'modules' => $modules,
330335
'type' => RuleInterface::TYPE_HARD,
@@ -337,10 +342,136 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array
337342
throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e);
338343
}
339344
}
340-
341345
return $dependencies;
342346
}
343347

348+
/**
349+
* Helper method to get module dependencies used by a wildcard Url
350+
*
351+
* @param string $urlPath
352+
* @param string $filePath
353+
* @return string[]
354+
* @throws NoSuchActionException
355+
*/
356+
private function processWildcardUrl(string $urlPath, string $filePath)
357+
{
358+
$filePath = strtolower($filePath);
359+
$urlRoutePieces = explode('/', $urlPath);
360+
$routeId = array_shift($urlRoutePieces);
361+
//Skip route wildcard processing as this requires using the routeMapper
362+
if ('*' === $routeId) {
363+
return [];
364+
}
365+
366+
/**
367+
* Only handle Controllers. ie: Ignore Blocks, Templates, and Models due to complexity in static resolution
368+
* of route
369+
*/
370+
if (!preg_match(
371+
'#controller/(adminhtml/)?(?<controller_name>.+)/(?<action_name>\w+).php$#',
372+
$filePath,
373+
$fileParts
374+
)) {
375+
return [];
376+
}
377+
378+
$controllerName = array_shift($urlRoutePieces);
379+
if ('*' === $controllerName) {
380+
$controllerName = str_replace('/', '_', $fileParts['controller_name']);
381+
}
382+
383+
if (empty($urlRoutePieces) || !$urlRoutePieces[0]) {
384+
$actionName = UrlInterface::DEFAULT_ACTION_NAME;
385+
} else {
386+
$actionName = array_shift($urlRoutePieces);
387+
if ('*' === $actionName) {
388+
$actionName = $fileParts['action_name'];
389+
}
390+
}
391+
392+
return $this->routeMapper->getDependencyByRoutePath(
393+
strtolower($routeId),
394+
strtolower($controllerName),
395+
strtolower($actionName)
396+
);
397+
}
398+
399+
/**
400+
* Helper method to get module dependencies used by a standard URL
401+
*
402+
* @param string $path
403+
* @return string[]
404+
* @throws NoSuchActionException
405+
*/
406+
private function processStandardUrl(string $path)
407+
{
408+
$pattern = '#(?<route_id>[a-z0-9\-_]{3,})'
409+
. '(/(?<controller_name>[a-z0-9\-_]+))?(/(?<action_name>[a-z0-9\-_]+))?#i';
410+
if (!preg_match($pattern, $path, $match)) {
411+
throw new NoSuchActionException('Failed to parse standard url path: ' . $path);
412+
}
413+
$routeId = $match['route_id'];
414+
$controllerName = $match['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME;
415+
$actionName = $match['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME;
416+
417+
return $this->routeMapper->getDependencyByRoutePath(
418+
$routeId,
419+
$controllerName,
420+
$actionName
421+
);
422+
}
423+
424+
/**
425+
* Create regex patterns from service url paths
426+
*
427+
* @return array
428+
*/
429+
private function getServiceMethodRegexps(): array
430+
{
431+
if (!$this->serviceMethods) {
432+
$this->serviceMethods = [];
433+
$serviceRoutes = $this->configReader->read()['routes'];
434+
foreach ($serviceRoutes as $serviceRouteUrl => $methods) {
435+
$pattern = '#:\w+#';
436+
$replace = '\w+';
437+
$serviceRouteUrlRegex = preg_replace($pattern, $replace, $serviceRouteUrl);
438+
$serviceRouteUrlRegex = '#^' . $serviceRouteUrlRegex . '$#';
439+
$this->serviceMethods[$serviceRouteUrlRegex] = $methods;
440+
}
441+
}
442+
return $this->serviceMethods;
443+
}
444+
445+
/**
446+
* Helper method to get module dependencies used by an API URL
447+
*
448+
* @param string $path
449+
* @return string[]
450+
*
451+
* @throws NoSuchActionException
452+
* @throws Exception
453+
*/
454+
private function processApiUrl(string $path): array
455+
{
456+
foreach ($this->getServiceMethodRegexps() as $serviceRouteUrlRegex => $methods) {
457+
/**
458+
* Since we expect that every service method should be within the same module, we can use the class from
459+
* any method
460+
*/
461+
if (preg_match($serviceRouteUrlRegex, $path)) {
462+
$method = reset($methods);
463+
464+
$className = $method['service']['class'];
465+
//get module from className
466+
if (preg_match('#^(?<module>\w+[\\\]\w+)#', $className, $match)) {
467+
return [$match['module']];
468+
}
469+
throw new Exception('Failed to parse class from className: ' . $className);
470+
}
471+
}
472+
throw new NoSuchActionException('Failed to match service with url path: ' . $path);
473+
}
474+
344475
/**
345476
* Check layout blocks
346477
*

0 commit comments

Comments
 (0)