├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── doc ├── context.md ├── definition │ ├── accessor.md │ ├── alias.md │ ├── exclusion_policy.md │ ├── groups.md │ ├── max_depth.md │ ├── mutator.md │ ├── order.md │ ├── readable.md │ ├── type.md │ ├── version.md │ ├── writable.md │ ├── xml_attribute.md │ ├── xml_collection.md │ ├── xml_root.md │ └── xml_value.md ├── docker.md ├── event.md ├── installation.md ├── mapping.md ├── type.md ├── usage.md └── visitor.md └── src ├── Accessor ├── AccessorInterface.php ├── ReflectionAccessor.php └── SymfonyAccessor.php ├── Context ├── Context.php └── ContextInterface.php ├── Direction.php ├── Event ├── AbstractClassMetadataEvent.php ├── AbstractEvent.php ├── AbstractPreEvent.php ├── ClassMetadataLoadEvent.php ├── ClassMetadataNotFoundEvent.php ├── PostDeserializeEvent.php ├── PostSerializeEvent.php ├── PreDeserializeEvent.php ├── PreSerializeEvent.php └── SerializerEvents.php ├── Exclusion ├── ChainExclusionStrategy.php ├── ExclusionPolicy.php ├── ExclusionStrategy.php ├── ExclusionStrategyInterface.php ├── GroupsExclusionStrategy.php ├── MaxDepthExclusionStrategy.php └── VersionExclusionStrategy.php ├── Format.php ├── Instantiator ├── DoctrineInstantiator.php └── InstantiatorInterface.php ├── Mapping ├── Annotation │ ├── Accessor.php │ ├── Alias.php │ ├── Exclude.php │ ├── ExclusionPolicy.php │ ├── Expose.php │ ├── Groups.php │ ├── MaxDepth.php │ ├── Mutator.php │ ├── Order.php │ ├── Readable.php │ ├── Since.php │ ├── Type.php │ ├── Until.php │ ├── Writable.php │ ├── XmlAttribute.php │ ├── XmlCollection.php │ ├── XmlRoot.php │ └── XmlValue.php ├── ClassMetadata.php ├── ClassMetadataInterface.php ├── Factory │ ├── AbstractClassMetadataFactory.php │ ├── CacheClassMetadataFactory.php │ ├── ClassMetadataFactory.php │ ├── ClassMetadataFactoryInterface.php │ └── EventClassMetadataFactory.php ├── Loader │ ├── AbstractClassMetadataLoader.php │ ├── AbstractFileClassMetadataLoader.php │ ├── AbstractReflectionClassMetadataLoader.php │ ├── AnnotationClassMetadataLoader.php │ ├── ChainClassMetadataLoader.php │ ├── ClassMetadataLoaderInterface.php │ ├── DirectoryClassMetadataLoader.php │ ├── FileClassMetadataLoader.php │ ├── JsonClassMetadataLoader.php │ ├── MappedClassMetadataLoaderInterface.php │ ├── ReflectionClassMetadataLoader.php │ ├── XmlClassMetadataLoader.php │ └── YamlClassMetadataLoader.php ├── MetadataInterface.php ├── PropertyMetadata.php ├── PropertyMetadataInterface.php ├── Resource │ └── mapping.xsd ├── TypeMetadata.php └── TypeMetadataInterface.php ├── Mutator ├── MutatorInterface.php ├── ReflectionMutator.php └── SymfonyMutator.php ├── Naming ├── AbstractNamingStrategy.php ├── AbstractSeparatorNamingStrategy.php ├── CacheNamingStrategy.php ├── CamelCaseNamingStrategy.php ├── IdenticalNamingStrategy.php ├── KebabCaseNamingStrategy.php ├── NamingStrategyInterface.php ├── SnakeCaseNamingStrategy.php ├── SpaceNamingStrategy.php └── StudlyCapsNamingStrategy.php ├── Navigator ├── EventNavigator.php ├── Navigator.php └── NavigatorInterface.php ├── Registry ├── TypeRegistry.php ├── TypeRegistryInterface.php ├── VisitorRegistry.php └── VisitorRegistryInterface.php ├── Serializer.php ├── SerializerInterface.php ├── Type ├── ArrayType.php ├── BooleanType.php ├── ClosureType.php ├── DateTimeType.php ├── ExceptionType.php ├── FloatType.php ├── Guesser │ ├── TypeGuesser.php │ └── TypeGuesserInterface.php ├── IntegerType.php ├── NullType.php ├── ObjectType.php ├── Parser │ ├── TypeLexer.php │ ├── TypeParser.php │ └── TypeParserInterface.php ├── ResourceType.php ├── StdClassType.php ├── StringType.php ├── Type.php └── TypeInterface.php └── Visitor ├── AbstractDeserializationVisitor.php ├── AbstractGenericVisitor.php ├── AbstractSerializationVisitor.php ├── AbstractVisitor.php ├── Csv ├── CsvDeserializationVisitor.php └── CsvSerializationVisitor.php ├── Json ├── JsonDeserializationVisitor.php └── JsonSerializationVisitor.php ├── VisitorInterface.php ├── Xml ├── XmlDeserializationVisitor.php └── XmlSerializationVisitor.php └── Yaml ├── YamlDeserializationVisitor.php └── YamlSerializationVisitor.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### 1.0.0 (2017-02-27) 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017 Eric GELOEN 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | [![Travis Build Status](https://travis-ci.org/egeloen/ivory-serializer.svg?branch=master)](http://travis-ci.org/egeloen/ivory-serializer) 4 | [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/q2htd5scotsvjhox/branch/master?svg=true)](https://ci.appveyor.com/project/egeloen/ivory-serializer/branch/master) 5 | [![Code Coverage](https://scrutinizer-ci.com/g/egeloen/ivory-serializer/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/egeloen/ivory-serializer/?branch=master) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/egeloen/ivory-serializer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/egeloen/ivory-serializer/?branch=master) 7 | [![Dependency Status](http://www.versioneye.com/php/egeloen:serializer/badge.svg)](http://www.versioneye.com/php/egeloen:serializer) 8 | 9 | [![Latest Stable Version](https://poser.pugx.org/egeloen/serializer/v/stable.svg)](https://packagist.org/packages/egeloen/serializer) 10 | [![Latest Unstable Version](https://poser.pugx.org/egeloen/serializer/v/unstable.svg)](https://packagist.org/packages/egeloen/serializer) 11 | [![Total Downloads](https://poser.pugx.org/egeloen/serializer/downloads.svg)](https://packagist.org/packages/egeloen/serializer) 12 | [![License](https://poser.pugx.org/egeloen/serializer/license.svg)](https://packagist.org/packages/egeloen/serializer) 13 | 14 | ## Overview 15 | 16 | The Ivory Serializer is a PHP 5.6+ library allowing you to (de)-serialize complex data using the visitor pattern 17 | recursively on each node of the graph. It supports the CSV, JSON, XML and YAML formats. It also supports features such 18 | as exclusion strategies (groups, max depth, circular reference, version, ...), naming strategies (camel case, snake 19 | case, studly caps), automatic/explicit mapping (reflection, annotation, XML, YAML, JSON) and many others... 20 | 21 | ``` php 22 | use Ivory\Serializer\Format; 23 | use Ivory\Serializer\Serializer; 24 | 25 | $stdClass = new \stdClass(); 26 | $stdClass->foo = true; 27 | $stdClass->bar = ['foo', [123, 432.1]]; 28 | 29 | $serializer = new Serializer(); 30 | 31 | echo $serializer->serialize($stdClass, Format::JSON); 32 | // {"foo": true,"bar": ["foo", [123, 432.1]]} 33 | 34 | $deserialize = $serializer->deserialize($json, \stdClass::class, Format::JSON); 35 | // $deserialize == $stdClass 36 | ``` 37 | 38 | ## Documentation 39 | 40 | - [Installation](/doc/installation.md) 41 | - [Usage](/doc/usage.md) 42 | - [Mapping](/doc/mapping.md) 43 | - [Type](/doc/type.md) 44 | - [Event](/doc/event.md) 45 | - [Visitor](/doc/visitor.md) 46 | - [Context](/doc/context.md) 47 | - [Exclusion strategies](/doc/context.md#exclusion-strategies) 48 | - [Naming strategies](/doc/context.md#naming-strategies) 49 | 50 | ## Testing 51 | 52 | The library is fully unit tested by [PHPUnit](http://www.phpunit.de/) with a code coverage close to **100%**. To 53 | execute the test suite, check the travis [configuration](/.travis.yml). 54 | 55 | ## Contribute 56 | 57 | We love contributors! Ivory is an open source project. If you'd like to contribute, feel free to propose a PR! You 58 | can follow the [CONTRIBUTING](/CONTRIBUTING.md) file which will explain you how to set up the project. 59 | 60 | ## License 61 | 62 | The Ivory Serializer is under the MIT license. For the full copyright and license information, please read the 63 | [LICENSE](/LICENSE) file that was distributed with this source code. 64 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egeloen/serializer", 3 | "description": "Serializer for PHP 5.6+ supporting JSON, XML, YAML & CSV", 4 | "keywords": [ "serializer" ], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Eric GELOEN", 9 | "email": "geloen.eric@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^5.6|^7.0", 14 | "doctrine/instantiator": "^1.0", 15 | "doctrine/lexer": "^1.0", 16 | "symfony/options-resolver": "^2.7|^3.0" 17 | }, 18 | "require-dev": { 19 | "doctrine/annotations": "^1.2", 20 | "ext-json": "*", 21 | "ext-dom": "*", 22 | "ext-simplexml": "*", 23 | "friendsofphp/php-cs-fixer": "^2.0", 24 | "nikic/php-parser": "^1.4", 25 | "phpdocumentor/reflection": "^3.0", 26 | "phpunit/phpunit": "^5.4", 27 | "phpunit/phpunit-mock-objects": "^3.2.3", 28 | "psr/cache": "^1.0", 29 | "symfony/event-dispatcher": "^2.7|^3.0", 30 | "symfony/phpunit-bridge": "^2.7|^3.0", 31 | "symfony/property-info": "^2.7|^3.0", 32 | "symfony/property-access": "^2.7|^3.0", 33 | "symfony/yaml": "^2.7|^3.0" 34 | }, 35 | "suggest": { 36 | "doctrine/annotations": "Allow you to use the mapping annotations.", 37 | "ext-dom": "Allow you to use the XML serialization visitor and mapping.", 38 | "ext-json": "Allow you to use the JSON serialization/deserialization visitors and mapping.", 39 | "ext-simplexml": "Allow you to use the XML deserialization visitor and mapping.", 40 | "phpdocumentor/reflection": "Allow you to parse PHP type more efficiently", 41 | "symfony/event-dispatcher": "Allow you to use the event dispatching system.", 42 | "symfony/property-access": "Allow you to use the Symfony accessor/mutator.", 43 | "symfony/property-info": "Allow you to use the mapping type auto discovery.", 44 | "symfony/yaml": "Allow you to use the YAML serialization/deserialization visitors and mapping." 45 | }, 46 | "autoload": { 47 | "psr-4": { "Ivory\\Serializer\\": "src/" } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { "Ivory\\Tests\\Serializer\\": "tests/" } 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "1.0-dev" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /doc/definition/accessor.md: -------------------------------------------------------------------------------- 1 | # Accessor 2 | 3 | The accessor allows you to configure how to get a property. 4 | 5 | ## Example 6 | 7 | In this example, we configure the username property to use the getUsername method when serializing. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\Accessor("getUsername") 18 | * 19 | * @var string 20 | */ 21 | public $username; 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getUsername() 27 | { 28 | return strtolower($this->username); 29 | } 30 | } 31 | ``` 32 | 33 | ## Usage 34 | 35 | ``` php 36 | use Acme\User; 37 | 38 | $user = new User(); 39 | $user->username = 'GeLo'; 40 | 41 | $serialize = $serializer->serialize($user, $format); 42 | // echo $serialize; 43 | 44 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 45 | // $deserialize->username === 'gelo' 46 | ``` 47 | 48 | ## Results 49 | 50 | ### JSON 51 | 52 | ``` json 53 | { 54 | "username": "gelo" 55 | } 56 | ``` 57 | 58 | ### XML 59 | 60 | ``` xml 61 | 62 | 63 | gelo 64 | 65 | ``` 66 | 67 | ### YAML 68 | 69 | ``` yaml 70 | username: gelo 71 | ``` 72 | 73 | ### CSV 74 | 75 | ``` csv 76 | username 77 | gelo 78 | ``` 79 | 80 | ## Definitions 81 | 82 | If you prefer use an other definition format, the following examples are identical. 83 | 84 | ### XML 85 | 86 | ``` xml 87 | 88 | 89 | 95 | 96 | 97 | 98 | 99 | ``` 100 | 101 | ### YAML 102 | 103 | ``` yaml 104 | Acme\User: 105 | properties: 106 | username: 107 | accessor: getUsername 108 | ``` 109 | 110 | ### JSON 111 | 112 | ``` json 113 | { 114 | "Acme\\User": { 115 | "properties": { 116 | "username": { 117 | "accessor": "getUsername" 118 | } 119 | } 120 | } 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /doc/definition/alias.md: -------------------------------------------------------------------------------- 1 | # Alias 2 | 3 | The alias allows you rename a property. 4 | 5 | ## Example 6 | 7 | In this example, we rename the username property to login. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\Alias("login") 18 | * 19 | * @var string 20 | */ 21 | public $username; 22 | } 23 | ``` 24 | 25 | ## Usage 26 | 27 | ``` php 28 | use Acme\User; 29 | 30 | $user = new User(); 31 | $user->username = 'GeLo'; 32 | 33 | $serialize = $serializer->serialize($user, $format); 34 | // echo $serialize; 35 | 36 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 37 | // $deserialize == $user 38 | ``` 39 | 40 | ## Results 41 | 42 | ### JSON 43 | 44 | ``` json 45 | { 46 | "login": "GeLo" 47 | } 48 | ``` 49 | 50 | ### XML 51 | 52 | ``` xml 53 | 54 | 55 | GeLo 56 | 57 | ``` 58 | 59 | ### YAML 60 | 61 | ``` yaml 62 | login: GeLo 63 | ``` 64 | 65 | ### CSV 66 | 67 | ``` csv 68 | login 69 | GeLo 70 | ``` 71 | 72 | ## Definitions 73 | 74 | If you prefer use an other definition format, the following examples are identical. 75 | 76 | ### XML 77 | 78 | ``` xml 79 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | ``` 92 | 93 | ### YAML 94 | 95 | ``` yaml 96 | Acme\User: 97 | properties: 98 | username: 99 | alias: login 100 | ``` 101 | 102 | ### JSON 103 | 104 | ``` json 105 | { 106 | "Acme\\User": { 107 | "properties": { 108 | "username": { 109 | "alias": "login" 110 | } 111 | } 112 | } 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /doc/definition/groups.md: -------------------------------------------------------------------------------- 1 | # Groups 2 | 3 | The groups allows you include properties based on the runtime groups provided via the context. Be aware that if you 4 | don't configure groups, the serializer will automatically use the group `Default` for your property. 5 | 6 | ## Example 7 | 8 | In this example, we only (de)-serialize the username property for groups: group1 or group2. 9 | 10 | ``` php 11 | namespace Acme; 12 | 13 | use Ivory\Serializer\Mapping\Annotation as Serializer; 14 | 15 | class User 16 | { 17 | /** 18 | * @Serializer\Groups(["group1", "group2"]) 19 | * 20 | * @var string 21 | */ 22 | public $username; 23 | 24 | /** 25 | * @var string|null 26 | */ 27 | publiic $plainPassword; 28 | } 29 | ``` 30 | 31 | ## Usage 32 | 33 | ``` php 34 | use Acme\User; 35 | use Ivory\Serializer\Context\Context; 36 | use Ivory\Serializer\Exclusion\GroupsExclusionStrategy; 37 | 38 | $user = new User(); 39 | $user->username = 'GeLo'; 40 | $user->plainPassword = 'azerty'; 41 | 42 | $context = new Context(); 43 | $context->setExclusionStrategy(new GroupsExclusionStrategy(['group1'])); 44 | 45 | $serialize = $serializer->serialize($user, $format, $context); 46 | // echo $serialize; 47 | 48 | $deserialize = $serializer->deserialize($serialize, User::class, $format, $context); 49 | // $deserialize->username === $user->username 50 | // $deserialize->plainPassword === null 51 | ``` 52 | 53 | **If you don't use the groups exclusion strategy, all properties are (de)-serialized regardless configured groups.** 54 | 55 | ## Results 56 | 57 | ### JSON 58 | 59 | ``` json 60 | { 61 | "username": "GeLo" 62 | } 63 | ``` 64 | 65 | ### XML 66 | 67 | ``` xml 68 | 69 | 70 | GeLo 71 | 72 | ``` 73 | 74 | ### YAML 75 | 76 | ``` yaml 77 | username: GeLo 78 | ``` 79 | 80 | ### CSV 81 | 82 | ``` csv 83 | username 84 | GeLo 85 | ``` 86 | 87 | ## Definitions 88 | 89 | If you prefer use an other definition format, the following examples are identical. 90 | 91 | ### XML 92 | 93 | ``` xml 94 | 95 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | ``` 108 | 109 | ### YAML 110 | 111 | ``` yaml 112 | Acme\User: 113 | properties: 114 | username: 115 | groups: [group1, group2] 116 | plainPassword: ~ 117 | ``` 118 | 119 | ### JSON 120 | 121 | ``` json 122 | { 123 | "Acme\\User": { 124 | "properties": { 125 | "username": { 126 | "groups": ["group1", "group2"] 127 | }, 128 | "plainPassword": {} 129 | } 130 | } 131 | } 132 | ``` 133 | -------------------------------------------------------------------------------- /doc/definition/max_depth.md: -------------------------------------------------------------------------------- 1 | # Max Depth 2 | 3 | The max depth allows you limit the depth of the graph traversal for a property and also limit circular references. 4 | 5 | ## Example 6 | 7 | In this example, we only (de)-serialize one level of friends. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @var string 18 | */ 19 | public $username; 20 | 21 | /** 22 | * @Serializer\MaxDepth(2) 23 | * 24 | * @var User[] 25 | */ 26 | publiic $friends = []; 27 | } 28 | ``` 29 | 30 | ## Usage 31 | 32 | ``` php 33 | use Acme\User; 34 | use Ivory\Serializer\Context\Context; 35 | use Ivory\Serializer\Exclusion\MapDepthExclusionStrategy; 36 | 37 | $friend2 = new User(); 38 | $friend2->username = 'Ben'; 39 | 40 | $friend1 = new User(); 41 | $friend1->username = 'Tim'; 42 | $friend1->friends[] = $friend2; 43 | 44 | $user = new User(); 45 | $user->username = 'GeLo'; 46 | $user->friends[] = $friend1; 47 | 48 | $context = new Context(); 49 | $context->setExclusionStrategy(new MapDepthExclusionStrategy()); 50 | 51 | $serialize = $serializer->serialize($user, $format, $context); 52 | // echo $serialize; 53 | 54 | $deserialize = $serializer->deserialize($serialize, User::class, $format, $context); 55 | // $deserialize->username === $user->username 56 | // $deserialize->plainPassword === null 57 | ``` 58 | 59 | **If you don't use the max depth exclusion strategy, all properties are (de)-serialized regardless configured max 60 | depths.** 61 | 62 | ## Results 63 | 64 | ### JSON 65 | 66 | ``` json 67 | { 68 | "username": "GeLo" 69 | "friends": [ 70 | { 71 | "username": "Tim", 72 | "friends": [] 73 | } 74 | ] 75 | } 76 | ``` 77 | 78 | ### XML 79 | 80 | ``` xml 81 | 82 | 83 | GeLo 84 | 85 | 86 | Tim 87 | 88 | 89 | 90 | 91 | ``` 92 | 93 | ### YAML 94 | 95 | ``` yaml 96 | username: GeLo 97 | friends: 98 | - 99 | username: Tim 100 | friends: [] 101 | ``` 102 | 103 | ### CSV 104 | 105 | ``` csv 106 | username;friends.0.username;friends.0.friends 107 | GeLo;Tim;[] 108 | ``` 109 | 110 | ## Definitions 111 | 112 | If you prefer use an other definition format, the following examples are identical. 113 | 114 | ### XML 115 | 116 | ``` xml 117 | 118 | 119 | 125 | 126 | 127 | 128 | 129 | 130 | ``` 131 | 132 | ### YAML 133 | 134 | ``` yaml 135 | Acme\User: 136 | properties: 137 | username: 138 | groups: [group1, group2] 139 | plainPassword: ~ 140 | ``` 141 | 142 | ### JSON 143 | 144 | ``` json 145 | { 146 | "Acme\\User": { 147 | "properties": { 148 | "username": { 149 | "groups": ["group1", "group2"] 150 | }, 151 | "plainPassword": {} 152 | } 153 | } 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /doc/definition/mutator.md: -------------------------------------------------------------------------------- 1 | # Mutator 2 | 3 | The mutator allows you to configure how to set a property. 4 | 5 | ## Example 6 | 7 | In this example, we configure the username property to use the setUsername method when de-serializing. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\Mutator("setUsername") 18 | * 19 | * @var string 20 | */ 21 | public $username; 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function setUsername($username) 27 | { 28 | $this->username = strtolower($username); 29 | } 30 | } 31 | ``` 32 | 33 | ## Usage 34 | 35 | ``` php 36 | use Acme\User; 37 | 38 | $user = new User(); 39 | $user->username = 'GeLo'; 40 | 41 | $serialize = $serializer->serialize($user, $format); 42 | // echo $serialize; 43 | 44 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 45 | // $deserialize->username === 'gelo' 46 | ``` 47 | 48 | ## Definitions 49 | 50 | If you prefer use an other definition format, the following examples are identical. 51 | 52 | ### XML 53 | 54 | ``` xml 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | ``` 68 | 69 | ### YAML 70 | 71 | ``` yaml 72 | Acme\User: 73 | properties: 74 | username: 75 | mutator: setUsername 76 | ``` 77 | 78 | ### JSON 79 | 80 | ``` json 81 | { 82 | "Acme\\User": { 83 | "properties": { 84 | "username": { 85 | "mutator": "setUsername" 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /doc/definition/order.md: -------------------------------------------------------------------------------- 1 | # Order 2 | 3 | The order allows you to order properties. 4 | 5 | ## Example 6 | 7 | In this example, we configure a custom ordering for the User class. The order supports custom ordering or alphabetical 8 | ordering via `ASC` or `DESC`. 9 | 10 | ### Alphabetical Ordering 11 | 12 | ``` php 13 | namespace Acme; 14 | 15 | use Ivory\Serializer\Mapping\Annotation as Serializer; 16 | 17 | /** 18 | * @Serializer\Order("ASC") 19 | */ 20 | class User 21 | { 22 | /** 23 | * @var string 24 | */ 25 | public $username; 26 | 27 | /** 28 | * @var bool 29 | */ 30 | public $active; 31 | } 32 | ``` 33 | 34 | ### Custom Ordering 35 | 36 | ``` php 37 | namespace Acme; 38 | 39 | use Ivory\Serializer\Mapping\Annotation as Serializer; 40 | 41 | /** 42 | * @Serializer\Order("active,username") 43 | */ 44 | class User 45 | { 46 | /** 47 | * @var string 48 | */ 49 | public $username; 50 | 51 | /** 52 | * @var bool 53 | */ 54 | public $active; 55 | } 56 | ``` 57 | 58 | ## Usage 59 | 60 | ``` php 61 | use Acme\User; 62 | 63 | $user = new User(); 64 | $user->username = 'GeLo'; 65 | user->active = true; 66 | 67 | $serialize = $serializer->serialize($user, $format); 68 | // echo $serialize; 69 | 70 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 71 | // $deserialize == $user 72 | ``` 73 | 74 | ## Results 75 | 76 | ### JSON 77 | 78 | ``` json 79 | { 80 | "active": true, 81 | "username": "GeLo" 82 | } 83 | ``` 84 | 85 | ### XML 86 | 87 | ``` xml 88 | 89 | 90 | true 91 | GeLo 92 | 93 | ``` 94 | 95 | ### YAML 96 | 97 | ``` yaml 98 | active: true 99 | username: GeLo 100 | ``` 101 | 102 | ### CSV 103 | 104 | ``` csv 105 | active;username 106 | true;GeLo 107 | ``` 108 | 109 | ## Definitions 110 | 111 | If you prefer use an other definition format, the following examples are identical. 112 | 113 | ### XML 114 | 115 | #### Alphabetical Ordering 116 | 117 | ``` xml 118 | 119 | 120 | 126 | 127 | 128 | 129 | 130 | 131 | ``` 132 | 133 | #### Custom Ordering 134 | 135 | ``` xml 136 | 137 | 138 | 144 | 145 | 146 | 147 | 148 | 149 | ``` 150 | 151 | ### YAML 152 | 153 | #### Alphabetical Ordering 154 | 155 | ``` yaml 156 | Acme\User: 157 | order: "ASC" 158 | properties: 159 | username: ~ 160 | active: ~ 161 | ``` 162 | 163 | #### Custom Ordering 164 | 165 | ``` yaml 166 | Acme\User: 167 | order: ["active", "username"] 168 | properties: 169 | username: ~ 170 | active: ~ 171 | ``` 172 | 173 | ### JSON 174 | 175 | #### Alphabetical Ordering 176 | 177 | ``` json 178 | { 179 | "Acme\\User": { 180 | "order": "ASC", 181 | "properties": { 182 | "username": {}, 183 | "active": {} 184 | } 185 | } 186 | } 187 | ``` 188 | 189 | #### Custom Ordering 190 | 191 | ``` json 192 | { 193 | "Acme\\User": { 194 | "order": ["active", "username"], 195 | "properties": { 196 | "username": {}, 197 | "active": {} 198 | } 199 | } 200 | } 201 | ``` 202 | -------------------------------------------------------------------------------- /doc/definition/readable.md: -------------------------------------------------------------------------------- 1 | # Readable 2 | 3 | The readable allows you configure if a property is serializable (by default, it is). This property is useful if you 4 | want to not serialize a property but still de-serialize it. 5 | 6 | ## Example 7 | 8 | In this example, we configure the plainPassword property to not be serializable. 9 | 10 | ### Property Readable 11 | 12 | ``` php 13 | namespace Acme; 14 | 15 | use Ivory\Serializer\Mapping\Annotation as Serializer; 16 | 17 | class User 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $username; 23 | 24 | /** 25 | * @Serializer\Readable(false) 26 | * 27 | * @var string|null 28 | */ 29 | public $plainPassword; 30 | } 31 | ``` 32 | 33 | ### Class Readable 34 | 35 | By default, all properties are readable but you can alter this behavior by putting the readable option directly on the 36 | class: 37 | 38 | ``` php 39 | namespace Acme; 40 | 41 | use Ivory\Serializer\Mapping\Annotation as Serializer; 42 | 43 | /** 44 | * @Serializer\Readable(false) 45 | */ 46 | class User 47 | { 48 | /** 49 | * @Serializer\Readable(true) 50 | * 51 | * @var string 52 | */ 53 | public $username; 54 | 55 | /** 56 | * @var string|null 57 | */ 58 | public $plainPassword; 59 | } 60 | ``` 61 | 62 | ## Usage 63 | 64 | ``` php 65 | use Acme\User; 66 | 67 | $user = new User(); 68 | $user->username = 'GeLo'; 69 | user->plainPassword = 'azerty'; 70 | 71 | $serialize = $serializer->serialize($user, $format); 72 | // echo $serialize; 73 | 74 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 75 | // $deserialize->username === $user->username 76 | // $deserialize->plainPassword === null 77 | ``` 78 | 79 | ## Results 80 | 81 | ### JSON 82 | 83 | ``` json 84 | { 85 | "username": "GeLo" 86 | } 87 | ``` 88 | 89 | ### XML 90 | 91 | ``` xml 92 | 93 | 94 | GeLo 95 | 96 | ``` 97 | 98 | ### YAML 99 | 100 | ``` yaml 101 | username: GeLo 102 | ``` 103 | 104 | ### CSV 105 | 106 | ``` csv 107 | username 108 | GeLo 109 | ``` 110 | 111 | ## Definitions 112 | 113 | If you prefer use an other definition format, the following examples are identical. 114 | 115 | ### XML 116 | 117 | #### Property Readable 118 | 119 | ``` xml 120 | 121 | 122 | 128 | 129 | 130 | 131 | 132 | 133 | ``` 134 | 135 | #### Class Readable 136 | 137 | ``` xml 138 | 139 | 140 | 146 | 147 | 148 | 149 | 150 | 151 | ``` 152 | 153 | ### YAML 154 | 155 | #### Property Readable 156 | 157 | ``` yaml 158 | Acme\User: 159 | properties: 160 | username: ~ 161 | plainPassword: 162 | readable: false 163 | ``` 164 | 165 | #### Class Readable 166 | 167 | ``` yaml 168 | Acme\User: 169 | readable: false 170 | properties: 171 | username: 172 | readable: true 173 | plainPassword: ~ 174 | ``` 175 | 176 | ### JSON 177 | 178 | #### Property Readable 179 | 180 | ``` json 181 | { 182 | "Acme\\User": { 183 | "properties": { 184 | "username": {}, 185 | "plainPassword": { 186 | "readable": false 187 | } 188 | } 189 | } 190 | } 191 | ``` 192 | 193 | #### Class Readable 194 | 195 | ``` json 196 | { 197 | "Acme\\User": { 198 | "readable": false, 199 | "properties": { 200 | "username": { 201 | "reaadable": true 202 | }, 203 | "plainPassword": {} 204 | } 205 | } 206 | } 207 | ``` 208 | -------------------------------------------------------------------------------- /doc/definition/type.md: -------------------------------------------------------------------------------- 1 | # Type 2 | 3 | The type allows you to configure a property type. You can find the list of supported types [here](/doc/type.md). 4 | 5 | ## Example 6 | 7 | In this example, we configure the type string on the username property. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\Type("string") 18 | */ 19 | public $username; 20 | } 21 | ``` 22 | 23 | ## Usage 24 | 25 | ``` php 26 | use Acme\User; 27 | 28 | $user = new User(); 29 | $user->username = 'GeLo'; 30 | 31 | $serialize = $serializer->serialize($user, $format); 32 | // echo $serialize; 33 | 34 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 35 | // $deserialize == $user 36 | ``` 37 | 38 | ## Results 39 | 40 | ### JSON 41 | 42 | ``` json 43 | { 44 | "login": "GeLo" 45 | } 46 | ``` 47 | 48 | ### XML 49 | 50 | ``` xml 51 | 52 | 53 | GeLo 54 | 55 | ``` 56 | 57 | ### YAML 58 | 59 | ``` yaml 60 | login: GeLo 61 | ``` 62 | 63 | ### CSV 64 | 65 | ``` csv 66 | login 67 | GeLo 68 | ``` 69 | 70 | ## Definitions 71 | 72 | If you prefer use an other definition format, the following examples are identical. 73 | 74 | ### XML 75 | 76 | ``` xml 77 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | ### YAML 92 | 93 | ``` yaml 94 | Acme\User: 95 | properties: 96 | username: 97 | type: string 98 | ``` 99 | 100 | ### JSON 101 | 102 | ``` json 103 | { 104 | "Acme\\User": { 105 | "properties": { 106 | "username": { 107 | "type": "string" 108 | } 109 | } 110 | } 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /doc/definition/version.md: -------------------------------------------------------------------------------- 1 | # Version 2 | 3 | The since & until allows you include properties based on the context version provided via the context. 4 | 5 | ## Example 6 | 7 | In this example, we only include the username property since the 1.0.0 version and include the plainPassword until the 8 | 2.0.0 version. 9 | 10 | ``` php 11 | namespace Acme; 12 | 13 | use Ivory\Serializer\Mapping\Annotation as Serializer; 14 | 15 | class User 16 | { 17 | /** 18 | * @Serializer\Since("1.0.0") 19 | * 20 | * @var string 21 | */ 22 | public $username; 23 | 24 | /** 25 | * @Serializer\Until("2.0.0") 26 | * 27 | * @var string|null 28 | */ 29 | publiic $plainPassword; 30 | } 31 | ``` 32 | 33 | ## Usage 34 | 35 | ``` php 36 | use Acme\User; 37 | use Ivory\Serializer\Context\Context; 38 | use Ivory\Serializer\Exclusion\VersionExclusionStrategy; 39 | 40 | $user = new User(); 41 | $user->username = 'GeLo'; 42 | $user->plainPassword = 'azerty'; 43 | 44 | $context = new Context(); 45 | $context->setExclusionStrategy(new VersionExclusionStrategy('2.1.0')); 46 | 47 | $serialize = $serializer->serialize($user, $format, $context); 48 | // echo $serialize; 49 | 50 | $deserialize = $serializer->deserialize($serialize, User::class, $format, $context); 51 | // $deserialize->username === $user->username 52 | // $deserialize->plainPassword === null 53 | ``` 54 | 55 | **If you don't use the version exclusion strategy, all properties are (de)-serialized regardless configured versions.** 56 | 57 | ## Results 58 | 59 | ### JSON 60 | 61 | ``` json 62 | { 63 | "username": "GeLo" 64 | } 65 | ``` 66 | 67 | ### XML 68 | 69 | ``` xml 70 | 71 | 72 | GeLo 73 | 74 | ``` 75 | 76 | ### YAML 77 | 78 | ``` yaml 79 | username: GeLo 80 | ``` 81 | 82 | ### CSV 83 | 84 | ``` csv 85 | username 86 | GeLo 87 | ``` 88 | 89 | ## Definitions 90 | 91 | If you prefer use an other definition format, the following examples are identical. 92 | 93 | ### XML 94 | 95 | ``` xml 96 | 97 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | ### YAML 112 | 113 | ``` yaml 114 | Acme\User: 115 | properties: 116 | username: 117 | since: "1.0.0" 118 | plainPassword: 119 | until: "2.0.0" 120 | ``` 121 | 122 | ### JSON 123 | 124 | ``` json 125 | { 126 | "Acme\\User": { 127 | "properties": { 128 | "username": { 129 | "since": "1.0.0" 130 | }, 131 | "plainPassword": { 132 | "until": "2.0.0" 133 | } 134 | } 135 | } 136 | } 137 | ``` 138 | -------------------------------------------------------------------------------- /doc/definition/writable.md: -------------------------------------------------------------------------------- 1 | # Writable 2 | 3 | The writable allows you configure if a property is de-serializable (by default, it is). This property is useful if you 4 | want to not de-serialize a property but still serialize it. 5 | 6 | ## Example 7 | 8 | In this example, we configure the plainPassword property to not be de-serializable. 9 | 10 | ### Property Writable 11 | 12 | ``` php 13 | namespace Acme; 14 | 15 | use Ivory\Serializer\Mapping\Annotation as Serializer; 16 | 17 | class User 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $username; 23 | 24 | /** 25 | * @Serializer\Writable(false) 26 | * 27 | * @var string|null 28 | */ 29 | public $plainPassword; 30 | } 31 | ``` 32 | 33 | ### Class Writable 34 | 35 | By default, all properties are writable but you can alter this behavior by putting the writable option directly on the 36 | class: 37 | 38 | ``` php 39 | namespace Acme; 40 | 41 | use Ivory\Serializer\Mapping\Annotation as Serializer; 42 | 43 | /** 44 | * @Serializer\Writable(false) 45 | */ 46 | class User 47 | { 48 | /** 49 | * @Serializer\Writable(true) 50 | * 51 | * @var string 52 | */ 53 | public $username; 54 | 55 | /** 56 | * @var string|null 57 | */ 58 | public $plainPassword; 59 | } 60 | ``` 61 | 62 | ## Usage 63 | 64 | ``` php 65 | use Acme\User; 66 | 67 | $user = new User(); 68 | $user->username = 'GeLo'; 69 | user->plainPassword = 'azerty'; 70 | 71 | $serialize = $serializer->serialize($user, $format); 72 | // echo $serialize; 73 | 74 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 75 | // $deserialize->username === $user->username 76 | // $deserialize->plainPassword === null 77 | ``` 78 | 79 | ## Definitions 80 | 81 | If you prefer use an other definition format, the following examples are identical. 82 | 83 | ### XML 84 | 85 | #### Property Writable 86 | 87 | ``` xml 88 | 89 | 90 | 96 | 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | #### Class Writable 104 | 105 | ``` xml 106 | 107 | 108 | 114 | 115 | 116 | 117 | 118 | 119 | ``` 120 | 121 | ### YAML 122 | 123 | #### Property Writable 124 | 125 | ``` yaml 126 | Acme\User: 127 | properties: 128 | username: ~ 129 | plainPassword: 130 | writable: false 131 | ``` 132 | 133 | #### Class Writable 134 | 135 | ``` yaml 136 | Acme\User: 137 | writable: false 138 | properties: 139 | username: 140 | writable: true 141 | plainPassword: ~ 142 | ``` 143 | 144 | ### JSON 145 | 146 | #### Property Writable 147 | 148 | ``` json 149 | { 150 | "Acme\\User": { 151 | "properties": { 152 | "username": {}, 153 | "plainPassword": { 154 | "writable": false 155 | } 156 | } 157 | } 158 | } 159 | ``` 160 | 161 | #### Class Writable 162 | 163 | ``` json 164 | { 165 | "Acme\\User": { 166 | "writable": false, 167 | "properties": { 168 | "username": { 169 | "reaadable": true 170 | }, 171 | "plainPassword": {} 172 | } 173 | } 174 | } 175 | ``` 176 | -------------------------------------------------------------------------------- /doc/definition/xml_attribute.md: -------------------------------------------------------------------------------- 1 | # XML Attribute 2 | 3 | The XML attribute is dedicated to XML and allow you to put a property as XML attribute. 4 | 5 | ## Example 6 | 7 | In this example, we configure the username property to be put as XML attribute. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\XmlAttribute 18 | * 19 | * @var string 20 | */ 21 | public $username; 22 | } 23 | ``` 24 | 25 | ## Usage 26 | 27 | ``` php 28 | use Acme\User; 29 | 30 | $user = new User(); 31 | $user->username = 'GeLo'; 32 | 33 | $serialize = $serializer->serialize($user, $format); 34 | // echo $serialize; 35 | 36 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 37 | // $deserialize == $user 38 | ``` 39 | 40 | ## Result 41 | 42 | ``` xml 43 | 44 | 45 | ``` 46 | 47 | ## Definitions 48 | 49 | If you prefer use an other definition format, the following examples are identical. 50 | 51 | ### XML 52 | 53 | ``` xml 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ### YAML 69 | 70 | ``` yaml 71 | Acme\User: 72 | properties: 73 | username: 74 | xml_attribute: true 75 | ``` 76 | 77 | ### JSON 78 | 79 | ``` json 80 | { 81 | "Acme\\User": { 82 | "properties": { 83 | "username": { 84 | "xml_attribute": true 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /doc/definition/xml_collection.md: -------------------------------------------------------------------------------- 1 | # XML Collection 2 | 3 | The XML collection is dedicated to XML and allow you to control how a collection is (de)-serialized in XML. 4 | 5 | ## Example 6 | 7 | In this example, we configure the roles property. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\XmlCollection({ 18 | * entry = "entry", 19 | * entryAttribute = "key", 20 | * keyAsNode = true, 21 | * keyAsAttibute = false, 22 | * inline = false 23 | * }) 24 | * 25 | * @var string[] 26 | */ 27 | public $roles = []; 28 | } 29 | ``` 30 | 31 | ## Usage 32 | 33 | ``` php 34 | use Acme\User; 35 | 36 | $user = new User(); 37 | $user->roles[] = 'reader'; 38 | $user->roles[] = 'writer'; 39 | 40 | $serialize = $serializer->serialize($user, $format); 41 | // echo $serialize; 42 | 43 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 44 | // $deserialize == $user 45 | ``` 46 | 47 | ## Results 48 | 49 | ``` xml 50 | 51 | 52 | 53 | reader 54 | writer 55 | 56 | 57 | ``` 58 | 59 | ### Entry 60 | 61 | The entry option allows you to configure the node name of each entry of the collection (default "entry"). 62 | 63 | ### Entry Attribute 64 | 65 | The entry attribute option allows you to configure the name of each entry attribute of the collection (default "key"). 66 | 67 | ### Key As Node 68 | 69 | The key as node option allows you to configure if each keys of the collection should put the as node. If the key is 70 | not valid, then the key will be put as attribute using the entry attribute option. 71 | 72 | ### Key As Attribute 73 | 74 | The key as attribute option allows you to configure if each keys of the collection should put as attribute using the 75 | entry attribute option. 76 | 77 | ### Inline 78 | 79 | The inline option allows you to configure if the collection should be inlined. 80 | 81 | ``` php 82 | namespace Acme; 83 | 84 | use Ivory\Serializer\Mapping\Annotation as Serializer; 85 | 86 | class User 87 | { 88 | /** 89 | * @Serializer\XmlCollection({ 90 | * entry = "role", 91 | * inline = true 92 | * }) 93 | * 94 | * @var string[] 95 | */ 96 | public $roles = []; 97 | } 98 | ``` 99 | 100 | ``` xml 101 | 102 | 103 | reader 104 | writer 105 | 106 | ``` 107 | 108 | ## Definitions 109 | 110 | If you prefer use an other definition format, the following examples are identical. 111 | 112 | ### XML 113 | 114 | ``` xml 115 | 116 | 117 | 123 | 124 | 132 | 133 | 134 | ``` 135 | 136 | ### YAML 137 | 138 | ``` yaml 139 | Acme\User: 140 | properties: 141 | roles: 142 | xml_entry: entry 143 | xml_entry_attribute: key 144 | xml_key_as_node: true 145 | xml_key_as_attribute: false 146 | xml_inline: false 147 | ``` 148 | 149 | ### JSON 150 | 151 | ``` json 152 | { 153 | "Acme\\User": { 154 | "properties": { 155 | "username": { 156 | "xml_entry": "entry", 157 | "xml_entry_attribute": "key", 158 | "xml_key_as_node": true, 159 | "xml_key_as_attribute": false, 160 | "xml_inline": false 161 | } 162 | } 163 | } 164 | } 165 | ``` 166 | 167 | 168 | -------------------------------------------------------------------------------- /doc/definition/xml_root.md: -------------------------------------------------------------------------------- 1 | # XML Root 2 | 3 | The XML root is dedicated to XML and allows you to configure the top XML root node name. 4 | 5 | ## Example 6 | 7 | In this example, we configure the XML top root node name for the User class. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | /** 15 | * @Serializer\XmlRoot("user") 16 | */ 17 | class User 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $username; 23 | } 24 | ``` 25 | 26 | **The XML root node is only used for the TOP root node and is not used for collection. If you want to configure the 27 | collection entry, use the [`XmlCollection`](/doc/definition/xml_collection.md) instead.** 28 | 29 | ## Usage 30 | 31 | ``` php 32 | use Acme\User; 33 | 34 | $user = new User(); 35 | $user->username = 'GeLo'; 36 | 37 | $serialize = $serializer->serialize($user, $format); 38 | // echo $serialize; 39 | 40 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 41 | // $deserialize == $user 42 | ``` 43 | 44 | ## Result 45 | 46 | ``` xml 47 | 48 | 49 | GeLo 50 | 51 | ``` 52 | 53 | ## Definitions 54 | 55 | If you prefer use an other definition format, the following examples are identical. 56 | 57 | ### XML 58 | 59 | ``` xml 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | ### YAML 75 | 76 | ``` yaml 77 | Acme\User: 78 | xml_root: user 79 | properties: 80 | username: ~ 81 | ``` 82 | 83 | ### JSON 84 | 85 | ``` json 86 | { 87 | "Acme\\User": { 88 | "xml_root": "user", 89 | "properties": { 90 | "username": {} 91 | } 92 | } 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /doc/definition/xml_value.md: -------------------------------------------------------------------------------- 1 | # XML Value 2 | 3 | The XML value is dedicated to XML and allow you to put a property as XML value. 4 | 5 | ## Example 6 | 7 | In this example, we configure the username property to be put as XML value. 8 | 9 | ``` php 10 | namespace Acme; 11 | 12 | use Ivory\Serializer\Mapping\Annotation as Serializer; 13 | 14 | class User 15 | { 16 | /** 17 | * @Serializer\XmlValue 18 | * 19 | * @var string 20 | */ 21 | public $username; 22 | } 23 | ``` 24 | 25 | ## Usage 26 | 27 | ``` php 28 | use Acme\User; 29 | 30 | $user = new User(); 31 | $user->username = 'GeLo'; 32 | 33 | $serialize = $serializer->serialize($user, $format); 34 | // echo $serialize; 35 | 36 | $deserialize = $serializer->deserialize($serialize, User::class, $format); 37 | // $deserialize == $user 38 | ``` 39 | 40 | ## Result 41 | 42 | ``` xml 43 | 44 | GeLo 45 | ``` 46 | 47 | ## Definitions 48 | 49 | If you prefer use an other definition format, the following examples are identical. 50 | 51 | ### XML 52 | 53 | ``` xml 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ### YAML 69 | 70 | ``` yaml 71 | Acme\User: 72 | properties: 73 | username: 74 | xml_value: true 75 | ``` 76 | 77 | ### JSON 78 | 79 | ``` json 80 | { 81 | "Acme\\User": { 82 | "properties": { 83 | "username": { 84 | "xml_value": true 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /doc/docker.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | The most easy way to set up the project is to install [Docker](https://www.docker.com) and 4 | [Docker Composer](https://docs.docker.com/compose/) and build the project. 5 | 6 | ## Configure 7 | 8 | The configuration is shipped with a distribution environment file allowing you to customize your IDE and XDebug 9 | settings as well as your current user/group ID: 10 | 11 | ``` bash 12 | $ cp .env.dist .env 13 | ``` 14 | 15 | **The most important part is the `USER_ID` and `GROUP_ID` which should match your current user/group.** 16 | 17 | ## Build 18 | 19 | Once you have configured your environment, you can build the project: 20 | 21 | ``` bash 22 | $ docker-compose build 23 | ``` 24 | 25 | ## Composer 26 | 27 | Install the dependencies via [Composer](https://getcomposer.org/): 28 | 29 | ``` bash 30 | $ docker-compose run --rm php composer install 31 | ``` 32 | 33 | ## Tests 34 | 35 | To run the test suite, you can use: 36 | 37 | ``` bash 38 | $ docker-compose run --rm php vendor/bin/phpunit 39 | ``` 40 | 41 | If you want to run the test suite against [HHVM](http://hhvm.com/), you can use: 42 | 43 | ``` bash 44 | $ docker-compose run --rm hhvm vendor/bin/phpunit 45 | ``` 46 | 47 | ## XDebug 48 | 49 | If you want to use XDebug, make sure you have fully configured your `.env` file and use: 50 | 51 | ``` bash 52 | $ docker-compose run --rm -e XDEBUG=1 php vendor/bin/phpunit 53 | ``` 54 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | To install the Ivory Serializer library, you will need [Composer](http://getcomposer.org). It's a PHP 5.3+ dependency 4 | manager which allows you to declare the dependent libraries your project needs and it will install & autoload them for 5 | you. 6 | 7 | ## Set up Composer 8 | 9 | Composer comes with a simple phar file. To easily access it from anywhere on your system, you can execute: 10 | 11 | ``` bash 12 | $ curl -s https://getcomposer.org/installer | php 13 | $ sudo mv composer.phar /usr/local/bin/composer 14 | ``` 15 | 16 | ## Download the library 17 | 18 | Require the library in your `composer.json` file: 19 | 20 | ``` bash 21 | $ composer require egeloen/serializer 22 | ``` 23 | 24 | ## Download additional libraries (optional) 25 | 26 | The Ivory Serializer relies on third libraries in order to not reinvent the wheel... 27 | 28 | ### Doctrine annotations 29 | 30 | The Doctrine annotations library allows you to use the annotations mapping on your classes. 31 | 32 | ``` bash 33 | $ composer require doctrine/annotations 34 | ``` 35 | 36 | ### Symfony event dispatcher 37 | 38 | The Symfony event dispatcher library allows you to hook into the (de)-serialization. 39 | 40 | ``` bash 41 | $ composer require symfony/event-dispatcher 42 | ``` 43 | 44 | ### Symfony property info 45 | 46 | The Symfony property info library allows you to get a more efficient mapping discovery. 47 | 48 | ``` bash 49 | $ composer require symfony/property-info 50 | ``` 51 | 52 | ### Symfony property access 53 | 54 | The Symfony property access library allows you to get/set values from your objects. 55 | 56 | ``` bash 57 | $ composer require symfony/property-access 58 | ``` 59 | 60 | ### Symfony yaml 61 | 62 | The Symfony yaml library allows you to use the yaml format in your mapping or your (de)-serialization. 63 | 64 | ``` bash 65 | $ composer require symfony/yaml 66 | ``` 67 | 68 | ## Autoload 69 | 70 | So easy, you just have to require the generated autoload file and you are already ready to play: 71 | 72 | ``` php 73 | serialize(new AcmeObject(), Format::JSON); 21 | ``` 22 | 23 | The `serialize` method also accepts a context as third argument which is useful if you want to exclude properties 24 | according to groups or API version. If you want to learn more about it, you can read this 25 | [documentation](/doc/context.md). 26 | 27 | ``` php 28 | use Acme\Model\AcmeObject; 29 | use Ivory\Serializer\Context\Context; 30 | use Ivory\Serializer\Format; 31 | 32 | $context = new Context(); 33 | // Configure the context... 34 | 35 | $json = $serializer->serialize( 36 | new AcmeObject(), 37 | Format::JSON, 38 | $context 39 | ); 40 | ``` 41 | 42 | ## Deserialization 43 | 44 | For deserializing data, call `deserialize` with your data as first argument, your type as second argument and your 45 | format as third argument: 46 | 47 | ``` php 48 | use Acme\Model\AcmeObject; 49 | use Ivory\Serializer\Format; 50 | 51 | $object = $serializer->deserialize('{"foo":"bar"}', AcmeObject::class, Format::JSON); 52 | ``` 53 | 54 | The `deserialize` method also accepts a context as fourth argument which is useful if you want to exclude properties 55 | according to groups or API version. If you want to learn more about it, you can read this 56 | [documentation](/doc/context.md). 57 | 58 | ``` php 59 | use Acme\Model\AcmeObject; 60 | use Ivory\Serializer\Context\Context; 61 | use Ivory\Serializer\Format; 62 | 63 | $context = new Context(); 64 | // Configure the context... 65 | 66 | $object = $serializer->deserialize( 67 | '{"foo":"bar"}', 68 | AcmeObject::class, 69 | Format::JSON, 70 | $context 71 | ); 72 | ``` 73 | -------------------------------------------------------------------------------- /doc/visitor.md: -------------------------------------------------------------------------------- 1 | # Visitor 2 | 3 | When you (de)-serialize your data, the serializer will choose a visitor according to your format (csv, json, ...) and 4 | your direction (serialization or deserialization). Each format/direction have a dedicated visitor in order to 5 | handle this specific use case. 6 | 7 | ## Built-in 8 | 9 | The library is shipped with some built-in visitors: 10 | 11 | | Name | Serialization | Deserialization | 12 | | ---- | ------------------------ | -------------------------- | 13 | | CSV | CsvSerializationVisitor | CsvDeserializationVisitor | 14 | | JSON | JsonSerializationVisitor | JsonDeserializationVisitor | 15 | | XML | XmlSerializationVisitor | XmlDeserializationVisitor | 16 | | YAML | YamlSerializationVisitor | YamlDeserializationVisitor | 17 | 18 | ## Custom 19 | 20 | If you want to create your own visitor for a new format for example, you can implement the 21 | `Ivory\Serializer\Visitor\VisitorInterface` or extend the `Ivory\Serializer\Visitor\AbstractSerializationVisitor` 22 | for serializing or the `Ivory\Serializer\Visitor\AbstractDeserializationVisitor` for deserializing. 23 | 24 | **Implementing a visitor is a tedious work and require to master the internal of the library...** 25 | 26 | Once you have created your visitor, you need to register it on the Serializer in order to use it: 27 | 28 | ``` php 29 | use Acme\Serializer\Visitor\CustomDeserializationVisitor; 30 | use Acme\Serializer\Visitor\CustomSerializationVisitor; 31 | use Ivory\Serializer\Direction; 32 | use Ivory\Serializer\Serializer; 33 | use Ivory\Serializer\Visitor\VisitorRegistry; 34 | 35 | $format = 'custom'; 36 | 37 | $visitorRegistry = VisitorRegistry::create([ 38 | Direction::SERIALIZATION => [ 39 | $format => new CustomSerializationVisitor(), 40 | ], 41 | Direction::DESERIALIZATION => [ 42 | $format => new CustomDeserializationVisitor(), 43 | ], 44 | ]); 45 | 46 | $serializer = new Serializer(null, $visitorRegistry); 47 | 48 | $stdClass = new \stdClass(); 49 | $stdClass->foo = true; 50 | $stdClass->bar = ['foo', [123, 432.1]]; 51 | 52 | echo $serializer->serialize($stdClass, $format); 53 | ``` 54 | -------------------------------------------------------------------------------- /src/Accessor/AccessorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Accessor; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface AccessorInterface 18 | { 19 | /** 20 | * @param object $object 21 | * @param string $property 22 | * 23 | * @return mixed 24 | */ 25 | public function getValue($object, $property); 26 | } 27 | -------------------------------------------------------------------------------- /src/Accessor/ReflectionAccessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Accessor; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class ReflectionAccessor implements AccessorInterface 18 | { 19 | /** 20 | * @var \ReflectionProperty[] 21 | */ 22 | private $properties = []; 23 | 24 | /** 25 | * @var \ReflectionMethod[] 26 | */ 27 | private $methods = []; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getValue($object, $property) 33 | { 34 | if (property_exists($object, $property)) { 35 | return $this->getReflectionProperty($object, $property)->getValue($object); 36 | } 37 | 38 | return $this->getMethodValue($object, $property); 39 | } 40 | 41 | /** 42 | * @param object $object 43 | * @param string $property 44 | * 45 | * @return mixed 46 | */ 47 | private function getMethodValue($object, $property) 48 | { 49 | $methods = [$property]; 50 | 51 | if (method_exists($object, $property)) { 52 | return $this->getReflectionMethod($object, $property)->invoke($object); 53 | } 54 | 55 | $methodSuffix = ucfirst($property); 56 | 57 | foreach (['get', 'has', 'is'] as $methodPrefix) { 58 | $methods[] = $method = $methodPrefix.$methodSuffix; 59 | 60 | if (method_exists($object, $method)) { 61 | return $this->getReflectionMethod($object, $method)->invoke($object); 62 | } 63 | } 64 | 65 | throw new \InvalidArgumentException(sprintf( 66 | 'The property "%s" or methods %s don\'t exist on class "%s".', 67 | $property, 68 | '"'.implode('", "', $methods).'"', 69 | get_class($object) 70 | )); 71 | } 72 | 73 | /** 74 | * @param object $object 75 | * @param string $property 76 | * 77 | * @return \ReflectionProperty 78 | */ 79 | private function getReflectionProperty($object, $property) 80 | { 81 | if (isset($this->properties[$key = $this->getCacheKey($object, $property)])) { 82 | return $this->properties[$key]; 83 | } 84 | 85 | $reflection = new \ReflectionProperty($object, $property); 86 | $reflection->setAccessible(true); 87 | 88 | return $this->properties[$key] = $reflection; 89 | } 90 | 91 | /** 92 | * @param object $object 93 | * @param string $method 94 | * 95 | * @return \ReflectionMethod 96 | */ 97 | private function getReflectionMethod($object, $method) 98 | { 99 | if (isset($this->methods[$key = $this->getCacheKey($object, $method)])) { 100 | return $this->methods[$key]; 101 | } 102 | 103 | $reflection = new \ReflectionMethod($object, $method); 104 | $reflection->setAccessible(true); 105 | 106 | return $this->methods[$key] = $reflection; 107 | } 108 | 109 | /** 110 | * @param object $object 111 | * @param string $key 112 | * 113 | * @return string 114 | */ 115 | private function getCacheKey($object, $key) 116 | { 117 | return get_class($object).'::'.$key; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Accessor/SymfonyAccessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Accessor; 13 | 14 | use Symfony\Component\PropertyAccess\PropertyAccess; 15 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class SymfonyAccessor implements AccessorInterface 21 | { 22 | /** 23 | * @var PropertyAccessorInterface 24 | */ 25 | private $propertyAccessor; 26 | 27 | /** 28 | * @param PropertyAccessorInterface|null $propertyAccessor 29 | */ 30 | public function __construct(PropertyAccessorInterface $propertyAccessor = null) 31 | { 32 | $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getValue($object, $property) 39 | { 40 | return $this->propertyAccessor->getValue($object, $property); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Direction.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | final class Direction 18 | { 19 | const SERIALIZATION = 1; 20 | const DESERIALIZATION = 2; 21 | 22 | /** 23 | * @codeCoverageIgnore 24 | */ 25 | private function __construct() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Event/AbstractClassMetadataEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | use Symfony\Component\EventDispatcher\Event; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class AbstractClassMetadataEvent extends Event 21 | { 22 | /** 23 | * @var ClassMetadataInterface|null 24 | */ 25 | protected $classMetadata; 26 | 27 | /** 28 | * @return ClassMetadataInterface 29 | */ 30 | public function getClassMetadata() 31 | { 32 | return $this->classMetadata; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Event/AbstractEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | use Symfony\Component\EventDispatcher\Event; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | abstract class AbstractEvent extends Event 22 | { 23 | /** 24 | * @var mixed 25 | */ 26 | protected $data; 27 | 28 | /** 29 | * @var TypeMetadataInterface 30 | */ 31 | protected $type; 32 | 33 | /** 34 | * @var ContextInterface 35 | */ 36 | private $context; 37 | 38 | /** 39 | * @param mixed $data 40 | * @param TypeMetadataInterface $type 41 | * @param ContextInterface $context 42 | */ 43 | public function __construct($data, TypeMetadataInterface $type, ContextInterface $context) 44 | { 45 | $this->data = $data; 46 | $this->type = $type; 47 | $this->context = $context; 48 | } 49 | 50 | /** 51 | * @return mixed 52 | */ 53 | public function getData() 54 | { 55 | return $this->data; 56 | } 57 | 58 | /** 59 | * @return TypeMetadataInterface 60 | */ 61 | public function getType() 62 | { 63 | return $this->type; 64 | } 65 | 66 | /** 67 | * @return ContextInterface 68 | */ 69 | public function getContext() 70 | { 71 | return $this->context; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Event/AbstractPreEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | abstract class AbstractPreEvent extends AbstractEvent 20 | { 21 | /** 22 | * @param mixed $data 23 | */ 24 | public function setData($data) 25 | { 26 | $this->data = $data; 27 | } 28 | 29 | /** 30 | * @param TypeMetadataInterface $type 31 | */ 32 | public function setType(TypeMetadataInterface $type) 33 | { 34 | $this->type = $type; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Event/ClassMetadataLoadEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class ClassMetadataLoadEvent extends AbstractClassMetadataEvent 20 | { 21 | /** 22 | * @param ClassMetadataInterface $classMetadata 23 | */ 24 | public function __construct(ClassMetadataInterface $classMetadata) 25 | { 26 | $this->classMetadata = $classMetadata; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Event/ClassMetadataNotFoundEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class ClassMetadataNotFoundEvent extends AbstractClassMetadataEvent 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $class; 25 | 26 | /** 27 | * @param string $class 28 | */ 29 | public function __construct($class) 30 | { 31 | $this->class = $class; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getClass() 38 | { 39 | return $this->class; 40 | } 41 | 42 | /** 43 | * @param ClassMetadataInterface $classMetadata 44 | */ 45 | public function setClassMetadata(ClassMetadataInterface $classMetadata) 46 | { 47 | $this->classMetadata = $classMetadata; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Event/PostDeserializeEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class PostDeserializeEvent extends AbstractEvent 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Event/PostSerializeEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class PostSerializeEvent extends AbstractEvent 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Event/PreDeserializeEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class PreDeserializeEvent extends AbstractPreEvent 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Event/PreSerializeEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class PreSerializeEvent extends AbstractPreEvent 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Event/SerializerEvents.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Event; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | final class SerializerEvents 18 | { 19 | const CLASS_METADATA_LOAD = 'serializer.class_metadata.load'; 20 | const CLASS_METADATA_NOT_FOUND = 'serializer.class_metadata.not_found'; 21 | 22 | const PRE_SERIALIZE = 'serializer.pre_serialize'; 23 | const POST_SERIALIZE = 'serializer.post_serialize'; 24 | 25 | const PRE_DESERIALIZE = 'serializer.pre_deserialize'; 26 | const POST_DESERIALIZE = 'serializer.post_deserialize'; 27 | 28 | private function __construct() 29 | { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Exclusion/ChainExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class ChainExclusionStrategy implements ExclusionStrategyInterface 23 | { 24 | /** 25 | * @var ExclusionStrategyInterface[] 26 | */ 27 | private $strategies = []; 28 | 29 | /** 30 | * @param ExclusionStrategyInterface[] $strategies 31 | */ 32 | public function __construct(array $strategies) 33 | { 34 | $this->strategies = $strategies; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function skipClass(ClassMetadataInterface $class, ContextInterface $context) 41 | { 42 | foreach ($this->strategies as $strategy) { 43 | if ($strategy->skipClass($class, $context)) { 44 | return true; 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function skipProperty(PropertyMetadataInterface $property, ContextInterface $context) 55 | { 56 | foreach ($this->strategies as $strategy) { 57 | if ($strategy->skipProperty($property, $context)) { 58 | return true; 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function skipType(TypeMetadataInterface $type, ContextInterface $context) 69 | { 70 | foreach ($this->strategies as $strategy) { 71 | if ($strategy->skipType($type, $context)) { 72 | return true; 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Exclusion/ExclusionPolicy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | final class ExclusionPolicy 18 | { 19 | const ALL = 'all'; 20 | const NONE = 'none'; 21 | 22 | /** 23 | * @codeCoverageIgnore 24 | */ 25 | private function __construct() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Exclusion/ExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class ExclusionStrategy implements ExclusionStrategyInterface 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function skipClass(ClassMetadataInterface $class, ContextInterface $context) 28 | { 29 | return false; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function skipProperty(PropertyMetadataInterface $property, ContextInterface $context) 36 | { 37 | return false; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function skipType(TypeMetadataInterface $type, ContextInterface $context) 44 | { 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exclusion/ExclusionStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | interface ExclusionStrategyInterface 23 | { 24 | /** 25 | * @param ClassMetadataInterface $class 26 | * @param ContextInterface $context 27 | * 28 | * @return bool 29 | */ 30 | public function skipClass(ClassMetadataInterface $class, ContextInterface $context); 31 | 32 | /** 33 | * @param PropertyMetadataInterface $property 34 | * @param ContextInterface $context 35 | * 36 | * @return bool 37 | */ 38 | public function skipProperty(PropertyMetadataInterface $property, ContextInterface $context); 39 | 40 | /** 41 | * @param TypeMetadataInterface $type 42 | * @param ContextInterface $context 43 | * 44 | * @return bool 45 | */ 46 | public function skipType(TypeMetadataInterface $type, ContextInterface $context); 47 | } 48 | -------------------------------------------------------------------------------- /src/Exclusion/GroupsExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class GroupsExclusionStrategy extends ExclusionStrategy 21 | { 22 | const GROUP_DEFAULT = 'Default'; 23 | 24 | /** 25 | * @var string[] 26 | */ 27 | private $groups; 28 | 29 | /** 30 | * @param string[] $groups 31 | */ 32 | public function __construct(array $groups) 33 | { 34 | $this->groups = $groups; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function skipProperty(PropertyMetadataInterface $property, ContextInterface $context) 41 | { 42 | if (!$property->hasGroups()) { 43 | return !in_array(self::GROUP_DEFAULT, $this->groups, true); 44 | } 45 | 46 | foreach ($this->groups as $group) { 47 | if ($property->hasGroup($group)) { 48 | return false; 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Exclusion/MaxDepthExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class MaxDepthExclusionStrategy extends ExclusionStrategy 23 | { 24 | /** 25 | * @var int 26 | */ 27 | private $circularReferenceLimit; 28 | 29 | /** 30 | * @param int $circularReferenceLimit 31 | */ 32 | public function __construct($circularReferenceLimit = 1) 33 | { 34 | $this->circularReferenceLimit = $circularReferenceLimit; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function skipClass(ClassMetadataInterface $class, ContextInterface $context) 41 | { 42 | return $this->skip($context); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function skipType(TypeMetadataInterface $type, ContextInterface $context) 49 | { 50 | return $this->skip($context); 51 | } 52 | 53 | /** 54 | * @param ContextInterface $context 55 | * 56 | * @return bool 57 | */ 58 | private function skip(ContextInterface $context) 59 | { 60 | $dataStack = $context->getDataStack(); 61 | $metadataStack = $context->getMetadataStack(); 62 | $references = []; 63 | $depth = 0; 64 | 65 | for ($index = count($metadataStack) - 1; $index >= 0; --$index) { 66 | $metadata = $metadataStack[$index]; 67 | $data = $dataStack[$index]; 68 | 69 | if ($metadata instanceof ClassMetadataInterface) { 70 | $hash = spl_object_hash($data); 71 | 72 | if (!isset($references[$hash])) { 73 | $references[$hash] = 0; 74 | } 75 | 76 | if (++$references[$hash] > $this->circularReferenceLimit) { 77 | return true; 78 | } 79 | } 80 | 81 | if ($metadata instanceof TypeMetadataInterface) { 82 | ++$depth; 83 | } 84 | 85 | if (!$metadata instanceof PropertyMetadataInterface) { 86 | continue; 87 | } 88 | 89 | ++$depth; 90 | 91 | if (!$metadata->hasMaxDepth()) { 92 | continue; 93 | } 94 | 95 | if ($depth > $metadata->getMaxDepth()) { 96 | return true; 97 | } 98 | } 99 | 100 | return false; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Exclusion/VersionExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Exclusion; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class VersionExclusionStrategy extends ExclusionStrategy 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $version; 26 | 27 | /** 28 | * @param string $version 29 | */ 30 | public function __construct($version) 31 | { 32 | $this->version = $version; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function skipProperty(PropertyMetadataInterface $property, ContextInterface $context) 39 | { 40 | if ($property->hasSinceVersion() && version_compare($property->getSinceVersion(), $this->version, '>')) { 41 | return true; 42 | } 43 | 44 | if ($property->hasUntilVersion() && version_compare($property->getUntilVersion(), $this->version, '<')) { 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Format.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | final class Format 18 | { 19 | const CSV = 'csv'; 20 | const JSON = 'json'; 21 | const XML = 'xml'; 22 | const YAML = 'yaml'; 23 | 24 | /** 25 | * @codeCoverageIgnore 26 | */ 27 | private function __construct() 28 | { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Instantiator/DoctrineInstantiator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Instantiator; 13 | 14 | use Doctrine\Instantiator\Instantiator; 15 | use Doctrine\Instantiator\InstantiatorInterface as DoctrineInstantiatorInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class DoctrineInstantiator implements InstantiatorInterface 21 | { 22 | /** 23 | * @var DoctrineInstantiatorInterface 24 | */ 25 | private $instantiator; 26 | 27 | /** 28 | * @param DoctrineInstantiatorInterface|null $instantiator 29 | */ 30 | public function __construct(DoctrineInstantiatorInterface $instantiator = null) 31 | { 32 | $this->instantiator = $instantiator ?: new Instantiator(); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function instantiate($class) 39 | { 40 | return $this->instantiator->instantiate($class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Instantiator/InstantiatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Instantiator; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface InstantiatorInterface 18 | { 19 | /** 20 | * @param string $class 21 | * 22 | * @return object 23 | */ 24 | public function instantiate($class); 25 | } 26 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Accessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Accessor 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $accessor; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Alias.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Alias 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $alias; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Exclude.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Exclude 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/ExclusionPolicy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"CLASS"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class ExclusionPolicy 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $policy; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Expose.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Expose 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Groups.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Groups 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string[] 26 | */ 27 | public $groups; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/MaxDepth.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class MaxDepth 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var int 26 | */ 27 | public $maxDepth; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Mutator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Mutator 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $mutator; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Order.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"CLASS"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Order 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string|string[] 26 | */ 27 | public $order; 28 | 29 | /** 30 | * @param mixed[] $data 31 | */ 32 | public function __construct(array $data) 33 | { 34 | $this->order = $data['value']; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Readable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"CLASS", "PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Readable 21 | { 22 | /** 23 | * @var bool 24 | */ 25 | public $readable = true; 26 | } 27 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Since.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Since 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $version; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Type.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Type 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $type; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Until.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Until 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $version; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/Writable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"CLASS", "PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class Writable 21 | { 22 | /** 23 | * @var bool 24 | */ 25 | public $writable = true; 26 | } 27 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/XmlAttribute.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class XmlAttribute 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/XmlCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class XmlCollection 21 | { 22 | /** 23 | * @var string 24 | */ 25 | public $entry; 26 | 27 | /** 28 | * @var string 29 | */ 30 | public $entryAttribute; 31 | 32 | /** 33 | * @var bool 34 | */ 35 | public $keyAsAttribute; 36 | 37 | /** 38 | * @var bool 39 | */ 40 | public $keyAsNode; 41 | 42 | /** 43 | * @var bool 44 | */ 45 | public $inline; 46 | } 47 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/XmlRoot.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"CLASS"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class XmlRoot 21 | { 22 | /** 23 | * @Required 24 | * 25 | * @var string 26 | */ 27 | public $name; 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Annotation/XmlValue.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Annotation; 13 | 14 | /** 15 | * @Annotation 16 | * @Target({"PROPERTY", "METHOD"}) 17 | * 18 | * @author GeLo 19 | */ 20 | class XmlValue 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Mapping/ClassMetadata.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class ClassMetadata implements ClassMetadataInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $name; 23 | 24 | /** 25 | * @var PropertyMetadataInterface[] 26 | */ 27 | private $properties = []; 28 | 29 | /** 30 | * @var string|null 31 | */ 32 | private $xmlRoot; 33 | 34 | /** 35 | * @param string $name 36 | */ 37 | public function __construct($name) 38 | { 39 | $this->setName($name); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getName() 46 | { 47 | return $this->name; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function setName($name) 54 | { 55 | if (!class_exists($name)) { 56 | throw new \InvalidArgumentException(sprintf('The class "%s" does not exist.', $name)); 57 | } 58 | 59 | $this->name = $name; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function hasProperties() 66 | { 67 | return !empty($this->properties); 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function getProperties() 74 | { 75 | return $this->properties; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function setProperties(array $properties) 82 | { 83 | $this->properties = []; 84 | 85 | foreach ($properties as $property) { 86 | $this->addProperty($property); 87 | } 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function hasProperty($name) 94 | { 95 | return isset($this->properties[$name]); 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function getProperty($name) 102 | { 103 | return $this->hasProperty($name) ? $this->properties[$name] : null; 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function addProperty(PropertyMetadataInterface $property) 110 | { 111 | $name = $property->getName(); 112 | 113 | if ($this->hasProperty($name)) { 114 | $this->getProperty($name)->merge($property); 115 | } else { 116 | $this->properties[$name] = $property; 117 | } 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function removeProperty($name) 124 | { 125 | unset($this->properties[$name]); 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function hasXmlRoot() 132 | { 133 | return $this->xmlRoot !== null; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function getXmlRoot() 140 | { 141 | return $this->xmlRoot; 142 | } 143 | 144 | /** 145 | * {@inheritdoc} 146 | */ 147 | public function setXmlRoot($xmlRoot) 148 | { 149 | $this->xmlRoot = $xmlRoot; 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function merge(ClassMetadataInterface $classMetadata) 156 | { 157 | if ($classMetadata->hasXmlRoot()) { 158 | $this->setXmlRoot($classMetadata->getXmlRoot()); 159 | } 160 | 161 | foreach ($classMetadata->getProperties() as $property) { 162 | $this->addProperty($property); 163 | } 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function serialize() 170 | { 171 | return serialize([ 172 | $this->name, 173 | $this->properties, 174 | $this->xmlRoot, 175 | ]); 176 | } 177 | 178 | /** 179 | * {@inheritdoc} 180 | */ 181 | public function unserialize($serialized) 182 | { 183 | list( 184 | $this->name, 185 | $this->properties, 186 | $this->xmlRoot 187 | ) = unserialize($serialized); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/Mapping/ClassMetadataInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface ClassMetadataInterface extends MetadataInterface 18 | { 19 | /** 20 | * @return bool 21 | */ 22 | public function hasProperties(); 23 | 24 | /** 25 | * @return PropertyMetadataInterface[] 26 | */ 27 | public function getProperties(); 28 | 29 | /** 30 | * @param PropertyMetadataInterface[] $properties 31 | */ 32 | public function setProperties(array $properties); 33 | 34 | /** 35 | * @param string $name 36 | * 37 | * @return bool 38 | */ 39 | public function hasProperty($name); 40 | 41 | /** 42 | * @param string $name 43 | * 44 | * @return PropertyMetadataInterface|null 45 | */ 46 | public function getProperty($name); 47 | 48 | /** 49 | * @param PropertyMetadataInterface $property 50 | */ 51 | public function addProperty(PropertyMetadataInterface $property); 52 | 53 | /** 54 | * @param string $name 55 | */ 56 | public function removeProperty($name); 57 | 58 | /** 59 | * @return bool 60 | */ 61 | public function hasXmlRoot(); 62 | 63 | /** 64 | * @return string|null 65 | */ 66 | public function getXmlRoot(); 67 | 68 | /** 69 | * @param string|null $xmlRoot 70 | */ 71 | public function setXmlRoot($xmlRoot); 72 | 73 | /** 74 | * @param ClassMetadataInterface $classMetadata 75 | */ 76 | public function merge(ClassMetadataInterface $classMetadata); 77 | } 78 | -------------------------------------------------------------------------------- /src/Mapping/Factory/AbstractClassMetadataFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Factory; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | abstract class AbstractClassMetadataFactory implements ClassMetadataFactoryInterface 20 | { 21 | /** 22 | * @var ClassMetadataInterface[] 23 | */ 24 | private $classMetadatas = []; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getClassMetadata($class) 30 | { 31 | return array_key_exists($class, $this->classMetadatas) 32 | ? $this->classMetadatas[$class] 33 | : $this->classMetadatas[$class] = $this->fetchClassMetadata($class); 34 | } 35 | 36 | /** 37 | * @param string $class 38 | * 39 | * @return ClassMetadataInterface|null 40 | */ 41 | abstract protected function fetchClassMetadata($class); 42 | } 43 | -------------------------------------------------------------------------------- /src/Mapping/Factory/CacheClassMetadataFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Factory; 13 | 14 | use Psr\Cache\CacheItemPoolInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class CacheClassMetadataFactory extends AbstractClassMetadataFactory 20 | { 21 | /** 22 | * @var ClassMetadataFactoryInterface 23 | */ 24 | private $factory; 25 | 26 | /** 27 | * @var CacheItemPoolInterface 28 | */ 29 | private $pool; 30 | 31 | /** 32 | * @var string|null 33 | */ 34 | private $prefix; 35 | 36 | /** 37 | * @param ClassMetadataFactoryInterface $factory 38 | * @param CacheItemPoolInterface $pool 39 | * @param string|null $prefix 40 | */ 41 | public function __construct(ClassMetadataFactoryInterface $factory, CacheItemPoolInterface $pool, $prefix = null) 42 | { 43 | $this->factory = $factory; 44 | $this->pool = $pool; 45 | $this->prefix = $prefix; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | protected function fetchClassMetadata($class) 52 | { 53 | $item = $this->pool->getItem(strtr($this->prefix.$class, '\\', '_')); 54 | 55 | if ($item->isHit()) { 56 | return $item->get(); 57 | } 58 | 59 | $classMetadata = $this->factory->getClassMetadata($class); 60 | $this->pool->save($item->set($classMetadata)); 61 | 62 | return $classMetadata; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Mapping/Factory/ClassMetadataFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Factory; 13 | 14 | use Doctrine\Common\Annotations\AnnotationReader; 15 | use Ivory\Serializer\Mapping\ClassMetadata; 16 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 17 | use Ivory\Serializer\Mapping\Loader\AnnotationClassMetadataLoader; 18 | use Ivory\Serializer\Mapping\Loader\ChainClassMetadataLoader; 19 | use Ivory\Serializer\Mapping\Loader\ClassMetadataLoaderInterface; 20 | use Ivory\Serializer\Mapping\Loader\ReflectionClassMetadataLoader; 21 | use phpDocumentor\Reflection\ClassReflector; 22 | use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; 23 | use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; 24 | use Symfony\Component\PropertyInfo\PropertyInfoExtractor; 25 | 26 | /** 27 | * @author GeLo 28 | */ 29 | class ClassMetadataFactory extends AbstractClassMetadataFactory 30 | { 31 | /** 32 | * @var ClassMetadataLoaderInterface 33 | */ 34 | private $loader; 35 | 36 | /** 37 | * @param ClassMetadataLoaderInterface $loader 38 | */ 39 | public function __construct(ClassMetadataLoaderInterface $loader) 40 | { 41 | $this->loader = $loader; 42 | } 43 | 44 | /** 45 | * @param ClassMetadataLoaderInterface[] $loaders 46 | * 47 | * @return ClassMetadataFactoryInterface 48 | */ 49 | public static function create(array $loaders = []) 50 | { 51 | if (empty($loaders)) { 52 | $extractor = null; 53 | 54 | if (class_exists(PropertyInfoExtractor::class)) { 55 | $extractors = $typeExtractors = [new ReflectionExtractor()]; 56 | 57 | if (class_exists(ClassReflector::class)) { 58 | array_unshift($typeExtractors, new PhpDocExtractor()); 59 | } 60 | 61 | $extractor = new PropertyInfoExtractor($extractors, $typeExtractors, [], $extractors); 62 | } 63 | 64 | $loaders = [new ReflectionClassMetadataLoader($extractor)]; 65 | 66 | if (class_exists(AnnotationReader::class)) { 67 | $loaders[] = new AnnotationClassMetadataLoader(new AnnotationReader()); 68 | } 69 | } 70 | 71 | return new static(count($loaders) > 1 ? new ChainClassMetadataLoader($loaders) : array_shift($loaders)); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | protected function fetchClassMetadata($class) 78 | { 79 | $classMetadata = new ClassMetadata($class); 80 | $found = false; 81 | 82 | if (($parentMetadata = $this->getParentClassMetadata($class)) !== null) { 83 | $classMetadata->merge($parentMetadata); 84 | $found = true; 85 | } 86 | 87 | $found = $this->loader->loadClassMetadata($classMetadata) || $found; 88 | 89 | return $found ? $classMetadata : null; 90 | } 91 | 92 | /** 93 | * @param string $class 94 | * 95 | * @return ClassMetadataInterface|null 96 | */ 97 | private function getParentClassMetadata($class) 98 | { 99 | if (($parent = get_parent_class($class)) !== false) { 100 | return $this->getClassMetadata($parent); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Mapping/Factory/ClassMetadataFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Factory; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface ClassMetadataFactoryInterface 20 | { 21 | /** 22 | * @param string $class 23 | * 24 | * @return ClassMetadataInterface|null 25 | */ 26 | public function getClassMetadata($class); 27 | } 28 | -------------------------------------------------------------------------------- /src/Mapping/Factory/EventClassMetadataFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Factory; 13 | 14 | use Ivory\Serializer\Event\ClassMetadataLoadEvent; 15 | use Ivory\Serializer\Event\ClassMetadataNotFoundEvent; 16 | use Ivory\Serializer\Event\SerializerEvents; 17 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class EventClassMetadataFactory implements ClassMetadataFactoryInterface 23 | { 24 | /** 25 | * @var ClassMetadataFactoryInterface 26 | */ 27 | private $factory; 28 | 29 | /** 30 | * @var EventDispatcherInterface 31 | */ 32 | private $dispatcher; 33 | 34 | /** 35 | * @param ClassMetadataFactoryInterface $factory 36 | * @param EventDispatcherInterface $dispatcher 37 | */ 38 | public function __construct(ClassMetadataFactoryInterface $factory, EventDispatcherInterface $dispatcher) 39 | { 40 | $this->factory = $factory; 41 | $this->dispatcher = $dispatcher; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getClassMetadata($class) 48 | { 49 | $classMetadata = $this->factory->getClassMetadata($class); 50 | 51 | if ($classMetadata === null) { 52 | $this->dispatcher->dispatch( 53 | SerializerEvents::CLASS_METADATA_NOT_FOUND, 54 | $event = new ClassMetadataNotFoundEvent($class) 55 | ); 56 | } else { 57 | $this->dispatcher->dispatch( 58 | SerializerEvents::CLASS_METADATA_LOAD, 59 | $event = new ClassMetadataLoadEvent($classMetadata) 60 | ); 61 | } 62 | 63 | return $event->getClassMetadata(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Mapping/Loader/AbstractFileClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Type\Parser\TypeParserInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | abstract class AbstractFileClassMetadataLoader extends AbstractClassMetadataLoader implements MappedClassMetadataLoaderInterface 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $file; 25 | 26 | /** 27 | * @var mixed[] 28 | */ 29 | private $data; 30 | 31 | /** 32 | * @param string $file 33 | * @param TypeParserInterface|null $typeParser 34 | */ 35 | public function __construct($file, TypeParserInterface $typeParser = null) 36 | { 37 | parent::__construct($typeParser); 38 | 39 | if (!is_file($file)) { 40 | throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); 41 | } 42 | 43 | if (!is_readable($file)) { 44 | throw new \InvalidArgumentException(sprintf('The file "%s" is not readable.', $file)); 45 | } 46 | 47 | $this->file = $file; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function getMappedClasses() 54 | { 55 | if ($this->data === null) { 56 | $this->data = $this->loadFile($this->file); 57 | } 58 | 59 | return array_keys($this->data); 60 | } 61 | 62 | /** 63 | * @param string $file 64 | * 65 | * @return mixed[] 66 | */ 67 | abstract protected function loadFile($file); 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | protected function loadData($class) 73 | { 74 | if ($this->data === null) { 75 | $this->data = $this->loadFile($this->file); 76 | } 77 | 78 | if (isset($this->data[$class])) { 79 | return $this->data[$class]; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Mapping/Loader/AbstractReflectionClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | abstract class AbstractReflectionClassMetadataLoader extends AbstractClassMetadataLoader 18 | { 19 | /** 20 | * @param \ReflectionProperty $property 21 | * 22 | * @return mixed[]|null 23 | */ 24 | abstract protected function loadProperty(\ReflectionProperty $property); 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function loadData($class) 30 | { 31 | $reflection = new \ReflectionClass($class); 32 | $result = $this->loadClass($reflection); 33 | $properties = []; 34 | 35 | foreach ($reflection->getMethods() as $method) { 36 | if ($method->class !== $class) { 37 | continue; 38 | } 39 | 40 | if (($methodName = $this->validateMethod($method->name)) === null) { 41 | continue; 42 | } 43 | 44 | $data = $this->loadMethod($method); 45 | 46 | if (is_array($data)) { 47 | $properties[$methodName] = $data; 48 | } 49 | } 50 | 51 | foreach ($reflection->getProperties() as $property) { 52 | if ($property->class !== $class) { 53 | continue; 54 | } 55 | 56 | $data = $this->loadProperty($property); 57 | 58 | if (is_array($data)) { 59 | $name = $property->getName(); 60 | 61 | $properties[$name] = isset($properties[$name]) 62 | ? array_merge_recursive($properties[$name], $data) 63 | : $data; 64 | } 65 | } 66 | 67 | if (!empty($properties)) { 68 | $result['properties'] = $properties; 69 | } 70 | 71 | if (!empty($result)) { 72 | return $result; 73 | } 74 | } 75 | 76 | /** 77 | * @param \ReflectionClass $class 78 | * 79 | * @return mixed[] 80 | */ 81 | protected function loadClass(\ReflectionClass $class) 82 | { 83 | return []; 84 | } 85 | 86 | /** 87 | * @param \ReflectionMethod $method 88 | * 89 | * @return mixed[]|null 90 | */ 91 | protected function loadMethod(\ReflectionMethod $method) 92 | { 93 | } 94 | 95 | /** 96 | * @param string $method 97 | * 98 | * @return string|null 99 | */ 100 | private function validateMethod($method) 101 | { 102 | $prefix = substr($method, 0, 3); 103 | 104 | if ($prefix === 'get' || $prefix === 'set' || $prefix === 'has') { 105 | return lcfirst(substr($method, 3)); 106 | } 107 | 108 | $prefix = substr($prefix, 0, 2); 109 | 110 | if ($prefix === 'is') { 111 | return lcfirst(substr($method, 2)); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Mapping/Loader/ChainClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class ChainClassMetadataLoader implements MappedClassMetadataLoaderInterface 20 | { 21 | /** 22 | * @var ClassMetadataLoaderInterface[] 23 | */ 24 | private $loaders; 25 | 26 | /** 27 | * @param ClassMetadataLoaderInterface[] $loaders 28 | */ 29 | public function __construct(array $loaders) 30 | { 31 | $this->loaders = $loaders; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function loadClassMetadata(ClassMetadataInterface $classMetadata) 38 | { 39 | $result = false; 40 | 41 | foreach ($this->loaders as $loader) { 42 | $result = $loader->loadClassMetadata($classMetadata) || $result; 43 | } 44 | 45 | return $result; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getMappedClasses() 52 | { 53 | $classes = []; 54 | 55 | foreach ($this->loaders as $loader) { 56 | if ($loader instanceof MappedClassMetadataLoaderInterface) { 57 | $classes = array_merge($classes, $loader->getMappedClasses()); 58 | } 59 | } 60 | 61 | return array_unique($classes); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Mapping/Loader/ClassMetadataLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface ClassMetadataLoaderInterface 20 | { 21 | /** 22 | * @param ClassMetadataInterface $classMetadata 23 | * 24 | * @return bool 25 | */ 26 | public function loadClassMetadata(ClassMetadataInterface $classMetadata); 27 | } 28 | -------------------------------------------------------------------------------- /src/Mapping/Loader/DirectoryClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | use Ivory\Serializer\Type\Parser\TypeParser; 16 | use Ivory\Serializer\Type\Parser\TypeParserInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class DirectoryClassMetadataLoader implements MappedClassMetadataLoaderInterface 22 | { 23 | /** 24 | * @var string[] 25 | */ 26 | private $directories; 27 | 28 | /** 29 | * @var TypeParserInterface 30 | */ 31 | private $typeParser; 32 | 33 | /** 34 | * @var MappedClassMetadataLoaderInterface|null 35 | */ 36 | private $loader; 37 | 38 | /** 39 | * @var bool 40 | */ 41 | private $initialized = false; 42 | 43 | /** 44 | * @param string|string[] $directories 45 | * @param TypeParserInterface|null $typeParser 46 | */ 47 | public function __construct($directories, TypeParserInterface $typeParser = null) 48 | { 49 | $directories = is_array($directories) ? $directories : [$directories]; 50 | 51 | foreach ($directories as $directory) { 52 | if (!is_dir($directory)) { 53 | throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $directory)); 54 | } 55 | 56 | if (!is_readable($directory)) { 57 | throw new \InvalidArgumentException(sprintf('The directory "%s" is not readable.', $directory)); 58 | } 59 | } 60 | 61 | $this->directories = $directories; 62 | $this->typeParser = $typeParser ?: new TypeParser(); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function loadClassMetadata(ClassMetadataInterface $classMetadata) 69 | { 70 | return ($loader = $this->getLoader()) !== null && $loader->loadClassMetadata($classMetadata); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function getMappedClasses() 77 | { 78 | return ($loader = $this->getLoader()) !== null ? $loader->getMappedClasses() : []; 79 | } 80 | 81 | /** 82 | * @return MappedClassMetadataLoaderInterface|null 83 | */ 84 | private function getLoader() 85 | { 86 | if (!$this->initialized) { 87 | $this->createLoader(); 88 | $this->initialized = true; 89 | } 90 | 91 | return $this->loader; 92 | } 93 | 94 | private function createLoader() 95 | { 96 | $extensions = [ 97 | FileClassMetadataLoader::EXTENSION_JSON, 98 | FileClassMetadataLoader::EXTENSION_XML, 99 | FileClassMetadataLoader::EXTENSION_YAML, 100 | ]; 101 | 102 | $loaders = []; 103 | 104 | foreach ($this->directories as $directory) { 105 | $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)); 106 | 107 | foreach ($iterator as $file) { 108 | if ($file->isDir()) { 109 | continue; 110 | } 111 | 112 | $path = $file->getRealPath(); 113 | 114 | if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $extensions, true)) { 115 | continue; 116 | } 117 | 118 | $loaders[] = new FileClassMetadataLoader($path, $this->typeParser); 119 | } 120 | } 121 | 122 | if (!empty($loaders)) { 123 | $this->loader = count($loaders) > 1 ? new ChainClassMetadataLoader($loaders) : array_shift($loaders); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Mapping/Loader/FileClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 15 | use Ivory\Serializer\Type\Parser\TypeParserInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class FileClassMetadataLoader implements MappedClassMetadataLoaderInterface 21 | { 22 | const EXTENSION_JSON = 'json'; 23 | const EXTENSION_XML = 'xml'; 24 | const EXTENSION_YAML = 'yml'; 25 | 26 | /** 27 | * @var MappedClassMetadataLoaderInterface 28 | */ 29 | private $loader; 30 | 31 | /** 32 | * @param string $file 33 | * @param TypeParserInterface|null $typeParser 34 | */ 35 | public function __construct($file, TypeParserInterface $typeParser = null) 36 | { 37 | if (!file_exists($file)) { 38 | throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); 39 | } 40 | 41 | switch (pathinfo($file, PATHINFO_EXTENSION)) { 42 | case self::EXTENSION_JSON: 43 | $this->loader = new JsonClassMetadataLoader($file, $typeParser); 44 | break; 45 | 46 | case self::EXTENSION_XML: 47 | $this->loader = new XmlClassMetadataLoader($file, $typeParser); 48 | break; 49 | 50 | case self::EXTENSION_YAML: 51 | $this->loader = new YamlClassMetadataLoader($file, $typeParser); 52 | break; 53 | } 54 | 55 | if ($this->loader === null) { 56 | throw new \InvalidArgumentException(sprintf('The file "%s" is not supported.', $file)); 57 | } 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function loadClassMetadata(ClassMetadataInterface $classMetadata) 64 | { 65 | return $this->loader->loadClassMetadata($classMetadata); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function getMappedClasses() 72 | { 73 | return $this->loader->getMappedClasses(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Mapping/Loader/JsonClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class JsonClassMetadataLoader extends AbstractFileClassMetadataLoader 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | protected function loadFile($file) 23 | { 24 | $data = @json_decode(@file_get_contents($file), true); 25 | 26 | if (json_last_error() !== JSON_ERROR_NONE) { 27 | throw new \InvalidArgumentException(json_last_error_msg()); 28 | } 29 | 30 | if (!is_array($data)) { 31 | throw new \InvalidArgumentException(sprintf('The json mapping file "%s" is not valid.', $file)); 32 | } 33 | 34 | return $data; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Mapping/Loader/MappedClassMetadataLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface MappedClassMetadataLoaderInterface extends ClassMetadataLoaderInterface 18 | { 19 | /** 20 | * @return string[] 21 | */ 22 | public function getMappedClasses(); 23 | } 24 | -------------------------------------------------------------------------------- /src/Mapping/Loader/ReflectionClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Ivory\Serializer\Type\Parser\TypeParserInterface; 15 | use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class ReflectionClassMetadataLoader extends AbstractReflectionClassMetadataLoader 21 | { 22 | /** 23 | * @var PropertyInfoExtractorInterface|null 24 | */ 25 | private $extractor; 26 | 27 | /** 28 | * @param PropertyInfoExtractorInterface|null $extractor 29 | * @param TypeParserInterface|null $typeParser 30 | */ 31 | public function __construct( 32 | PropertyInfoExtractorInterface $extractor = null, 33 | TypeParserInterface $typeParser = null 34 | ) { 35 | parent::__construct($typeParser); 36 | 37 | $this->extractor = $extractor; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function loadProperty(\ReflectionProperty $property) 44 | { 45 | $result = []; 46 | $type = $this->loadPropertyType($property); 47 | 48 | if ($type !== null) { 49 | $result['type'] = $type; 50 | } 51 | 52 | return $result; 53 | } 54 | 55 | /** 56 | * @param \ReflectionProperty $property 57 | * 58 | * @return string|null 59 | */ 60 | private function loadPropertyType(\ReflectionProperty $property) 61 | { 62 | if ($this->extractor === null) { 63 | return; 64 | } 65 | 66 | $types = $this->extractor->getTypes($property->class, $property->name); 67 | 68 | if (empty($types)) { 69 | return; 70 | } 71 | 72 | $extractedType = current($types); 73 | $type = $extractedType->getBuiltinType(); 74 | 75 | if ($type === 'object') { 76 | $type = $extractedType->getClassName(); 77 | } 78 | 79 | return $type; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Mapping/Loader/YamlClassMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping\Loader; 13 | 14 | use Symfony\Component\Yaml\Exception\ParseException; 15 | use Symfony\Component\Yaml\Yaml; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class YamlClassMetadataLoader extends AbstractFileClassMetadataLoader 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | protected function loadFile($file) 26 | { 27 | try { 28 | return Yaml::parse(file_get_contents($file)); 29 | } catch (ParseException $e) { 30 | throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Mapping/MetadataInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface MetadataInterface extends \Serializable 18 | { 19 | /** 20 | * @return string 21 | */ 22 | public function getName(); 23 | 24 | /** 25 | * @param string $name 26 | */ 27 | public function setName($name); 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Resource/mapping.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/Mapping/TypeMetadata.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class TypeMetadata implements TypeMetadataInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $name; 23 | 24 | /** 25 | * @var mixed[] 26 | */ 27 | private $options = []; 28 | 29 | /** 30 | * @param string $name 31 | * @param mixed[] $options 32 | */ 33 | public function __construct($name, array $options = []) 34 | { 35 | $this->setName($name); 36 | $this->setOptions($options); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getName() 43 | { 44 | return $this->name; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function setName($name) 51 | { 52 | $this->name = $name; 53 | } 54 | 55 | /** 56 | * @return bool 57 | */ 58 | public function hasOptions() 59 | { 60 | return !empty($this->options); 61 | } 62 | 63 | /** 64 | * @return mixed[] 65 | */ 66 | public function getOptions() 67 | { 68 | return $this->options; 69 | } 70 | 71 | /** 72 | * @param mixed[] $options 73 | */ 74 | public function setOptions(array $options) 75 | { 76 | foreach ($options as $option => $value) { 77 | $this->setOption($option, $value); 78 | } 79 | } 80 | 81 | /** 82 | * @param string $option 83 | * 84 | * @return bool 85 | */ 86 | public function hasOption($option) 87 | { 88 | return isset($this->options[$option]); 89 | } 90 | 91 | /** 92 | * @param string $option 93 | * @param mixed $default 94 | * 95 | * @return mixed 96 | */ 97 | public function getOption($option, $default = null) 98 | { 99 | return $this->hasOption($option) ? $this->options[$option] : $default; 100 | } 101 | 102 | /** 103 | * @param string $option 104 | * @param mixed $value 105 | */ 106 | public function setOption($option, $value) 107 | { 108 | $this->options[$option] = $value; 109 | } 110 | 111 | /** 112 | * @param string $option 113 | */ 114 | public function removeOption($option) 115 | { 116 | unset($this->options[$option]); 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function serialize() 123 | { 124 | return serialize([ 125 | $this->name, 126 | $this->options, 127 | ]); 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function unserialize($serialized) 134 | { 135 | list( 136 | $this->name, 137 | $this->options 138 | ) = unserialize($serialized); 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function __toString() 145 | { 146 | $name = (string) $this->getName(); 147 | 148 | if (!$this->hasOptions()) { 149 | return $name; 150 | } 151 | 152 | $options = $this->getOptions(); 153 | 154 | array_walk($options, function (&$value, $option) { 155 | if (is_string($value)) { 156 | $value = '\''.$value.'\''; 157 | } 158 | 159 | $value = $option.'='.$value; 160 | }); 161 | 162 | return $name.'<'.implode(', ', $options).'>'; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Mapping/TypeMetadataInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mapping; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface TypeMetadataInterface extends MetadataInterface 18 | { 19 | /** 20 | * @return bool 21 | */ 22 | public function hasOptions(); 23 | 24 | /** 25 | * @return mixed[] 26 | */ 27 | public function getOptions(); 28 | 29 | /** 30 | * @param mixed[] $options 31 | */ 32 | public function setOptions(array $options); 33 | 34 | /** 35 | * @param string $option 36 | * 37 | * @return bool 38 | */ 39 | public function hasOption($option); 40 | 41 | /** 42 | * @param string $option 43 | * @param mixed $default 44 | * 45 | * @return mixed 46 | */ 47 | public function getOption($option, $default = null); 48 | 49 | /** 50 | * @param string $option 51 | * @param mixed $value 52 | */ 53 | public function setOption($option, $value); 54 | 55 | /** 56 | * @param string $option 57 | */ 58 | public function removeOption($option); 59 | } 60 | -------------------------------------------------------------------------------- /src/Mutator/MutatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mutator; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface MutatorInterface 18 | { 19 | /** 20 | * @param object $object 21 | * @param string $property 22 | * @param mixed $value 23 | */ 24 | public function setValue($object, $property, $value); 25 | } 26 | -------------------------------------------------------------------------------- /src/Mutator/ReflectionMutator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mutator; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class ReflectionMutator implements MutatorInterface 18 | { 19 | /** 20 | * @var \ReflectionProperty[] 21 | */ 22 | private $properties = []; 23 | 24 | /** 25 | * @var \ReflectionMethod[] 26 | */ 27 | private $methods = []; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function setValue($object, $property, $value) 33 | { 34 | if (property_exists($object, $property)) { 35 | $this->getReflectionProperty($object, $property)->setValue($object, $value); 36 | } else { 37 | $this->setMethodValue($object, $property, $value); 38 | } 39 | } 40 | 41 | /** 42 | * @param object $object 43 | * @param string $property 44 | * @param mixed $value 45 | */ 46 | private function setMethodValue($object, $property, $value) 47 | { 48 | $methods = [$property]; 49 | 50 | if (method_exists($object, $property)) { 51 | $this->getReflectionMethod($object, $property)->invoke($object, $value); 52 | 53 | return; 54 | } 55 | 56 | $methodSuffix = ucfirst($property); 57 | 58 | foreach (['get', 'has', 'is'] as $methodPrefix) { 59 | $methods[] = $method = $methodPrefix.$methodSuffix; 60 | 61 | if (method_exists($object, $method)) { 62 | $this->getReflectionMethod($object, $method)->invoke($object, $value); 63 | 64 | return; 65 | } 66 | } 67 | 68 | throw new \InvalidArgumentException(sprintf( 69 | 'The property "%s" or methods %s don\'t exist on class "%s".', 70 | $property, 71 | '"'.implode('", "', $methods).'"', 72 | get_class($object) 73 | )); 74 | } 75 | 76 | /** 77 | * @param object $object 78 | * @param string $property 79 | * 80 | * @return \ReflectionProperty 81 | */ 82 | private function getReflectionProperty($object, $property) 83 | { 84 | if (isset($this->properties[$key = $this->getCacheKey($object, $property)])) { 85 | return $this->properties[$key]; 86 | } 87 | 88 | $reflection = new \ReflectionProperty($object, $property); 89 | $reflection->setAccessible(true); 90 | 91 | return $this->properties[$key] = $reflection; 92 | } 93 | 94 | /** 95 | * @param object $object 96 | * @param string $method 97 | * 98 | * @return \ReflectionMethod 99 | */ 100 | private function getReflectionMethod($object, $method) 101 | { 102 | if (isset($this->methods[$key = $this->getCacheKey($object, $method)])) { 103 | return $this->methods[$key]; 104 | } 105 | 106 | $reflection = new \ReflectionMethod($object, $method); 107 | $reflection->setAccessible(true); 108 | 109 | return $this->methods[$key] = $reflection; 110 | } 111 | 112 | /** 113 | * @param object $object 114 | * @param string $key 115 | * 116 | * @return string 117 | */ 118 | private function getCacheKey($object, $key) 119 | { 120 | return get_class($object).'::'.$key; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Mutator/SymfonyMutator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Mutator; 13 | 14 | use Symfony\Component\PropertyAccess\PropertyAccess; 15 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class SymfonyMutator implements MutatorInterface 21 | { 22 | /** 23 | * @var PropertyAccessorInterface 24 | */ 25 | private $propertyAccessor; 26 | 27 | /** 28 | * @param PropertyAccessorInterface|null $propertyAccessor 29 | */ 30 | public function __construct(PropertyAccessorInterface $propertyAccessor = null) 31 | { 32 | $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function setValue($object, $property, $value) 39 | { 40 | $this->propertyAccessor->setValue($object, $property, $value); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Naming/AbstractNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | abstract class AbstractNamingStrategy implements NamingStrategyInterface 18 | { 19 | /** 20 | * @var string[] 21 | */ 22 | private $names = []; 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function convert($name) 28 | { 29 | return isset($this->names[$name]) 30 | ? $this->names[$name] 31 | : $this->names[$name] = $this->doConvert($name); 32 | } 33 | 34 | /** 35 | * @param string $name 36 | * 37 | * @return string 38 | */ 39 | abstract protected function doConvert($name); 40 | } 41 | -------------------------------------------------------------------------------- /src/Naming/AbstractSeparatorNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class AbstractSeparatorNamingStrategy extends AbstractNamingStrategy 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $separator; 23 | 24 | /** 25 | * @param string $separator 26 | */ 27 | public function __construct($separator) 28 | { 29 | $this->separator = $separator; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | protected function doConvert($name) 36 | { 37 | $name = str_replace(['--', '__', ' '], ' ', $name); 38 | $name = lcfirst(str_replace(['-', '_', ' '], $this->separator, $name)); 39 | 40 | return strtolower(preg_replace('/([A-Z])/', $this->separator.'$1', $name)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Naming/CacheNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | use Psr\Cache\CacheItemPoolInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class CacheNamingStrategy extends AbstractNamingStrategy 20 | { 21 | /** 22 | * @var NamingStrategyInterface 23 | */ 24 | private $strategy; 25 | 26 | /** 27 | * @var CacheItemPoolInterface 28 | */ 29 | private $cache; 30 | 31 | /** 32 | * @param NamingStrategyInterface $strategy 33 | * @param CacheItemPoolInterface $cache 34 | */ 35 | public function __construct(NamingStrategyInterface $strategy, CacheItemPoolInterface $cache) 36 | { 37 | $this->strategy = $strategy; 38 | $this->cache = $cache; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function doConvert($name) 45 | { 46 | $item = $this->cache->getItem($name); 47 | 48 | if ($item->isHit()) { 49 | return $item->get(); 50 | } 51 | 52 | $result = $this->strategy->convert($name); 53 | $this->cache->save($item->set($result)); 54 | 55 | return $result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Naming/CamelCaseNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class CamelCaseNamingStrategy extends StudlyCapsNamingStrategy 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | protected function doConvert($name) 23 | { 24 | return lcfirst(parent::doConvert($name)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Naming/IdenticalNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class IdenticalNamingStrategy implements NamingStrategyInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function convert($name) 23 | { 24 | return $name; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Naming/KebabCaseNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class KebabCaseNamingStrategy extends AbstractSeparatorNamingStrategy 18 | { 19 | public function __construct() 20 | { 21 | parent::__construct('-'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Naming/NamingStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | interface NamingStrategyInterface 18 | { 19 | /** 20 | * @param string $name 21 | * 22 | * @return string 23 | */ 24 | public function convert($name); 25 | } 26 | -------------------------------------------------------------------------------- /src/Naming/SnakeCaseNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class SnakeCaseNamingStrategy extends AbstractSeparatorNamingStrategy 18 | { 19 | public function __construct() 20 | { 21 | parent::__construct('_'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Naming/SpaceNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class SpaceNamingStrategy extends AbstractSeparatorNamingStrategy 18 | { 19 | public function __construct() 20 | { 21 | parent::__construct(' '); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Naming/StudlyCapsNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Naming; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | class StudlyCapsNamingStrategy extends AbstractNamingStrategy 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | protected function doConvert($name) 23 | { 24 | return str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Navigator/EventNavigator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Navigator; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Direction; 16 | use Ivory\Serializer\Event\PostDeserializeEvent; 17 | use Ivory\Serializer\Event\PostSerializeEvent; 18 | use Ivory\Serializer\Event\PreDeserializeEvent; 19 | use Ivory\Serializer\Event\PreSerializeEvent; 20 | use Ivory\Serializer\Event\SerializerEvents; 21 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 22 | use Ivory\Serializer\Type\Guesser\TypeGuesser; 23 | use Ivory\Serializer\Type\Guesser\TypeGuesserInterface; 24 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 25 | 26 | /** 27 | * @author GeLo 28 | */ 29 | class EventNavigator implements NavigatorInterface 30 | { 31 | /** 32 | * @var NavigatorInterface 33 | */ 34 | private $navigator; 35 | 36 | /** 37 | * @var EventDispatcherInterface 38 | */ 39 | private $dispatcher; 40 | 41 | /** 42 | * @var TypeGuesserInterface 43 | */ 44 | private $typeGuesser; 45 | 46 | /** 47 | * @param NavigatorInterface $navigator 48 | * @param EventDispatcherInterface $dispatcher 49 | * @param TypeGuesserInterface|null $typeGuesser 50 | */ 51 | public function __construct( 52 | NavigatorInterface $navigator, 53 | EventDispatcherInterface $dispatcher, 54 | TypeGuesserInterface $typeGuesser = null 55 | ) { 56 | $this->navigator = $navigator; 57 | $this->dispatcher = $dispatcher; 58 | $this->typeGuesser = $typeGuesser ?: new TypeGuesser(); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function navigate($data, ContextInterface $context, TypeMetadataInterface $type = null) 65 | { 66 | $type = $type ?: $this->typeGuesser->guess($data); 67 | $serialization = $context->getDirection() === Direction::SERIALIZATION; 68 | 69 | if ($serialization) { 70 | $this->dispatcher->dispatch( 71 | SerializerEvents::PRE_SERIALIZE, 72 | $event = new PreSerializeEvent($data, $type, $context) 73 | ); 74 | } else { 75 | $this->dispatcher->dispatch( 76 | SerializerEvents::PRE_DESERIALIZE, 77 | $event = new PreDeserializeEvent($data, $type, $context) 78 | ); 79 | } 80 | 81 | $result = $this->navigator->navigate($data = $event->getData(), $context, $type = $event->getType()); 82 | 83 | if ($serialization) { 84 | $this->dispatcher->dispatch( 85 | SerializerEvents::POST_SERIALIZE, 86 | new PostSerializeEvent($data, $type, $context) 87 | ); 88 | } else { 89 | $this->dispatcher->dispatch( 90 | SerializerEvents::POST_DESERIALIZE, 91 | new PostDeserializeEvent($result, $type, $context) 92 | ); 93 | } 94 | 95 | return $result; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Navigator/Navigator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Navigator; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | use Ivory\Serializer\Registry\TypeRegistry; 17 | use Ivory\Serializer\Registry\TypeRegistryInterface; 18 | use Ivory\Serializer\Type\Guesser\TypeGuesser; 19 | use Ivory\Serializer\Type\Guesser\TypeGuesserInterface; 20 | use Ivory\Serializer\Type\Type; 21 | 22 | /** 23 | * @author GeLo 24 | */ 25 | class Navigator implements NavigatorInterface 26 | { 27 | /** 28 | * @var TypeRegistryInterface 29 | */ 30 | private $typeRegistry; 31 | 32 | /** 33 | * @var TypeGuesserInterface 34 | */ 35 | private $typeGuesser; 36 | 37 | /** 38 | * @param TypeRegistryInterface|null $typeRegistry 39 | * @param TypeGuesserInterface|null $typeGuesser 40 | */ 41 | public function __construct(TypeRegistryInterface $typeRegistry = null, TypeGuesserInterface $typeGuesser = null) 42 | { 43 | $this->typeRegistry = $typeRegistry ?: TypeRegistry::create(); 44 | $this->typeGuesser = $typeGuesser ?: new TypeGuesser(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function navigate($data, ContextInterface $context, TypeMetadataInterface $type = null) 51 | { 52 | $type = $type ?: $this->typeGuesser->guess($data); 53 | $name = $type->getName(); 54 | 55 | if ($data === null) { 56 | $name = Type::NULL; 57 | } 58 | 59 | return $this->typeRegistry->getType($name, $context->getDirection())->convert($data, $type, $context); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Navigator/NavigatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Navigator; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface NavigatorInterface 21 | { 22 | /** 23 | * @param mixed $data 24 | * @param ContextInterface $context 25 | * @param TypeMetadataInterface|null $type 26 | * 27 | * @return mixed 28 | */ 29 | public function navigate($data, ContextInterface $context, TypeMetadataInterface $type = null); 30 | } 31 | -------------------------------------------------------------------------------- /src/Registry/TypeRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Registry; 13 | 14 | use Ivory\Serializer\Type\TypeInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface TypeRegistryInterface 20 | { 21 | /** 22 | * @param string $name 23 | * @param int $direction 24 | * @param TypeInterface $type 25 | */ 26 | public function registerType($name, $direction, TypeInterface $type); 27 | 28 | /** 29 | * @param string $name 30 | * @param int $direction 31 | * 32 | * @return TypeInterface 33 | */ 34 | public function getType($name, $direction); 35 | } 36 | -------------------------------------------------------------------------------- /src/Registry/VisitorRegistry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Registry; 13 | 14 | use Ivory\Serializer\Accessor\AccessorInterface; 15 | use Ivory\Serializer\Accessor\ReflectionAccessor; 16 | use Ivory\Serializer\Direction; 17 | use Ivory\Serializer\Format; 18 | use Ivory\Serializer\Instantiator\DoctrineInstantiator; 19 | use Ivory\Serializer\Instantiator\InstantiatorInterface; 20 | use Ivory\Serializer\Mutator\MutatorInterface; 21 | use Ivory\Serializer\Mutator\ReflectionMutator; 22 | use Ivory\Serializer\Visitor\Csv\CsvDeserializationVisitor; 23 | use Ivory\Serializer\Visitor\Csv\CsvSerializationVisitor; 24 | use Ivory\Serializer\Visitor\Json\JsonDeserializationVisitor; 25 | use Ivory\Serializer\Visitor\Json\JsonSerializationVisitor; 26 | use Ivory\Serializer\Visitor\VisitorInterface; 27 | use Ivory\Serializer\Visitor\Xml\XmlDeserializationVisitor; 28 | use Ivory\Serializer\Visitor\Xml\XmlSerializationVisitor; 29 | use Ivory\Serializer\Visitor\Yaml\YamlDeserializationVisitor; 30 | use Ivory\Serializer\Visitor\Yaml\YamlSerializationVisitor; 31 | 32 | /** 33 | * @author GeLo 34 | */ 35 | class VisitorRegistry implements VisitorRegistryInterface 36 | { 37 | /** 38 | * @var VisitorInterface[][] 39 | */ 40 | private $visitors = []; 41 | 42 | /** 43 | * @param VisitorInterface[][] $visitors 44 | */ 45 | public function __construct(array $visitors = []) 46 | { 47 | foreach ($visitors as $direction => $formattedVisitors) { 48 | foreach ($formattedVisitors as $format => $visitor) { 49 | $this->registerVisitor($direction, $format, $visitor); 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * @param VisitorInterface[][] $visitors 56 | * @param InstantiatorInterface|null $instantiator 57 | * @param AccessorInterface|null $accessor 58 | * @param MutatorInterface|null $mutator 59 | * 60 | * @return VisitorRegistryInterface 61 | */ 62 | public static function create( 63 | array $visitors = [], 64 | InstantiatorInterface $instantiator = null, 65 | AccessorInterface $accessor = null, 66 | MutatorInterface $mutator = null 67 | ) { 68 | $instantiator = $instantiator ?: new DoctrineInstantiator(); 69 | $accessor = $accessor ?: new ReflectionAccessor(); 70 | $mutator = $mutator ?: new ReflectionMutator(); 71 | 72 | return new static(array_replace_recursive([ 73 | Direction::SERIALIZATION => [ 74 | Format::CSV => new CsvSerializationVisitor($accessor), 75 | Format::JSON => new JsonSerializationVisitor($accessor), 76 | Format::XML => new XmlSerializationVisitor($accessor), 77 | Format::YAML => new YamlSerializationVisitor($accessor), 78 | ], 79 | Direction::DESERIALIZATION => [ 80 | Format::CSV => new CsvDeserializationVisitor($instantiator, $mutator), 81 | Format::JSON => new JsonDeserializationVisitor($instantiator, $mutator), 82 | Format::XML => new XmlDeserializationVisitor($instantiator, $mutator), 83 | Format::YAML => new YamlDeserializationVisitor($instantiator, $mutator), 84 | ], 85 | ], $visitors)); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function registerVisitor($direction, $format, VisitorInterface $visitor) 92 | { 93 | if (!isset($this->visitors[$direction])) { 94 | $this->visitors[$direction] = []; 95 | } 96 | 97 | $this->visitors[$direction][$format] = $visitor; 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getVisitor($direction, $format) 104 | { 105 | if (!isset($this->visitors[$direction]) || !isset($this->visitors[$direction][$format])) { 106 | throw new \InvalidArgumentException(sprintf( 107 | 'The visitor for direction "%s" and format "%s" does not exist.', 108 | $direction === Direction::SERIALIZATION ? 'serialization' : 'deserialization', 109 | $format 110 | )); 111 | } 112 | 113 | return $this->visitors[$direction][$format]; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Registry/VisitorRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Registry; 13 | 14 | use Ivory\Serializer\Visitor\VisitorInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface VisitorRegistryInterface 20 | { 21 | /** 22 | * @param int $direction 23 | * @param string $format 24 | * @param VisitorInterface $visitor 25 | */ 26 | public function registerVisitor($direction, $format, VisitorInterface $visitor); 27 | 28 | /** 29 | * @param int $direction 30 | * @param string $format 31 | * 32 | * @return VisitorInterface 33 | */ 34 | public function getVisitor($direction, $format); 35 | } 36 | -------------------------------------------------------------------------------- /src/Serializer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer; 13 | 14 | use Ivory\Serializer\Context\Context; 15 | use Ivory\Serializer\Context\ContextInterface; 16 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 17 | use Ivory\Serializer\Navigator\Navigator; 18 | use Ivory\Serializer\Navigator\NavigatorInterface; 19 | use Ivory\Serializer\Registry\VisitorRegistry; 20 | use Ivory\Serializer\Registry\VisitorRegistryInterface; 21 | use Ivory\Serializer\Type\Parser\TypeParser; 22 | use Ivory\Serializer\Type\Parser\TypeParserInterface; 23 | 24 | /** 25 | * @author GeLo 26 | */ 27 | class Serializer implements SerializerInterface 28 | { 29 | /** 30 | * @var NavigatorInterface 31 | */ 32 | private $navigator; 33 | 34 | /** 35 | * @var VisitorRegistryInterface 36 | */ 37 | private $visitorRegistry; 38 | 39 | /** 40 | * @var TypeParserInterface 41 | */ 42 | private $typeParser; 43 | 44 | /** 45 | * @param NavigatorInterface|null $navigator 46 | * @param VisitorRegistryInterface|null $visitorRegistry 47 | * @param TypeParserInterface|null $typeParser 48 | */ 49 | public function __construct( 50 | NavigatorInterface $navigator = null, 51 | VisitorRegistryInterface $visitorRegistry = null, 52 | TypeParserInterface $typeParser = null 53 | ) { 54 | $this->navigator = $navigator ?: new Navigator(); 55 | $this->visitorRegistry = $visitorRegistry ?: VisitorRegistry::create(); 56 | $this->typeParser = $typeParser ?: new TypeParser(); 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function serialize($data, $format, ContextInterface $context = null) 63 | { 64 | return $this->navigate($data, Direction::SERIALIZATION, $format, $context); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function deserialize($data, $type, $format, ContextInterface $context = null) 71 | { 72 | if (is_string($type)) { 73 | $type = $this->typeParser->parse($type); 74 | } 75 | 76 | if (!$type instanceof TypeMetadataInterface) { 77 | throw new \InvalidArgumentException(sprintf( 78 | 'The type must be a string or a "%s", got "%s".', 79 | TypeMetadataInterface::class, 80 | is_object($type) ? get_class($type) : gettype($type) 81 | )); 82 | } 83 | 84 | return $this->navigate($data, Direction::DESERIALIZATION, $format, $context, $type); 85 | } 86 | 87 | /** 88 | * @param mixed $data 89 | * @param int $direction 90 | * @param string $format 91 | * @param ContextInterface|null $context 92 | * @param TypeMetadataInterface|null $type 93 | * 94 | * @return mixed 95 | */ 96 | private function navigate( 97 | $data, 98 | $direction, 99 | $format, 100 | ContextInterface $context = null, 101 | TypeMetadataInterface $type = null 102 | ) { 103 | $visitor = $this->visitorRegistry->getVisitor($direction, $format); 104 | 105 | $context = $context ?: new Context(); 106 | $context->initialize($this->navigator, $visitor, $direction, $format); 107 | $this->navigator->navigate($visitor->prepare($data, $context), $context, $type); 108 | 109 | return $visitor->getResult(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface SerializerInterface 21 | { 22 | /** 23 | * @param mixed $data 24 | * @param string $format 25 | * @param ContextInterface|null $context 26 | * 27 | * @return string 28 | */ 29 | public function serialize($data, $format, ContextInterface $context = null); 30 | 31 | /** 32 | * @param string $data 33 | * @param TypeMetadataInterface|string $type 34 | * @param string $format 35 | * @param ContextInterface|null $context 36 | * 37 | * @return mixed 38 | */ 39 | public function deserialize($data, $type, $format, ContextInterface $context = null); 40 | } 41 | -------------------------------------------------------------------------------- /src/Type/ArrayType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class ArrayType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitArray($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/BooleanType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class BooleanType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitBoolean($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/ClosureType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class ClosureType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | throw new \RuntimeException('(De)Serializing a closure is not supported.'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/DateTimeType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Direction; 16 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class DateTimeType implements TypeInterface 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $format; 27 | 28 | /** 29 | * @var string 30 | */ 31 | private $timeZone; 32 | 33 | /** 34 | * @param string $format 35 | * @param string|null $timeZone 36 | */ 37 | public function __construct($format = \DateTime::RFC3339, $timeZone = null) 38 | { 39 | $this->format = $format; 40 | $this->timeZone = $timeZone ?: date_default_timezone_get(); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 47 | { 48 | $result = $context->getDirection() === Direction::SERIALIZATION 49 | ? $this->serialize($data, $type, $context) 50 | : $this->deserialize($data, $type, $context); 51 | 52 | return $context->getVisitor()->visitData($result, $type, $context); 53 | } 54 | 55 | /** 56 | * @param \DateTimeInterface $data 57 | * @param TypeMetadataInterface $type 58 | * @param ContextInterface $context 59 | * 60 | * @return string 61 | */ 62 | private function serialize($data, TypeMetadataInterface $type, ContextInterface $context) 63 | { 64 | $class = $type->getName(); 65 | 66 | if (!$data instanceof $class) { 67 | throw new \InvalidArgumentException(sprintf( 68 | 'Expected a "%s", got "%s".', 69 | $class, 70 | is_object($data) ? get_class($data) : gettype($data) 71 | )); 72 | } 73 | 74 | $result = $data->format($format = $type->getOption('format', $this->format)); 75 | 76 | if ($result === false) { 77 | throw new \InvalidArgumentException(sprintf('The date format "%s" is not valid.', $format)); 78 | } 79 | 80 | return $result; 81 | } 82 | 83 | /** 84 | * @param mixed $data 85 | * @param TypeMetadataInterface $type 86 | * @param ContextInterface $context 87 | * 88 | * @return \DateTimeInterface 89 | */ 90 | private function deserialize($data, TypeMetadataInterface $type, ContextInterface $context) 91 | { 92 | $class = $type->getName(); 93 | 94 | if (!method_exists($class, 'createFromFormat')) { 95 | throw new \InvalidArgumentException(sprintf( 96 | 'The method "%s" does not exist on "%s".', 97 | 'createFromFormat', 98 | $class 99 | )); 100 | } 101 | 102 | if (!method_exists($class, 'getLastErrors')) { 103 | throw new \InvalidArgumentException(sprintf( 104 | 'The method "%s" does not exist on "%s".', 105 | 'getLastErrors', 106 | $class 107 | )); 108 | } 109 | 110 | $result = $class::createFromFormat( 111 | $format = $type->getOption('format', $this->format), 112 | (string) $data, 113 | $timezone = new \DateTimeZone($type->getOption('timezone', $this->timeZone)) 114 | ); 115 | 116 | $errors = $class::getLastErrors(); 117 | 118 | if (!empty($errors['warnings']) || !empty($errors['errors'])) { 119 | throw new \InvalidArgumentException(sprintf( 120 | 'The date "%s" with format "%s" and timezone "%s" is not valid.', 121 | $data, 122 | $format, 123 | $timezone->getName() 124 | )); 125 | } 126 | 127 | return $result; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Type/ExceptionType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Direction; 16 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class ExceptionType implements TypeInterface 22 | { 23 | /** 24 | * @var bool 25 | */ 26 | private $debug; 27 | 28 | /** 29 | * @param bool $debug 30 | */ 31 | public function __construct($debug = false) 32 | { 33 | $this->debug = $debug; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function convert($exception, TypeMetadataInterface $type, ContextInterface $context) 40 | { 41 | if ($context->getDirection() === Direction::DESERIALIZATION) { 42 | throw new \RuntimeException(sprintf('De-serializing an "Exception" is not supported.')); 43 | } 44 | 45 | $result = [ 46 | 'code' => 500, 47 | 'message' => 'Internal Server Error', 48 | ]; 49 | 50 | if ($this->debug) { 51 | $result['exception'] = $this->serializeException($exception); 52 | } 53 | 54 | return $context->getVisitor()->visitArray($result, $type, $context); 55 | } 56 | 57 | /** 58 | * @param \Exception $exception 59 | * 60 | * @return mixed[] 61 | */ 62 | private function serializeException(\Exception $exception) 63 | { 64 | $result = [ 65 | 'code' => $exception->getCode(), 66 | 'message' => $exception->getMessage(), 67 | 'file' => $exception->getFile(), 68 | 'line' => $exception->getLine(), 69 | 'trace' => $exception->getTraceAsString(), 70 | ]; 71 | 72 | if ($exception->getPrevious() !== null) { 73 | $result['previous'] = $this->serializeException($exception->getPrevious()); 74 | } 75 | 76 | return $result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Type/FloatType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class FloatType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitFloat($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/Guesser/TypeGuesser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type\Guesser; 13 | 14 | use Ivory\Serializer\Mapping\TypeMetadata; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class TypeGuesser implements TypeGuesserInterface 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function guess($data) 25 | { 26 | return new TypeMetadata(is_object($data) ? get_class($data) : strtolower(gettype($data))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Type/Guesser/TypeGuesserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type\Guesser; 13 | 14 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface TypeGuesserInterface 20 | { 21 | /** 22 | * @param mixed $data 23 | * 24 | * @return TypeMetadataInterface 25 | */ 26 | public function guess($data); 27 | } 28 | -------------------------------------------------------------------------------- /src/Type/IntegerType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class IntegerType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitInteger($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/NullType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class NullType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitNull($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/ObjectType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\Factory\ClassMetadataFactory; 16 | use Ivory\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class ObjectType implements TypeInterface 23 | { 24 | /** 25 | * @var ClassMetadataFactoryInterface 26 | */ 27 | private $classMetadataFactory; 28 | 29 | /** 30 | * @param ClassMetadataFactoryInterface|null $classMetadataFactory 31 | */ 32 | public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null) 33 | { 34 | $this->classMetadataFactory = $classMetadataFactory ?: ClassMetadataFactory::create(); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 41 | { 42 | $class = $this->classMetadataFactory->getClassMetadata($type->getName()); 43 | 44 | if ($class === null) { 45 | throw new \RuntimeException(sprintf('The class metadata "%s" does not exit.', $type->getName())); 46 | } 47 | 48 | $visitor = $context->getVisitor(); 49 | 50 | if (!$visitor->startVisitingObject($data, $class, $context)) { 51 | return $visitor->visitNull(null, $type, $context); 52 | } 53 | 54 | foreach ($class->getProperties() as $property) { 55 | $visitor->visitObjectProperty($data, $property, $context); 56 | } 57 | 58 | return $visitor->finishVisitingObject($data, $class, $context); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Type/Parser/TypeLexer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type\Parser; 13 | 14 | use Doctrine\Common\Lexer\AbstractLexer; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | class TypeLexer extends AbstractLexer 20 | { 21 | const T_NONE = 1; 22 | const T_NAME = 2; 23 | const T_STRING = 3; 24 | const T_GREATER_THAN = 4; 25 | const T_LOWER_THAN = 5; 26 | const T_COMMA = 6; 27 | const T_EQUAL = 7; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | protected function getCatchablePatterns() 33 | { 34 | return [ 35 | '\'(?:[^\']|\'\')*\'', 36 | '([a-z0-9\\\\]+)', 37 | ]; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function getNonCatchablePatterns() 44 | { 45 | return [ 46 | '\s+', 47 | '(.)', 48 | ]; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | protected function getType(&$value) 55 | { 56 | if (ctype_alpha($value[0])) { 57 | return self::T_NAME; 58 | } 59 | 60 | if ($value[0] === '\'') { 61 | $value = str_replace('\'\'', '\'', substr($value, 1, -1)); 62 | 63 | return self::T_STRING; 64 | } 65 | 66 | if ($value === '>') { 67 | return self::T_GREATER_THAN; 68 | } 69 | 70 | if ($value === '<') { 71 | return self::T_LOWER_THAN; 72 | } 73 | 74 | if ($value === ',') { 75 | return self::T_COMMA; 76 | } 77 | 78 | if ($value === '=') { 79 | return self::T_EQUAL; 80 | } 81 | 82 | return self::T_NONE; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Type/Parser/TypeParserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type\Parser; 13 | 14 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 15 | 16 | /** 17 | * @author GeLo 18 | */ 19 | interface TypeParserInterface 20 | { 21 | /** 22 | * @param string $type 23 | * 24 | * @return TypeMetadataInterface 25 | */ 26 | public function parse($type); 27 | } 28 | -------------------------------------------------------------------------------- /src/Type/ResourceType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class ResourceType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitResource($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/StdClassType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Direction; 16 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class StdClassType implements TypeInterface 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 27 | { 28 | $visitor = $context->getVisitor(); 29 | 30 | if ($context->getDirection() === Direction::SERIALIZATION) { 31 | return $visitor->visitArray((array) $data, $type, $context); 32 | } 33 | 34 | return (object) $visitor->visitArray($data, $type, $context); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Type/StringType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | class StringType implements TypeInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context) 26 | { 27 | return $context->getVisitor()->visitString($data, $type, $context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Type/Type.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | /** 15 | * @author GeLo 16 | */ 17 | final class Type 18 | { 19 | const ARRAY_ = 'array'; 20 | const BOOL = 'bool'; 21 | const BOOLEAN = 'boolean'; 22 | const CLOSURE = \Closure::class; 23 | const DATE_TIME = \DateTimeInterface::class; 24 | const DOUBLE = 'double'; 25 | const EXCEPTION = \Exception::class; 26 | const FLOAT = 'float'; 27 | const INT = 'int'; 28 | const INTEGER = 'integer'; 29 | const NULL = 'null'; 30 | const NUMERIC = 'numeric'; 31 | const OBJECT = 'object'; 32 | const RESOURCE = 'resource'; 33 | const STD_CLASS = \stdClass::class; 34 | const STRING = 'string'; 35 | 36 | /** 37 | * @codeCoverageIgnore 38 | */ 39 | private function __construct() 40 | { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Type/TypeInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Type; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 16 | 17 | /** 18 | * @author GeLo 19 | */ 20 | interface TypeInterface 21 | { 22 | /** 23 | * @param mixed $data 24 | * @param TypeMetadataInterface $type 25 | * @param ContextInterface $context 26 | * 27 | * @return mixed 28 | */ 29 | public function convert($data, TypeMetadataInterface $type, ContextInterface $context); 30 | } 31 | -------------------------------------------------------------------------------- /src/Visitor/AbstractDeserializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Instantiator\InstantiatorInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | use Ivory\Serializer\Mutator\MutatorInterface; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | abstract class AbstractDeserializationVisitor extends AbstractGenericVisitor 24 | { 25 | /** 26 | * @var InstantiatorInterface 27 | */ 28 | private $instantiator; 29 | 30 | /** 31 | * @var MutatorInterface 32 | */ 33 | private $mutator; 34 | 35 | /** 36 | * @param InstantiatorInterface $instantiator 37 | * @param MutatorInterface $mutator 38 | */ 39 | public function __construct(InstantiatorInterface $instantiator, MutatorInterface $mutator) 40 | { 41 | $this->instantiator = $instantiator; 42 | $this->mutator = $mutator; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function prepare($data, ContextInterface $context) 49 | { 50 | return $this->decode(parent::prepare($data, $context)); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function visitBoolean($data, TypeMetadataInterface $type, ContextInterface $context) 57 | { 58 | return parent::visitBoolean( 59 | filter_var($data, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), 60 | $type, 61 | $context 62 | ); 63 | } 64 | 65 | /** 66 | * @param mixed $data 67 | * 68 | * @return mixed 69 | */ 70 | abstract protected function decode($data); 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | protected function doVisitObjectProperty( 76 | $data, 77 | $name, 78 | PropertyMetadataInterface $property, 79 | ContextInterface $context 80 | ) { 81 | $type = $property->getType(); 82 | 83 | if ($type === null) { 84 | throw new \RuntimeException(sprintf( 85 | 'You must define the type of the %s:%s.', 86 | $property->getClass(), 87 | $property->getName() 88 | )); 89 | } 90 | 91 | if (!$property->isWritable() || !isset($data[$name])) { 92 | return false; 93 | } 94 | 95 | $value = $this->navigator->navigate($data[$name], $context, $type); 96 | 97 | if ($value === null && $context->isNullIgnored()) { 98 | return false; 99 | } 100 | 101 | $this->mutator->setValue( 102 | $this->result, 103 | $property->hasMutator() ? $property->getMutator() : $property->getName(), 104 | $value 105 | ); 106 | 107 | return true; 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | protected function createResult($class) 114 | { 115 | return $this->instantiator->instantiate($class); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Visitor/AbstractGenericVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | abstract class AbstractGenericVisitor extends AbstractVisitor 22 | { 23 | /** 24 | * @var mixed[] 25 | */ 26 | private $stack; 27 | 28 | /** 29 | * @var mixed 30 | */ 31 | protected $result; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function prepare($data, ContextInterface $context) 37 | { 38 | $this->stack = []; 39 | $this->result = null; 40 | 41 | return parent::prepare($data, $context); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function visitArray($data, TypeMetadataInterface $type, ContextInterface $context) 48 | { 49 | $result = []; 50 | 51 | if (!empty($data)) { 52 | $this->enterScope(); 53 | $result = parent::visitArray($data, $type, $context); 54 | $this->leaveScope(); 55 | } 56 | 57 | return $this->visitData($result, $type, $context); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function visitData($data, TypeMetadataInterface $type, ContextInterface $context) 64 | { 65 | if ($this->result === null) { 66 | $this->result = $data; 67 | } 68 | 69 | return $data; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function startVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context) 76 | { 77 | if (!parent::startVisitingObject($data, $class, $context)) { 78 | return false; 79 | } 80 | 81 | $this->enterScope(); 82 | $this->result = $this->createResult($class->getName()); 83 | 84 | return true; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function finishVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context) 91 | { 92 | parent::finishVisitingObject($data, $class, $context); 93 | 94 | return $this->leaveScope(); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function getResult() 101 | { 102 | return $this->result; 103 | } 104 | 105 | /** 106 | * @param string $class 107 | * 108 | * @return mixed 109 | */ 110 | abstract protected function createResult($class); 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context) 116 | { 117 | $this->result = []; 118 | 119 | $keyType = $type->getOption('key'); 120 | $valueType = $type->getOption('value'); 121 | $ignoreNull = $context->isNullIgnored(); 122 | 123 | foreach ($data as $key => $value) { 124 | $value = $this->navigator->navigate($value, $context, $valueType); 125 | 126 | if ($value === null && $ignoreNull) { 127 | continue; 128 | } 129 | 130 | $key = $this->navigator->navigate($key, $context, $keyType); 131 | $this->result[$key] = $value; 132 | } 133 | 134 | return $this->result; 135 | } 136 | 137 | private function enterScope() 138 | { 139 | $this->stack[] = $this->result; 140 | } 141 | 142 | /** 143 | * @return mixed 144 | */ 145 | private function leaveScope() 146 | { 147 | $result = $this->result; 148 | $this->result = array_pop($this->stack); 149 | 150 | if ($this->result === null) { 151 | $this->result = $result; 152 | } 153 | 154 | return $result; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Visitor/AbstractSerializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor; 13 | 14 | use Ivory\Serializer\Accessor\AccessorInterface; 15 | use Ivory\Serializer\Context\ContextInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | abstract class AbstractSerializationVisitor extends AbstractGenericVisitor 22 | { 23 | /** 24 | * @var AccessorInterface 25 | */ 26 | private $accessor; 27 | 28 | /** 29 | * @param AccessorInterface $accessor 30 | */ 31 | public function __construct(AccessorInterface $accessor) 32 | { 33 | $this->accessor = $accessor; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getResult() 40 | { 41 | return $this->encode($this->result); 42 | } 43 | 44 | /** 45 | * @param mixed $data 46 | * 47 | * @return mixed 48 | */ 49 | abstract protected function encode($data); 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | protected function doVisitObjectProperty( 55 | $data, 56 | $name, 57 | PropertyMetadataInterface $property, 58 | ContextInterface $context 59 | ) { 60 | if (!$property->isReadable()) { 61 | return false; 62 | } 63 | 64 | $result = $this->navigator->navigate( 65 | $this->accessor->getValue( 66 | $data, 67 | $property->hasAccessor() ? $property->getAccessor() : $property->getName() 68 | ), 69 | $context, 70 | $property->getType() 71 | ); 72 | 73 | if ($result === null && $context->isNullIgnored()) { 74 | return false; 75 | } 76 | 77 | $this->result[$name] = $result; 78 | 79 | return true; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | protected function createResult($class) 86 | { 87 | return []; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Visitor/Json/JsonDeserializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor\Json; 13 | 14 | use Ivory\Serializer\Instantiator\InstantiatorInterface; 15 | use Ivory\Serializer\Mutator\MutatorInterface; 16 | use Ivory\Serializer\Visitor\AbstractDeserializationVisitor; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class JsonDeserializationVisitor extends AbstractDeserializationVisitor 22 | { 23 | /** 24 | * @var int 25 | */ 26 | private $maxDepth; 27 | 28 | /** 29 | * @var int 30 | */ 31 | private $options; 32 | 33 | /** 34 | * @param InstantiatorInterface $instantiator 35 | * @param MutatorInterface $mutator 36 | * @param int $maxDepth 37 | * @param int $options 38 | */ 39 | public function __construct( 40 | InstantiatorInterface $instantiator, 41 | MutatorInterface $mutator, 42 | $maxDepth = 512, 43 | $options = 0 44 | ) { 45 | parent::__construct($instantiator, $mutator); 46 | 47 | $this->maxDepth = $maxDepth; 48 | $this->options = $options; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | protected function decode($data) 55 | { 56 | $result = @json_decode($data, true, $this->maxDepth, $this->options); 57 | 58 | if (json_last_error() !== JSON_ERROR_NONE) { 59 | throw new \InvalidArgumentException(json_last_error_msg()); 60 | } 61 | 62 | return $result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Visitor/Json/JsonSerializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor\Json; 13 | 14 | use Ivory\Serializer\Accessor\AccessorInterface; 15 | use Ivory\Serializer\Context\ContextInterface; 16 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | use Ivory\Serializer\Visitor\AbstractSerializationVisitor; 19 | 20 | /** 21 | * @author GeLo 22 | */ 23 | class JsonSerializationVisitor extends AbstractSerializationVisitor 24 | { 25 | /** 26 | * @var int 27 | */ 28 | private $options; 29 | 30 | /** 31 | * @param AccessorInterface $accessor 32 | * @param int $options 33 | */ 34 | public function __construct(AccessorInterface $accessor, $options = 0) 35 | { 36 | parent::__construct($accessor); 37 | 38 | if (defined('JSON_PRESERVE_ZERO_FRACTION')) { 39 | $options |= JSON_PRESERVE_ZERO_FRACTION; 40 | } 41 | 42 | $this->options = $options; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function visitData($data, TypeMetadataInterface $type, ContextInterface $context) 49 | { 50 | if ($data === [] && class_exists($type->getName())) { 51 | $data = (object) $data; 52 | } 53 | 54 | return parent::visitData($data, $type, $context); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function finishVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context) 61 | { 62 | if ($this->result === []) { 63 | $this->result = (object) $this->result; 64 | } 65 | 66 | return parent::finishVisitingObject($data, $class, $context); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | protected function encode($data) 73 | { 74 | $result = @json_encode($data, $this->options); 75 | 76 | if (json_last_error() !== JSON_ERROR_NONE) { 77 | throw new \InvalidArgumentException(json_last_error_msg()); 78 | } 79 | 80 | return $result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Visitor/VisitorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor; 13 | 14 | use Ivory\Serializer\Context\ContextInterface; 15 | use Ivory\Serializer\Mapping\ClassMetadataInterface; 16 | use Ivory\Serializer\Mapping\PropertyMetadataInterface; 17 | use Ivory\Serializer\Mapping\TypeMetadataInterface; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | interface VisitorInterface 23 | { 24 | /** 25 | * @param mixed $data 26 | * @param ContextInterface $context 27 | * 28 | * @return mixed 29 | */ 30 | public function prepare($data, ContextInterface $context); 31 | 32 | /** 33 | * @param mixed $data 34 | * @param TypeMetadataInterface $type 35 | * @param ContextInterface $context 36 | * 37 | * @return mixed 38 | */ 39 | public function visitArray($data, TypeMetadataInterface $type, ContextInterface $context); 40 | 41 | /** 42 | * @param mixed $data 43 | * @param TypeMetadataInterface $type 44 | * @param ContextInterface $context 45 | * 46 | * @return mixed 47 | */ 48 | public function visitBoolean($data, TypeMetadataInterface $type, ContextInterface $context); 49 | 50 | /** 51 | * @param mixed $data 52 | * @param TypeMetadataInterface $type 53 | * @param ContextInterface $context 54 | * 55 | * @return mixed 56 | */ 57 | public function visitData($data, TypeMetadataInterface $type, ContextInterface $context); 58 | 59 | /** 60 | * @param mixed $data 61 | * @param TypeMetadataInterface $type 62 | * @param ContextInterface $context 63 | * 64 | * @return mixed 65 | */ 66 | public function visitFloat($data, TypeMetadataInterface $type, ContextInterface $context); 67 | 68 | /** 69 | * @param mixed $data 70 | * @param TypeMetadataInterface $type 71 | * @param ContextInterface $context 72 | * 73 | * @return mixed 74 | */ 75 | public function visitInteger($data, TypeMetadataInterface $type, ContextInterface $context); 76 | 77 | /** 78 | * @param mixed $data 79 | * @param TypeMetadataInterface $type 80 | * @param ContextInterface $context 81 | * 82 | * @return mixed 83 | */ 84 | public function visitNull($data, TypeMetadataInterface $type, ContextInterface $context); 85 | 86 | /** 87 | * @param mixed $data 88 | * @param TypeMetadataInterface $type 89 | * @param ContextInterface $context 90 | * 91 | * @return mixed 92 | */ 93 | public function visitResource($data, TypeMetadataInterface $type, ContextInterface $context); 94 | 95 | /** 96 | * @param mixed $data 97 | * @param TypeMetadataInterface $type 98 | * @param ContextInterface $context 99 | * 100 | * @return mixed 101 | */ 102 | public function visitString($data, TypeMetadataInterface $type, ContextInterface $context); 103 | 104 | /** 105 | * @param mixed $data 106 | * @param ClassMetadataInterface $class 107 | * @param ContextInterface $context 108 | * 109 | * @return bool 110 | */ 111 | public function startVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context); 112 | 113 | /** 114 | * @param mixed $data 115 | * @param PropertyMetadataInterface $property 116 | * @param ContextInterface $context 117 | * 118 | * @return bool 119 | */ 120 | public function visitObjectProperty($data, PropertyMetadataInterface $property, ContextInterface $context); 121 | 122 | /** 123 | * @param mixed $data 124 | * @param ClassMetadataInterface $class 125 | * @param ContextInterface $context 126 | * 127 | * @return mixed 128 | */ 129 | public function finishVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context); 130 | 131 | /** 132 | * @return mixed 133 | */ 134 | public function getResult(); 135 | } 136 | -------------------------------------------------------------------------------- /src/Visitor/Yaml/YamlDeserializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor\Yaml; 13 | 14 | use Ivory\Serializer\Instantiator\InstantiatorInterface; 15 | use Ivory\Serializer\Mutator\MutatorInterface; 16 | use Ivory\Serializer\Visitor\AbstractDeserializationVisitor; 17 | use Symfony\Component\Yaml\Yaml; 18 | 19 | /** 20 | * @author GeLo 21 | */ 22 | class YamlDeserializationVisitor extends AbstractDeserializationVisitor 23 | { 24 | /** 25 | * @var int 26 | */ 27 | private $options; 28 | 29 | /** 30 | * @param InstantiatorInterface $instantiator 31 | * @param MutatorInterface $mutator 32 | * @param int $options 33 | */ 34 | public function __construct(InstantiatorInterface $instantiator, MutatorInterface $mutator, $options = 0) 35 | { 36 | parent::__construct($instantiator, $mutator); 37 | 38 | $this->options = $options; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function decode($data) 45 | { 46 | try { 47 | return Yaml::parse($data, $this->options); 48 | } catch (\Exception $e) { 49 | throw new \InvalidArgumentException('Unable to deserialize data.', 0, $e); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Visitor/Yaml/YamlSerializationVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Ivory\Serializer\Visitor\Yaml; 13 | 14 | use Ivory\Serializer\Accessor\AccessorInterface; 15 | use Ivory\Serializer\Visitor\AbstractSerializationVisitor; 16 | use Symfony\Component\Yaml\Yaml; 17 | 18 | /** 19 | * @author GeLo 20 | */ 21 | class YamlSerializationVisitor extends AbstractSerializationVisitor 22 | { 23 | /** 24 | * @var int 25 | */ 26 | private $inline; 27 | 28 | /** 29 | * @var int 30 | */ 31 | private $indent; 32 | 33 | /** 34 | * @var int 35 | */ 36 | private $options; 37 | 38 | /** 39 | * @param AccessorInterface $accessor 40 | * @param int $inline 41 | * @param int $indent 42 | * @param int $options 43 | */ 44 | public function __construct(AccessorInterface $accessor, $inline = 2, $indent = 4, $options = 0) 45 | { 46 | parent::__construct($accessor); 47 | 48 | $this->inline = $inline; 49 | $this->indent = $indent; 50 | $this->options = $options; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | protected function encode($data) 57 | { 58 | try { 59 | return Yaml::dump($data, $this->inline, $this->indent, $this->options); 60 | } catch (\Exception $e) { 61 | throw new \InvalidArgumentException('Unable to serialize data.', 0, $e); 62 | } 63 | } 64 | } 65 | --------------------------------------------------------------------------------