PluginProbe
Depicter — Popup & Slider Builder / trunk
Depicter — Popup & Slider Builder vtrunk
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / vendor / netresearch / jsonmapper / src / JsonMapper.php

JsonMapper.php in Depicter — Popup & Slider Builder trunk, at vendor/netresearch/jsonmapper/src/JsonMapper.php

984 lines 32.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Automatically map JSON structures into objects.
4 *
5 * @package JsonMapper
6 * @author Christian Weiske <[email protected]>
7 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0
8 * @link http://cweiske.de/
9 */
10 class JsonMapper
11 {
12 /**
13 * PSR-3 compatible logger object
14 *
15 * @link http://www.php-fig.org/psr/psr-3/
16 * @var \Psr\Log\LoggerInterface|null
17 * @see setLogger()
18 */
19 protected $logger;
20
21 /**
22 * Throw an exception when JSON data contain a property
23 * that is not defined in the PHP class
24 *
25 * @var boolean
26 */
27 public $bExceptionOnUndefinedProperty = false;
28
29 /**
30 * Throw an exception if the JSON data miss a property
31 * that is marked with @required in the PHP class
32 *
33 * @var boolean
34 */
35 public $bExceptionOnMissingData = false;
36
37 /**
38 * If the types of map() parameters shall be checked.
39 *
40 * You have to disable it if you're using the json_decode "assoc" parameter.
41 *
42 * json_decode($str, false)
43 *
44 * @var boolean
45 */
46 public $bEnforceMapType = true;
47
48 /**
49 * Throw an exception when an object is expected but the JSON contains
50 * a non-object type.
51 *
52 * @var boolean
53 */
54 public $bStrictObjectTypeChecking = true;
55
56 /**
57 * Throw an exception, if null value is found
58 * but the type of attribute does not allow nulls.
59 *
60 * @var boolean
61 */
62 public $bStrictNullTypes = true;
63
64 /**
65 * Throw an exception if null value is found in an array
66 * but the type of attribute does not allow nulls.
67 *
68 * @var boolean
69 */
70 public $bStrictNullTypesInArrays = true;
71
72 /**
73 * Allow mapping of private and protected properties.
74 *
75 * @var boolean
76 */
77 public $bIgnoreVisibility = false;
78
79 /**
80 * Remove attributes that were not passed in JSON,
81 * to avoid confusion between them and NULL values.
82 *
83 * @var boolean
84 */
85 public $bRemoveUndefinedAttributes = false;
86
87 /**
88 * Override class names that JsonMapper uses to create objects.
89 * Useful when your setter methods accept abstract classes or interfaces.
90 *
91 * @var array
92 */
93 public $classMap = array();
94
95 /**
96 * Callback used when an undefined property is found.
97 *
98 * Works only when $bExceptionOnUndefinedProperty is disabled.
99 *
100 * Parameters to this function are:
101 * 1. Object that is being filled
102 * 2. Name of the unknown JSON property
103 * 3. JSON value of the property
104 *
105 * @var callable
106 */
107 public $undefinedPropertyHandler = null;
108
109 /**
110 * Runtime cache for inspected classes. This is particularly effective if
111 * mapArray() is called with a large number of objects
112 *
113 * @var array property inspection result cache
114 */
115 protected $arInspectedClasses = array();
116
117 /**
118 * Method to call on each object after deserialization is done.
119 *
120 * Is only called if it exists on the object.
121 *
122 * @var string|null
123 */
124 public $postMappingMethod = null;
125
126 /**
127 * Optional arguments that are passed to the post mapping method
128 *
129 * @var array
130 */
131 public $postMappingMethodArguments = array();
132
133 /**
134 * Map data all data in $json into the given $object instance.
135 *
136 * @param object|array $json JSON object structure from json_decode()
137 * @param object|class-string $object Object to map $json data into
138 *
139 * @return mixed Mapped object is returned.
140 *
141 * @template T
142 * @phpstan-param class-string<T>|T $object
143 * @phpstan-return T
144 *
145 * @see mapArray()
146 */
147 public function map($json, $object)
148 {
149 if ($this->bEnforceMapType && !is_object($json)) {
150 throw new InvalidArgumentException(
151 'JsonMapper::map() requires first argument to be an object'
152 . ', ' . gettype($json) . ' given.'
153 );
154 }
155 if (!is_object($object) && (!is_string($object) || !class_exists($object))) {
156 throw new InvalidArgumentException(
157 'JsonMapper::map() requires second argument to '
158 . 'be an object or existing class name'
159 . ', ' . gettype($object) . ' given.'
160 );
161 }
162
163 if (is_string($object)) {
164 $object = $this->createInstance($object);
165 }
166
167 $strClassName = get_class($object);
168 $rc = new ReflectionClass($object);
169 $strNs = $rc->getNamespaceName();
170 $providedProperties = array();
171 foreach ($json as $key => $jvalue) {
172 $key = $this->getSafeName($key);
173 $providedProperties[$key] = true;
174
175 // Store the property inspection results so we don't have to do it
176 // again for subsequent objects of the same type
177 if (!isset($this->arInspectedClasses[$strClassName][$key])) {
178 $this->arInspectedClasses[$strClassName][$key]
179 = $this->inspectProperty($rc, $key);
180 }
181
182 list($hasProperty, $accessor, $type, $isNullable)
183 = $this->arInspectedClasses[$strClassName][$key];
184
185 if (!$hasProperty) {
186 if ($this->bExceptionOnUndefinedProperty) {
187 throw new JsonMapper_Exception(
188 'JSON property "' . $key . '" does not exist'
189 . ' in object of type ' . $strClassName
190 );
191 } else if ($this->undefinedPropertyHandler !== null) {
192 $undefinedPropertyKey = call_user_func(
193 $this->undefinedPropertyHandler,
194 $object, $key, $jvalue
195 );
196
197 if (is_string($undefinedPropertyKey)) {
198 list($hasProperty, $accessor, $type, $isNullable)
199 = $this->inspectProperty($rc, $undefinedPropertyKey);
200 }
201 } else {
202 $this->log(
203 'info',
204 'Property {property} does not exist in {class}',
205 array('property' => $key, 'class' => $strClassName)
206 );
207 }
208
209 if (!$hasProperty) {
210 continue;
211 }
212 }
213
214 if ($accessor === null) {
215 if ($this->bExceptionOnUndefinedProperty) {
216 throw new JsonMapper_Exception(
217 'JSON property "' . $key . '" has no public setter method'
218 . ' in object of type ' . $strClassName
219 );
220 }
221 $this->log(
222 'info',
223 'Property {property} has no public setter method in {class}',
224 array('property' => $key, 'class' => $strClassName)
225 );
226 continue;
227 }
228
229 if ($isNullable || !$this->bStrictNullTypes) {
230 if ($jvalue === null) {
231 $this->setProperty($object, $accessor, null);
232 continue;
233 }
234 $type = $this->removeNullable($type);
235 } else if ($jvalue === null) {
236 throw new JsonMapper_Exception(
237 'JSON property "' . $key . '" in class "'
238 . $strClassName . '" must not be NULL'
239 );
240 }
241
242 $type = $this->getFullNamespace($type, $strNs);
243 $type = $this->getMappedType($type, $jvalue);
244
245 if ($type === null || $type === 'mixed') {
246 //no given type - simply set the json data
247 $this->setProperty($object, $accessor, $jvalue);
248 continue;
249 } else if ($this->isObjectOfSameType($type, $jvalue)) {
250 $this->setProperty($object, $accessor, $jvalue);
251 continue;
252 } else if ($this->isSimpleType($type)
253 && !(is_array($jvalue) && $this->hasVariadicArrayType($accessor))
254 ) {
255 if ($this->isFlatType($type)
256 && !$this->isFlatType(gettype($jvalue))
257 ) {
258 throw new JsonMapper_Exception(
259 'JSON property "' . $key . '" in class "'
260 . $strClassName . '" is of type ' . gettype($jvalue) . ' and'
261 . ' cannot be converted to ' . $type
262 );
263 }
264 settype($jvalue, $type);
265 $this->setProperty($object, $accessor, $jvalue);
266 continue;
267 }
268
269 //FIXME: check if type exists, give detailed error message if not
270 if ($type === '') {
271 throw new JsonMapper_Exception(
272 'Empty type at property "'
273 . $strClassName . '::$' . $key . '"'
274 );
275
276 } else if (strpos(str_replace('|null', '', $type), '|')) {
277 throw new JsonMapper_Exception(
278 'Cannot decide which of the union types shall be used: '
279 . $type
280 );
281 }
282
283 $array = null;
284 $subtype = null;
285 if ($this->isArrayOfType($type)) {
286 //array
287 $array = array();
288 $subtype = substr($type, 0, -2);
289 } else if (substr($type, -1) == ']') {
290 list($proptype, $subtype) = explode('[', substr($type, 0, -1));
291 if ($proptype == 'array') {
292 $array = array();
293 } else {
294 $array = $this->createInstance($proptype, false, $jvalue);
295 }
296 } else if (is_array($jvalue) && $this->hasVariadicArrayType($accessor)) {
297 $array = array();
298 $subtype = $type;
299 } else {
300 if (is_a($type, 'ArrayAccess', true)
301 && is_a($type, 'Traversable', true)
302 ) {
303 $array = $this->createInstance($type, false, $jvalue);
304 }
305 }
306
307 if ($array !== null) {
308 if (!is_array($jvalue) && $this->isFlatType(gettype($jvalue))) {
309 throw new JsonMapper_Exception(
310 'JSON property "' . $key . '" must be an array, '
311 . gettype($jvalue) . ' given'
312 );
313 }
314
315 $subtypeNullable = $this->isNullable($subtype);
316 $cleanSubtype = $this->removeNullable($subtype);
317 $subtype = $this->getFullNamespace($cleanSubtype, $strNs);
318 if ($subtypeNullable) {
319 $subtype = '?' . $subtype;
320 }
321 $child = $this->mapArray($jvalue, $array, $subtype, $key);
322
323 } else if ($this->isFlatType(gettype($jvalue))) {
324 //use constructor parameter if we have a class
325 // but only a flat type (i.e. string, int)
326 if ($this->bStrictObjectTypeChecking) {
327 throw new JsonMapper_Exception(
328 'JSON property "' . $key . '" must be an object, '
329 . gettype($jvalue) . ' given'
330 );
331 }
332 $child = $this->createInstance($type, true, $jvalue);
333 } else {
334 $child = $this->createInstance($type, false, $jvalue);
335 $this->map($jvalue, $child);
336 }
337 $this->setProperty($object, $accessor, $child);
338 }
339
340 if ($this->bExceptionOnMissingData) {
341 $this->checkMissingData($providedProperties, $rc);
342 }
343
344 if ($this->bRemoveUndefinedAttributes) {
345 $this->removeUndefinedAttributes($object, $providedProperties);
346 }
347
348 if ($this->postMappingMethod !== null
349 && $rc->hasMethod($this->postMappingMethod)
350 ) {
351 $refDeserializePostMethod = $rc->getMethod(
352 $this->postMappingMethod
353 );
354 $refDeserializePostMethod->setAccessible(true);
355 $refDeserializePostMethod->invoke(
356 $object, ...$this->postMappingMethodArguments
357 );
358 }
359
360 return $object;
361 }
362
363 /**
364 * Convert a type name to a fully namespaced type name.
365 *
366 * @param string|null $type Type name (simple type or class name)
367 * @param string $strNs Base namespace that gets prepended to the type name
368 *
369 * @return string|null Fully-qualified type name with namespace
370 */
371 protected function getFullNamespace($type, $strNs)
372 {
373 if ($type === null || $type === '' || $type[0] === '\\' || $strNs === '') {
374 return $type;
375 }
376 list($first) = explode('[', $type, 2);
377 if ($this->isSimpleType($first)) {
378 return $type;
379 }
380
381 //create a full qualified namespace
382 return '\\' . $strNs . '\\' . $type;
383 }
384
385 /**
386 * Check required properties exist in json
387 *
388 * @param array $providedProperties array with json properties
389 * @param ReflectionClass $rc Reflection class to check
390 *
391 * @throws JsonMapper_Exception
392 *
393 * @return void
394 */
395 protected function checkMissingData($providedProperties, ReflectionClass $rc)
396 {
397 foreach ($rc->getProperties() as $property) {
398 $rprop = $rc->getProperty($property->name);
399 $docblock = $rprop->getDocComment();
400 $annotations = static::parseAnnotations($docblock);
401 if (isset($annotations['required'])
402 && !isset($providedProperties[$property->name])
403 ) {
404 throw new JsonMapper_Exception(
405 'Required property "' . $property->name . '" of class '
406 . $rc->getName()
407 . ' is missing in JSON data'
408 );
409 }
410 }
411 }
412
413 /**
414 * Remove attributes from object that were not passed in JSON data.
415 *
416 * This is to avoid confusion between those that were actually passed
417 * as NULL, and those that weren't provided at all.
418 *
419 * @param object $object Object to remove properties from
420 * @param array $providedProperties Array with JSON properties
421 *
422 * @return void
423 */
424 protected function removeUndefinedAttributes($object, $providedProperties)
425 {
426 foreach (get_object_vars($object) as $propertyName => $dummy) {
427 if (!isset($providedProperties[$propertyName])) {
428 unset($object->{$propertyName});
429 }
430 }
431 }
432
433 /**
434 * Map an array
435 *
436 * @param array $json JSON array structure from json_decode()
437 * @param mixed $array Array or ArrayObject that gets filled with
438 * data from $json
439 * @param string $class Class name for children objects.
440 * All children will get mapped onto this type.
441 * Supports class names and simple types
442 * like "string" and nullability "string|null".
443 * Pass "null" to not convert any values
444 * @param string $parent_key Defines the key this array belongs to
445 * in order to aid debugging.
446 *
447 * @return mixed Mapped $array is returned
448 */
449 public function mapArray($json, $array, $class = null, $parent_key = '')
450 {
451 $isNullable = $this->isNullable($class);
452 $class = $this->removeNullable($class);
453 $originalClass = $class;
454
455 foreach ($json as $key => $jvalue) {
456 if ($jvalue === null && !$isNullable && $this->bStrictNullTypesInArrays) {
457 throw new JsonMapper_Exception(
458 'JSON property'
459 . ' "' . ($parent_key ? $parent_key : '?') . '[' . $key . ']"'
460 . ' must not be NULL'
461 );
462 }
463
464 $class = $this->getMappedType($originalClass, $jvalue);
465 if ($class === null) {
466 $array[$key] = $jvalue;
467 } else if ($this->isArrayOfType($class)) {
468 $array[$key] = $this->mapArray(
469 $jvalue,
470 array(),
471 substr($class, 0, -2)
472 );
473 } else if ($this->isFlatType(gettype($jvalue))) {
474 //use constructor parameter if we have a class
475 // but only a flat type (i.e. string, int)
476 if ($jvalue === null) {
477 $array[$key] = null;
478 } else {
479 if ($this->isSimpleType($class)) {
480 settype($jvalue, $class);
481 $array[$key] = $jvalue;
482 } else if ($this->bStrictObjectTypeChecking) {
483 throw new JsonMapper_Exception(
484 'JSON property'
485 . ' "' . ($parent_key ? $parent_key : '?') . '[' . $key . ']"'
486 . ' must be an object, ' . gettype($jvalue) . ' given'
487 );
488 } else {
489 $array[$key] = $this->createInstance(
490 $class, true, $jvalue
491 );
492 }
493 }
494 } else if ($this->isFlatType($class)) {
495 throw new JsonMapper_Exception(
496 'JSON property "' . ($parent_key ? $parent_key : '?') . '"'
497 . ' is an array of type "' . $class . '"'
498 . ' but contained a value of type'
499 . ' "' . gettype($jvalue) . '"'
500 );
501 } else if (is_a($class, 'ArrayObject', true)) {
502 $array[$key] = $this->mapArray(
503 $jvalue,
504 $this->createInstance($class)
505 );
506 } else {
507 $array[$key] = $this->map(
508 $jvalue, $this->createInstance($class, false, $jvalue)
509 );
510 }
511 }
512 return $array;
513 }
514
515 /**
516 * Try to find out if a property exists in a given class.
517 * Checks property first, falls back to setter method.
518 *
519 * @param ReflectionClass $rc Reflection class to check
520 * @param string $name Property name
521 *
522 * @return array First value: if the property exists
523 * Second value: the accessor to use (
524 * ReflectionMethod or ReflectionProperty, or null)
525 * Third value: type of the property
526 * Fourth value: if the property is nullable
527 */
528 protected function inspectProperty(ReflectionClass $rc, $name)
529 {
530 //try setter method first
531 $setter = 'set' . $this->getCamelCaseName($name);
532
533 if ($rc->hasMethod($setter)) {
534 $rmeth = $rc->getMethod($setter);
535 if ($rmeth->isPublic() || $this->bIgnoreVisibility) {
536 $isNullable = false;
537 $rparams = $rmeth->getParameters();
538 if (count($rparams) > 0) {
539 $isNullable = $rparams[0]->allowsNull();
540 $ptype = $rparams[0]->getType();
541 if ($ptype !== null) {
542 $typeName = $this->stringifyReflectionType($ptype);
543 //allow overriding an "array" type hint
544 // with a more specific class in the docblock
545 if ($typeName !== 'array') {
546 return array(
547 true, $rmeth,
548 $typeName,
549 $isNullable,
550 );
551 }
552 }
553 }
554
555 $docblock = $rmeth->getDocComment();
556 $annotations = static::parseAnnotations($docblock);
557
558 if (!isset($annotations['param'][0])) {
559 return array(true, $rmeth, null, $isNullable);
560 }
561 list($type) = explode(' ', trim($annotations['param'][0]));
562 return array(true, $rmeth, $type, $this->isNullable($type));
563 }
564 }
565
566 //now try to set the property directly
567 //we have to look it up in the class hierarchy
568 $class = $rc;
569 $rprop = null;
570 do {
571 if ($class->hasProperty($name)) {
572 $rprop = $class->getProperty($name);
573 }
574 } while ($rprop === null && $class = $class->getParentClass());
575
576 if ($rprop === null) {
577 //case-insensitive property matching
578 foreach ($rc->getProperties() as $p) {
579 if ((strcasecmp($p->name, $name) === 0)) {
580 $rprop = $p;
581 $class = $rc;
582 break;
583 }
584 }
585 }
586 if ($rprop !== null) {
587 if ($rprop->isPublic() || $this->bIgnoreVisibility) {
588 $docblock = $rprop->getDocComment();
589 if (PHP_VERSION_ID >= 80000 && $docblock === false
590 && $class->hasMethod('__construct')
591 ) {
592 $docblock = $class->getMethod('__construct')->getDocComment();
593 }
594 $annotations = static::parseAnnotations($docblock);
595
596 if (!isset($annotations['var'][0])) {
597 if (PHP_VERSION_ID >= 80000 && $rprop->hasType()
598 && isset($annotations['param'])
599 ) {
600 foreach ($annotations['param'] as $param) {
601 if (strpos($param, '$' . $rprop->getName()) !== false) {
602 list($type) = explode(' ', $param);
603 return array(
604 true, $rprop, $type, $this->isNullable($type)
605 );
606 }
607 }
608 }
609
610 // If there is no annotations (higher priority) inspect
611 // if there's a scalar type being defined
612 if (PHP_VERSION_ID >= 70400 && $rprop->hasType()) {
613 $rPropType = $rprop->getType();
614 $propTypeName = $this->stringifyReflectionType($rPropType);
615 if ($this->isSimpleType($propTypeName)) {
616 return array(
617 true,
618 $rprop,
619 $propTypeName,
620 $rPropType->allowsNull()
621 );
622 }
623
624 return array(
625 true,
626 $rprop,
627 '\\' . ltrim($propTypeName, '\\'),
628 $rPropType->allowsNull()
629 );
630 }
631
632 return array(true, $rprop, null, false);
633 }
634
635 //support "@var type description"
636 list($type) = explode(' ', $annotations['var'][0]);
637
638 return array(true, $rprop, $type, $this->isNullable($type));
639 } else {
640 //no setter, private property
641 return array(true, null, null, false);
642 }
643 }
644
645 //no setter, no property
646 return array(false, null, null, false);
647 }
648
649 /**
650 * Removes - and _ and makes the next letter uppercase
651 *
652 * @param string $name Property name
653 *
654 * @return string CamelCasedVariableName
655 */
656 protected function getCamelCaseName($name)
657 {
658 return str_replace(
659 ' ', '', ucwords(str_replace(array('_', '-'), ' ', $name))
660 );
661 }
662
663 /**
664 * Since hyphens cannot be used in variables we have to uppercase them.
665 *
666 * Technically you may use them, but they are awkward to access.
667 *
668 * @param string $name Property name
669 *
670 * @return string Name without hyphen
671 */
672 protected function getSafeName($name)
673 {
674 if (strpos($name, '-') !== false) {
675 $name = $this->getCamelCaseName($name);
676 }
677
678 return $name;
679 }
680
681 /**
682 * Set a property on a given object to a given value.
683 *
684 * Checks if the setter or the property are public are made before
685 * calling this method.
686 *
687 * @param object $object Object to set property on
688 * @param object $accessor ReflectionMethod or ReflectionProperty
689 * @param mixed $value Value of property
690 *
691 * @return void
692 */
693 protected function setProperty(
694 $object, $accessor, $value
695 ) {
696 if (!$accessor->isPublic() && $this->bIgnoreVisibility) {
697 $accessor->setAccessible(true);
698 }
699 if ($accessor instanceof ReflectionProperty) {
700 $accessor->setValue($object, $value);
701 } else if (is_array($value) && $this->hasVariadicArrayType($accessor)) {
702 $accessor->invoke($object, ...$value);
703 } else {
704 //setter method
705 $accessor->invoke($object, $value);
706 }
707 }
708
709 /**
710 * Create a new object of the given type.
711 *
712 * This method exists to be overwritten in child classes,
713 * so you can do dependency injection or so.
714 *
715 * @param string $class Class name to instantiate
716 * @param boolean $useParameter Pass $parameter to the constructor or not
717 * @param mixed $jvalue Constructor parameter (the json value)
718 *
719 * @return object Freshly created object
720 */
721 protected function createInstance(
722 $class, $useParameter = false, $jvalue = null
723 ) {
724 if ($useParameter) {
725 if (PHP_VERSION_ID >= 80100
726 && is_subclass_of($class, \BackedEnum::class)
727 ) {
728 return $class::from($jvalue);
729 }
730
731 return new $class($jvalue);
732 } else {
733 $reflectClass = new ReflectionClass($class);
734 $constructor = $reflectClass->getConstructor();
735 if (null === $constructor
736 || $constructor->getNumberOfRequiredParameters() > 0
737 ) {
738 return $reflectClass->newInstanceWithoutConstructor();
739 }
740 return $reflectClass->newInstance();
741 }
742 }
743
744 /**
745 * Get the mapped class/type name for this class.
746 * Returns the incoming classname if not mapped.
747 *
748 * Lets you override class names via the $classMap property.
749 *
750 * @param string|null $type Type name to map
751 * @param mixed $jvalue Constructor parameter (the json value)
752 *
753 * @return string|null The mapped type/class name
754 */
755 protected function getMappedType($type, $jvalue = null)
756 {
757 if (isset($this->classMap[$type])) {
758 $target = $this->classMap[$type];
759 } else if (is_string($type) && $type !== '' && $type[0] == '\\'
760 && isset($this->classMap[substr($type, 1)])
761 ) {
762 $target = $this->classMap[substr($type, 1)];
763 } else {
764 $target = null;
765 }
766
767 if ($target) {
768 if (is_callable($target)) {
769 $type = $target($type, $jvalue);
770 } else {
771 $type = $target;
772 }
773 }
774 return $type;
775 }
776
777 /**
778 * Checks if the given type is a "simple type"
779 *
780 * @param string $type type name from gettype()
781 *
782 * @return boolean True if it is a simple PHP type
783 *
784 * @see isFlatType()
785 */
786 protected function isSimpleType($type)
787 {
788 return $type == 'string'
789 || $type == 'boolean' || $type == 'bool'
790 || $type == 'integer' || $type == 'int'
791 || $type == 'double' || $type == 'float'
792 || $type == 'array' || $type == 'object'
793 || $type === 'mixed';
794 }
795
796 /**
797 * Checks if the object is of this type or has this type as one of its parents
798 *
799 * @param string $type class name of type being required
800 * @param mixed $value Some PHP value to be tested
801 *
802 * @return boolean True if $object has type of $type
803 */
804 protected function isObjectOfSameType($type, $value)
805 {
806 if (false === is_object($value)) {
807 return false;
808 }
809
810 return is_a($value, $type);
811 }
812
813 /**
814 * Checks if the given type is a type that is not nested
815 * (simple type except array, object and mixed)
816 *
817 * @param string $type type name from gettype()
818 *
819 * @return boolean True if it is a non-nested PHP type
820 *
821 * @see isSimpleType()
822 */
823 protected function isFlatType($type)
824 {
825 return $type == 'NULL'
826 || $type == 'string'
827 || $type == 'boolean' || $type == 'bool'
828 || $type == 'integer' || $type == 'int'
829 || $type == 'double' || $type == 'float';
830 }
831
832 /**
833 * Returns true if type is an array of elements
834 * (bracket notation)
835 *
836 * @param string $strType type to be matched
837 *
838 * @return bool
839 */
840 protected function isArrayOfType($strType)
841 {
842 return substr($strType, -2) === '[]';
843 }
844
845 /**
846 * Returns true if accessor is a method and has only one parameter
847 * which is variadic ("...$args").
848 *
849 * @param ReflectionMethod|ReflectionProperty|null $accessor accessor
850 * to set value
851 *
852 * @return bool
853 */
854 protected function hasVariadicArrayType($accessor)
855 {
856 if (!$accessor instanceof ReflectionMethod) {
857 return false;
858 }
859
860 $parameters = $accessor->getParameters();
861
862 if (count($parameters) !== 1) {
863 return false;
864 }
865
866 $parameter = $parameters[0];
867
868 return $parameter->isVariadic();
869 }
870
871 /**
872 * Checks if the given type is nullable
873 *
874 * @param string $type type name from the phpdoc param
875 *
876 * @return boolean True if it is nullable
877 */
878 protected function isNullable($type)
879 {
880 return stripos('|' . $type . '|', '|null|') !== false
881 || strpos('|' . $type, '|?') !== false;
882 }
883
884 /**
885 * Remove the 'null' section of a type
886 *
887 * @param string|null $type type name from the phpdoc param
888 *
889 * @return string|null The new type value
890 */
891 protected function removeNullable($type)
892 {
893 if ($type === null) {
894 return null;
895 }
896 return substr(
897 str_ireplace(['|null|', '|?'], '|', '|' . $type . '|'),
898 1, -1
899 );
900 }
901
902 /**
903 * Get a string representation of the reflection type.
904 * Required because named, union and intersection types need to be handled.
905 *
906 * @param ReflectionType $type Native PHP type
907 *
908 * @return string "foo|bar"
909 */
910 protected function stringifyReflectionType(ReflectionType $type)
911 {
912 if ($type instanceof ReflectionNamedType) {
913 return ($type->isBuiltin() ? '' : '\\') . $type->getName();
914 }
915
916 return implode(
917 '|',
918 array_map(
919 function (ReflectionNamedType $type) {
920 return ($type->isBuiltin() ? '' : '\\') . $type->getName();
921 },
922 $type->getTypes()
923 )
924 );
925 }
926
927 /**
928 * Copied from PHPUnit 3.7.29, Util/Test.php
929 *
930 * @param string $docblock Full method docblock
931 *
932 * @return array Array of arrays.
933 * Key is the "@"-name like "param",
934 * each value is an array of the rest of the @-lines
935 */
936 protected static function parseAnnotations($docblock)
937 {
938 $annotations = array();
939 // Strip away the docblock header and footer
940 // to ease parsing of one line annotations
941 $docblock = substr($docblock, 3, -2);
942
943 $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
944 if (preg_match_all($re, $docblock, $matches)) {
945 $numMatches = count($matches[0]);
946
947 for ($i = 0; $i < $numMatches; ++$i) {
948 $annotations[$matches['name'][$i]][] = $matches['value'][$i];
949 }
950 }
951
952 return $annotations;
953 }
954
955 /**
956 * Log a message to the $logger object
957 *
958 * @param string $level Logging level
959 * @param string $message Text to log
960 * @param array $context Additional information
961 *
962 * @return void
963 */
964 protected function log($level, $message, array $context = array())
965 {
966 if ($this->logger) {
967 $this->logger->log($level, $message, $context);
968 }
969 }
970
971 /**
972 * Sets a logger instance on the object
973 *
974 * @param \Psr\Log\LoggerInterface $logger PSR-3 compatible logger object
975 *
976 * @return void
977 */
978 public function setLogger($logger)
979 {
980 $this->logger = $logger;
981 }
982 }
983 ?>
984