.gitignore000064400000000102144760130500006525 0ustar00/README.html /*.tgz /dist/ /vendor/ .buildpath .project .settings .travis.yml000064400000000425144760130500006656 0ustar00language: php sudo: false php: - 5.3 - 5.4 - 5.5 - 5.6 - 7 - hhvm before_script: - composer install --no-interaction --prefer-source --dev - phpenv rehash script: - cd tests - phpunit --coverage-text . - cd .. - ./vendor/bin/phpcs --standard=PEAR src/ ChangeLog000064400000007746144760130500006334 0ustar002016-11-17 Christian Weiske * Add support for mapping to private properties/methods by @jonaswouters * Add classname to "NULL not allowed" exception message by @veteran29 * Version 1.1.0 2016-10-11 Christian Weiske * Add option to turn off the "must not be NULL" exception by @ kamranahmedse (issue #58) * Add support for properties with hyphens "-" by @redshark1802 (issue #55) * Add support for both "double" and "float" types * Move to PEAR channel zustellzentrum.cweiske.de * Version 1.0.0 2016-04-14 Christian Weiske * Add $undefinedPropertyHandler (issue #51) * Add $classMap to override class names (issue #53) * Add option to enable strict object type checks (issue #50) * Add composer autoloader performance improvement * Add exception when an array is expected, but simple type given (issue #44) * Fix non-nullable object handling: Exception is now thrown when NULL is given and an object was expected. THIS WILL PROBABLY BREAK YOUR CODE Add "|null" to all your "@var ClassName" lines to fix that. * Version 0.11.0 2015-09-24 Christian Weiske * Add dependency injection support (issue #42) * Fix casting of arrays of simple types * Version 0.10.0 2015-08-14 Christian Weiske * Add case-insensitive property matching (issue #40) * Add option to disable map() parameter type enforcement (issue #37) * Release 0.9.0 2015-07-06 Christian Weiske * Add support for seting objects directly if they have the correct type already by @radmen * Throw exception when a non-object is passed to map() * Release 0.8.0 2015-06-19 Christian Weiske * Support "mixed" variable type (issue #33) * Release 0.7.0 2015-05-28 Christian Weiske * Fix namespace error with setter type hints * Release 0.6.1 2015-04-09 Christian Weiske * Prefer setter methods over directy property access * Change setter method name calculation for properties with _ underscores by @msankhala * Release 0.6.0 2015-03-18 Christian Weiske * Add support for nullable types (int|null) by @barryvdh * Increase test coverage to 100% * Fix float value detection by @sonicgd * Release 0.5.0 2015-01-08 Christian Weiske * Fix bug #23: handle empty variable types * Fix bug #24: Namespaced ArrayObject class with namespaced value type does not work * Release 0.4.4 2014-12-17 Christian Weiske * Change license from AGPL v3 to OSL-3.0 * Release 0.4.3 2014-12-05 Christian Weiske * Fix array mapping when value is NULL by @darkgaro * Release 0.4.2 2014-11-04 Christian Weiske * Fix handling of private properties with public setters * Fix handling of simple array types in namespaced files * Release 0.4.1 2014-08-20 Sebastian Mendel * Incorporate performance tweaks from @Jalle19 * Release 0.4.0 2014-06-11 Andre Hähnel * Optional exceptions for missing or undefined data * Release 0.3.0 2014-05-16 Christian Weiske * Handle NULL values when mapping simple data types onto objects * Release 0.2.1 2014-05-15 Christian Weiske * Add support for mapping simple data types onto objects * Fix tests on phpunit 4.x * Release version 0.2.0 2014-03-17 Christian Weiske * Prevent autoloading classes with ] in its name * Release version 0.1.3 2014-02-03 Christian Weiske * Fix issue #2: Namespace is prepended two times * Fix issue #1: Remove declare(encoding="UTF-8") calls * Release version 0.1.2 README.rst000064400000026166144760130500006246 0ustar00******************************************************** JsonMapper - map nested JSON structures onto PHP classes ******************************************************** .. image:: https://api.travis-ci.org/cweiske/jsonmapper.png :target: https://travis-ci.org/cweiske/jsonmapper :align: right Takes data retrieved from a JSON__ web service and converts them into nested object and arrays - using your own model classes. Starting from a base object, it maps JSON data on class properties, converting them into the correct simple types or objects. It's a bit like the native SOAP parameter mapping PHP's ``SoapClient`` gives you, but for JSON. It does not rely on any schema, only your PHP class definitions. Type detection works by parsing ``@var`` docblock annotations of class properties, as well as type hints in setter methods. You do not have to modify your model classes by adding JSON specific code; it works automatically by parsing already-existing docblocks. Keywords: deserialization, hydration __ http://json.org/ .. contents:: ============ Pro & contra ============ Benefits ======== - Autocompletion in IDEs - It's easy to add comfort methods to data model classes - Your JSON API may change, but your models can stay the same - not breaking applications that use the model classes. Drawbacks ========= - Model classes need to be written by hand Since JsonMapper does not rely on any schema information (e.g. from `json-schema`__), model classes cannot be generated automatically. __ http://json-schema.org/ ===== Usage ===== Basic usage =========== #. Register an autoloader that can load `PSR-0`__ compatible classes. #. Create a ``JsonMapper`` object instance #. Call the ``map`` or ``mapArray`` method, depending on your data Map a normal object: .. code:: php map($jsonContact, new Contact()); ?> Map an array of objects: .. code:: php mapArray( $jsonContacts, array(), 'Contact' ); ?> Instead of ``array()`` you may also use ``ArrayObject`` and descending classes. __ http://www.php-fig.org/psr/psr-0/ Example ======= JSON from an address book web service: .. code:: javascript { 'name':'Sheldon Cooper', 'address': { 'street': '2311 N. Los Robles Avenue', 'city': 'Pasadena' } } Your local ``Contact`` class: .. code:: php Your local ``Address`` class: .. code:: php Your application code: .. code:: php map($json, new Contact()); echo "Geo coordinates for " . $contact->name . ": " . var_export($contact->address->getGeoCoords(), true); ?> Property type mapping ===================== ``JsonMapper`` uses several sources to detect the correct type of a property: #. The setter method (``set`` + ``ucwords($propertyname)``) is inspected. Underscores "``_``" and hyphens "``-``" make the next letter uppercase. Property ``foo_bar-baz`` leads to setter method ``setFooBarBaz``. #. If it has a type hint in the method signature then its type used:: public function setPerson(Contact $person) {...} #. The method's docblock is inspected for ``@param $type`` annotations:: /** * @param Contact $person Main contact for this application */ public function setPerson($person) {...} #. If no type could be detected, the plain JSON value is passed to the setter method. #. ``@var $type`` docblock annotation of class properties:: /** * @var \my\application\model\Contact */ public $person; The property has to be public to be used directly. Protected and private properties cannot be set; you will have to provide a setter method for them. If no type could be detected, the property gets the plain JSON value set. If a property can not be found, JsonMapper tries to find the property in a case-insensitive manner. A JSON property ``isempty`` would then be mapped to a PHP property ``isEmpty``. Supported type names -------------------- - Simple types - ``string`` - ``bool``, ``boolean`` - ``int``, ``integer`` - ``double``, ``float`` - ``array`` - ``object`` - Class names, with and without namespaces - ``Contact`` - exception will be thrown if the JSON value is ``null`` - Arrays of simple types and class names: - ``int[]`` - ``Contact[]`` - ArrayObjects of simple types and class names: - ``ContactList[Contact]`` - ``NumberList[int]`` - Nullable types: - ``int|null`` - will be ``null`` if the value in JSON is ``null``, otherwise it will be an integer - ``Contact|null`` - will be ``null`` if the value in JSON is ``null``, otherwise it will be an object of type ``Contact`` ArrayObjects and extending classes are treated as arrays. Variables without a type or with type ``mixed`` will get the JSON value set directly without any conversion. See `phpdoc's type documentation`__ for more information. __ http://phpdoc.org/docs/latest/references/phpdoc/types.html Simple type mapping ------------------- When an object shall be created but the JSON contains a simple type only (e.g. string, float, boolean), this value is passed to the classes' constructor. Example: PHP code: .. code:: php /** * @var DateTime */ public $date; JSON: .. code:: js {"date":"2014-05-15"} This will result in ``new DateTime('2014-05-15')`` being called. Class map --------- When variables are defined as objects of abstract classes or interfaces, JsonMapper would normally try to instantiate those directly and crash. Using JsonMapper's ``$classMap`` property, you can specify which classes shall get instantiated instead: .. code:: php $jm = new JsonMapper(); $jm->classMap['Foo'] = 'Bar'; $jm->map(...); This would create objects of type ``Bar`` when a variable is defined to be of type ``Foo``. Nullables --------- JsonMapper throws an exception when a JSON property is ``null``, unless the PHP class property has a nullable type - e.g. ``Contact|null``. If your API contains many fields that may be ``null`` and you do not want to make all your type definitions nullable, set: .. code:: php $jm->bStrictNullTypes = false; Logging ======= JsonMapper's ``setLogger()`` method supports all PSR-3__ compatible logger instances. Events that get logged: - JSON data contain a key, but the class does not have a property or setter method for it. - Neither setter nor property can be set from outside because they are protected or private __ http://www.php-fig.org/psr/psr-3/ Handling invalid or missing data ================================ During development, APIs often change. To get notified about such changes, JsonMapper can be configured to throw exceptions in case of either missing or yet unknown data. Unknown properties ------------------ When JsonMapper sees properties in the JSON data that are not defined in the PHP class, you can let it throw an exception by setting ``$bExceptionOnUndefinedProperty``: .. code:: php $jm = new JsonMapper(); $jm->bExceptionOnUndefinedProperty = true; $jm->map(...); You may also choose to handle those properties yourself by setting a callable__ to ``$undefinedPropertyHandler``: __ http://php.net/manual/en/language.types.callable.php .. code:: php /** * Handle undefined properties during JsonMapper::map() * * @param object $object Object that is being filled * @param string $propName Name of the unknown JSON property * @param mixed $jsonValue JSON value of the property * * @return void */ function setUndefinedProperty($object, $propName, $jsonValue) { $object->{'UNDEF' . $propName} = $jsonValue; } $jm = new JsonMapper(); $jm->undefinedPropertyHandler = 'setUndefinedProperty'; $jm->map(...); Missing properties ------------------ Properties in your PHP classes can be marked as "required" by putting ``@required`` in their docblock: .. code:: php /** * @var string * @required */ public $someDatum; When the JSON data do not contain this property, JsonMapper will throw an exception when ``$bExceptionOnMissingData`` is activated: .. code:: php $jm = new JsonMapper(); $jm->bExceptionOnMissingData = true; $jm->map(...); Private properties and functions -------------------------------- You can allow mapping to private and protected properties and setter methods by setting ``$bIgnoreVisibility`` to true: .. code:: php $jm = new JsonMapper(); $jm->bIgnoreVisibility = true; $jm->map(...); Simple types instead of objects ------------------------------- When a variable's type is a class and JSON data is a simple type like ``string``, JsonMapper passes this value to the class' constructor. If you do not want this, set ``$bStrictObjectTypeChecking`` to ``true``: .. code:: php $jm = new JsonMapper(); $jm->bStrictObjectTypeChecking = true; $jm->map(...); An exception is then thrown in such cases. Passing arrays to ``map()`` --------------------------- You may wish to pass array data into ``map()`` that you got by calling .. code:: php json_decode($jsonString, true) By default, JsonMapper will throw an exception because ``map()`` requires an object as first parameter. You can circumvent that by setting ``$bEnforceMapType`` to ``false``: .. code:: php $jm = new JsonMapper(); $jm->bEnforceMapType = false; $jm->map(...); ============ Installation ============ via Composer ============ From Packagist__:: $ composer require netresearch/jsonmapper __ https://packagist.org/packages/netresearch/jsonmapper via PEAR ======== .. note:: With version 1.0.0 JsonMapper moved to PEAR channel https://zustellzentrum.cweiske.de/ From our `PEAR channel`__:: $ pear channel-discover zustellzentrum.cweiske.de $ pear install zz/jsonmapper __ https://zustellzentrum.cweiske.de/ ================ Related software ================ - `Jackson's data binding`__ for Java - `Johannes Schmitt Serializer`__ for PHP - `metassione`__ for PHP __ http://wiki.fasterxml.com/JacksonDataBinding __ http://jmsyst.com/libs/serializer __ https://github.com/drbonzo/metassione ================ About JsonMapper ================ License ======= JsonMapper is licensed under the `OSL 3.0`__. __ http://opensource.org/licenses/osl-3.0 Coding style ============ JsonMapper follows the `PEAR Coding Standards`__. __ http://pear.php.net/manual/en/standards.php Author ====== `Christian Weiske`__, `cweiske.de`__ __ mailto:cweiske+jsonmapper@cweiske.de __ http://cweiske.de/ composer.json000064400000001222144760130500007263 0ustar00{ "name": "netresearch/jsonmapper", "description": "Map nested JSON structures onto PHP classes", "license": "OSL-3.0", "autoload": { "psr-0": {"JsonMapper": "src/"} }, "authors": [ { "name": "Christian Weiske", "email": "cweiske@cweiske.de", "homepage": "http://github.com/cweiske/jsonmapper/", "role": "Developer" } ], "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues" }, "require-dev": { "phpunit/phpunit": "4.2.*", "squizlabs/php_codesniffer": "~1.5" } } example/Address.php000064400000000746144760130500010304 0ustar00street) . ',' . urlencode($this->city) . '&format=json&addressdetails=1' ); $json = json_decode($data); return array( 'lat' => $json[0]->lat, 'lon' => $json[0]->lon ); } } ?> example/Contact.php000064400000000147144760130500010305 0ustar00 example/run.php000064400000000622144760130500007514 0ustar00map($json, new Contact()); $coords = $contact->address->getGeoCoords(); echo $contact->name . ' lives at coordinates ' . $coords['lat'] . ',' . $coords['lon'] . "\n"; ?>example/single.json000064400000000135144760130500010352 0ustar00{"name":"Sheldon Cooper","address":{"street":"2311 N. Los Robles Avenue","city":"Pasadena"}} package.xml000064400000030545144760130500006670 0ustar00 JsonMapper zustellzentrum.cweiske.de Map nested JSON structures onto PHP classes Map nested JSON structures onto PHP classes Christian Weiske cweiske cweiske@cweiske.de yes 2016-11-17 1.1.0 1.1.0 stable stable OSL-3.0 * Add support for mapping to private properties/methods by @jonaswouters * Add classname to "NULL not allowed" exception message by @veteran29 5.3.0 1.9.1 1.1.0 1.1.0 stable stable 2016-11-17 OSL-3.0 * Add support for mapping to private properties/methods by @jonaswouters * Add classname to "NULL not allowed" exception message by @veteran29 1.0.0 1.0.0 stable stable 2016-10-11 OSL-3.0 * Add option to turn off the "must not be NULL" exception by @ kamranahmedse (issue #58) * Add support for properties with hyphens "-" by @redshark1802 (issue #55) * Add support for both "double" and "float" types * Move to PEAR channel zustellzentrum.cweiske.de 0.11.0 0.11.0 beta beta 2016-04-14 OSL-3.0 * Add $undefinedPropertyHandler (issue #51) * Add $classMap to override class names (issue #53) * Add option to enable strict object type checks (issue #50) * Add composer autoloader performance improvement * Add exception when an array is expected, but simple type given (issue #44) * Fix non-nullable object handling: Exception is now thrown when NULL is given and an object was expected. THIS WILL PROBABLY BREAK YOUR CODE Add "|null" to all your "@var ClassName" lines to fix that. 0.10.0 0.10.0 beta beta 2015-09-24 OSL-3.0 * Add dependency injection support (issue #42) * Fix casting of arrays of simple types 0.9.0 0.9.0 alpha alpha 2015-08-14 OSL-3.0 * Add case-insensitive property matching (issue #40) * Add option to disable map() parameter type enforcement (issue #37) 0.8.0 0.8.0 alpha alpha 2015-07-06 OSL-3.0 * Add support for seting objects directly if they have the correct type already by @radmen * Throw exception when a non-object is passed to map() 0.7.0 0.7.0 alpha alpha 2015-06-19 OSL-3.0 * Support "mixed" variable type (issue #33) 0.6.1 0.6.0 alpha alpha 2015-05-28 OSL-3.0 * Fix namespace error with setter type hints 0.6.0 0.6.0 alpha alpha 2015-04-09 OSL-3.0 * Prefer setter methods over directy property access * Change setter method name calculation for properties with _ underscores by @msankhala 0.5.0 0.3.0 alpha alpha 2015-03-18 OSL-3.0 * Add support for nullable types (int|null) by @barryvdh * Increase test coverage to 100% * Fix float value detection by @sonicgd 0.4.4 0.3.0 alpha alpha 2015-01-08 OSL-3.0 * Fix bug #23: handle empty variable types * Fix bug #24: Namespaced ArrayObject class with namespaced value type does not work 0.4.3 0.3.0 alpha alpha 2014-12-17 OSL-3.0 * Change license from AGPL 3.0 to OSL 3.0 0.4.2 0.3.0 alpha alpha 2014-12-05 AGPL * Fix array mapping when value is NULL by @darkgaro 0.4.1 0.3.0 alpha alpha 2014-11-04 AGPL * Fix handling of private properties with public setters * Fix handling of simple array types in namespaced files 0.4.0 0.3.0 alpha alpha 2014-08-20 AGPL * Incorporate performance tweaks from @Jalle19 0.3.0 0.3.0 alpha alpha 2014-06-11 AGPL * Optional exceptions for missing or undefined data 0.2.1 0.2.0 alpha alpha 2014-05-16 AGPL * Handle NULL values when mapping simple data types onto objects 0.2.0 0.2.0 alpha alpha 2014-05-15 AGPL * Add support for mapping simple data types onto objects * Fix tests on phpunit 4.x 0.1.3 0.1.0 alpha alpha 2014-03-17 AGPL * Prevent autoloading classes with ] in its name 0.1.2 0.1.0 alpha alpha 2014-02-03 AGPL * Fix issue #2: Namespace is prepended two times * Fix issue #1: Remove declare(encoding="UTF-8") calls 0.1.1 0.1.0 alpha alpha 2014-01-28 AGPL Properly resolve namespace for array subtypes 0.1.0 0.1.0 alpha alpha 2014-01-28 AGPL First release src/JsonMapper.php000064400000053152144760130500010130 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Automatically map JSON structures into objects. * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.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; /** * 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; /** * Throw an exception when an object is expected but the JSON contains * a non-object type. * * @var boolean */ public $bStrictObjectTypeChecking = false; /** * Throw an exception, if null value is found * but the type of attribute does not allow nulls. * * @var bool */ public $bStrictNullTypes = true; /** * Allow mapping of private and proteted properties. * * @var boolean */ public $bIgnoreVisibility = false; /** * Override class names that JsonMapper uses to create objects. * Useful when your setter methods accept abstract classes or interfaces. * * Works only when $bExceptionOnUndefinedProperty is disabled. * * Parameters to this function are: * 1. Object that is being filled * 2. Name of the unknown JSON property * 3. JSON value of the property * * @var array */ public $classMap = array(); /** * Callback used when an undefined property is found. * * @var callable */ public $undefinedPropertyHandler = null; /** * 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(); /** * 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 * * @return object Mapped object is returned. * @see mapArray() */ public function map($json, $object) { 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(); foreach ($json as $key => $jvalue) { $key = $this->getSafeName($key); $providedProperties[$key] = true; // 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) = $this->arInspectedClasses[$strClassName][$key]; if (!$hasProperty) { if ($this->bExceptionOnUndefinedProperty) { throw new JsonMapper_Exception( 'JSON property "' . $key . '" does not exist' . ' in object of type ' . $strClassName ); } else if ($this->undefinedPropertyHandler !== null) { call_user_func( $this->undefinedPropertyHandler, $object, $key, $jvalue ); } else { $this->log( 'info', 'Property {property} does not exist in {class}', array('property' => $key, 'class' => $strClassName) ); } continue; } if ($accessor === null) { if ($this->bExceptionOnUndefinedProperty) { throw new JsonMapper_Exception( 'JSON property "' . $key . '" has no public setter method' . ' in object of type ' . $strClassName ); } $this->log( 'info', 'Property {property} has no public setter method in {class}', array('property' => $key, 'class' => $strClassName) ); continue; } if ($this->isNullable($type) || !$this->bStrictNullTypes) { if ($jvalue === null) { $this->setProperty($object, $accessor, null); continue; } $type = $this->removeNullable($type); } else if ($jvalue === null) { throw new JsonMapper_Exception( 'JSON property "' . $key . '" in class "' . $strClassName . '" must not be NULL' ); } if ($type === null || $type === 'mixed') { //no given type - simply set the json data $this->setProperty($object, $accessor, $jvalue); continue; } else if ($this->isObjectOfSameType($type, $jvalue)) { $this->setProperty($object, $accessor, $jvalue); continue; } else if ($this->isSimpleType($type)) { settype($jvalue, $type); $this->setProperty($object, $accessor, $jvalue); continue; } //FIXME: check if type exists, give detailled error message if not if ($type === '') { throw new JsonMapper_Exception( 'Empty type at property "' . $strClassName . '::$' . $key . '"' ); } $array = null; $subtype = null; if (substr($type, -2) == '[]') { //array $array = array(); $subtype = substr($type, 0, -2); } else if (substr($type, -1) == ']') { list($proptype, $subtype) = explode('[', substr($type, 0, -1)); if (!$this->isSimpleType($proptype)) { $proptype = $this->getFullNamespace($proptype, $strNs); } if ($proptype == 'array') { $array = array(); } else { $array = $this->createInstance($proptype); } } else if ($type == 'ArrayObject' || is_subclass_of($type, 'ArrayObject') ) { $array = $this->createInstance($type); } if ($array !== null) { if (!is_array($jvalue) && $this->isFlatType(gettype($jvalue))) { throw new JsonMapper_Exception( 'JSON property "' . $key . '" must be an array, ' . gettype($jvalue) . ' given' ); } $cleanSubtype = $this->removeNullable($subtype); if (!$this->isSimpleType($cleanSubtype)) { $subtype = $this->getFullNamespace($cleanSubtype, $strNs); } $child = $this->mapArray($jvalue, $array, $subtype); } else if ($this->isFlatType(gettype($jvalue))) { //use constructor parameter if we have a class // but only a flat type (i.e. string, int) if ($this->bStrictObjectTypeChecking) { throw new JsonMapper_Exception( 'JSON property "' . $key . '" must be an object, ' . gettype($jvalue) . ' given' ); } $type = $this->getFullNamespace($type, $strNs); $child = $this->createInstance($type, true, $jvalue); } else { $type = $this->getFullNamespace($type, $strNs); $child = $this->createInstance($type); $this->map($jvalue, $child); } $this->setProperty($object, $accessor, $child); } if ($this->bExceptionOnMissingData) { $this->checkMissingData($providedProperties, $rc); } return $object; } /** * 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 ($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 object $rc Reflection class to check * * @throws JsonMapper_Exception * * @return void */ 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 new JsonMapper_Exception( 'Required property "' . $property->name . '" of class ' . $rc->getName() . ' is missing in JSON data' ); } } } /** * Map an array * * @param array $json JSON array structure from json_decode() * @param mixed $array Array or ArrayObject that gets filled with * data from $json * @param string $class Class name for children objects. * All children will get mapped onto this type. * Supports class names and simple types * like "string" and nullability "string|null". * Pass "null" to not convert any values * * @return mixed Mapped $array is returned */ public function mapArray($json, $array, $class = null) { foreach ($json as $key => $jvalue) { $key = $this->getSafeName($key); if ($class === null) { $array[$key] = $jvalue; } 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)) { settype($jvalue, $class); $array[$key] = $jvalue; } else { $array[$key] = $this->createInstance( $class, true, $jvalue ); } } } else { $array[$key] = $this->map( $jvalue, $this->createInstance($class) ); } } return $array; } /** * Try to find out if a property exists in a given class. * Checks property first, falls back to setter method. * * @param object $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 */ protected function inspectProperty(ReflectionClass $rc, $name) { //try setter method first $setter = 'set' . $this->getCamelCaseName($name); if ($rc->hasMethod($setter)) { $rmeth = $rc->getMethod($setter); if ($rmeth->isPublic() || $this->bIgnoreVisibility) { $rparams = $rmeth->getParameters(); if (count($rparams) > 0) { $pclass = $rparams[0]->getClass(); $nullability = ''; if ($rparams[0]->isOptional()) { if ($rparams[0]->getDefaultValue() === null) { $nullability = '|null'; } } if ($pclass !== null) { return array( true, $rmeth, '\\' . $pclass->getName() . $nullability ); } } $docblock = $rmeth->getDocComment(); $annotations = $this->parseAnnotations($docblock); if (!isset($annotations['param'][0])) { return array(true, $rmeth, null); } list($type) = explode(' ', trim($annotations['param'][0])); return array(true, $rmeth, $type); } } //now try to set the property directly if ($rc->hasProperty($name)) { $rprop = $rc->getProperty($name); } else { //case-insensitive property matching $rprop = null; foreach ($rc->getProperties() as $p) { if ((strcasecmp($p->name, $name) === 0)) { $rprop = $p; break; } } } if ($rprop !== null) { if ($rprop->isPublic() || $this->bIgnoreVisibility) { $docblock = $rprop->getDocComment(); $annotations = $this->parseAnnotations($docblock); if (!isset($annotations['var'][0])) { return array(true, $rprop, null); } //support "@var type description" list($type) = explode(' ', $annotations['var'][0]); return array(true, $rprop, $type); } else { //no setter, private property return array(true, null, null); } } //no setter, no property return array(false, null, null); } /** * Removes - and _ and makes the next letter uppercase * * @param string $name Property name * * @return string CamelCasedVariableName */ protected function getCamelCaseName($name) { return str_replace( ' ', '', ucwords(str_replace(array('_', '-'), ' ', $name)) ); } /** * Since hyphens cannot be used in variables we have to uppercase them. * * Technically you may use them, but they are awkward to access. * * @param string $name Property name * * @return string Name without hyphen */ protected function getSafeName($name) { if (strpos($name, '-') !== false) { $name = $this->getCamelCaseName($name); } return $name; } /** * 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->isPublic() && $this->bIgnoreVisibility) { $accessor->setAccessible(true); } if ($accessor instanceof ReflectionProperty) { $accessor->setValue($object, $value); } else { //setter method $accessor->invoke($object, $value); } } /** * Create a new object of the given type. * * This method exists to be overwritten in child classes, * so you can do dependency injection or so. * * @param string $class Class name to instantiate * @param boolean $useParameter Pass $parameter to the constructor or not * @param mixed $parameter Constructor parameter * * @return object Freshly created object */ public function createInstance( $class, $useParameter = false, $parameter = null ) { if (isset($this->classMap[$class])) { $class = $this->classMap[$class]; } if ($useParameter) { return new $class($parameter); } else { return new $class(); } } /** * 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 * * @see isFlatType() */ protected function isSimpleType($type) { return $type == 'string' || $type == 'boolean' || $type == 'bool' || $type == 'integer' || $type == 'int' || $type == 'double' || $type == 'float' || $type == 'array' || $type == 'object'; } /** * 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 * * @see isSimpleType() */ protected function isFlatType($type) { return $type == 'NULL' || $type == 'string' || $type == 'boolean' || $type == 'bool' || $type == 'integer' || $type == 'int' || $type == 'double' || $type == 'float'; } /** * 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) { if ($type === null) { return null; } 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 static 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[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \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; } } ?> src/JsonMapper/Exception.php000064400000001017144760130500012057 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Simple exception * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class JsonMapper_Exception extends Exception { } ?> tests/ArrayTest.php000064400000025163144760130500010344 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ require_once 'JsonMapperTest/Array.php'; require_once 'JsonMapperTest/Broken.php'; require_once 'JsonMapperTest/Simple.php'; /** * Unit tests for JsonMapper's array handling * * @category Tools * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ class ArrayTest extends \PHPUnit_Framework_TestCase { /** * Test for an array of classes "@var Classname[]" */ public function testMapTypedArray() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"typedArray":[{"str":"stringvalue"},{"fl":"1.2"}]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->typedArray); $this->assertEquals(2, count($sn->typedArray)); $this->assertInstanceOf('JsonMapperTest_Simple', $sn->typedArray[0]); $this->assertInstanceOf('JsonMapperTest_Simple', $sn->typedArray[1]); $this->assertEquals('stringvalue', $sn->typedArray[0]->str); $this->assertEquals(1.2, $sn->typedArray[1]->fl); } /** * Test for an array of classes "@var ClassName[]" with * flat/simple json values (string) */ public function testMapTypedSimpleArray() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"typedSimpleArray":["2014-01-02",null,"2014-05-07"]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->typedSimpleArray); $this->assertEquals(3, count($sn->typedSimpleArray)); $this->assertInstanceOf('DateTime', $sn->typedSimpleArray[0]); $this->assertNull($sn->typedSimpleArray[1]); $this->assertInstanceOf('DateTime', $sn->typedSimpleArray[2]); $this->assertEquals( '2014-01-02', $sn->typedSimpleArray[0]->format('Y-m-d') ); $this->assertEquals( '2014-05-07', $sn->typedSimpleArray[2]->format('Y-m-d') ); } /** * Test for an array that is nullable - "@var string[]|null" */ public function testNullableSimple() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"nullableSimpleArray":null}'), new JsonMapperTest_Array() ); $this->assertNull($sn->nullableSimpleArray); } public function testMapArrayJsonNoTypeEnforcement() { $jm = new JsonMapper(); $jm->bEnforceMapType = false; $sn = $jm->map(array(), new JsonMapperTest_Simple()); $this->assertInstanceOf('JsonMapperTest_Simple', $sn); } /** * Test for an array of float "@var float[]" */ public function testFlArray() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"flArray":[1.23,3.14,2.048]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->flArray); $this->assertEquals(3, count($sn->flArray)); $this->assertTrue(is_float($sn->flArray[0])); $this->assertTrue(is_float($sn->flArray[1])); $this->assertTrue(is_float($sn->flArray[2])); } /** * Test for an array of float "@var float[]" */ public function testFlArrayKeyed() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"flArray":{"foo":1.23,"bar":3.14,"baz":2.048}}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->flArray); $this->assertEquals(3, count($sn->flArray)); $this->assertTrue(is_float($sn->flArray['foo'])); $this->assertTrue(is_float($sn->flArray['bar'])); $this->assertTrue(is_float($sn->flArray['baz'])); } /** * Test for an array of strings - "@var string[]" */ public function testStrArray() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"strArray":["str",false,2.048]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->strArray); $this->assertEquals(3, count($sn->strArray)); $this->assertInternalType('string', $sn->strArray[0]); $this->assertInternalType('string', $sn->strArray[1]); $this->assertInternalType('string', $sn->strArray[2]); } /** * Test for an array of strings - "@var array[string]" */ public function testStrArrayV2() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"strArrayV2":["str",false,2.048]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->strArrayV2); $this->assertEquals(3, count($sn->strArrayV2)); $this->assertInternalType('string', $sn->strArrayV2[0]); $this->assertInternalType('string', $sn->strArrayV2[1]); $this->assertInternalType('string', $sn->strArrayV2[2]); } /** * Test for "@var ArrayObject" */ public function testMapArrayObject() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pArrayObject":[{"str":"stringvalue"},{"fl":"1.2"}]}'), new JsonMapperTest_Array() ); $this->assertInstanceOf('ArrayObject', $sn->pArrayObject); $this->assertEquals(2, count($sn->pArrayObject)); $this->assertInstanceOf('\stdClass', $sn->pArrayObject[0]); $this->assertInstanceOf('\stdClass', $sn->pArrayObject[1]); $this->assertEquals('stringvalue', $sn->pArrayObject[0]->str); $this->assertEquals('1.2', $sn->pArrayObject[1]->fl); } /** * Test for "@var ArrayObject[Classname]" */ public function testMapTypedArrayObject() { $jm = new JsonMapper(); $sn = $jm->map( json_decode( '{"pTypedArrayObject":[{"str":"stringvalue"},{"fl":"1.2"}]}' ), new JsonMapperTest_Array() ); $this->assertInstanceOf('ArrayObject', $sn->pTypedArrayObject); $this->assertEquals(2, count($sn->pTypedArrayObject)); $this->assertInstanceOf('JsonMapperTest_Simple', $sn->pTypedArrayObject[0]); $this->assertInstanceOf('JsonMapperTest_Simple', $sn->pTypedArrayObject[1]); $this->assertEquals('stringvalue', $sn->pTypedArrayObject[0]->str); $this->assertEquals('1.2', $sn->pTypedArrayObject[1]->fl); } /** * Test for "@var ArrayObject[int]" */ public function testMapSimpleArrayObject() { $jm = new JsonMapper(); $sn = $jm->map( json_decode( '{"pSimpleArrayObject":{"eins":"1","zwei":"1.2"}}' ), new JsonMapperTest_Array() ); $this->assertInstanceOf('ArrayObject', $sn->pSimpleArrayObject); $this->assertEquals(2, count($sn->pSimpleArrayObject)); $this->assertInternalType('int', $sn->pSimpleArrayObject['eins']); $this->assertInternalType('int', $sn->pSimpleArrayObject['zwei']); $this->assertEquals(1, $sn->pSimpleArrayObject['eins']); $this->assertEquals(1, $sn->pSimpleArrayObject['zwei']); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "flArray" must be an array, integer given */ public function testInvalidArray() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"flArray": 4 }'), new JsonMapperTest_Array() ); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "pArrayObject" must be an array, double given */ public function testInvalidArrayObject() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pArrayObject": 4.2 }'), new JsonMapperTest_Array() ); } /** * A nullable ArrayObject which is null. */ public function testArrayObjectNullable() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pNullableArrayObject": null}'), new JsonMapperTest_Array() ); $this->assertNull($sn->pNullableArrayObject); } /** * An ArrayObject which may not be null but is. * * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "pArrayObject" in class "JsonMapperTest_Array" must not be NULL */ public function testArrayObjectInvalidNull() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pArrayObject": null}'), new JsonMapperTest_Array() ); } /** * Exception is not thrown when a non-nullable object has null value * but strict nullable checks are turned off */ public function testNonNullableArrayObjectWithLooseNullChecks() { $jm = new JsonMapper(); $jm->bStrictNullTypes = false; $sn = $jm->map( json_decode('{"pArrayObject": null}'), new JsonMapperTest_Array() ); $this->assertNull($sn->pArrayObject); } /** * The TYPO3 autoloader breaks if we autoload a class with a [ or ] * in its name. * * @runInSeparateProcess */ public function testMapTypedArrayObjectDoesNotExist() { $this->assertTrue( spl_autoload_register( array($this, 'mapTypedArrayObjectDoesNotExistAutoloader') ) ); $jm = new JsonMapper(); $sn = $jm->map( json_decode( '{"pTypedArrayObjectNoClass":[{"str":"stringvalue"}]}' ), new JsonMapperTest_Broken() ); $this->assertInstanceOf('ArrayObject', $sn->pTypedArrayObjectNoClass); $this->assertEquals(1, count($sn->pTypedArrayObjectNoClass)); $this->assertInstanceOf( 'ThisClassDoesNotExist', $sn->pTypedArrayObjectNoClass[0] ); } public function mapTypedArrayObjectDoesNotExistAutoloader($class) { $this->assertFalse( strpos($class, '['), 'class name contains a "[": ' . $class ); $code = ''; if (strpos($class, '\\') !== false) { $lpos = strrpos($class, '\\'); $namespace = substr($class, 0, $lpos); $class = substr($class, $lpos + 1); $code .= 'namespace ' . $namespace . ";\n"; } $code .= 'class ' . $class . '{}'; eval($code); } } ?> tests/JsonMapperTest/Array.php000064400000003051144760130500012412 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Unit test helper class for testing property mapping * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class JsonMapperTest_Array { /** * @var float[] */ public $flArray; /** * @var string[] */ public $strArray; /** * @var array[string] */ public $strArrayV2; /** * @var JsonMapperTest_Simple[] * @see http://phpdoc.org/docs/latest/references/phpdoc/types.html#arrays */ public $typedArray; /** * @var DateTime[] */ public $typedSimpleArray; /** * @var string[]|null */ public $nullableSimpleArray; /** * This generates an array object with original json values * @var ArrayObject */ public $pArrayObject; /** * This generates an array object with original json values, * and may be NULL * @var ArrayObject|null */ public $pNullableArrayObject; /** * This generates an array object with JsonMapperTest_Simple instances * @var ArrayObject[JsonMapperTest_Simple] */ public $pTypedArrayObject; /** * @var ArrayObject[int] */ public $pSimpleArrayObject; } ?> tests/JsonMapperTest/Broken.php000064400000001334144760130500012556 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Unit test helper class for testing property mapping * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class JsonMapperTest_Broken { /** * @var ArrayObject[ThisClassDoesNotExist] */ public $pTypedArrayObjectNoClass; /** * * @var string * @required */ public $pMissingData; } ?> tests/JsonMapperTest/DependencyInjector.php000064400000001425144760130500015113 0ustar00db = 'database'; return $object; } } ?>tests/JsonMapperTest/Logger.php000064400000001622144760130500012555 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Simple in-memory logger * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class JsonMapperTest_Logger { public $log = array(); /** * 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 */ public function log($level, $message, array $context = array()) { $this->log[] = array($level, $message, $context); } } ?>tests/JsonMapperTest/Object.php000064400000001241144760130500012541 0ustar00nullableObject = $obj; } /** * @param JsonMapperTest_PlainObject|null $obj */ public function setDocblockNullableObject($obj) { $this->docblockNullableObject = $obj; } } ?> tests/JsonMapperTest/PlainObject.php000064400000000143144760130500013525 0ustar00 tests/JsonMapperTest/PrivateWithSetter.php000064400000002707144760130500015000 0ustar00privateProperty = $privateProperty; return $this; } /** * @param int $ppps * * @return $this */ private function setPrivatePropertyPrivateSetter($ppps) { $this->privatePropertyPrivateSetter = $ppps; return $this; } /** * There is no property with this name, only a setter method * * @param int $ps * * @return $this */ private function setPrivateSetter($ps) { $this->_internal['ps'] = $ps; return $this; } /** * @return int */ public function getPrivateProperty() { return $this->privateProperty; } /** * @return int */ public function getPrivateNoSetter() { return $this->privateNoSetter; } /** * @return int */ public function getPrivatePropertyPrivateSetter() { return $this->privatePropertyPrivateSetter; } public function getPrivateSetter() { return $this->_internal['ps']; } } ?> tests/JsonMapperTest/Simple.php000064400000005663144760130500012600 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ /** * Unit test helper class for testing property mapping * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class JsonMapperTest_Simple { /** * @var bool */ public $pbool; /** * @var boolean */ public $pboolean; /** * @var int */ public $pint; /** * @var integer */ public $pinteger; /** * @var int|null */ public $pnullable; /** * @var float */ public $fl; /** * @var mixed */ public $mixed; /** * @var string */ public $str; /** * This property has no @-var type hint */ public $notype; /** * @var string A protected property without a setter method */ protected $protectedStrNoSetter; /** * @var JsonMapperTest_Simple */ public $simple; public $internalData; /** * Variable name with underscore * @var string */ public $under_score; /** * Variable name with hyphen (-) * @var string */ public $hyphenValue; /** * @var */ public $empty; /** * @var string */ public $setterPreferredOverProperty; /** * Value object which needs to be set as an instance (without mapping) * @var JsonMapperTest_ValueObject */ public $valueObject; public function setSimpleSetterOnlyTypeHint(JsonMapperTest_Simple $s) { $this->internalData['typehint'] = $s; } /** * @param JsonMapperTest_Simple $s Some test object */ public function setSimpleSetterOnlyDocblock($s) { $this->internalData['docblock'] = $s; } public function setSimpleSetterOnlyNoType($s) { $this->internalData['notype'] = $s; } public function getProtectedStrNoSetter() { return $this->protectedStrNoSetter; } public function setUnderScoreSetter($v) { $this->internalData['under_score_setter'] = $v; } public function setHyphenValueSetter($v) { $this->internalData['hyphen-value-setter'] = $v; } public function setSetterPreferredOverProperty($v) { $this->setterPreferredOverProperty = 'set via setter: ' . $v; } /** * @return JsonMapperTest_ValueObject */ public function getValueObject() { return $this->valueObject; } /** * @param JsonMapperTest_ValueObject $valueObject */ public function setValueObject(JsonMapperTest_ValueObject $valueObject) { $this->valueObject = $valueObject; } } ?> tests/JsonMapperTest/ValueObject.php000064400000000227144760130500013541 0ustar00value = $value; } } tests/ObjectTest.php000064400000012213144760130500010464 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ require_once 'JsonMapperTest/Simple.php'; require_once 'JsonMapperTest/Object.php'; require_once 'JsonMapperTest/PlainObject.php'; require_once 'JsonMapperTest/ValueObject.php'; /** * Unit tests for JsonMapper's object handling * * @category Tools * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ class ObjectTest extends \PHPUnit_Framework_TestCase { /** * Test for a class name "@var Classname" */ public function testMapObject() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"simple":{"str":"stringvalue"}}'), new JsonMapperTest_Simple() ); $this->assertInternalType('object', $sn->simple); $this->assertInstanceOf('JsonMapperTest_Simple', $sn->simple); $this->assertEquals('stringvalue', $sn->simple->str); } public function testMapDateTime() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"datetime":"2014-04-01T00:00:00+02:00"}'), new JsonMapperTest_Object() ); $this->assertInstanceOf('DateTime', $sn->datetime); $this->assertEquals( '2014-04-01T00:00:00+02:00', $sn->datetime->format('c') ); } public function testMapDateTimeNull() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"datetime":null}'), new JsonMapperTest_Object() ); $this->assertNull($sn->datetime); } public function testSettingValueObjects() { $valueObject = new JsonMapperTest_ValueObject('test'); $jm = new JsonMapper(); $sn = $jm->map( (object) array('value_object' => $valueObject), new JsonMapperTest_Simple() ); $this->assertSame($valueObject, $sn->getValueObject()); } public function testStrictTypeCheckingObject() { $jm = new JsonMapper(); $jm->bStrictObjectTypeChecking = true; $sn = $jm->map( json_decode('{"pPlainObject":{"pStr":"abc"}}'), new JsonMapperTest_Object() ); $this->assertInternalType('object', $sn->pPlainObject); $this->assertInstanceOf('JsonMapperTest_PlainObject', $sn->pPlainObject); $this->assertEquals('abc', $sn->pPlainObject->pStr); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "pValueObject" must be an object, string given */ public function testStrictTypeCheckingObjectError() { $jm = new JsonMapper(); $jm->bStrictObjectTypeChecking = true; $sn = $jm->map( json_decode('{"pValueObject":"abc"}'), new JsonMapperTest_Object() ); } /** * Test for "@var object|null" with null value */ public function testObjectNullable() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pValueObjectNullable":null}'), new JsonMapperTest_Object() ); $this->assertNull($sn->pValueObjectNullable); } /** * Test for "Object $obj = null" with null value */ public function testObjectSetterNullable() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"nullableObject":null}'), new JsonMapperTest_Object() ); $this->assertNull($sn->nullableObject); } /** * Test for "@param object|null" with null value */ public function testObjectSetterDockblockNullable() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"docblockNullableObject":null}'), new JsonMapperTest_Object() ); $this->assertNull($sn->docblockNullableObject); } /** * Test for "@var object" with null value * * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "pValueObject" in class "JsonMapperTest_Object" must not be NULL */ public function testObjectInvalidNull() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pValueObject":null}'), new JsonMapperTest_Object() ); $this->assertInternalType('null', $sn->pValueObjectNullable); } public function testClassMap() { $jm = new JsonMapper(); $jm->classMap['JsonMapperTest_PlainObject'] = 'DateTime'; $sn = $jm->map( json_decode('{"pPlainObject":"2016-04-14T23:15:42+02:00"}'), new JsonMapperTest_Object() ); $this->assertInternalType('object', $sn->pPlainObject); $this->assertInstanceOf('DateTime', $sn->pPlainObject); $this->assertEquals( '2016-04-14T23:15:42+02:00', $sn->pPlainObject->format('c') ); } } ?> tests/OtherTest.php000064400000023734144760130500010351 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ require_once 'JsonMapperTest/Broken.php'; require_once 'JsonMapperTest/DependencyInjector.php'; require_once 'JsonMapperTest/Simple.php'; require_once 'JsonMapperTest/Logger.php'; require_once 'JsonMapperTest/PrivateWithSetter.php'; require_once 'JsonMapperTest/ValueObject.php'; /** * Unit tests for JsonMapper that don't fit in other categories * * @category Netresearch * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link http://cweiske.de/ */ class OtherTest extends \PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException * @expectedExceptionMessage JsonMapper::map() requires first argument to be an object, NULL given. */ public function testMapNullJson() { $jm = new JsonMapper(); $sn = $jm->map(null, new JsonMapperTest_Simple()); } /** * @expectedException InvalidArgumentException * @expectedExceptionMessage JsonMapper::map() requires second argument to be an object, NULL given. */ public function testMapNullObject() { $jm = new JsonMapper(); $sn = $jm->map(new stdClass(), null); } /** * Test for "@var " * * @expectedException JsonMapper_Exception * @expectedExceptionMessage Empty type at property "JsonMapperTest_Simple::$empty" */ public function testMapEmpty() { $jm = new JsonMapper(); $sn = $jm->map( json_decode( '{"empty":{"a":"b"}}' ), new JsonMapperTest_Simple() ); } /** * There is no property, but a setter method. * The parameter has a type hint. */ public function testMapOnlySetterTypeHint() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"simpleSetterOnlyTypeHint":{"str":"stringvalue"}}'), new JsonMapperTest_Simple() ); $this->assertInternalType('object', $sn->internalData['typehint']); $this->assertInstanceOf( 'JsonMapperTest_Simple', $sn->internalData['typehint'] ); $this->assertEquals( 'stringvalue', $sn->internalData['typehint']->str ); } /** * There is no property, but a setter method. * It indicates the type in the docblock's @param annotation */ public function testMapOnlySetterDocblock() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"simpleSetterOnlyDocblock":{"str":"stringvalue"}}'), new JsonMapperTest_Simple() ); $this->assertInternalType('object', $sn->internalData['docblock']); $this->assertInstanceOf( 'JsonMapperTest_Simple', $sn->internalData['docblock'] ); $this->assertEquals( 'stringvalue', $sn->internalData['docblock']->str ); } /** * There is no property, but a setter method, but it indicates no type */ public function testMapOnlySetterNoType() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"simpleSetterOnlyNoType":{"str":"stringvalue"}}'), new JsonMapperTest_Simple() ); $this->assertInternalType('object', $sn->internalData['notype']); $this->assertInstanceOf( 'stdClass', $sn->internalData['notype'] ); $this->assertEquals( 'stringvalue', $sn->internalData['notype']->str ); } /** * Test for protected properties that have no setter method */ public function testMapProtectedWithoutSetterMethod() { $jm = new JsonMapper(); $logger = new JsonMapperTest_Logger(); $jm->setLogger($logger); $sn = $jm->map( json_decode('{"protectedStrNoSetter":"stringvalue"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('null', $sn->getProtectedStrNoSetter()); $this->assertEquals( array( array( 'info', 'Property {property} has no public setter method in {class}', array( 'class' => 'JsonMapperTest_Simple', 'property' => 'protectedStrNoSetter' ) ) ), $logger->log ); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage Required property "pMissingData" of class JsonMapperTest_Broken is missing in JSON data */ public function testMissingDataException() { $jm = new JsonMapper(); $jm->bExceptionOnMissingData = true; $sn = $jm->map( json_decode('{}'), new JsonMapperTest_Broken() ); } /** * We check that checkMissingData exits cleanly; needed for 100% coverage. */ public function testMissingDataNoException() { $jm = new JsonMapper(); $jm->bExceptionOnMissingData = true; $sn = $jm->map( json_decode('{"pMissingData":1}'), new JsonMapperTest_Broken() ); $this->assertTrue(true); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "undefinedProperty" does not exist in object of type JsonMapperTest_Broken */ public function testUndefinedPropertyException() { $jm = new JsonMapper(); $jm->bExceptionOnUndefinedProperty = true; $sn = $jm->map( json_decode('{"undefinedProperty":123}'), new JsonMapperTest_Broken() ); } public function testUndefinedPropertyHandler() { $jm = new JsonMapper(); $jm->undefinedPropertyHandler = array($this, 'setUnknownProperty'); $sn = $jm->map( json_decode('{"undefinedProperty":123}'), new JsonMapperTest_Broken() ); $this->assertEquals(123, $sn->ADDundefinedProperty); } public function setUnknownProperty($object, $propName, $jsonValue) { $object->{'ADD' . $propName} = $jsonValue; } public function testPrivatePropertyWithPublicSetter() { $jm = new JsonMapper(); $jm->bExceptionOnUndefinedProperty = true; $logger = new JsonMapperTest_Logger(); $jm->setLogger($logger); $json = '{"privateProperty" : 1}'; $result = $jm->map(json_decode($json), new PrivateWithSetter()); $this->assertEquals(1, $result->getPrivateProperty()); $this->assertTrue(empty($logger->log)); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "privateNoSetter" has no public setter method in object of type PrivateWithSetter */ public function testPrivatePropertyWithNoSetter() { $jm = new JsonMapper(); $jm->bExceptionOnUndefinedProperty = true; $logger = new JsonMapperTest_Logger(); $jm->setLogger($logger); $json = '{"privateNoSetter" : 1}'; $result = $jm->map(json_decode($json), new PrivateWithSetter()); $this->assertEquals(1, $result->getPrivateProperty()); $this->assertTrue(empty($logger->log)); } public function testPrivatePropertyWithNoSetterButAllowed() { $jm = new JsonMapper(); $jm->bIgnoreVisibility = true; $json = '{"privateNoSetter" : 1}'; $result = $jm->map(json_decode($json), new PrivateWithSetter()); $this->assertEquals(1, $result->getPrivateNoSetter()); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "privatePropertyPrivateSetter" has no public setter method in object of type PrivateWithSetter */ public function testPrivatePropertyWithPrivateSetter() { $jm = new JsonMapper(); $jm->bExceptionOnUndefinedProperty = true; $logger = new JsonMapperTest_Logger(); $jm->setLogger($logger); $json = '{"privatePropertyPrivateSetter" : 1}'; $result = $jm->map(json_decode($json), new PrivateWithSetter()); } public function testPrivateSetterButAllowed() { $jm = new JsonMapper(); $jm->bIgnoreVisibility = true; $jm->bExceptionOnUndefinedProperty = true; $json = '{"privateSetter" : 1}'; $result = $jm->map(json_decode($json), new PrivateWithSetter()); $this->assertEquals(1, $result->getPrivateSetter()); } public function testSetterIsPreferredOverProperty() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"setterPreferredOverProperty":"foo"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('string', $sn->setterPreferredOverProperty); $this->assertEquals( 'set via setter: foo', $sn->setterPreferredOverProperty ); } public function testCaseInsensitivePropertyMatching() { $jm = new JsonMapper(); $sn = $jm->map( (object) array('PINT' => 2), new JsonMapperTest_Simple() ); $this->assertSame(2, $sn->pint); } public function testDependencyInjection() { $jm = new JsonMapperTest_DependencyInjector(); $sn = $jm->map( (object) array( 'str' => 'first level', 'simple' => (object) array( 'str' => 'second level' ) ), $jm->createInstance('JsonMapperTest_Simple') ); $this->assertEquals('first level', $sn->str); $this->assertEquals('database', $sn->db); $this->assertEquals('second level', $sn->simple->str); $this->assertEquals('database', $sn->simple->db); } } ?> tests/SimpleTest.php000064400000015077144760130500010522 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ require_once 'JsonMapperTest/Simple.php'; /** * Unit tests for JsonMapper's simple type handling * * @category Tools * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ class SimpleTest extends \PHPUnit_Framework_TestCase { /** * Test for "@var string" */ public function testMapSimpleString() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"str":"stringvalue"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('string', $sn->str); $this->assertEquals('stringvalue', $sn->str); } /** * Test for "@var float" */ public function testMapSimpleFloat() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"fl":"1.2"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('float', $sn->fl); $this->assertEquals(1.2, $sn->fl); } /** * Test for "@var bool" */ public function testMapSimpleBool() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pbool":"1"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('boolean', $sn->pbool); $this->assertEquals(true, $sn->pbool); } /** * Test for "@var boolean" */ public function testMapSimpleBoolean() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pboolean":"0"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('boolean', $sn->pboolean); $this->assertEquals(false, $sn->pboolean); } /** * Test for "@var int" */ public function testMapSimpleInt() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pint":"123"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('integer', $sn->pint); $this->assertEquals(123, $sn->pint); } /** * Test for "@var integer" */ public function testMapSimpleInteger() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pinteger":"12345"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('integer', $sn->pinteger); $this->assertEquals(12345, $sn->pinteger); } /** * Test for "@var mixed" */ public function testMapSimpleMixed() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"mixed":12345}'), new JsonMapperTest_Simple() ); $this->assertInternalType('integer', $sn->mixed); $this->assertEquals('12345', $sn->mixed); $sn = $jm->map( json_decode('{"mixed":"12345"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('string', $sn->mixed); $this->assertEquals(12345, $sn->mixed); } /** * Test for "@var int|null" with int value */ public function testMapSimpleNullableInt() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pnullable":0}'), new JsonMapperTest_Simple() ); $this->assertInternalType('integer', $sn->pnullable); $this->assertEquals(0, $sn->pnullable); } /** * Test for "@var int|null" with null value */ public function testMapSimpleNullableNull() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pnullable":null}'), new JsonMapperTest_Simple() ); $this->assertInternalType('null', $sn->pnullable); $this->assertEquals(null, $sn->pnullable); } /** * Test for "@var int|null" with string value */ public function testMapSimpleNullableWrong() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pnullable":"12345"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('integer', $sn->pnullable); $this->assertEquals(12345, $sn->pnullable); } /** * Test for variable with no @var annotation */ public function testMapSimpleNoType() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"notype":{"k":"v"}}'), new JsonMapperTest_Simple() ); $this->assertInternalType('object', $sn->notype); $this->assertEquals((object) array('k' => 'v'), $sn->notype); } /** * Variable with an underscore */ public function testMapSimpleUnderscore() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"under_score":"f"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('string', $sn->under_score); $this->assertEquals('f', $sn->under_score); } /** * Variable with an underscore and a setter method */ public function testMapSimpleUnderscoreSetter() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"under_score_setter":"blubb"}'), new JsonMapperTest_Simple() ); $this->assertInternalType( 'string', $sn->internalData['under_score_setter'] ); $this->assertEquals( 'blubb', $sn->internalData['under_score_setter'] ); } /** * Variable with hyphen (-) */ public function testMapSimpleHyphen() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"hyphen-value":"test"}'), new JsonMapperTest_Simple() ); $this->assertInternalType('string', $sn->hyphenValue); $this->assertEquals('test', $sn->hyphenValue); } /** * Variable with hyphen and a setter method */ public function testMapSimpleHyphenSetter() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"hyphen-value-setter":"blubb"}'), new JsonMapperTest_Simple() ); $this->assertInternalType( 'string', $sn->internalData['hyphen-value-setter'] ); $this->assertEquals( 'blubb', $sn->internalData['hyphen-value-setter'] ); } } ?> tests/bootstrap.php000064400000000337144760130500010437 0ustar00 tests/namespacetest/NamespaceTest.php000064400000007630144760130500014015 0ustar00map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\Unit', $res->data[0]); } public function testMapSimpleArrayNamespace() { $mapper = new \JsonMapper(); $json = '{"units":[{"value":"1.2"}]}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\Unit', $res->units[0]); } public function testMapSimpleStringArrayNamespace() { $mapper = new \JsonMapper(); $json = '{"messages":["message 1", "message 2"]}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertNotNull($res->messages); $this->assertCount(2, $res->messages); } public function testMapChildClassNamespace() { $mapper = new \JsonMapper(); $json = '{"user":{"name": "John Smith"}}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\model\User', $res->user); } public function testMapChildClassConstructorNamespace() { $mapper = new \JsonMapper(); $json = '{"user":"John Smith"}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\model\User', $res->user); } public function testMapChildObjectArrayNamespace() { $mapper = new \JsonMapper(); $json = '{"data":[],"user":{"name": "John Smith"}}'; /* @var \namespacetest\UnitData $res */ $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\\ArrayObject', $res->data); $this->assertInstanceOf('\namespacetest\model\User', $res->user); } /** * @expectedException JsonMapper_Exception * @expectedExceptionMessage Empty type at property "namespacetest\UnitData::$empty" */ public function testMapEmpty() { $mapper = new \JsonMapper(); $json = '{"empty":{}}'; /* @var \namespacetest\UnitData $res */ $res = $mapper->map(json_decode($json), new UnitData()); } public function testMapCustomArraObjectWithChildType() { $mapper = new \JsonMapper(); $json = '{"users":[{"user":"John Smith"}]}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\model\UserList', $res->users); $this->assertInstanceOf('\namespacetest\model\User', $res->users[0]); } /** * Test a setter method with a namespaced type hint that * is within another namespace than the object itself. */ public function testSetterNamespacedTypeHint() { $mapper = new \JsonMapper(); $json = '{"namespacedTypeHint":"Foo"}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf( '\othernamespace\Foo', $res->internalData['namespacedTypeHint'] ); $this->assertEquals( 'Foo', $res->internalData['namespacedTypeHint']->name ); } } ?> tests/namespacetest/Unit.php000064400000000105144760130500012166 0ustar00 tests/namespacetest/UnitData.php000064400000001120144760130500012756 0ustar00internalData['namespacedTypeHint'] = $foo; } } ?> tests/namespacetest/model/User.php000064400000000263144760130500013272 0ustar00name = $name; } } ?> tests/namespacetest/model/UserList.php000064400000000122144760130500014120 0ustar00 tests/othernamespace/Foo.php000064400000000225144760130500012137 0ustar00name = $name; } } ?> tests/phpunit.xml000064400000000725144760130500010123 0ustar00 ../src/