.gitignore000064400000000102144760115760006540 0ustar00/README.html /*.tgz /dist/ /vendor/ .buildpath .project .settings .travis.yml000064400000000442144760115760006670 0ustar00language: php sudo: false dist: trusty php: - 5.6 - 7 - 7.1 - hhvm before_script: - composer install --no-interaction --prefer-source --dev - phpenv rehash script: - cd tests - ./../vendor/bin/phpunit --coverage-text . - cd .. - ./vendor/bin/phpcs --standard=PEAR src/ ChangeLog000064400000011060144760115760006327 0ustar002017-08-14 Christian Weiske * Fix "Missing argument" error when instantiating class with required constructor parameter by @jaredchu (pull request #85) * Raise minimum PHP version to 5.6.0 * Version 1.3.0 2017-07-13 Christian Weiske * Add support for multidimensional arrays by @thomasphansen (issue #81) * Version 1.2.0 2017-01-27 Christian Weiske * Fix namespace resolution of ArrayObject child classes (issue #69) * Add PHP 7.1 nullability support by @MHarutunian (issue #67) * Version 1.1.1 2016-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 LICENSE000064400000024122144760115760005565 0ustar00Open Software License v. 3.0 (OSL-3.0) This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: Licensed under the Open Software License version 3.0 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: a) to reproduce the Original Work in copies, either alone or as part of a collective work; b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; d) to perform the Original Work publicly; and e) to display the Original Work publicly. 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor’s trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. 5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. 9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including “fair use” or “fair dealing”). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. 12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. 16) Modification of This License. This License is Copyright (c) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.README.rst000064400000026275144760115760006262 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[]`` - Multidimensional arrays: - ``int[][]`` - ``TreeDeePixel[][][]`` - 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.json000064400000001276144760115760007307 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":{ "php": ">=5.6" }, "require-dev": { "phpunit/phpunit": "4.2.*", "squizlabs/php_codesniffer": "~1.5" } } example/Address.php000064400000000746144760115760010317 0ustar00street) . ',' . urlencode($this->city) . '&format=json&addressdetails=1' ); $json = json_decode($data); return array( 'lat' => $json[0]->lat, 'lon' => $json[0]->lon ); } } ?> example/Contact.php000064400000000147144760115760010320 0ustar00 example/run.php000064400000000622144760115760007527 0ustar00map($json, new Contact()); $coords = $contact->address->getGeoCoords(); echo $contact->name . ' lives at coordinates ' . $coords['lat'] . ',' . $coords['lon'] . "\n"; ?>example/single.json000064400000000135144760115760010365 0ustar00{"name":"Sheldon Cooper","address":{"street":"2311 N. Los Robles Avenue","city":"Pasadena"}} package.xml000064400000033576144760115760006712 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 2017-08-14 1.3.0 1.1.0 stable stable OSL-3.0 * Fix "Missing argument" error when instantiating class with required constructor parameter by @jaredchu (pull request #85) * Raise minimum PHP version to 5.6.0 5.6.0 1.9.1 1.3.0 1.1.0 stable stable 2017-08-14 OSL-3.0 * Fix "Missing argument" error when instantiating class with required constructor parameter by @jaredchu (pull request #85) * Raise minimum PHP version to 5.6.0 1.2.0 1.1.0 stable stable 2017-07-13 OSL-3.0 * Add support for multidimensional arrays by @thomasphansen (issue #81) 1.1.1 1.1.0 stable stable 2017-01-27 OSL-3.0 * Fix namespace resolution of ArrayObject child classes (issue #69) * Add PHP 7.1 nullability support by @MHarutunian (issue #67) 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.php000064400000054340144760115760010143 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. * * @var array */ public $classMap = array(); /** * Callback used when an undefined property is found. * * 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 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 detailed error message if not if ($type === '') { throw new JsonMapper_Exception( 'Empty type at property "' . $strClassName . '::$' . $key . '"' ); } $array = null; $subtype = null; if ($this->isArrayOfType($type)) { //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 { $type = $this->getFullNamespace($type, $strNs); 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) && $cleanSubtype !== null ) { $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->isArrayOfType($class)) { $array[$key] = $this->mapArray( $jvalue, array(), substr($class, 0, -2) ); } 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]->allowsNull()) { $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 ReflectionClass($class))->newInstanceWithoutConstructor(); } } /** * 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'; } /** * Returns true if type is an array of elements * (bracket notation) * * @param string $strType type to be matched * * @return bool */ protected function isArrayOfType($strType) { return substr($strType, -2) === '[]'; } /** * 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.php000064400000001017144760115760012072 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.php000064400000030422144760115760010351 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); } /** * Test for an array of array of integers "@var int[][]" */ public function testNMatrix() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"nMatrix":[[1,2],[3,4],[5]]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->nMatrix); $this->assertEquals(3, count($sn->nMatrix)); $this->assertInternalType('array', $sn->nMatrix[0]); $this->assertInternalType('array', $sn->nMatrix[1]); $this->assertInternalType('array', $sn->nMatrix[2]); $this->assertEquals(2, count($sn->nMatrix[0])); $this->assertInternalType('int', $sn->nMatrix[0][0]); $this->assertInternalType('int', $sn->nMatrix[0][1]); $this->assertEquals(2, count($sn->nMatrix[1])); $this->assertEquals(1, count($sn->nMatrix[2])); } /** * Test for an array of arrays of arrays of objects * "@var JsonMapper_Simple[][][]" */ public function testObjectMultiverse() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"pMultiverse":[[[{"pint":23}]]]}'), new JsonMapperTest_Array() ); $this->assertInternalType('array', $sn->pMultiverse); $this->assertEquals(1, count($sn->pMultiverse)); $this->assertInternalType('array', $sn->pMultiverse[0]); $this->assertEquals(1, count($sn->pMultiverse[0])); $this->assertInternalType('array', $sn->pMultiverse[0][0]); $this->assertEquals(1, count($sn->pMultiverse[0][0])); $this->assertInstanceOf( 'JsonMapperTest_Simple', $sn->pMultiverse[0][0][0] ); } } ?> tests/JsonMapperTest/Array.php000064400000003354144760115760012433 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; /** * 2D matrix of numbers * @var int[][] */ public $nMatrix; /** * 3D matrix of objects * @var JsonMapperTest_Simple[][][] */ public $pMultiverse; } ?> tests/JsonMapperTest/Broken.php000064400000001334144760115760012571 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/ComplexObject.php000064400000000765144760115760014116 0ustar00valueObject = $valueObject; } }tests/JsonMapperTest/DependencyInjector.php000064400000001425144760115760015126 0ustar00db = 'database'; return $object; } } ?>tests/JsonMapperTest/Logger.php000064400000001622144760115760012570 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.php000064400000001241144760115760012554 0ustar00nullableObject = $obj; } /** * @param JsonMapperTest_PlainObject|null $obj */ public function setDocblockNullableObject($obj) { $this->docblockNullableObject = $obj; } } ?> tests/JsonMapperTest/PHP7_Object.php000064400000000524144760115760013355 0ustar00typeNullableObject = $obj; } /** * @param JsonMapperTest_PlainObject $obj */ public function setNonNullableObject($obj) { $this->nonNullableObject = $obj; } } ?> tests/JsonMapperTest/PlainObject.php000064400000000143144760115760013540 0ustar00 tests/JsonMapperTest/PrivateWithSetter.php000064400000002707144760115760015013 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.php000064400000005663144760115760012613 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.php000064400000000451144760115760013553 0ustar00value = $value; $this->publicValue = $value; } public function getPublicValue() { return $this->publicValue; } } tests/ObjectTest.php000064400000013355144760115760010507 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'; require_once 'JsonMapperTest/ComplexObject.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 testComplexObject() { $valueObject = new JsonMapperTest_ValueObject('test'); $complexObject = new JsonMapperTest_ComplexObject($valueObject); $jm = new JsonMapper(); $sn = $jm->map( json_decode(json_encode($complexObject)), (new ReflectionClass(JsonMapperTest_ComplexObject::class))->newInstanceWithoutConstructor() ); $this->assertEquals( $complexObject->valueObject->getPublicValue(), $sn->valueObject->getPublicValue() ); } 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.php000064400000023734144760115760010364 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/PHP7_ObjectTest.php000064400000003477144760115760011311 0ustar00 * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper */ /** * Unit tests for JsonMapper's object handling using PHP 7.1 syntax * * @category Tools * @package JsonMapper * @author Christian Weiske * @license OSL-3.0 http://opensource.org/licenses/osl-3.0 * @link https://github.com/cweiske/jsonmapper * @requires PHP 7.1 */ class PHP7_1_ObjectTest extends \PHPUnit_Framework_TestCase { /** * Sets up test cases loading required classes. * * This is in setUp and not at the top of this file to ensure this is only * executed with PHP 7.1 (due to the `@requires` tag). */ protected function setUp() { require_once 'JsonMapperTest/PlainObject.php'; require_once 'JsonMapperTest/PHP7_Object.php'; } /** * Test for PHP7 nullable types like "?Object" */ public function testObjectSetterTypeNullable() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"typeNullableObject":null}'), new JsonMapperTest_PHP7_Object() ); $this->assertNull($sn->typeNullableObject); } /** * Test for non-nullable types like "@param object" with null value * * @expectedException JsonMapper_Exception * @expectedExceptionMessage JSON property "nonNullableObject" in class "JsonMapperTest_PHP7_Object" must not be NULL */ public function testObjectSetterDocblockInvalidNull() { $jm = new JsonMapper(); $sn = $jm->map( json_decode('{"nonNullableObject":null}'), new JsonMapperTest_PHP7_Object() ); } } ?> tests/SimpleTest.php000064400000015077144760115760010535 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.php000064400000000337144760115760010452 0ustar00 tests/namespacetest/NamespaceTest.php000064400000010636144760115760014030 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 testMapCustomArrayObjectWithChildType() { $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]); } public function testMapCustomArrayObject() { $mapper = new \JsonMapper(); $json = '{"aodata":["foo"]}'; $res = $mapper->map(json_decode($json), new UnitData()); $this->assertInstanceOf('\namespacetest\UnitData', $res); $this->assertInstanceOf('\namespacetest\model\MyArrayObject', $res->aodata); $this->assertInternalType('string', $res->aodata[0]); $this->assertEquals('foo', $res->aodata[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.php000064400000000105144760115760012201 0ustar00 tests/namespacetest/UnitData.php000064400000001225144760115760012777 0ustar00internalData['namespacedTypeHint'] = $foo; } } ?> tests/namespacetest/model/MyArrayObject.php000064400000000126144760115760015100 0ustar00 tests/namespacetest/model/User.php000064400000000263144760115760013305 0ustar00name = $name; } } ?> tests/namespacetest/model/UserList.php000064400000000122144760115760014133 0ustar00 tests/othernamespace/Foo.php000064400000000225144760115760012152 0ustar00name = $name; } } ?> tests/phpunit.xml000064400000000725144760115760010136 0ustar00 ../src/