Proxy.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php
  2. /**
  3. * Proxy generator
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\ObjectManager\Code\Generator;
  9. /**
  10. * Class Proxy
  11. *
  12. * @package Magento\Framework\ObjectManager\Code\Generator
  13. */
  14. class Proxy extends \Magento\Framework\Code\Generator\EntityAbstract
  15. {
  16. /**
  17. * Entity type
  18. */
  19. const ENTITY_TYPE = 'proxy';
  20. /**
  21. * Marker interface
  22. */
  23. const NON_INTERCEPTABLE_INTERFACE = \Magento\Framework\ObjectManager\NoninterceptableInterface::class;
  24. /**
  25. * Returns default result class name
  26. *
  27. * @param string $modelClassName
  28. * @return string
  29. */
  30. protected function _getDefaultResultClassName($modelClassName)
  31. {
  32. return $modelClassName . '_' . ucfirst(static::ENTITY_TYPE);
  33. }
  34. /**
  35. * Retrieve class properties
  36. *
  37. * @return array
  38. */
  39. protected function _getClassProperties()
  40. {
  41. $properties = parent::_getClassProperties();
  42. // protected $_instanceName = null;
  43. $properties[] = [
  44. 'name' => '_instanceName',
  45. 'visibility' => 'protected',
  46. 'docblock' => [
  47. 'shortDescription' => 'Proxied instance name',
  48. 'tags' => [['name' => 'var', 'description' => 'string']],
  49. ],
  50. ];
  51. $properties[] = [
  52. 'name' => '_subject',
  53. 'visibility' => 'protected',
  54. 'docblock' => [
  55. 'shortDescription' => 'Proxied instance',
  56. 'tags' => [['name' => 'var', 'description' => $this->getSourceClassName()]],
  57. ],
  58. ];
  59. // protected $_shared = null;
  60. $properties[] = [
  61. 'name' => '_isShared',
  62. 'visibility' => 'protected',
  63. 'docblock' => [
  64. 'shortDescription' => 'Instance shareability flag',
  65. 'tags' => [['name' => 'var', 'description' => 'bool']],
  66. ],
  67. ];
  68. return $properties;
  69. }
  70. /**
  71. * Returns list of methods for class generator
  72. *
  73. * @return array
  74. */
  75. protected function _getClassMethods()
  76. {
  77. $construct = $this->_getDefaultConstructorDefinition();
  78. // create proxy methods for all non-static and non-final public methods (excluding constructor)
  79. $methods = [$construct];
  80. $methods[] = [
  81. 'name' => '__sleep',
  82. 'body' => 'return [\'_subject\', \'_isShared\', \'_instanceName\'];',
  83. 'docblock' => ['tags' => [['name' => 'return', 'description' => 'array']]],
  84. ];
  85. $methods[] = [
  86. 'name' => '__wakeup',
  87. 'body' => '$this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance();',
  88. 'docblock' => ['shortDescription' => 'Retrieve ObjectManager from global scope'],
  89. ];
  90. $methods[] = [
  91. 'name' => '__clone',
  92. 'body' => "\$this->_subject = clone \$this->_getSubject();",
  93. 'docblock' => ['shortDescription' => 'Clone proxied instance'],
  94. ];
  95. $methods[] = [
  96. 'name' => '_getSubject',
  97. 'visibility' => 'protected',
  98. 'body' => "if (!\$this->_subject) {\n" .
  99. " \$this->_subject = true === \$this->_isShared\n" .
  100. " ? \$this->_objectManager->get(\$this->_instanceName)\n" .
  101. " : \$this->_objectManager->create(\$this->_instanceName);\n" .
  102. "}\n" .
  103. "return \$this->_subject;",
  104. 'docblock' => [
  105. 'shortDescription' => 'Get proxied instance',
  106. 'tags' => [['name' => 'return', 'description' => $this->getSourceClassName()]],
  107. ],
  108. ];
  109. $reflectionClass = new \ReflectionClass($this->getSourceClassName());
  110. $publicMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
  111. foreach ($publicMethods as $method) {
  112. if (!(
  113. $method->isConstructor() ||
  114. $method->isFinal() ||
  115. $method->isStatic() ||
  116. $method->isDestructor()
  117. )
  118. && !in_array(
  119. $method->getName(),
  120. ['__sleep', '__wakeup', '__clone']
  121. )
  122. ) {
  123. $methods[] = $this->_getMethodInfo($method);
  124. }
  125. }
  126. return $methods;
  127. }
  128. /**
  129. * Generates code
  130. *
  131. * @return string
  132. */
  133. protected function _generateCode()
  134. {
  135. $typeName = $this->getSourceClassName();
  136. $reflection = new \ReflectionClass($typeName);
  137. if ($reflection->isInterface()) {
  138. $this->_classGenerator->setImplementedInterfaces([$typeName, '\\' . self::NON_INTERCEPTABLE_INTERFACE]);
  139. } else {
  140. $this->_classGenerator->setExtendedClass($typeName);
  141. $this->_classGenerator->setImplementedInterfaces(['\\' . self::NON_INTERCEPTABLE_INTERFACE]);
  142. }
  143. return parent::_generateCode();
  144. }
  145. /**
  146. * Collect method info
  147. *
  148. * @param \ReflectionMethod $method
  149. * @return array
  150. */
  151. protected function _getMethodInfo(\ReflectionMethod $method)
  152. {
  153. $parameterNames = [];
  154. $parameters = [];
  155. foreach ($method->getParameters() as $parameter) {
  156. $name = $parameter->isVariadic() ? '... $' . $parameter->getName() : '$' . $parameter->getName();
  157. $parameterNames[] = $name;
  158. $parameters[] = $this->_getMethodParameterInfo($parameter);
  159. }
  160. $returnTypeValue = $this->getReturnTypeValue($method->getReturnType());
  161. $methodInfo = [
  162. 'name' => $method->getName(),
  163. 'parameters' => $parameters,
  164. 'body' => $this->_getMethodBody(
  165. $method->getName(),
  166. $parameterNames,
  167. $returnTypeValue === 'void'
  168. ),
  169. 'docblock' => ['shortDescription' => '{@inheritdoc}'],
  170. 'returntype' => $returnTypeValue,
  171. ];
  172. return $methodInfo;
  173. }
  174. /**
  175. * Get default constructor definition for generated class
  176. *
  177. * @return array
  178. */
  179. protected function _getDefaultConstructorDefinition()
  180. {
  181. /*
  182. * public function __construct(
  183. * \Magento\Framework\ObjectManagerInterface $objectManager,
  184. * $instanceName,
  185. * $shared = false
  186. * )
  187. */
  188. return [
  189. 'name' => '__construct',
  190. 'parameters' => [
  191. ['name' => 'objectManager', 'type' => '\\' . \Magento\Framework\ObjectManagerInterface::class],
  192. ['name' => 'instanceName', 'defaultValue' => $this->getSourceClassName()],
  193. ['name' => 'shared', 'defaultValue' => true],
  194. ],
  195. 'body' => "\$this->_objectManager = \$objectManager;" .
  196. "\n\$this->_instanceName = \$instanceName;" .
  197. "\n\$this->_isShared = \$shared;",
  198. 'docblock' => [
  199. 'shortDescription' => ucfirst(static::ENTITY_TYPE) . ' constructor',
  200. 'tags' => [
  201. [
  202. 'name' => 'param',
  203. 'description' => '\Magento\Framework\ObjectManagerInterface $objectManager',
  204. ],
  205. ['name' => 'param', 'description' => 'string $instanceName'],
  206. ['name' => 'param', 'description' => 'bool $shared'],
  207. ],
  208. ]
  209. ];
  210. }
  211. /**
  212. * Build proxy method body
  213. *
  214. * @param string $name
  215. * @param array $parameters
  216. * @param bool $withoutReturn
  217. * @return string
  218. */
  219. protected function _getMethodBody(
  220. $name,
  221. array $parameters = [],
  222. bool $withoutReturn = false
  223. ) {
  224. if (count($parameters) == 0) {
  225. $methodCall = sprintf('%s()', $name);
  226. } else {
  227. $methodCall = sprintf('%s(%s)', $name, implode(', ', $parameters));
  228. }
  229. return ($withoutReturn ? '' : 'return ')
  230. . '$this->_getSubject()->' . $methodCall . ';';
  231. }
  232. /**
  233. * Validates data
  234. *
  235. * @return bool
  236. */
  237. protected function _validateData()
  238. {
  239. $result = parent::_validateData();
  240. if ($result) {
  241. $sourceClassName = $this->getSourceClassName();
  242. $resultClassName = $this->_getResultClassName();
  243. if ($resultClassName !== $sourceClassName . '\\Proxy') {
  244. $this->_addError(
  245. 'Invalid Proxy class name [' . $resultClassName . ']. Use ' . $sourceClassName . '\\Proxy'
  246. );
  247. $result = false;
  248. }
  249. }
  250. return $result;
  251. }
  252. /**
  253. * Returns return type
  254. *
  255. * @param mixed $returnType
  256. * @return null|string
  257. */
  258. private function getReturnTypeValue($returnType): ?string
  259. {
  260. $returnTypeValue = null;
  261. if ($returnType) {
  262. $returnTypeValue = ($returnType->allowsNull() ? '?' : '');
  263. $returnTypeValue .= ($returnType->getName() === 'self')
  264. ? $this->getSourceClassName()
  265. : $returnType->getName();
  266. }
  267. return $returnTypeValue;
  268. }
  269. }