Skip to content

Commit 65335ee

Browse files
author
Alexander Paliarush
committed
MAGETWO-31936: Implement Extension Code Generator
- Implemented extension code generation - Improved extension interface code generation: interfaces are now generated with setters
1 parent 74b9029 commit 65335ee

File tree

6 files changed

+349
-39
lines changed

6 files changed

+349
-39
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Framework\Api\Code\Generator;
8+
9+
class ExtensionGeneratorTest extends \PHPUnit_Framework_TestCase
10+
{
11+
/**
12+
* @var \Magento\Framework\Api\Config\Reader|\PHPUnit_Framework_MockObject_MockObject
13+
*/
14+
protected $configReaderMock;
15+
16+
/**
17+
* @var \Magento\Framework\Api\Code\Generator\ObjectExtension|\PHPUnit_Framework_MockObject_MockObject
18+
*/
19+
protected $model;
20+
21+
protected function setUp()
22+
{
23+
$this->configReaderMock = $this->getMockBuilder('Magento\Framework\Api\Config\Reader')
24+
->disableOriginalConstructor()
25+
->getMock();
26+
27+
$objectManager = new \Magento\TestFramework\Helper\ObjectManager($this);
28+
$this->model = $objectManager->getObject(
29+
'Magento\Framework\Api\Code\Generator\ObjectExtension',
30+
[
31+
'configReader' => $this->configReaderMock,
32+
'sourceClassName' => '\Magento\Catalog\Api\Data\Product',
33+
'resultClassName' => '\Magento\Catalog\Api\Data\ProductExtension',
34+
'classGenerator' => null
35+
]
36+
);
37+
parent::setUp();
38+
}
39+
40+
public function testGenerate()
41+
{
42+
$this->configReaderMock->expects($this->any())
43+
->method('read')
44+
->willReturn(
45+
[
46+
'Magento\Catalog\Api\Data\ProductInterface' => [
47+
'string_attribute' => 'string',
48+
'complex_object_attribute' => '\Magento\Bundle\Api\Data\OptionInterface[]'
49+
],
50+
'Magento\Catalog\Api\Data\Product' => [
51+
'should_not_include' => 'string',
52+
],
53+
]
54+
);
55+
$expectedResult = file_get_contents(__DIR__ . '/_files/SampleExtension.txt');
56+
$this->validateGeneratedCode($expectedResult);
57+
}
58+
59+
public function testGenerateEmptyExtension()
60+
{
61+
$this->configReaderMock->expects($this->any())
62+
->method('read')
63+
->willReturn(['Magento\Catalog\Api\Data\Product' => ['should_not_include' => 'string']]);
64+
$expectedResult = file_get_contents(__DIR__ . '/_files/SampleEmptyExtension.txt');
65+
$this->validateGeneratedCode($expectedResult);
66+
}
67+
68+
public function testValidateException()
69+
{
70+
$objectManager = new \Magento\TestFramework\Helper\ObjectManager($this);
71+
/** @var \Magento\Framework\Api\Code\Generator\ObjectExtension $model */
72+
$model = $objectManager->getObject(
73+
'Magento\Framework\Api\Code\Generator\ObjectExtension',
74+
[
75+
'sourceClassName' => '\Magento\Catalog\Api\Data\Product',
76+
'resultClassName' => '\Magento\Catalog\Api\Data\ProductInterface'
77+
]
78+
);
79+
$reflectionObject = new \ReflectionObject($model);
80+
$reflectionMethod = $reflectionObject->getMethod('_validateData');
81+
$reflectionMethod->setAccessible(true);
82+
83+
$expectedValidationResult = false;
84+
$this->assertEquals($expectedValidationResult, $reflectionMethod->invoke($model));
85+
$this->assertTrue(
86+
in_array(
87+
'Invalid extension name [\Magento\Catalog\Api\Data\ProductInterface].'
88+
. ' Use \Magento\Catalog\Api\Data\ProductExtension',
89+
$model->getErrors()
90+
),
91+
'Expected validation error message is missing.'
92+
);
93+
}
94+
95+
/**
96+
* Check if generated code matches provided expected result.
97+
*
98+
* @param string $expectedResult
99+
*/
100+
protected function validateGeneratedCode($expectedResult)
101+
{
102+
$reflectionObject = new \ReflectionObject($this->model);
103+
$reflectionMethod = $reflectionObject->getMethod('_generateCode');
104+
$reflectionMethod->setAccessible(true);
105+
$generatedCode = $reflectionMethod->invoke($this->model);
106+
$this->assertEquals($expectedResult, $generatedCode);
107+
}
108+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Magento\Catalog\Api\Data;
2+
3+
/**
4+
* Extension class for @see \Magento\Catalog\Api\Data\ProductInterface
5+
*/
6+
class ProductExtension implements \Magento\Catalog\Api\Data\ProductExtensionInterface
7+
{
8+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace Magento\Catalog\Api\Data;
2+
3+
/**
4+
* Extension class for @see \Magento\Catalog\Api\Data\ProductInterface
5+
*/
6+
class ProductExtension implements \Magento\Catalog\Api\Data\ProductExtensionInterface
7+
{
8+
/**
9+
* @var string
10+
*/
11+
protected $stringAttribute = null;
12+
13+
/**
14+
* @var \Magento\Bundle\Api\Data\OptionInterface[]
15+
*/
16+
protected $complexObjectAttribute = null;
17+
18+
/**
19+
* @return string
20+
*/
21+
public function getStringAttribute()
22+
{
23+
return $this->stringAttribute;
24+
}
25+
26+
/**
27+
* @param string $stringAttribute
28+
* @return $this
29+
*/
30+
public function setStringAttribute($stringAttribute)
31+
{
32+
$this->stringAttribute = $stringAttribute;
33+
return $this;
34+
}
35+
36+
/**
37+
* @return \Magento\Bundle\Api\Data\OptionInterface[]
38+
*/
39+
public function getComplexObjectAttribute()
40+
{
41+
return $this->complexObjectAttribute;
42+
}
43+
44+
/**
45+
* @param \Magento\Bundle\Api\Data\OptionInterface[] $complexObjectAttribute
46+
* @return $this
47+
*/
48+
public function setComplexObjectAttribute($complexObjectAttribute)
49+
{
50+
$this->complexObjectAttribute = $complexObjectAttribute;
51+
return $this;
52+
}
53+
}

dev/tests/unit/testsuite/Magento/Framework/Api/Code/Generator/_files/SampleExtensionInterface.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,20 @@ interface ProductExtensionInterface
1010
*/
1111
public function getStringAttribute();
1212

13+
/**
14+
* @param string $stringAttribute
15+
* @return $this
16+
*/
17+
public function setStringAttribute($stringAttribute);
18+
1319
/**
1420
* @return \Magento\Bundle\Api\Data\OptionInterface[]
1521
*/
1622
public function getComplexObjectAttribute();
23+
24+
/**
25+
* @param \Magento\Bundle\Api\Data\OptionInterface[] $complexObjectAttribute
26+
* @return $this
27+
*/
28+
public function setComplexObjectAttribute($complexObjectAttribute);
1729
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
/**
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Api\Code\Generator;
7+
8+
use Magento\Framework\Code\Generator\DefinedClasses;
9+
use Magento\Framework\Code\Generator\Io;
10+
use Magento\Framework\Api\SimpleDataObjectConverter;
11+
12+
/**
13+
* Code generator for data object extensions.
14+
*/
15+
class ObjectExtension extends \Magento\Framework\Code\Generator\EntityAbstract
16+
{
17+
const ENTITY_TYPE = 'extension';
18+
19+
const EXTENSION_SUFFIX = 'Extension';
20+
21+
/**
22+
* @var \Magento\Framework\Api\Config\Reader
23+
*/
24+
protected $configReader;
25+
26+
/**
27+
* @var array
28+
*/
29+
protected $allCustomAttributes;
30+
31+
/**
32+
* Initialize dependencies.
33+
*
34+
* @param \Magento\Framework\Api\Config\Reader $configReader
35+
* @param string|null $sourceClassName
36+
* @param string|null $resultClassName
37+
* @param Io $ioObject
38+
* @param \Magento\Framework\Code\Generator\CodeGeneratorInterface $classGenerator
39+
* @param DefinedClasses $definedClasses
40+
*/
41+
public function __construct(
42+
\Magento\Framework\Api\Config\Reader $configReader,
43+
$sourceClassName = null,
44+
$resultClassName = null,
45+
Io $ioObject = null,
46+
\Magento\Framework\Code\Generator\CodeGeneratorInterface $classGenerator = null,
47+
DefinedClasses $definedClasses = null
48+
) {
49+
$sourceClassName .= 'Interface';
50+
$this->configReader = $configReader;
51+
parent::__construct(
52+
$sourceClassName,
53+
$resultClassName,
54+
$ioObject,
55+
$classGenerator,
56+
$definedClasses
57+
);
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
protected function _getDefaultConstructorDefinition()
64+
{
65+
return [];
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
protected function _getClassProperties()
72+
{
73+
$properties = [];
74+
foreach ($this->getCustomAttributes() as $attributeName => $attributeType) {
75+
$propertyName = SimpleDataObjectConverter::snakeCaseToCamelCase($attributeName);
76+
$properties[] = [
77+
'name' => $propertyName,
78+
'visibility' => 'protected',
79+
'docblock' => ['tags' => [['name' => 'var', 'description' => $attributeType]]],
80+
];
81+
}
82+
return $properties;
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
protected function _getClassMethods()
89+
{
90+
$methods = [];
91+
foreach ($this->getCustomAttributes() as $attributeName => $attributeType) {
92+
$propertyName = SimpleDataObjectConverter::snakeCaseToCamelCase($attributeName);
93+
$getterName = 'get' . ucfirst($propertyName);
94+
$setterName = 'set' . ucfirst($propertyName);
95+
$methods[] = [
96+
'name' => $getterName,
97+
'body' => "return \$this->{$propertyName};",
98+
'docblock' => ['tags' => [['name' => 'return', 'description' => $attributeType]]],
99+
];
100+
$methods[] = [
101+
'name' => $setterName,
102+
'parameters' => [['name' => $propertyName]],
103+
'body' => "\$this->{$propertyName} = \${$propertyName};" . PHP_EOL . "return \$this;",
104+
'docblock' => [
105+
'tags' => [
106+
[
107+
'name' => 'param',
108+
'description' => "{$attributeType} \${$propertyName}"
109+
],
110+
[
111+
'name' => 'return',
112+
'description' => '$this'
113+
]
114+
]
115+
],
116+
];
117+
}
118+
return $methods;
119+
}
120+
121+
/**
122+
* {@inheritdoc}
123+
*/
124+
protected function _validateData()
125+
{
126+
$result = true;
127+
$sourceClassName = $this->_getSourceClassName();
128+
$resultClassName = $this->_getResultClassName();
129+
$interfaceSuffix = 'Interface';
130+
$expectedResultClassName = substr($sourceClassName, 0, -strlen($interfaceSuffix)) . self::EXTENSION_SUFFIX;
131+
if ($resultClassName !== $expectedResultClassName) {
132+
$this->_addError(
133+
'Invalid extension name [' . $resultClassName . ']. Use ' . $expectedResultClassName
134+
);
135+
$result = false;
136+
}
137+
return parent::_validateData() && $result;
138+
}
139+
140+
/**
141+
* {@inheritdoc}
142+
*/
143+
protected function _generateCode()
144+
{
145+
$this->_classGenerator->setImplementedInterfaces([$this->_getResultClassName() . 'Interface']);
146+
return parent::_generateCode();
147+
}
148+
149+
/**
150+
* Retrieve a list of attributes associated with current source class.
151+
*
152+
* @return array
153+
*/
154+
protected function getCustomAttributes()
155+
{
156+
if (!isset($this->allCustomAttributes)) {
157+
$this->allCustomAttributes = $this->configReader->read();
158+
}
159+
$dataInterface = ltrim($this->_getSourceClassName(), '\\');
160+
if (isset($this->allCustomAttributes[$dataInterface])) {
161+
return $this->allCustomAttributes[$dataInterface];
162+
} else {
163+
return [];
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)