MethodsMap.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Reflection;
  7. use Magento\Framework\Serialize\SerializerInterface;
  8. use Zend\Code\Reflection\ClassReflection;
  9. use Zend\Code\Reflection\MethodReflection;
  10. use Zend\Code\Reflection\ParameterReflection;
  11. use Magento\Framework\App\Cache\Type\Reflection as ReflectionCache;
  12. /**
  13. * Gathers method metadata information.
  14. */
  15. class MethodsMap
  16. {
  17. const SERVICE_METHOD_PARAMS_CACHE_PREFIX = 'service_method_params_';
  18. const SERVICE_INTERFACE_METHODS_CACHE_PREFIX = 'serviceInterfaceMethodsMap';
  19. const BASE_MODEL_CLASS = \Magento\Framework\Model\AbstractExtensibleModel::class;
  20. const METHOD_META_NAME = 'name';
  21. const METHOD_META_TYPE = 'type';
  22. const METHOD_META_HAS_DEFAULT_VALUE = 'isDefaultValueAvailable';
  23. const METHOD_META_DEFAULT_VALUE = 'defaultValue';
  24. /**
  25. * @var \Magento\Framework\Cache\FrontendInterface
  26. */
  27. private $cache;
  28. /**
  29. * @var TypeProcessor
  30. */
  31. private $typeProcessor;
  32. /**
  33. * @var array
  34. */
  35. private $serviceInterfaceMethodsMap = [];
  36. /**
  37. * @var FieldNamer
  38. */
  39. private $fieldNamer;
  40. /**
  41. * @var \Magento\Framework\Serialize\SerializerInterface
  42. */
  43. private $serializer;
  44. /**
  45. * @param \Magento\Framework\Cache\FrontendInterface $cache
  46. * @param TypeProcessor $typeProcessor
  47. * @param \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver
  48. * @param FieldNamer $fieldNamer
  49. */
  50. public function __construct(
  51. \Magento\Framework\Cache\FrontendInterface $cache,
  52. TypeProcessor $typeProcessor,
  53. \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver,
  54. FieldNamer $fieldNamer
  55. ) {
  56. $this->cache = $cache;
  57. $this->typeProcessor = $typeProcessor;
  58. $this->attributeTypeResolver = $typeResolver;
  59. $this->fieldNamer = $fieldNamer;
  60. }
  61. /**
  62. * Get return type by type name and method name.
  63. *
  64. * @param string $typeName
  65. * @param string $methodName
  66. * @return string
  67. */
  68. public function getMethodReturnType($typeName, $methodName)
  69. {
  70. return $this->getMethodsMap($typeName)[$methodName]['type'];
  71. }
  72. /**
  73. * Return service interface or Data interface methods loaded from cache
  74. *
  75. * @param string $interfaceName
  76. * @return array
  77. * <pre>
  78. * Service methods' reflection data stored in cache as 'methodName' => 'returnType'
  79. * ex.
  80. * [
  81. * 'create' => '\Magento\Customer\Api\Data\Customer',
  82. * 'validatePassword' => 'boolean'
  83. * ]
  84. * </pre>
  85. * @throws \InvalidArgumentException if methods don't have annotation
  86. * @throws \ReflectionException for missing DocBock or invalid reflection class
  87. */
  88. public function getMethodsMap($interfaceName)
  89. {
  90. $key = self::SERVICE_INTERFACE_METHODS_CACHE_PREFIX . "-" . md5($interfaceName);
  91. if (!isset($this->serviceInterfaceMethodsMap[$key])) {
  92. $methodMap = $this->cache->load($key);
  93. if ($methodMap) {
  94. $this->serviceInterfaceMethodsMap[$key] = $this->getSerializer()->unserialize($methodMap);
  95. } else {
  96. $methodMap = $this->getMethodMapViaReflection($interfaceName);
  97. $this->serviceInterfaceMethodsMap[$key] = $methodMap;
  98. $this->cache->save($this->getSerializer()->serialize($this->serviceInterfaceMethodsMap[$key]), $key);
  99. }
  100. }
  101. return $this->serviceInterfaceMethodsMap[$key];
  102. }
  103. /**
  104. * Retrieve requested service method params metadata.
  105. *
  106. * @param string $serviceClassName
  107. * @param string $serviceMethodName
  108. * @return array
  109. */
  110. public function getMethodParams($serviceClassName, $serviceMethodName)
  111. {
  112. $cacheId = self::SERVICE_METHOD_PARAMS_CACHE_PREFIX . hash('md5', $serviceClassName . $serviceMethodName);
  113. $params = $this->cache->load($cacheId);
  114. if ($params !== false) {
  115. return $this->getSerializer()->unserialize($params);
  116. }
  117. $serviceClass = new ClassReflection($serviceClassName);
  118. /** @var MethodReflection $serviceMethod */
  119. $serviceMethod = $serviceClass->getMethod($serviceMethodName);
  120. $params = [];
  121. /** @var ParameterReflection $paramReflection */
  122. foreach ($serviceMethod->getParameters() as $paramReflection) {
  123. $isDefaultValueAvailable = $paramReflection->isDefaultValueAvailable();
  124. $params[] = [
  125. self::METHOD_META_NAME => $paramReflection->getName(),
  126. self::METHOD_META_TYPE => $this->typeProcessor->getParamType($paramReflection),
  127. self::METHOD_META_HAS_DEFAULT_VALUE => $isDefaultValueAvailable,
  128. self::METHOD_META_DEFAULT_VALUE => $isDefaultValueAvailable ? $paramReflection->getDefaultValue() : null
  129. ];
  130. }
  131. $this->cache->save($this->getSerializer()->serialize($params), $cacheId, [ReflectionCache::CACHE_TAG]);
  132. return $params;
  133. }
  134. /**
  135. * Use reflection to load the method information
  136. *
  137. * @param string $interfaceName
  138. * @return array
  139. * @throws \ReflectionException for missing DocBock or invalid reflection class
  140. * @throws \InvalidArgumentException if methods don't have annotation
  141. */
  142. private function getMethodMapViaReflection($interfaceName)
  143. {
  144. $methodMap = [];
  145. $class = new ClassReflection($interfaceName);
  146. $baseClassMethods = false;
  147. foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
  148. // Include all the methods of classes inheriting from AbstractExtensibleObject.
  149. // Ignore all the methods of AbstractExtensibleModel's parent classes
  150. if ($method->class === self::BASE_MODEL_CLASS) {
  151. $baseClassMethods = true;
  152. } elseif ($baseClassMethods) {
  153. // ReflectionClass::getMethods() sorts the methods by class (lowest in inheritance tree first)
  154. // then by the order they are defined in the class definition
  155. break;
  156. }
  157. if ($this->isSuitableMethod($method)) {
  158. $methodMap[$method->getName()] = $this->typeProcessor->getGetterReturnType($method);
  159. }
  160. }
  161. return $methodMap;
  162. }
  163. /**
  164. * Determines if the method is suitable to be used by the processor.
  165. *
  166. * @param \ReflectionMethod $method
  167. * @return bool
  168. */
  169. private function isSuitableMethod($method)
  170. {
  171. $isSuitableMethodType = !($method->isConstructor() || $method->isFinal()
  172. || $method->isStatic() || $method->isDestructor());
  173. $isExcludedMagicMethod = strpos($method->getName(), '__') === 0;
  174. return $isSuitableMethodType && !$isExcludedMagicMethod;
  175. }
  176. /**
  177. * Determines if the given method's on the given type is suitable for an output data array.
  178. *
  179. * @param string $type
  180. * @param string $methodName
  181. * @return bool
  182. */
  183. public function isMethodValidForDataField($type, $methodName)
  184. {
  185. $methods = $this->getMethodsMap($type);
  186. if (isset($methods[$methodName])) {
  187. $methodMetadata = $methods[$methodName];
  188. // any method with parameter(s) gets ignored because we do not know the type and value of
  189. // the parameter(s), so we are not able to process
  190. if ($methodMetadata['parameterCount'] > 0) {
  191. return false;
  192. }
  193. return $this->fieldNamer->getFieldNameForMethodName($methodName) !== null;
  194. }
  195. return false;
  196. }
  197. /**
  198. * If the method has only non-null return types
  199. *
  200. * @param string $type
  201. * @param string $methodName
  202. * @return bool
  203. */
  204. public function isMethodReturnValueRequired($type, $methodName)
  205. {
  206. $methods = $this->getMethodsMap($type);
  207. return $methods[$methodName]['isRequired'];
  208. }
  209. /**
  210. * Get serializer
  211. *
  212. * @return \Magento\Framework\Serialize\SerializerInterface
  213. * @deprecated 101.0.0
  214. */
  215. private function getSerializer()
  216. {
  217. if ($this->serializer === null) {
  218. $this->serializer = \Magento\Framework\App\ObjectManager::getInstance()
  219. ->get(SerializerInterface::class);
  220. }
  221. return $this->serializer;
  222. }
  223. }