PluginProbe
Depicter — Popup & Slider Builder / 1.9.2
Depicter — Popup & Slider Builder v1.9.2
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 1.9.2, at vendor/netresearch/jsonmapper/src/JsonMapper.php

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