Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
TypeProcessor.php
Go to the documentation of this file.
1 <?php
8 
11 use Zend\Code\Reflection\ClassReflection;
12 use Zend\Code\Reflection\DocBlock\Tag\ParamTag;
13 use Zend\Code\Reflection\DocBlock\Tag\ReturnTag;
14 use Zend\Code\Reflection\DocBlockReflection;
15 use Zend\Code\Reflection\MethodReflection;
16 use Zend\Code\Reflection\ParameterReflection;
17 
25 {
29  const STRING_TYPE = 'str';
30  const INT_TYPE = 'integer';
31  const BOOLEAN_TYPE = 'bool';
32  const ANY_TYPE = 'mixed';
38  const NORMALIZED_STRING_TYPE = 'string';
39  const NORMALIZED_INT_TYPE = 'int';
40  const NORMALIZED_FLOAT_TYPE = 'float';
41  const NORMALIZED_DOUBLE_TYPE = 'double';
42  const NORMALIZED_BOOLEAN_TYPE = 'boolean';
43  const NORMALIZED_ANY_TYPE = 'anyType';
47  protected $_types = [];
48 
52  private $nameFinder;
53 
61  private function getNameFinder()
62  {
63  if ($this->nameFinder === null) {
65  ->get(\Magento\Framework\Reflection\NameFinder::class);
66  }
67  return $this->nameFinder;
68  }
69 
75  public function getTypesData()
76  {
77  return $this->_types;
78  }
79 
88  public function setTypesData($typesData)
89  {
90  $this->_types = $typesData;
91  return $this;
92  }
93 
101  public function getTypeData($typeName)
102  {
103  if (!isset($this->_types[$typeName])) {
104  throw new \InvalidArgumentException(
105  sprintf('The "%s" data type isn\'t declared. Verify the type and try again.', $typeName)
106  );
107  }
108  return $this->_types[$typeName];
109  }
110 
118  public function setTypeData($typeName, $data)
119  {
120  if (!isset($this->_types[$typeName])) {
121  $this->_types[$typeName] = $data;
122  } else {
123  $this->_types[$typeName] = array_merge_recursive($this->_types[$typeName], $data);
124  }
125  }
126 
134  public function register($type)
135  {
136  $typeName = $this->normalizeType($type);
137  if (null === $typeName) {
138  return null;
139  }
140  if (!$this->isTypeSimple($typeName) && !$this->isTypeAny($typeName)) {
141  $typeSimple = $this->getArrayItemType($type);
142  if (!(class_exists($typeSimple) || interface_exists($typeSimple))) {
143  throw new \LogicException(
144  sprintf(
145  'The "%s" class doesn\'t exist and the namespace must be specified. Verify and try again.',
146  $type
147  )
148  );
149  }
150  $complexTypeName = $this->translateTypeName($type);
151  if (!isset($this->_types[$complexTypeName])) {
152  $this->_processComplexType($type);
153  }
154  $typeName = $complexTypeName;
155  }
156 
157  return $typeName;
158  }
159 
168  protected function _processComplexType($class)
169  {
170  $typeName = $this->translateTypeName($class);
171  $this->_types[$typeName] = [];
172  if ($this->isArrayType($class)) {
173  $this->register($this->getArrayItemType($class));
174  } else {
175  if (!(class_exists($class) || interface_exists($class))) {
176  throw new \InvalidArgumentException(
177  sprintf('The "%s" class couldn\'t load as a parameter type.', $class)
178  );
179  }
180  $reflection = new ClassReflection($class);
181  $docBlock = $reflection->getDocBlock();
182  $this->_types[$typeName]['documentation'] = $docBlock ? $this->getDescription($docBlock) : '';
184  foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflection) {
185  if ($methodReflection->class === \Magento\Framework\Model\AbstractModel::class) {
186  continue;
187  }
188  $this->_processMethod($methodReflection, $typeName);
189  }
190  }
191 
192  return $this->_types[$typeName];
193  }
194 
202  protected function _processMethod(MethodReflection $methodReflection, $typeName)
203  {
204  $isGetter = (strpos($methodReflection->getName(), 'get') === 0)
205  || (strpos($methodReflection->getName(), 'is') === 0)
206  || (strpos($methodReflection->getName(), 'has') === 0);
208  if ($isGetter && !$methodReflection->getNumberOfRequiredParameters()) {
209  $returnMetadata = $this->getGetterReturnType($methodReflection);
210  $fieldName = $this->getNameFinder()->getFieldNameFromGetterName($methodReflection->getName());
211  if ($returnMetadata['description']) {
212  $description = $returnMetadata['description'];
213  } else {
214  $description = $this->getNameFinder()->getFieldDescriptionFromGetterDescription(
215  $methodReflection->getDocBlock()->getShortDescription()
216  );
217  }
218  $this->_types[$typeName]['parameters'][$fieldName] = [
219  'type' => $this->register($returnMetadata['type']),
220  'required' => $returnMetadata['isRequired'],
221  'documentation' => $description,
222  ];
223  }
224  }
225 
232  public function getDescription(DocBlockReflection $doc)
233  {
234  $shortDescription = $doc->getShortDescription();
235  $longDescription = $doc->getLongDescription();
236 
237  $description = rtrim($shortDescription);
238  $longDescription = str_replace(["\n", "\r"], '', $longDescription);
239  if (!empty($longDescription) && !empty($description)) {
240  $description .= " ";
241  }
242  $description .= ltrim($longDescription);
243 
244  return $description;
245  }
246 
255  public function dataObjectGetterNameToFieldName($getterName)
256  {
257  return $this->getNameFinder()->getFieldNameFromGetterName($getterName);
258  }
259 
268  protected function dataObjectGetterDescriptionToFieldDescription($shortDescription)
269  {
270  return $this->getNameFinder()->getFieldDescriptionFromGetterDescription($shortDescription);
271  }
272 
285  public function getGetterReturnType($methodReflection)
286  {
287  $returnAnnotation = $this->getMethodReturnAnnotation($methodReflection);
288  $types = $returnAnnotation->getTypes();
289  $returnType = current($types);
290  $nullable = in_array('null', $types);
291 
292  return [
293  'type' => $returnType,
294  'isRequired' => !$nullable,
295  'description' => $returnAnnotation->getDescription(),
296  'parameterCount' => $methodReflection->getNumberOfRequiredParameters()
297  ];
298  }
299 
306  public function getExceptions($methodReflection)
307  {
308  $exceptions = [];
309  $methodDocBlock = $methodReflection->getDocBlock();
310  if ($methodDocBlock->hasTag('throws')) {
311  $throwsTypes = $methodDocBlock->getTags('throws');
312  if (is_array($throwsTypes)) {
314  foreach ($throwsTypes as $throwsType) {
315  $exceptions = array_merge($exceptions, $throwsType->getTypes());
316  }
317  }
318  }
319 
320  return $exceptions;
321  }
322 
329  public function normalizeType($type)
330  {
331  if ($type == 'null') {
332  return null;
333  }
334  $normalizationMap = [
335  self::STRING_TYPE => self::NORMALIZED_STRING_TYPE,
336  self::INT_TYPE => self::NORMALIZED_INT_TYPE,
337  self::BOOLEAN_TYPE => self::NORMALIZED_BOOLEAN_TYPE,
338  self::ANY_TYPE => self::NORMALIZED_ANY_TYPE,
339  ];
340 
341  return is_string($type) && isset($normalizationMap[$type]) ? $normalizationMap[$type] : $type;
342  }
343 
350  public function isTypeSimple($type)
351  {
352  return in_array(
353  $this->getNormalizedType($type),
354  [
355  self::NORMALIZED_STRING_TYPE,
356  self::NORMALIZED_INT_TYPE,
357  self::NORMALIZED_FLOAT_TYPE,
358  self::NORMALIZED_DOUBLE_TYPE,
359  self::NORMALIZED_BOOLEAN_TYPE,
360  ]
361  );
362  }
363 
370  public function isTypeAny($type)
371  {
372  return ($this->getNormalizedType($type) == self::NORMALIZED_ANY_TYPE);
373  }
374 
386  public function isArrayType($type)
387  {
388  return (bool)preg_match('/(\[\]$|^ArrayOf)/', $type);
389  }
390 
398  public function isValidTypeDeclaration($type)
399  {
400  return !($this->isTypeSimple($type) || $this->isTypeAny($type) || $this->isArrayType($type));
401  }
402 
409  public function getArrayItemType($arrayType)
410  {
411  return $this->normalizeType(str_replace('[]', '', $arrayType));
412  }
413 
426  public function translateTypeName($class)
427  {
428  if (preg_match('/\\\\?(.*)\\\\(.*)\\\\(Service|Api)\\\\\2?(.*)/', $class, $matches)) {
429  $moduleNamespace = $matches[1] == 'Magento' ? '' : $matches[1];
430  $moduleName = $matches[2];
431  $typeNameParts = explode('\\', $matches[4]);
432 
433  return ucfirst($moduleNamespace . $moduleName . implode('', $typeNameParts));
434  }
435  throw new \InvalidArgumentException(
436  sprintf('The "%s" parameter type is invalid. Verify the parameter and try again.', $class)
437  );
438  }
439 
452  public function translateArrayTypeName($type)
453  {
454  return 'ArrayOf' . ucfirst($this->getArrayItemType($type));
455  }
456 
468  {
469  $isArrayType = $this->isArrayType($type);
470  if ($isArrayType && is_array($value)) {
471  $arrayItemType = $this->getArrayItemType($type);
472  foreach (array_keys($value) as $key) {
473  if ($value !== null && !settype($value[$key], $arrayItemType)) {
474  throw new SerializationException(
475  new Phrase(
476  'The "%value" value\'s type is invalid. The "%type" type was expected. '
477  . 'Verify and try again.',
478  ['value' => $value, 'type' => $type]
479  )
480  );
481  }
482  }
483  } elseif ($isArrayType && $value === null) {
484  return null;
485  } elseif (!$isArrayType && !is_array($value)) {
486  if ($value !== null && !$this->isTypeAny($type) && !$this->setType($value, $type)) {
487  throw new SerializationException(
488  new Phrase(
489  'The "%value" value\'s type is invalid. The "%type" type was expected. Verify and try again.',
490  ['value' => (string)$value, 'type' => $type]
491  )
492  );
493  }
494  } elseif (!$this->isTypeAny($type)) {
495  throw new SerializationException(
496  new Phrase(
497  'The "%value" value\'s type is invalid. The "%type" type was expected. Verify and try again.',
498  ['value' => gettype($value), 'type' => $type]
499  )
500  );
501  }
502  return $value;
503  }
504 
512  public function getParamType(ParameterReflection $param)
513  {
514  $type = $param->detectType();
515  if ($type == 'null') {
516  throw new \LogicException(sprintf(
517  '@param annotation is incorrect for the parameter "%s" in the method "%s:%s".'
518  . ' First declared type should not be null. E.g. string|null',
519  $param->getName(),
520  $param->getDeclaringClass()->getName(),
521  $param->getDeclaringFunction()->name
522  ));
523  }
524  if ($type == 'array') {
525  // try to determine class, if it's array of objects
526  $paramDocBlock = $this->getParamDocBlockTag($param);
527  $paramTypes = $paramDocBlock->getTypes();
528  $paramType = array_shift($paramTypes);
529  return strpos($paramType, '[]') !== false ? $paramType : "{$paramType}[]";
530  }
531  return $type;
532  }
533 
540  public function getParamDescription(ParameterReflection $param)
541  {
542  $paramDocBlock = $this->getParamDocBlockTag($param);
543  return $paramDocBlock->getDescription();
544  }
545 
556  public function findGetterMethodName(ClassReflection $class, $camelCaseProperty)
557  {
558  return $this->getNameFinder()->getGetterMethodName($class, $camelCaseProperty);
559  }
560 
568  protected function setType(&$value, $type)
569  {
570  // settype doesn't work for boolean string values.
571  // ex: custom_attributes passed from SOAP client can have boolean values as string
572  $booleanTypes = ['bool', 'boolean'];
573  if (in_array($type, $booleanTypes)) {
574  $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
575  return true;
576  }
577  $numType = ['int', 'float'];
578  if (in_array($type, $numType) && !is_numeric($value)) {
579  return false;
580  }
581  return settype($value, $type);
582  }
583 
594  public function findSetterMethodName(ClassReflection $class, $camelCaseProperty)
595  {
596  return $this->getNameFinder()->getSetterMethodName($class, $camelCaseProperty);
597  }
598 
611  protected function findAccessorMethodName(
612  ClassReflection $class,
613  $camelCaseProperty,
614  $accessorName,
615  $boolAccessorName
616  ) {
617  return $this->getNameFinder()
618  ->findAccessorMethodName($class, $camelCaseProperty, $accessorName, $boolAccessorName);
619  }
620 
632  protected function classHasMethod(ClassReflection $class, $methodName)
633  {
634  return $this->getNameFinder()->hasMethod($class, $methodName);
635  }
636 
646  public function processInterfaceCallInfo($interface, $serviceName, $methodName)
647  {
648  foreach ($interface as $direction => $interfaceData) {
649  $direction = ($direction == 'in') ? 'requiredInput' : 'returned';
650  if ($direction == 'returned' && !isset($interfaceData['parameters'])) {
652  return $this;
653  }
654  foreach ($interfaceData['parameters'] as $parameterData) {
655  if (!$this->isTypeSimple($parameterData['type']) && !$this->isTypeAny($parameterData['type'])) {
656  $operation = $this->getOperationName($serviceName, $methodName);
657  if ($parameterData['required']) {
658  $condition = ($direction == 'requiredInput') ? 'yes' : 'always';
659  } else {
660  $condition = ($direction == 'requiredInput') ? 'no' : 'conditionally';
661  }
662  $callInfo = [];
663  $callInfo[$direction][$condition]['calls'][] = $operation;
664  $this->setTypeData($parameterData['type'], ['callInfo' => $callInfo]);
665  }
666  }
667  }
668  return $this;
669  }
670 
678  public function getOperationName($serviceName, $methodName)
679  {
680  return $serviceName . ucfirst($methodName);
681  }
682 
689  private function getNormalizedType($type)
690  {
691  $type = $this->normalizeType($type);
692  if ($this->isArrayType($type)) {
693  $type = $this->getArrayItemType($type);
694  }
695  return $type;
696  }
697 
705  private function getMethodReturnAnnotation(MethodReflection $methodReflection)
706  {
707  $methodName = $methodReflection->getName();
708  $returnAnnotations = $this->getReturnFromDocBlock($methodReflection);
709  if (empty($returnAnnotations)) {
710  // method can inherit doc block from implemented interface, like for interceptors
711  $implemented = $methodReflection->getDeclaringClass()->getInterfaces();
713  foreach ($implemented as $parentClassReflection) {
714  if ($parentClassReflection->hasMethod($methodName)) {
715  $returnAnnotations = $this->getReturnFromDocBlock(
716  $parentClassReflection->getMethod($methodName)
717  );
718  break;
719  }
720  }
721  // throw an exception if even implemented interface doesn't have return annotations
722  if (empty($returnAnnotations)) {
723  throw new \InvalidArgumentException(
724  "Method's return type must be specified using @return annotation. "
725  . "See {$methodReflection->getDeclaringClass()->getName()}::{$methodName}()"
726  );
727  }
728  }
729  return $returnAnnotations;
730  }
731 
738  private function getReturnFromDocBlock(MethodReflection $methodReflection)
739  {
740  $methodDocBlock = $methodReflection->getDocBlock();
741  if (!$methodDocBlock) {
742  throw new \InvalidArgumentException(
743  "Each method must have a doc block. "
744  . "See {$methodReflection->getDeclaringClass()->getName()}::{$methodReflection->getName()}()"
745  );
746  }
747  return current($methodDocBlock->getTags('return'));
748  }
749 
756  private function getParamDocBlockTag(ParameterReflection $param): ParamTag
757  {
758  $docBlock = $param->getDeclaringFunction()
759  ->getDocBlock();
760  $paramsTag = $docBlock->getTags('param');
761  return $paramsTag[$param->getPosition()];
762  }
763 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
findSetterMethodName(ClassReflection $class, $camelCaseProperty)
dataObjectGetterDescriptionToFieldDescription($shortDescription)
processInterfaceCallInfo($interface, $serviceName, $methodName)
$type
Definition: item.phtml:13
findAccessorMethodName(ClassReflection $class, $camelCaseProperty, $accessorName, $boolAccessorName)
$_option $_optionId $class
Definition: date.phtml:13
$value
Definition: gender.phtml:16
getParamType(ParameterReflection $param)
classHasMethod(ClassReflection $class, $methodName)
getOperationName($serviceName, $methodName)
findGetterMethodName(ClassReflection $class, $camelCaseProperty)
getParamDescription(ParameterReflection $param)
_processMethod(MethodReflection $methodReflection, $typeName)