Skip to content

Commit

Permalink
Merge pull request #26 from magento-folks/MAGETWO-26655-TD
Browse files Browse the repository at this point in the history
[Folks] Technical debt (MAGETWO-26655)
  • Loading branch information
vpelipenko committed Jan 13, 2015
2 parents 7581dd6 + 4f294eb commit 1f4cf42
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\Code\Reader;

require_once __DIR__ . '/_files/SourceArgumentsReaderTest.php.sample';

class SourceArgumentsReaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Magento\Framework\Code\Reader\SourceArgumentsReader
*/
protected $sourceArgumentsReader;

protected function setUp()
{
$this->sourceArgumentsReader = new \Magento\Framework\Code\Reader\SourceArgumentsReader();
}

/**
* @param string $class
* @param array $expectedResult
* @dataProvider getConstructorArgumentTypesDataProvider
*/
public function testGetConstructorArgumentTypes($class, $expectedResult)
{
$class = new \ReflectionClass($class);
$actualResult = $this->sourceArgumentsReader->getConstructorArgumentTypes($class);
$this->assertEquals($expectedResult, $actualResult);
}

public function getConstructorArgumentTypesDataProvider()
{
return [
[
'Some\Testing\Name\Space\AnotherSimpleClass',
[
'\Some\Testing\Name\Space\Item',
'\Imported\Name\Space\One',
'\Imported\Name\Space\AnotherTest\Extended',
'\Imported\Name\Space\Test',
'\Imported\Name\Space\Object\Under\Test',
'\Imported\Name\Space\Object',
'\Some\Testing\Name\Space\Test',
'array',
''
],
],
[
'\stdClass',
[null]
]
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Some\Testing\Name\Space;

use Imported\Name\Space\One as FirstImport;
use Imported\Name\Space\Object;
use Imported\Name\Space\Test as Testing, \Imported\Name\Space\AnotherTest ;

class AnotherSimpleClass
{
public function __construct(
\Some\Testing\Name\Space\Item $itemOne,
FirstImport $itemTwo,
AnotherTest\Extended $itemThree,
Testing $itemFour,
Object\Under\Test $itemFive,
Object $itemSix,
Test $itemSeven,
array $itemEight = [],
$itemNine = 'test'
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ protected function setUp()
$this->_validator->add(new \Magento\Framework\Code\Validator\ContextAggregation());
$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 \Magento\Framework\Interception\Code\InterfaceValidator();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\Code\Validator;

class ConstructorArgumentTypesTest extends \PHPUnit_Framework_TestCase
{

/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $argumentsReaderMock;

/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $sourceArgumentsReaderMock;

/**
* @var \Magento\Framework\Code\Validator\ConstructorArgumentTypes
*/
protected $model;

protected function setUp()
{
$this->argumentsReaderMock = $this->getMock(
'\Magento\Framework\Code\Reader\ArgumentsReader',
[],
[],
'',
false
);
$this->sourceArgumentsReaderMock = $this->getMock(
'\Magento\Framework\Code\Reader\SourceArgumentsReader',
[],
[],
'',
false
);
$this->model = new \Magento\Framework\Code\Validator\ConstructorArgumentTypes(
$this->argumentsReaderMock,
$this->sourceArgumentsReaderMock
);
}

public function testValidate()
{
$className = '\stdClass';
$classMock = new \ReflectionClass($className);
$this->argumentsReaderMock->expects($this->once())->method('getConstructorArguments')->with($classMock)
->willReturn([['name' => 'Name1', 'type' => '\Type'], ['name' => 'Name2', 'type' => '\Type2']]);
$this->sourceArgumentsReaderMock->expects($this->once())->method('getConstructorArgumentTypes')
->with($classMock)->willReturn(['\Type', '\Type2']);
$this->assertTrue($this->model->validate($className));
}

/**
* @expectedException \Magento\Framework\Code\ValidationException
* @expectedExceptionMessage Invalid constructor argument(s) in \stdClass
*/
public function testValidateWithException()
{
$className = '\stdClass';
$classMock = new \ReflectionClass($className);
$this->argumentsReaderMock->expects($this->once())->method('getConstructorArguments')->with($classMock)
->willReturn([['name' => 'Name1', 'type' => '\FAIL']]);
$this->sourceArgumentsReaderMock->expects($this->once())->method('getConstructorArgumentTypes')
->with($classMock)->willReturn(['\Type', '\Fail']);
$this->assertTrue($this->model->validate($className));
}
}


173 changes: 173 additions & 0 deletions lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\Code\Reader;

class SourceArgumentsReader
{
/**
* Namespace separator
*/
const NS_SEPARATOR = '\\';

/**
* Read constructor argument types from source code and perform namespace resolution if required.
*
* @param \ReflectionClass $class
* @param bool $inherited
* @return array List of constructor argument types.
*/
public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited = false)
{
$output = [null];
if (!$class->getFileName() || false == $class->hasMethod(
'__construct'
) || !$inherited && $class->getConstructor()->class !== $class->getName()
) {
return $output;
}
$reflectionConstructor = $class->getConstructor();
$fileContent = file($class->getFileName());
$availableNamespaces = $this->getImportedNamespaces($fileContent);
$availableNamespaces[0] = $class->getNamespaceName();
$constructorStartLine = $reflectionConstructor->getStartLine() - 1;
$constructorEndLine = $reflectionConstructor->getEndLine();
$fileContent = array_slice($fileContent, $constructorStartLine, $constructorEndLine - $constructorStartLine);
$source = '<?php ' . trim(implode('', $fileContent));
$methodTokenized = token_get_all($source);
$argumentsStart = array_search('(', $methodTokenized) + 1;
$argumentsEnd = array_search(')', $methodTokenized);
$arguments = array_slice($methodTokenized, $argumentsStart, $argumentsEnd - $argumentsStart);
foreach ($arguments as &$argument) {
is_array($argument) ?: $argument = [1 => $argument];
}
unset($argument);
$arguments = array_filter($arguments, function ($token) {
$blacklist = [T_VARIABLE, T_WHITESPACE];
if (isset($token[0]) && in_array($token[0], $blacklist)) {
return false;
}
return true;
});
$arguments = array_map(function ($element) {
return $element[1];
}, $arguments);
$arguments = array_values($arguments);
$arguments = implode('', $arguments);
if (empty($arguments)) {
return $output;
}
$arguments = explode(',', $arguments);
foreach ($arguments as $key => &$argument) {
$argument = $this->removeDefaultValue($argument);
$argument = $this->resolveNamespaces($argument, $availableNamespaces);
}
unset($argument);
return $arguments;
}

/**
* Perform namespace resolution if required and return fully qualified name.
*
* @param string $argument
* @param array $availableNamespaces
* @return string
*/
protected function resolveNamespaces($argument, $availableNamespaces)
{
if (substr($argument, 0, 1) !== self::NS_SEPARATOR && $argument !== 'array' && !empty($argument)) {
$name = explode(self::NS_SEPARATOR, $argument);
$unqualifiedName = $name[0];
$isQualifiedName = count($name) > 1 ? true : false;
if (isset($availableNamespaces[$unqualifiedName])) {
$namespace = $availableNamespaces[$unqualifiedName];
if ($isQualifiedName) {
array_shift($name);
return $namespace . self::NS_SEPARATOR . implode(self::NS_SEPARATOR, $name);
}
return $namespace;
} else {
return self::NS_SEPARATOR . $availableNamespaces[0] . self::NS_SEPARATOR . $argument;
}
}
return $argument;
}

/**
* Remove default value from argument.
*
* @param string $argument
* @return string
*/
protected function removeDefaultValue($argument)
{
$position = strpos($argument, '=');
if (is_numeric($position)) {
return substr($argument, 0, $position);
}
return $argument;
}

/**
* Get all imported namespaces.
*
* @param array $file
* @return array
*/
protected function getImportedNamespaces(array $file)
{
$file = implode('', $file);
$file = token_get_all($file);
$classStart = array_search('{', $file);
$file = array_slice($file, 0, $classStart);
$output = [];
foreach ($file as $position => $token) {
if (is_array($token) && $token[0] === T_USE) {
$import = array_slice($file, $position);
$importEnd = array_search(';', $import);
$import = array_slice($import, 0, $importEnd);
$imports = [];
$importsCount = 0;
foreach ($import as $item) {
if ($item === ',') {
$importsCount++;
continue;
}
$imports[$importsCount][] = $item;
}
foreach ($imports as $import) {
$import = array_filter($import, function ($token) {
$whitelist = [T_NS_SEPARATOR, T_STRING, T_AS];
if (isset($token[0]) && in_array($token[0], $whitelist)) {
return true;
}
return false;
});
$import = array_map(function ($element) {
return $element[1];
}, $import);
$import = array_values($import);
if ($import[0] === self::NS_SEPARATOR) {
array_shift($import);
}
$importName = null;
if (in_array('as', $import)) {
$importName = array_splice($import, -1)[0];
array_pop($import);
}
$useStatement = implode('', $import);
if ($importName) {
$output[$importName] = self::NS_SEPARATOR . $useStatement;
} else {
$key = explode(self::NS_SEPARATOR, $useStatement);
$key = end($key);
$output[$key] = self::NS_SEPARATOR . $useStatement;
}
}
}
}
return $output;
}
}
Loading

0 comments on commit 1f4cf42

Please sign in to comment.