oont-contents/plugins/woocommerce-square/vendor/apimatic/jsonmapper/src/JsonMapper.php
2025-02-08 15:10:23 +01:00

1958 lines
70 KiB
PHP

<?php
/**
* Part of JsonMapper
*
* PHP version 5
*
* @category Netresearch
* @package JsonMapper
* @author Christian Weiske <christian.weiske@netresearch.de>
* @license OSL-3.0 http://opensource.org/licenses/osl-3.0
* @link http://www.netresearch.de/
*/
namespace apimatic\jsonmapper;
use Exception;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
/**
* Automatically map JSON structures into objects.
*
* @category Netresearch
* @package JsonMapper
* @author Christian Weiske <christian.weiske@netresearch.de>
* @license OSL-3.0 http://opensource.org/licenses/osl-3.0
* @link http://www.netresearch.de/
*/
class JsonMapper
{
/**
* PSR-3 compatible logger object
*
* @link http://www.php-fig.org/psr/psr-3/
* @var object
* @see setLogger()
*/
protected $logger;
/**
* Throw an exception when JSON data contain a property
* that is not defined in the PHP class
*
* @var boolean
*/
public $bExceptionOnUndefinedProperty = false;
/**
* Calls this method on the PHP class when an undefined property
* is found. This method should receive two arguments, $key
* and $value for the property key and value. Only works if
* $bExceptionOnUndefinedProperty is set to false.
*
* @var string
*/
public $sAdditionalPropertiesCollectionMethod = null;
/**
* Throw an exception if the JSON data miss a property
* that is marked with @required in the PHP class
*
* @var boolean
*/
public $bExceptionOnMissingData = false;
/**
* If the types of map() parameters shall be checked.
* You have to disable it if you're using the json_decode "assoc" parameter.
*
* `json_decode($str, false)`
*
* @var boolean
*/
public $bEnforceMapType = true;
/**
* Contains user provided map of class names vs their child classes.
* This is only needed if discriminators are to be used. PHP reflection is not
* used to get child classes because most code bases use autoloaders where
* classes are lazily loaded.
*
* @var array
*/
public $arChildClasses = array();
/**
* Contains user provided map of discriminators substitution along with
* its actual value.
* This is only needed if discriminators are to be used in type combinators,
* and their actual values are substituted in the type combinator templates.
*
* @var array<string,string>
*/
public $discriminatorSubs = array();
/**
* Runtime cache for inspected classes. This is particularly effective if
* mapArray() is called with a large number of objects
*
* @var array property inspection result cache
*/
protected $arInspectedClasses = array();
/**
* An array of directives from php defined configuration files.
*
* @var array|null Array of values from the configuration files.
*/
protected $config = null;
protected $zendOptimizerPlusExtensionLoaded = null;
/**
* Constructor for JsonMapper.
*
* @throws JsonMapperException
*/
function __construct()
{
$zendOptimizerPlus = "Zend Optimizer+";
$zendOptimizerPlusSaveCommentKey = "zend_optimizerplus.save_comments";
$opCacheSaveCommentKey = "opcache.save_comments";
if (!isset($this->config)) {
$iniPath = php_ini_loaded_file();
$accessAllowed = $this->isPathAllowed($iniPath, ini_get('open_basedir'));
if ($accessAllowed && is_readable($iniPath)) {
$this->config = parse_ini_file($iniPath);
}
}
if (!isset($this->zendOptimizerPlusExtensionLoaded)) {
$this->zendOptimizerPlusExtensionLoaded
= extension_loaded($zendOptimizerPlus);
}
$zendOptimizerDiscardedComments
= $this->zendOptimizerPlusExtensionLoaded === true
&& $this->commentsDiscardedFor($zendOptimizerPlusSaveCommentKey);
$opCacheDiscardedComments
= $this->commentsDiscardedFor($opCacheSaveCommentKey);
if ($zendOptimizerDiscardedComments || $opCacheDiscardedComments) {
throw JsonMapperException::commentsDisabledInConfigurationException(
array($zendOptimizerPlusSaveCommentKey, $opCacheSaveCommentKey)
);
}
}
/**
* Returns true if the provided file path is accessible and
* not restricted by open_basedir restriction.
*
* @param string|false $filePath Real file path to be checked.
* @param string|false $allowedPaths Allowed paths separated by os
* path separator.
*
* @return bool Whether the provided path is allowed to access.
*/
protected function isPathAllowed($filePath, $allowedPaths)
{
if (empty($filePath)) {
return false;
}
if (empty($allowedPaths)) {
return true;
}
$allowedPathArray = explode(PATH_SEPARATOR, $allowedPaths);
if (!in_array(dirname($filePath), $allowedPathArray)) {
return false;
}
return true;
}
/**
* Returns true if comments are disabled locally or in php.ini file.
* However, if comments are enabled locally by overwriting global
* php.ini configurations then returns false.
*
* @param string $configKey Configuration key to be checked.
*
* @return bool Whether comments are disabled in environment or php.ini file.
*/
protected function commentsDiscardedFor($configKey)
{
$localConfigVal = strtolower(ini_get($configKey));
$phpIniConfigVal = !isset($this->config[$configKey]) ? ''
: strtolower($this->config[$configKey]);
$enableValues = ["1", "on", "true", "yes"];
$disableValues = ["0", "off", "false", "no"];
$notEnabled = in_array($localConfigVal, $enableValues, true) === false;
$isDisabled = in_array($localConfigVal, $disableValues, true) === true;
$isDisabledInPhpIniFile = in_array(
$phpIniConfigVal, $disableValues, true
) === true;
return $notEnabled && ($isDisabled || $isDisabledInPhpIniFile);
}
/**
* Map data all data in $json into the given $object instance.
*
* @param object $json JSON object structure from json_decode()
* @param object $object Object to map $json data into
* @param bool $strict True if looking to map with strict type checking,
* Default: false
*
* @return object Mapped object is returned.
* @see mapArray()
*/
public function map($json, $object, $strict = false)
{
if ($this->bEnforceMapType && !is_object($json)) {
throw new \InvalidArgumentException(
'JsonMapper::map() requires first argument to be an object'
. ', ' . gettype($json) . ' given.'
);
}
if (!is_object($object)) {
throw new \InvalidArgumentException(
'JsonMapper::map() requires second argument to be an object'
. ', ' . gettype($object) . ' given.'
);
}
$strClassName = get_class($object);
$rc = new ReflectionClass($object);
$strNs = $rc->getNamespaceName();
$providedProperties = array();
$additionalPropertiesMethod = $this->getAdditionalPropertiesMethod($rc);
foreach ($json as $key => $jvalue) {
// $providedProperties[$key] = true;
$isAdditional = false;
// Store the property inspection results so we don't have to do it
// again for subsequent objects of the same type
if (!isset($this->arInspectedClasses[$strClassName][$key])) {
$this->arInspectedClasses[$strClassName][$key]
= $this->inspectProperty($rc, $key);
}
list($hasProperty, $accessor, $type, $factoryMethod, $mapsBy, $namespace)
= $this->arInspectedClasses[$strClassName][$key];
if ($accessor !== null) {
$providedProperties[$accessor->getName()] = true;
}
if (!$hasProperty) {
if ($this->bExceptionOnUndefinedProperty) {
throw JsonMapperException::undefinedPropertyException(
$key,
$strClassName
);
}
$isAdditional = true;
$this->log(
'info',
'Property {property} does not exist in {class}',
array('property' => $key, 'class' => $strClassName)
);
}
if ($accessor === null) {
if ($this->bExceptionOnUndefinedProperty) {
throw JsonMapperException::undefinedPropertyException(
$key,
$strClassName,
true
);
}
$isAdditional = true;
$this->log(
'info',
'Property {property} has no public setter method in {class}',
array('property' => $key, 'class' => $strClassName)
);
}
//FIXME: check if type exists, give detailled error message if not
if ($type === '') {
throw JsonMapperException::missingTypePropertyException(
$key,
$strClassName
);
}
if ($isAdditional) {
if ($additionalPropertiesMethod !== null) {
$additionalPropertiesMethod->invoke($object, $key, $jvalue);
}
continue;
}
$value = $this->getMappedValue(
$jvalue,
$type,
$mapsBy,
$factoryMethod,
$namespace,
$rc->getName(),
$strict
);
$this->setProperty($object, $accessor, $value, $strNs);
}
if ($this->bExceptionOnMissingData) {
$this->checkMissingData($providedProperties, $rc);
}
return $object;
}
/**
* Checks if type is an array, and extracts its dimensions and inner type.
*
* @param string $type Type to be checked for array.
* @param int $dimensions Dimensions passed in recursions, initial: 0.
*
* @return array
*/
public function getArrayTypeAndDimensions($type, $dimensions = 0)
{
list($isMap, $isArray, $innerType) = TypeCombination::extractTypeInfo($type);
if ($isMap || $isArray) {
// if it's an array or map of some type
// increment dimension and check for innerType
return $this->getArrayTypeAndDimensions($innerType, ++$dimensions);
}
return array($type, $dimensions);
}
/**
* Try calling the factory method if exists, otherwise throw JsonMapperException
*
* @param string $factoryMethod factory method in the format
* 'path/to/callable/function argType'
* @param mixed $value value to be passed in as param into factory
* method.
* @param string $strClassName strClassName referencing this factory method
*
* @return mixed|false
* @throws JsonMapperException
*/
protected function callFactoryMethod($factoryMethod, $value, $strClassName)
{
$factoryMethod = explode(' ', $factoryMethod)[0];
if (!is_callable($factoryMethod)) {
throw JsonMapperException::unCallableFactoryMethodException(
$factoryMethod,
$strClassName
);
}
return call_user_func($factoryMethod, $value);
}
/**
* Try calling the given function with value, return [true, updatedValue]
* if call successful.
*
* @param mixed $value value to be passed in argument of factory method.
* @param string $factoryMethod factory method string in the format
* 'path/to/callable/function argType'.
*
* @return array Return an array [bool $success, $value] and value will be the
* failure cause if not success.
*/
protected function callFactoryWithErrorHandling($value, $factoryMethod)
{
$success = true;
if (version_compare(phpversion(), '7.0', '<')) {
try {
$value = $this->callFactoryMethod($factoryMethod, $value, '');
} catch (Exception $e) {
// In Php versions < 7.0 catching only exceptions but not typeErrors
// since strict types were not available for php < 7.0
// also we can't use throwable since its only available after php 7.0
$success = false;
$value = $e->getMessage();
}
} else {
try {
$value = $this->callFactoryMethod($factoryMethod, $value, '');
} catch (\Throwable $e) {
// In Php versions >= 7.0 catching exceptions including typeErrors
// using Throwable since its base interface for Exceptions & Errors
// since types can be strict for php >= 7.0
$success = false;
$value = $e->getMessage();
}
}
return [$success, $value];
}
/**
* Get mapped value for a property in an object.
*
* @param mixed $jvalue Raw normalized data for the property
* @param string $type Type found by inspectProperty()
* @param string|null $mapsBy OneOf/AnyOf types hint found by
* inspectProperty in mapsBy annotation
* @param string[]|null $factoryMethods Callable factory methods for property
* @param string $namespace Namespace of the class
* @param string $className Name of the class
* @param bool $strict True if looking to map with strict
* type checking.
*
* @return array|false|mixed|object|null
* @throws JsonMapperException|ReflectionException
*/
protected function getMappedValue(
$jvalue,
$type,
$mapsBy,
$factoryMethods,
$namespace,
$className,
$strict
) {
if ($mapsBy) {
return $this->mapFor(
$jvalue,
$mapsBy,
$namespace,
$factoryMethods,
$className
);
}
//use factory method generated value if factory provided
if ($factoryMethods !== null && isset($factoryMethods[0])) {
return $this->callFactoryMethod(
$factoryMethods[0],
$jvalue,
$className
);
}
if ($this->isNullable($type)) {
if ($jvalue === null) {
return null;
}
$type = $this->removeNullable($type);
}
if ($type === null || $type === 'mixed' || $type === '') {
//no given type - simply return the json data
return $jvalue;
} else if ($this->isObjectOfSameType($type, $jvalue)) {
return $jvalue;
} else if ($this->isSimpleType($type)) {
if ($strict && !$this->isSimpleValue($jvalue, $type)) {
// if mapping strictly for multipleTypes
throw JsonMapperException::unableToSetTypeException(
$type,
json_encode($jvalue)
);
}
settype($jvalue, $type);
return $jvalue;
}
$array = null;
list($subtype, $dimension) = $this->getArrayTypeAndDimensions($type);
if ($dimension > 0) {
// array with some dimensions
$array = array();
} else if (substr($type, -1) == ']') {
list($proptype, $subtype) = explode('[', substr($type, 0, -1));
if (!$this->isSimpleType($proptype)) {
$proptype = $this->getFullNamespace($proptype, $namespace);
}
$array = $this->createInstance($proptype);
} else if ($type == 'ArrayObject'
|| is_subclass_of($type, 'ArrayObject')
) {
$subtype = null;
$array = $this->createInstance($type);
}
if ($array !== null) {
if (!$this->isSimpleType($subtype)) {
$subtype = $this->getFullNamespace($subtype, $namespace);
}
if ($jvalue === null) {
$child = null;
} else if ($this->isRegisteredType(
$this->getFullNamespace($subtype, $namespace)
)
) {
$child = $this->mapClassArray(
$jvalue,
$subtype,
$dimension,
$strict
);
} else {
$child = $this->mapArray(
$jvalue,
$array,
$subtype,
$dimension,
$strict
);
}
} else if ($this->isFlatType(gettype($jvalue))) {
//use constructor parameter if we have a class
// but only a flat type (i.e. string, int)
if ($jvalue === null) {
$child = null;
} else {
$type = $this->getFullNamespace($type, $namespace);
$child = new $type($jvalue);
}
} else {
$type = $this->getFullNamespace($type, $namespace);
$child = $this->mapClass($jvalue, $type, $strict);
}
return $child;
}
/**
* Check if an array is Associative (has string keys) or
* its Indexed (empty or non-string keys), returns [isAssociative, isIndexed]
*
* @param mixed $value A value that could be isAssociative or isIndexed array
*
* @return array Returns Array of result i.e [isAssociative, isIndexed]
*/
protected function isAssociativeOrIndexed($value)
{
if (is_object($value)) {
return [true, false];
}
if (!is_array($value)) {
return [false, false];
}
foreach ($value as $key => $v) {
if (is_string($key)) {
return [true, false];
}
}
return [false, true];
}
/**
* Gets not nested type for the given value
*
* @param mixed $value Value to be checked for type
*
* @return string|false Return flat PHP types for the given value
* and if not flat type return false.
*/
protected function getFlatType($value)
{
$type = gettype($value);
if (!$this->isFlatType($type)) {
return false;
}
switch ($type) {
case 'integer':
$type = 'int';
break;
case 'double':
$type = 'float';
break;
case 'boolean':
$type = 'bool';
break;
case 'NULL':
$type = 'null';
break;
}
return $type;
}
/**
* Check all given factory methods that can be called with given value.
*
* @param mixed $value Any value to be checked with factoryMethods.
* @param mixed $newVal A copy of value to be updated.
* @param string $type Extracted type of the value.
* @param string[] $factoryMethods Methods in the format 'path/to/method argType'
* which will be converting $value into any
* desirable type.
*
* @return string Returns the type or typeGroup of value based on
* given factory methods.
* @throws JsonMapperException
*/
protected function applyFactoryMethods($value, &$newVal, $type, $factoryMethods)
{
$errorMsg = [];
$types = [$type]; // list of possible types
foreach ($factoryMethods as $m) {
// checking each provided factory method
$method = explode(' ', $m);
// try calling factory method
list($success, $val) = $this->callFactoryWithErrorHandling($value, $m);
if ($success) {
if ($type == $method[1]) {
// if method call is successful
// and given type equals to argType of factory method
// update the value with returned $val of factory method
// and return with type early
$newVal = $val;
return $type;
}
// if method call is successful
// and given type is not same as argType of factory method
// then add argType in list of possible types for $value
array_push($types, $method[1]);
} elseif ($type == $method[1]) {
// if method call is failure given type equals to argType of
// factory method then add reason $val as an error message
array_push($errorMsg, "$method[0]: $val");
}
}
if (!empty($errorMsg)) {
// if any error msg is added then throw exception
throw JsonMapperException::invalidArgumentFactoryMethodException(
$type,
join("\n", $errorMsg)
);
}
// converting possible types array into the string format
// of an anyof typeGroup
$types = array_unique($types);
asort($types);
$type = join(',', $types);
if (count($types) > 1) {
// wrap in brackets for multiple types
$type = "($type)";
}
return $type;
}
/**
* Extract type from any given value.
*
* @param mixed $value Any value to be checked for type, should be
* an array if checking for inner type
* @param string[] $factory Methods in the format 'path/to/method argType'
* which will be converting $value into any
* desirable type, Default: []
* @param string $start string to be appended at the start of the
* extracted type, Default: ''
* @param string $end string to be appended at the end of the
* extracted type, Default: ''
*
* @return string Returns the type that could be mapped on the given value.
* @throws JsonMapperException
*/
protected function getType(&$value, $factory = [], $start = '', $end = '')
{
$type = $this->getFlatType($value);
$newVal = $value;
if (!$type && is_array($value)) {
if ($this->isAssociativeOrIndexed($value)[0]) {
// if value is associative array
$start .= 'array<string,';
$end = '>' . $end;
} else {
// if value is indexed array
if (empty($value)) {
return 'array';
}
$end = '[]' . $end;
}
$types = [];
foreach ($value as $k => $v) {
array_push($types, $this->getType($v, $factory));
$newVal[$k] = $v;
}
$types = array_unique($types);
asort($types);
$isOneOfOrAnyOf = !empty($types) && substr($types[0], -1) === ')';
if (count($types) > 1 || $isOneOfOrAnyOf) {
// wrap in brackets for multiple types or oneof/anyof type
$start .= '(';
$end = ')' . $end;
}
$type = join(',', $types);
} elseif (!$type && is_object($value)) {
$class = get_class($value); // returns full path of class
$slashPos = strrpos($class, '\\');
if (!$slashPos) {
// if slash not found then replace with -1
$slashPos = -1;
}
$type = substr($class, ++$slashPos);
}
$type = "$start$type$end";
if (!empty($factory)) {
$type = $this->applyFactoryMethods($value, $newVal, $type, $factory);
}
$value = $newVal;
return $type;
}
/**
* Check the given type/types in the provided typeGroup, return true if
* type(s) exists in the typeGroup
*
* @param TypeCombination|string $typeGroup TypesCombination object or string
* format for grouped types. All kind
* of groups are allowed here.
* @param TypeCombination|string $type Can be a normal type like string[],
* int, Car, etc. or a combination of
* types like (CarA,CarB)[], (int,Enum),
* or array<string,(CarA,CarB)>.
* @param string $start prefix used by string $type,
* Default: ""
* @param string $end postfix used by string $type,
* Default: ""
*
* @return bool
*/
protected function checkForType($typeGroup, $type, $start = '', $end = '')
{
if (is_string($typeGroup)) {
// convert into TypeCombination object
$typeGroup = TypeCombination::withFormat($typeGroup);
}
if (is_string($type) && strpos($type, '(') !== false) {
// for combination of types like: (A,B)[] or array<string,(A,(B,C)[])>
// convert into TypeCombination object
$type = TypeCombination::withFormat($type);
}
$checkAllInner = false; // required when $type instance of TypeCombination.
if (is_string($type)) {
// for checking simple types like: string, int[] or Car[]
if ($typeGroup->getGroupName() == 'map') {
$start .= 'array<string,';
$end = '>' . $end;
} elseif ($typeGroup->getGroupName() == 'array') {
$end = '[]' . $end;
}
foreach ($typeGroup->getTypes() as $t) {
if (is_string($t)) {
$matched = $type === "$start$t$end";
} else {
$matched = $this->checkForType($t, $type, $start, $end);
}
if ($matched) {
// if any type in the typeGroup matched with given type,
// then early return true
return true;
}
}
return false;
} elseif (in_array($type->getGroupName(), ['array','map'])) {
// To handle type if its array/map group of types
// extract all internal groups from the given typeGroup that
// are similar to $type
$typeGroup = TypeCombination::with($typeGroup->extractSimilar($type));
// update type to the innermost level of oneof/anyof
$type = $type->extractOneOfAnyOfGroup();
// check all inner elements of $type
$checkAllInner = true;
}
// To handle type if its oneof/anyof group of types
foreach ($type->getTypes() as $t) {
$contains = $this->checkForType($typeGroup, $t);
if (!$checkAllInner && $contains) {
// if any type is found then
// type is matched with $typeGroup
return true;
}
if ($checkAllInner && !$contains) {
// if any type is missing then
// type is not matched with $typeGroup
return false;
}
}
return $checkAllInner;
}
/**
* Converts the given typeCombination into its string format.
*
* @param TypeCombination|string $type Combined type/Single type.
*
* @return string
*/
protected function formatType($type)
{
return is_string($type) ? $type : $type->getFormat();
}
/**
* Checks if type of the given value is present in the type group,
* also updates the value when necessary.
*
* @param string $typeGroup String format for grouped types, i.e.
* oneof(Car,Atom)
* @param mixed $value Any value to be checked in type group
* @param array $factoryMethods Callable factory methods for the value, that
* are required to serialize it into any of the
* provided types in typeGroup in the format:
* 'path/to/method argType', Default: []
*
* @return mixed Returns the same value or updated one if any factory method
* is applied
* @throws JsonMapperException Throws exception if a factory method is provided
* but applicable on value, or also throws an
* exception if type of value didn't match with type
* group
*/
public function checkTypeGroupFor($typeGroup, $value, $factoryMethods = [])
{
$type = self::getType($value, $factoryMethods);
if ($this->checkForType($typeGroup, $type)) {
return $value;
}
throw JsonMapperException::unableToMapException('Type', $type, $typeGroup);
}
/**
* Map the data in $value by the provided $typeGroup i.e. oneOf(A,B)
* will try to map value with only one of A or B, that matched. While
* anyOf(A,B) will try to map it with any of A or B and sets its type to
* the first one that matched.
*
* @param mixed $value Raw normalized value to be
* mapped with any typeGroup
* @param string|TypeCombination $typeGroup TypesCombination object or
* string format for grouped types
* @param string $namespace Namespace of any customType
* class that's present in the
* provided typeGroup.
* @param string[]|null $factoryMethods Callable factory methods for
* the value, that are required
* to deserialize it into any of
* the provided types in typeGroup
* like ['path/to/method argType']
* @param string|null $className Name of the parent class that's
* holding this property (if any)
*
* @return array|mixed|object
* @throws JsonMapperException
*/
public function mapFor(
$value,
$typeGroup,
$namespace = '',
$factoryMethods = null,
$className = null
) {
if (is_string($typeGroup)) {
// convert into TypeCombination object
$typeGroup = TypeCombination::withFormat(
$typeGroup,
isset($factoryMethods) ? $factoryMethods : []
);
}
$isArrayGroup = $typeGroup->getGroupName() == 'array';
$isMapGroup = $typeGroup->getGroupName() == 'map';
if ($isArrayGroup || $isMapGroup) {
list($isAssociative, $isIndexed) = $this->isAssociativeOrIndexed($value);
if (($isMapGroup && !$isAssociative) || ($isArrayGroup && !$isIndexed)) {
// Throw exception:
// IF value is not associative array with groupType == map
// Or value is not indexed array with groupType == array
$typeName = $isMapGroup ? 'Associative Array' : 'Array';
throw JsonMapperException::unableToMapException(
$typeName,
$this->formatType($typeGroup),
json_encode($value)
);
}
$mappedObject = [];
foreach ($value as $k => $v) {
$mappedObject[$k] = $this->mapFor(
$v,
$typeGroup->getTypes()[0],
$namespace,
null,
$className
);
}
return $mappedObject;
}
return $this->checkMappingsFor(
$typeGroup,
$value,
$className,
$namespace,
function ($type, $value, $factoryMethods, $nspace, $className) {
if (is_string($type)) {
return $this->getMappedValue(
$value,
$type,
null,
$factoryMethods,
$nspace,
$className,
true
);
}
return $this->mapFor(
$value,
$type,
$nspace,
null,
$className
);
}
);
}
/**
* Checks mappings for all types with mappedObject, provided by
* mappedObjectCallback.
*
* @param TypeCombination $typeGroup TypesCombination object or string
* format for grouped types
* @param mixed $value Mixed typed value to be checked
* by mappings with each of the types
* @param string|null $className Name of the class
* @param string $namespace Namespace of the class
* @param callable $mappedObjCallback Callback function to be called with
* each type in provided types, this
* function must return the mapped
* Object, for which the mapping will
* be checked, and to ignore any type,
* it can throw JsonMapperException
*
* @return false|mixed|null Returns the final mapped object after checking
* for oneOf and anyOf cases
* @throws JsonMapperException
*/
protected function checkMappingsFor(
$typeGroup,
$value,
$className,
$namespace,
$mappedObjCallback
) {
$mappedObject = null;
$mappedWith = '';
$deserializers = $typeGroup->getDeserializers();
$selectedDeserializer = null;
$discSubs = isset($this->discriminatorSubs) ? $this->discriminatorSubs : [];
// check json value for each type in types array
foreach ($typeGroup->getTypes() as $type) {
try {
if (is_string($type)) {
list($matched, $method) = $this->isValueOfType(
$value,
$type,
$typeGroup->getDiscriminator($type, $discSubs),
$namespace,
$deserializers
);
if (!$matched) {
// skip this type as it can't be mapped on the given value.
continue;
}
$selectedDeserializer = isset($method) ? [$method] : null;
}
$mappedObject = call_user_func(
$mappedObjCallback,
$type,
$value,
$selectedDeserializer,
$namespace,
$className
);
} catch (Exception $e) {
continue; // ignore the type if it can't be mapped for given value
}
$matchedType = $type;
if ($typeGroup->getGroupName() == 'oneOf' && $mappedWith) {
// if its oneOf and we have a value that is already mapped,
// then throw jsonMapperException
throw OneOfValidationException::moreThanOneOfException(
$this->formatType($matchedType),
$this->formatType($mappedWith),
json_encode($value)
);
}
$mappedWith = $matchedType;
if ($typeGroup->getGroupName() == 'anyOf') {
break; // break if its anyOf, and we already have mapped its value
}
}
if (!$mappedWith) {
if ($typeGroup->getGroupName() == 'oneOf') {
throw OneOfValidationException::cannotMapAnyOfException(
$this->formatType($typeGroup),
json_encode($value)
);
}
throw AnyOfValidationException::cannotMapAnyOfException(
$this->formatType($typeGroup),
json_encode($value)
);
}
return $mappedObject;
}
/**
* Checks types against the value.
*
* @param mixed $value Value to be checked
* @param string $type type defined in param's typehint
* @param array|null $disc An array with format discriminatorFieldName
* as element 1 and discriminatorValue as
* element 2
* @param string $nspace Namespace of the class
* @param string[] $methods deserializer functions array in the format
* ["pathToCallableFunction typeOfValue", ...]
* Default: []
*
* @return array array(bool $matched, ?string $method) $matched represents if
* Type matched with value, $method represents the selected
* factory method (if any)
* @throws ReflectionException
* @throws JsonMapperException
*/
protected function isValueOfType($value, $type, $disc, $nspace, $methods = [])
{
if (!empty($methods)) {
$methodFound = false;
foreach ($methods as $method) {
if (isset($method) && explode(' ', $method)[1] == $type) {
$methodFound = true;
if ($this->callFactoryWithErrorHandling($value, $method)[0]) {
return array(true, $method);
}
}
}
if ($methodFound) {
// if any method was found but couldn't deserialize value
return array(false, null);
}
}
list($isMap, $isArray, $innerType) = TypeCombination::extractTypeInfo($type);
if ($isMap || $isArray) {
// if type is array like int[] or map like array<string,int>
list($isAssociative, $isIndexed) = $this->isAssociativeOrIndexed($value);
if (($isMap && $isAssociative) || ($isArray && $isIndexed)) {
// Value must be associativeArray/object for MapType
// Or it must be indexed array for ArrayType
foreach ($value as $v) {
if (!$this->isValueOfType($v, $innerType, $disc, $nspace)[0]) {
// false if any element is not of same type
return array(false, null);
}
}
// true only if all elements in the array/map are of same type
return array(true, null);
}
return array(false, null); // false if type is array/map but value is not
}
if ($type == 'mixed') {
return array(true, null);
}
if ($type == 'null' || $this->isSimpleType($type) || !is_object($value)) {
return array($this->isSimpleValue($value, $type), null);
}
if (!isset($disc)) {
// if default discriminator is not provided
// try getting it from the class annotations
$rc = new ReflectionClass($this->getFullNamespace($type, $nspace));
$disc = $this->getDiscriminator($rc);
}
return array($this->isComplexValue($value, $disc), null);
}
/**
* Check if value is a complex type with provided discriminator
*
* @param mixed $value Value to be checked
* @param array|null $discriminator An array with format discriminatorFieldName
* as element 1 and discriminatorValue as
* element 2
*
* @return bool True if value is a complexType with provided discriminator
*/
protected function isComplexValue($value, $discriminator)
{
if (!isset($discriminator)) {
// if discriminator is missing
return true;
}
list($discriminatorField, $discriminatorValue) = $discriminator;
if (!isset($value->{$discriminatorField})) {
// if value didn't have discriminatorField
return true;
}
// if discriminator field is set then decide w.r.t its value
return $value->{$discriminatorField} == $discriminatorValue;
}
/**
* Checks if the given type is a "simple type"
*
* @param string $type type name from gettype()
*
* @return boolean True if it is a simple PHP type
*/
protected function isSimpleType($type)
{
return $type == 'string'
|| $type == 'boolean' || $type == 'bool'
|| $type == 'integer' || $type == 'int' || $type == 'float'
|| $type == 'double' || $type == 'array' || $type == 'object';
}
/**
* Check if value is of simple type
*
* @param mixed $value Value to be checked
* @param string $type Type defined in param's typehint
*
* @return bool True if value is of the given simple type
*/
protected function isSimpleValue($value, $type)
{
return ($type == 'string' && is_string($value))
|| ($type == 'array' && (is_array($value) || is_object($value)))
|| ($type == 'object' && is_object($value))
|| ($type == 'bool' && is_bool($value))
|| ($type == 'boolean' && is_bool($value))
|| ($type == 'int' && is_int($value))
|| ($type == 'integer' && is_int($value))
|| ($type == 'float' && is_float($value))
|| ($type == 'double' && is_float($value))
|| ($type == 'null' && is_null($value));
}
/**
* Map all data in $json into a new instance of $type class.
*
* @param object|null $json JSON object structure from json_decode()
* @param string $type The type of class instance to map into.
* @param bool $strict True if looking to map with strict type checking,
* Default: false
*
* @return object|null Mapped object is returned.
* @throws ReflectionException|JsonMapperException
* @see mapClassArray()
*/
public function mapClass($json, $type, $strict = false)
{
if ($json === null) {
return null;
}
if (!is_object($json)) {
throw new \InvalidArgumentException(
'JsonMapper::mapClass() requires first argument to be an object'
. ', ' . gettype($json) . ' given.'
);
}
$ttype = ltrim($type, "\\");
if (!class_exists($type)) {
throw new \InvalidArgumentException(
'JsonMapper::mapClass() requires second argument to be a class name'
. ', ' . $type . ' given.'
);
}
$rc = new ReflectionClass($ttype);
//try and find a class with matching discriminator
$matchedRc = $this->getDiscriminatorMatch($json, $rc);
//otherwise fallback to an instance of $type class
if ($matchedRc === null) {
$instance = $this->createInstance($ttype, $json, $strict);
} else {
$instance = $this->createInstance(
$matchedRc->getName(),
$json,
$strict
);
}
return $this->map($json, $instance, $strict);
}
/**
* Get class instance that best matches the class
*
* @param object|null $json JSON object structure from json_decode()
* @param ReflectionClass $rc Class to get instance of. This method
* will try to first match the
* discriminator field with the
* discriminator value of the current
* class or its child class. If no
* matches is found, then the current
* class's instance is returned.
*
* @return ReflectionClass|null Object instance if match is found.
* @throws ReflectionException
*/
protected function getDiscriminatorMatch($json, $rc)
{
$discriminator = $this->getDiscriminator($rc);
if ($discriminator) {
list($fieldName, $fieldValue) = $discriminator;
if (isset($json->{$fieldName}) && $json->{$fieldName} === $fieldValue) {
return $rc;
}
if (!$this->isRegisteredType($rc->name)) {
return null;
}
foreach ($this->getChildClasses($rc) as $clazz) {
$childRc = $this->getDiscriminatorMatch($json, $clazz);
if ($childRc) {
return $childRc;
}
}
}
return null;
}
/**
* Get discriminator info
*
* @param ReflectionClass $rc ReflectionClass of class to inspect
*
* @return array|null An array with discriminator arguments
* Element 1 is discriminator field name
* and element 2 is discriminator value.
*/
protected function getDiscriminator($rc)
{
$annotations = $this->parseAnnotations($rc->getDocComment());
$annotationInfo = array();
if (isset($annotations['discriminator'])) {
$annotationInfo[0] = trim($annotations['discriminator'][0]);
if (isset($annotations['discriminatorType'])) {
$annotationInfo[1] = trim($annotations['discriminatorType'][0]);
} else {
$annotationInfo[1] = $rc->getShortName();
}
return $annotationInfo;
}
return null;
}
/**
* Get child classes from a ReflectionClass
*
* @param ReflectionClass $rc ReflectionClass of class to inspect
*
* @return ReflectionClass[] ReflectionClass instances for child classes
* @throws ReflectionException
*/
protected function getChildClasses($rc)
{
$children = array();
foreach ($this->arChildClasses[$rc->name] as $class) {
$child = new ReflectionClass($class);
if ($child->isSubclassOf($rc)) {
$children[] = $child;
}
}
return $children;
}
/**
* Convert a type name to a fully namespaced type name.
*
* @param string $type Type name (simple type or class name)
* @param string $strNs Base namespace that gets prepended to the type name
*
* @return string Fully-qualified type name with namespace
*/
protected function getFullNamespace($type, $strNs)
{
if (\is_string($type) && $type !== '' && $type[0] != '\\') {
//create a full qualified namespace
if ($strNs != '') {
$type = '\\' . $strNs . '\\' . $type;
}
}
return $type;
}
/**
* Check required properties exist in json
*
* @param array $providedProperties array with json properties
* @param ReflectionClass $rc Reflection class to check
*
* @return void
* @throws JsonMapperException
*/
protected function checkMissingData($providedProperties, ReflectionClass $rc)
{
foreach ($rc->getProperties() as $property) {
$rprop = $rc->getProperty($property->name);
$docblock = $rprop->getDocComment();
$annotations = $this->parseAnnotations($docblock);
if (isset($annotations['required'])
&& !isset($providedProperties[$property->name])
) {
throw JsonMapperException::requiredPropertyMissingException(
$property->name,
$rc->getName()
);
}
}
}
/**
* Get additional properties setter method for the class.
*
* @param ReflectionClass $rc Reflection class to check
*
* @return ReflectionMethod Method or null if disabled.
*/
protected function getAdditionalPropertiesMethod(ReflectionClass $rc)
{
if ($this->bExceptionOnUndefinedProperty === false
&& $this->sAdditionalPropertiesCollectionMethod !== null
) {
$additionalPropertiesMethod = null;
try {
$additionalPropertiesMethod
= $rc->getMethod($this->sAdditionalPropertiesCollectionMethod);
if (!$additionalPropertiesMethod->isPublic()) {
throw new \InvalidArgumentException(
$this->sAdditionalPropertiesCollectionMethod .
" method is not public on the given class."
);
}
if ($additionalPropertiesMethod->getNumberOfParameters() < 2) {
throw new \InvalidArgumentException(
$this->sAdditionalPropertiesCollectionMethod .
' method does not receive two args, $key and $value.'
);
}
} catch (\ReflectionException $e) {
throw new \InvalidArgumentException(
$this->sAdditionalPropertiesCollectionMethod .
" method is not available on the given class."
);
}
return $additionalPropertiesMethod;
} else {
return null;
}
}
/**
* Map an array
*
* @param array $jsonArray JSON array structure from json_decode()
* @param mixed $array Array or ArrayObject that gets filled with
* data from $json.
* @param string|object $class Class name for children objects. All children
* will get mapped onto this type. Supports class
* names and simple types like "string".
* @param int $dimension Dimension of array to map, i.e. 2 for 2D
* array, Default: 1
* @param bool $strict True if looking to map with strict type
* checking, Default: false
*
* @return mixed Mapped $array is returned
*/
public function mapArray(
$jsonArray,
$array,
$class = null,
$dimension = 1,
$strict = false
) {
foreach ($jsonArray as $key => $jvalue) {
if ($class === null) {
$array[$key] = $jvalue;
} else if ($dimension > 1) {
$array[$key] = $this->mapArray(
$jvalue,
array(),
$class,
$dimension - 1,
$strict
);
} else if ($this->isFlatType(gettype($jvalue))) {
// use constructor parameter if we have a class
// but only a flat type (i.e. string, int)
if ($jvalue === null) {
$array[$key] = null;
} else {
if ($this->isSimpleType($class)) {
if ($strict && !$this->isSimpleValue($jvalue, $class)) {
// if mapping strictly for multipleTypes
throw JsonMapperException::unableToSetTypeException(
$class,
json_encode($jvalue)
);
}
settype($jvalue, $class);
$array[$key] = $jvalue;
} else {
$array[$key] = new $class($jvalue);
}
}
} else {
$instance = $this->createInstance(
$class,
$jvalue,
$strict
);
$array[$key] = $this->map($jvalue, $instance, $strict);
}
}
return $array;
}
/**
* Map an array
*
* @param array|null $jsonArray JSON array structure from json_decode()
* @param string $type Class name
* @param int $dimension Dimension of array to map, i.e. 2 for 2D array,
* Default: 1
* @param bool $strict True if looking to map with strict type checking,
* Default: false
*
* @return array|null A new array containing object of $type
* which is mapped from $jsonArray
* @throws ReflectionException|JsonMapperException
*/
public function mapClassArray($jsonArray, $type, $dimension = 1, $strict = false)
{
if ($jsonArray === null) {
return null;
}
$array = array();
foreach ($jsonArray as $key => $jvalue) {
if ($dimension > 1) {
$array[$key] = $this->mapClassArray(
$jvalue,
$type,
$dimension - 1,
$strict
);
} else {
$array[$key] = $this->mapClass($jvalue, $type, $strict);
}
}
return $array;
}
/**
* Try to find out if a property exists in a given class.
* Checks property first, falls back to setter method.
*
* @param ReflectionClass $rc Reflection class to check
* @param string $name Property name
*
* @return array First value: if the property exists
* Second value: the accessor to use (
* ReflectionMethod or ReflectionProperty, or null)
* Third value: type of the property
* Fourth value: factory method
*/
protected function inspectProperty(ReflectionClass $rc, $name)
{
$rmeth = null;
$annotations = [];
$mapsBy = null;
$namespace = $rc->getNamespaceName();
foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$annotations = $this->parseAnnotations($method->getDocComment());
if ($name === $this->getMapAnnotationFromParsed($annotations)) {
$rmeth = $method;
$mapsBy = $this->getMapByAnnotationFromParsed($annotations);
break;
}
}
if ($rmeth === null) {
//try setter method
$setter = 'set' . str_replace(
' ', '', ucwords(str_replace('_', ' ', $name))
);
if ($rc->hasMethod($setter)) {
$rmeth = $rc->getMethod($setter);
$annotations = $this->parseAnnotations($rmeth->getDocComment());
}
}
if ($rmeth !== null && $rmeth->isPublic()) {
$type = null;
$factoryMethod = null;
$namespace = $rmeth->getDeclaringClass()->getNamespaceName();
$rparams = $rmeth->getParameters();
if (count($rparams) > 0) {
$type = $this->getParameterType($rparams[0]);
}
if (($type === null || $type === 'array' || $type === 'array|null')
&& isset($annotations['param'][0])
) {
list($type) = explode(' ', trim($annotations['param'][0]));
}
//support "@factory method_name"
if (isset($annotations['factory'])) {
$factoryMethod = $annotations['factory'];
}
return array(true, $rmeth, $type, $factoryMethod, $mapsBy, $namespace);
}
$rprop = null;
// check for @maps annotation for hints
foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
$mappedName = $this->getMapAnnotation($p);
if ($mappedName !== null && $name == $mappedName) {
$mapsBy = $this->getMapByAnnotation($p);
$rprop = $p;
break;
}
}
//now try to set the property directly
if ($rprop === null) {
if ($rc->hasProperty($name)
&& $this->getMapAnnotation($rc->getProperty($name)) === null
) {
$rprop = $rc->getProperty($name);
} else {
//case-insensitive property matching
foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
if ((strcasecmp($p->name, $name) === 0)
&& $this->getMapAnnotation($p) === null
) {
$rprop = $p;
break;
}
}
}
}
if ($rprop !== null) {
if ($rprop->isPublic()) {
$docblock = $rprop->getDocComment();
$annotations = $this->parseAnnotations($docblock);
$namespace = $rprop->getDeclaringClass()->getNamespaceName();
$type = null;
$factoryMethod = null;
//support "@var type description"
if (isset($annotations['var'][0])) {
list($type) = explode(' ', $annotations['var'][0]);
}
//support "@factory method_name"
if (isset($annotations['factory'])) {
$factoryMethod = $annotations['factory'];
}
return array(true, $rprop, $type, $factoryMethod, $mapsBy,
$namespace);
} else {
//no setter, private property
return array(true, null, null, null, $mapsBy, $namespace);
}
}
//no setter, no property
return array(false, null, null, null, $mapsBy, $namespace);
}
/**
* Get Phpdoc typehint for parameter
*
* @param \ReflectionParameter $param ReflectionParameter instance for parameter
*
* @return string|null
*/
protected function getParameterType(\ReflectionParameter $param)
{
if (PHP_VERSION_ID < 80000 && null !== $class = $param->getClass()) {
return "\\" . $class->getName();
}
if (is_callable([$param, 'hasType']) && $param->hasType()) {
$type = $param->getType();
if ($type->isBuiltIn()) {
$typeName = $this->reflectionTypeToString($type);
} else {
$typeName = "\\" . $this->reflectionTypeToString($type);
}
return $type->allowsNull() ? "$typeName|null" : $typeName;
}
return null;
}
/**
* Get name for a ReflectionType instance
*
* @param \ReflectionType $type Reflection type instance
*
* @return string
*/
protected function reflectionTypeToString($type)
{
if (\class_exists('ReflectionNamedType')
&& $type instanceof \ReflectionNamedType
) {
return $type->getName();
} else {
return (string)$type;
}
}
/**
* Get map annotation value for a property
*
* @param object $property Property of a class
*
* @return string|null Map annotation value
*/
protected function getMapAnnotation($property)
{
$annotations = $this->parseAnnotations($property->getDocComment());
return $this->getMapAnnotationFromParsed($annotations);
}
/**
* Get map annotation value from a parsed annotation list
*
* @param array $annotations Parsed annotation list
*
* @return string|null Map annotation value
*/
protected function getMapAnnotationFromParsed($annotations)
{
if (isset($annotations['maps'][0])) {
return $annotations['maps'][0];
}
return null;
}
/**
* Get mapBy annotation value for a property
*
* @param object $property Property of a class
*
* @return string|null MapBy annotation value
*/
protected function getMapByAnnotation($property)
{
$annotations = $this->parseAnnotations($property->getDocComment());
return $this->getMapByAnnotationFromParsed($annotations);
}
/**
* Get mapsBy annotation value from a parsed annotation list
*
* @param array $annotations Parsed annotation list
*
* @return string|null MapsBy annotation value
*/
protected function getMapByAnnotationFromParsed($annotations)
{
if (isset($annotations['mapsBy'][0])) {
return $annotations['mapsBy'][0];
}
return null;
}
/**
* Set a property on a given object to a given value.
*
* Checks if the setter or the property are public are made before
* calling this method.
*
* @param object $object Object to set property on
* @param object $accessor ReflectionMethod or ReflectionProperty
* @param mixed $value Value of property
*
* @return void
*/
protected function setProperty(
$object, $accessor, $value
) {
if ($accessor instanceof \ReflectionProperty) {
$object->{$accessor->getName()} = $value;
} else {
$object->{$accessor->getName()}($value);
}
}
/**
* Create a new object of the given type.
*
* @param string $class Class name to instantiate
* @param object $jobject Use jobject for constructor args
* @param bool $strict True if looking to map with strict type checking,
* Default: false
*
* @return object Freshly created object
* @throws ReflectionException|JsonMapperException
*/
protected function createInstance($class, &$jobject = null, $strict = false)
{
$rc = new ReflectionClass($class);
$ctor = $rc->getConstructor();
if ($ctor === null
|| 0 === $ctorReqParamsCount = $ctor->getNumberOfRequiredParameters()
) {
return new $class();
} else if ($jobject === null) {
throw JsonMapperException::noArgumentsException(
$class,
$ctor->getNumberOfRequiredParameters()
);
}
$ctorRequiredParams = array_slice(
$ctor->getParameters(),
0,
$ctorReqParamsCount
);
$ctorRequiredParamsName = array_map(
function (\ReflectionParameter $param) {
return $param->getName();
}, $ctorRequiredParams
);
$ctorRequiredParams = array_combine(
$ctorRequiredParamsName,
$ctorRequiredParams
);
$ctorArgs = [];
foreach ($jobject as $key => $jvalue) {
if (count($ctorArgs) === $ctorReqParamsCount) {
break;
}
// Store the property inspection results so we don't have to do it
// again for subsequent objects of the same type
if (!isset($this->arInspectedClasses[$class][$key])) {
$this->arInspectedClasses[$class][$key]
= $this->inspectProperty($rc, $key);
}
list($hasProperty, $accessor, $type, $factoryMethod, $mapsBy, $namespace)
= $this->arInspectedClasses[$class][$key];
if (!$hasProperty) {
// if no matching property or setter method found
if (isset($ctorRequiredParams[$key])) {
$rp = $ctorRequiredParams[$key];
$jtype = null;
} else {
continue;
}
} else if ($accessor instanceof \ReflectionProperty) {
// if a property was found
if (isset($ctorRequiredParams[$accessor->getName()])) {
$rp = $ctorRequiredParams[$accessor->getName()];
$jtype = $type;
} else {
continue;
}
} else {
// if a setter method was found
$methodName = $accessor->getName();
$methodName = substr($methodName, 0, 3) === 'set' ?
lcfirst(substr($methodName, 3)) : $methodName;
if (isset($ctorRequiredParams[$methodName])) {
$rp = $ctorRequiredParams[$methodName];
$jtype = $type;
} else {
continue;
}
}
$ttype = $this->getParameterType($rp);
if (($ttype !== null && $ttype !== 'array' && $ttype !== 'array|null')
|| $jtype === null
) {
// when $ttype is too generic, fallback to $jtype
$jtype = $ttype;
}
$ctorArgs[$rp->getPosition()] = $this->getMappedValue(
$jvalue,
$jtype,
$mapsBy,
$factoryMethod,
$namespace,
$rc->getName(),
$strict
);
if (!$strict) {
unset($jobject->{$key});
}
unset($ctorRequiredParamsName[$rp->getPosition()]);
}
if (count($ctorArgs) < $ctorReqParamsCount) {
throw JsonMapperException::fewerArgumentsException(
$class,
$ctorRequiredParamsName
);
}
ksort($ctorArgs);
return $rc->newInstanceArgs($ctorArgs);
}
/**
* Checks if the object is of this type or has this type as one of its parents
*
* @param string $type class name of type being required
* @param mixed $value Some PHP value to be tested
*
* @return boolean True if $object has type of $type
*/
protected function isObjectOfSameType($type, $value)
{
if (false === is_object($value)) {
return false;
}
return is_a($value, $type);
}
/**
* Checks if the given type is a type that is not nested
* (simple type except array and object)
*
* @param string $type type name from gettype()
*
* @return boolean True if it is a non-nested PHP type
*/
protected function isFlatType($type)
{
return $type == 'NULL'
|| $type == 'string'
|| $type == 'boolean' || $type == 'bool'
|| $type == 'integer' || $type == 'int'
|| $type == 'double';
}
/**
* Is type registered with mapper
*
* @param string|null $type Class name
*
* @return boolean True if registered with $this->arChildClasses
*/
protected function isRegisteredType($type)
{
if (!isset($type)) {
return false;
}
return isset($this->arChildClasses[ltrim($type, "\\")]);
}
/**
* Checks if the given type is nullable
*
* @param string $type type name from the phpdoc param
*
* @return boolean True if it is nullable
*/
protected function isNullable($type)
{
return stripos('|' . $type . '|', '|null|') !== false;
}
/**
* Remove the 'null' section of a type
*
* @param string $type type name from the phpdoc param
*
* @return string The new type value
*/
protected function removeNullable($type)
{
return substr(
str_ireplace('|null|', '|', '|' . $type . '|'),
1, -1
);
}
/**
* Copied from PHPUnit 3.7.29, Util/Test.php
*
* @param string $docblock Full method docblock
*
* @return array
*/
protected function parseAnnotations($docblock)
{
$annotations = array();
// Strip away the docblock header and footer
// to ease parsing of one line annotations
$docblock = substr($docblock, 3, -2);
$re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
if (preg_match_all($re, $docblock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
$annotations[$matches['name'][$i]][] = $matches['value'][$i];
}
}
return $annotations;
}
/**
* Log a message to the $logger object
*
* @param string $level Logging level
* @param string $message Text to log
* @param array $context Additional information
*
* @return null
*/
protected function log($level, $message, array $context = array())
{
if ($this->logger) {
$this->logger->log($level, $message, $context);
}
}
/**
* Sets a logger instance on the object
*
* @param LoggerInterface $logger PSR-3 compatible logger object
*
* @return null
*/
public function setLogger($logger)
{
$this->logger = $logger;
}
}
?>