Skip to content

Commit 1f4cf42

Browse files
committed
Merge pull request #26 from magento-folks/MAGETWO-26655-TD
[Folks] Technical debt (MAGETWO-26655)
2 parents 7581dd6 + 4f294eb commit 1f4cf42

File tree

7 files changed

+391
-1
lines changed

7 files changed

+391
-1
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Code\Reader;
7+
8+
require_once __DIR__ . '/_files/SourceArgumentsReaderTest.php.sample';
9+
10+
class SourceArgumentsReaderTest extends \PHPUnit_Framework_TestCase
11+
{
12+
/**
13+
* @var \Magento\Framework\Code\Reader\SourceArgumentsReader
14+
*/
15+
protected $sourceArgumentsReader;
16+
17+
protected function setUp()
18+
{
19+
$this->sourceArgumentsReader = new \Magento\Framework\Code\Reader\SourceArgumentsReader();
20+
}
21+
22+
/**
23+
* @param string $class
24+
* @param array $expectedResult
25+
* @dataProvider getConstructorArgumentTypesDataProvider
26+
*/
27+
public function testGetConstructorArgumentTypes($class, $expectedResult)
28+
{
29+
$class = new \ReflectionClass($class);
30+
$actualResult = $this->sourceArgumentsReader->getConstructorArgumentTypes($class);
31+
$this->assertEquals($expectedResult, $actualResult);
32+
}
33+
34+
public function getConstructorArgumentTypesDataProvider()
35+
{
36+
return [
37+
[
38+
'Some\Testing\Name\Space\AnotherSimpleClass',
39+
[
40+
'\Some\Testing\Name\Space\Item',
41+
'\Imported\Name\Space\One',
42+
'\Imported\Name\Space\AnotherTest\Extended',
43+
'\Imported\Name\Space\Test',
44+
'\Imported\Name\Space\Object\Under\Test',
45+
'\Imported\Name\Space\Object',
46+
'\Some\Testing\Name\Space\Test',
47+
'array',
48+
''
49+
],
50+
],
51+
[
52+
'\stdClass',
53+
[null]
54+
]
55+
];
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Some\Testing\Name\Space;
7+
8+
use Imported\Name\Space\One as FirstImport;
9+
use Imported\Name\Space\Object;
10+
use Imported\Name\Space\Test as Testing, \Imported\Name\Space\AnotherTest ;
11+
12+
class AnotherSimpleClass
13+
{
14+
public function __construct(
15+
\Some\Testing\Name\Space\Item $itemOne,
16+
FirstImport $itemTwo,
17+
AnotherTest\Extended $itemThree,
18+
Testing $itemFour,
19+
Object\Under\Test $itemFive,
20+
Object $itemSix,
21+
Test $itemSeven,
22+
array $itemEight = [],
23+
$itemNine = 'test'
24+
) {
25+
}
26+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ protected function setUp()
103103
$this->_validator->add(new \Magento\Framework\Code\Validator\ContextAggregation());
104104
$this->_validator->add(new \Magento\Framework\Code\Validator\TypeDuplication());
105105
$this->_validator->add(new \Magento\Framework\Code\Validator\ArgumentSequence());
106+
$this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorArgumentTypes());
106107
$this->pluginValidator = new \Magento\Framework\Interception\Code\InterfaceValidator();
107108
}
108109

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Code\Validator;
7+
8+
class ConstructorArgumentTypesTest extends \PHPUnit_Framework_TestCase
9+
{
10+
11+
/**
12+
* @var \PHPUnit_Framework_MockObject_MockObject
13+
*/
14+
protected $argumentsReaderMock;
15+
16+
/**
17+
* @var \PHPUnit_Framework_MockObject_MockObject
18+
*/
19+
protected $sourceArgumentsReaderMock;
20+
21+
/**
22+
* @var \Magento\Framework\Code\Validator\ConstructorArgumentTypes
23+
*/
24+
protected $model;
25+
26+
protected function setUp()
27+
{
28+
$this->argumentsReaderMock = $this->getMock(
29+
'\Magento\Framework\Code\Reader\ArgumentsReader',
30+
[],
31+
[],
32+
'',
33+
false
34+
);
35+
$this->sourceArgumentsReaderMock = $this->getMock(
36+
'\Magento\Framework\Code\Reader\SourceArgumentsReader',
37+
[],
38+
[],
39+
'',
40+
false
41+
);
42+
$this->model = new \Magento\Framework\Code\Validator\ConstructorArgumentTypes(
43+
$this->argumentsReaderMock,
44+
$this->sourceArgumentsReaderMock
45+
);
46+
}
47+
48+
public function testValidate()
49+
{
50+
$className = '\stdClass';
51+
$classMock = new \ReflectionClass($className);
52+
$this->argumentsReaderMock->expects($this->once())->method('getConstructorArguments')->with($classMock)
53+
->willReturn([['name' => 'Name1', 'type' => '\Type'], ['name' => 'Name2', 'type' => '\Type2']]);
54+
$this->sourceArgumentsReaderMock->expects($this->once())->method('getConstructorArgumentTypes')
55+
->with($classMock)->willReturn(['\Type', '\Type2']);
56+
$this->assertTrue($this->model->validate($className));
57+
}
58+
59+
/**
60+
* @expectedException \Magento\Framework\Code\ValidationException
61+
* @expectedExceptionMessage Invalid constructor argument(s) in \stdClass
62+
*/
63+
public function testValidateWithException()
64+
{
65+
$className = '\stdClass';
66+
$classMock = new \ReflectionClass($className);
67+
$this->argumentsReaderMock->expects($this->once())->method('getConstructorArguments')->with($classMock)
68+
->willReturn([['name' => 'Name1', 'type' => '\FAIL']]);
69+
$this->sourceArgumentsReaderMock->expects($this->once())->method('getConstructorArgumentTypes')
70+
->with($classMock)->willReturn(['\Type', '\Fail']);
71+
$this->assertTrue($this->model->validate($className));
72+
}
73+
}
74+
75+
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Code\Reader;
7+
8+
class SourceArgumentsReader
9+
{
10+
/**
11+
* Namespace separator
12+
*/
13+
const NS_SEPARATOR = '\\';
14+
15+
/**
16+
* Read constructor argument types from source code and perform namespace resolution if required.
17+
*
18+
* @param \ReflectionClass $class
19+
* @param bool $inherited
20+
* @return array List of constructor argument types.
21+
*/
22+
public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited = false)
23+
{
24+
$output = [null];
25+
if (!$class->getFileName() || false == $class->hasMethod(
26+
'__construct'
27+
) || !$inherited && $class->getConstructor()->class !== $class->getName()
28+
) {
29+
return $output;
30+
}
31+
$reflectionConstructor = $class->getConstructor();
32+
$fileContent = file($class->getFileName());
33+
$availableNamespaces = $this->getImportedNamespaces($fileContent);
34+
$availableNamespaces[0] = $class->getNamespaceName();
35+
$constructorStartLine = $reflectionConstructor->getStartLine() - 1;
36+
$constructorEndLine = $reflectionConstructor->getEndLine();
37+
$fileContent = array_slice($fileContent, $constructorStartLine, $constructorEndLine - $constructorStartLine);
38+
$source = '<?php ' . trim(implode('', $fileContent));
39+
$methodTokenized = token_get_all($source);
40+
$argumentsStart = array_search('(', $methodTokenized) + 1;
41+
$argumentsEnd = array_search(')', $methodTokenized);
42+
$arguments = array_slice($methodTokenized, $argumentsStart, $argumentsEnd - $argumentsStart);
43+
foreach ($arguments as &$argument) {
44+
is_array($argument) ?: $argument = [1 => $argument];
45+
}
46+
unset($argument);
47+
$arguments = array_filter($arguments, function ($token) {
48+
$blacklist = [T_VARIABLE, T_WHITESPACE];
49+
if (isset($token[0]) && in_array($token[0], $blacklist)) {
50+
return false;
51+
}
52+
return true;
53+
});
54+
$arguments = array_map(function ($element) {
55+
return $element[1];
56+
}, $arguments);
57+
$arguments = array_values($arguments);
58+
$arguments = implode('', $arguments);
59+
if (empty($arguments)) {
60+
return $output;
61+
}
62+
$arguments = explode(',', $arguments);
63+
foreach ($arguments as $key => &$argument) {
64+
$argument = $this->removeDefaultValue($argument);
65+
$argument = $this->resolveNamespaces($argument, $availableNamespaces);
66+
}
67+
unset($argument);
68+
return $arguments;
69+
}
70+
71+
/**
72+
* Perform namespace resolution if required and return fully qualified name.
73+
*
74+
* @param string $argument
75+
* @param array $availableNamespaces
76+
* @return string
77+
*/
78+
protected function resolveNamespaces($argument, $availableNamespaces)
79+
{
80+
if (substr($argument, 0, 1) !== self::NS_SEPARATOR && $argument !== 'array' && !empty($argument)) {
81+
$name = explode(self::NS_SEPARATOR, $argument);
82+
$unqualifiedName = $name[0];
83+
$isQualifiedName = count($name) > 1 ? true : false;
84+
if (isset($availableNamespaces[$unqualifiedName])) {
85+
$namespace = $availableNamespaces[$unqualifiedName];
86+
if ($isQualifiedName) {
87+
array_shift($name);
88+
return $namespace . self::NS_SEPARATOR . implode(self::NS_SEPARATOR, $name);
89+
}
90+
return $namespace;
91+
} else {
92+
return self::NS_SEPARATOR . $availableNamespaces[0] . self::NS_SEPARATOR . $argument;
93+
}
94+
}
95+
return $argument;
96+
}
97+
98+
/**
99+
* Remove default value from argument.
100+
*
101+
* @param string $argument
102+
* @return string
103+
*/
104+
protected function removeDefaultValue($argument)
105+
{
106+
$position = strpos($argument, '=');
107+
if (is_numeric($position)) {
108+
return substr($argument, 0, $position);
109+
}
110+
return $argument;
111+
}
112+
113+
/**
114+
* Get all imported namespaces.
115+
*
116+
* @param array $file
117+
* @return array
118+
*/
119+
protected function getImportedNamespaces(array $file)
120+
{
121+
$file = implode('', $file);
122+
$file = token_get_all($file);
123+
$classStart = array_search('{', $file);
124+
$file = array_slice($file, 0, $classStart);
125+
$output = [];
126+
foreach ($file as $position => $token) {
127+
if (is_array($token) && $token[0] === T_USE) {
128+
$import = array_slice($file, $position);
129+
$importEnd = array_search(';', $import);
130+
$import = array_slice($import, 0, $importEnd);
131+
$imports = [];
132+
$importsCount = 0;
133+
foreach ($import as $item) {
134+
if ($item === ',') {
135+
$importsCount++;
136+
continue;
137+
}
138+
$imports[$importsCount][] = $item;
139+
}
140+
foreach ($imports as $import) {
141+
$import = array_filter($import, function ($token) {
142+
$whitelist = [T_NS_SEPARATOR, T_STRING, T_AS];
143+
if (isset($token[0]) && in_array($token[0], $whitelist)) {
144+
return true;
145+
}
146+
return false;
147+
});
148+
$import = array_map(function ($element) {
149+
return $element[1];
150+
}, $import);
151+
$import = array_values($import);
152+
if ($import[0] === self::NS_SEPARATOR) {
153+
array_shift($import);
154+
}
155+
$importName = null;
156+
if (in_array('as', $import)) {
157+
$importName = array_splice($import, -1)[0];
158+
array_pop($import);
159+
}
160+
$useStatement = implode('', $import);
161+
if ($importName) {
162+
$output[$importName] = self::NS_SEPARATOR . $useStatement;
163+
} else {
164+
$key = explode(self::NS_SEPARATOR, $useStatement);
165+
$key = end($key);
166+
$output[$key] = self::NS_SEPARATOR . $useStatement;
167+
}
168+
}
169+
}
170+
}
171+
return $output;
172+
}
173+
}

0 commit comments

Comments
 (0)