├── .run └── phpunit.run.xml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── META.md ├── README.md ├── UPGRADING.md ├── composer.json ├── doc ├── LICENSE ├── conf.py ├── configuration.rst ├── cookbook.rst ├── cookbook │ ├── arrays.rst │ ├── exclusion_strategies.rst │ ├── object_constructor.rst │ └── stdclass.rst ├── event_system.rst ├── handlers.rst ├── index.rst ├── logo-small.png ├── logo.png ├── reference.rst ├── reference │ ├── annotations.rst │ ├── xml_reference.rst │ └── yml_reference.rst ├── requirements.txt └── usage.rst ├── phpbench.json ├── phpstan.neon.dist ├── rector.php └── src ├── AbstractVisitor.php ├── Accessor ├── AccessorStrategyInterface.php └── DefaultAccessorStrategy.php ├── Annotation ├── AccessType.php ├── Accessor.php ├── AccessorOrder.php ├── AnnotationUtilsTrait.php ├── DeprecatedReadOnly.php ├── Discriminator.php ├── Exclude.php ├── ExclusionPolicy.php ├── Expose.php ├── Groups.php ├── Inline.php ├── MaxDepth.php ├── PostDeserialize.php ├── PostSerialize.php ├── PreSerialize.php ├── ReadOnly.php ├── ReadOnlyProperty.php ├── SerializedName.php ├── SerializerAttribute.php ├── Since.php ├── SkipWhenEmpty.php ├── Type.php ├── UnionDiscriminator.php ├── Until.php ├── Version.php ├── VirtualProperty.php ├── XmlAttribute.php ├── XmlAttributeMap.php ├── XmlCollection.php ├── XmlDiscriminator.php ├── XmlElement.php ├── XmlKeyValuePairs.php ├── XmlList.php ├── XmlMap.php ├── XmlNamespace.php ├── XmlRoot.php └── XmlValue.php ├── ArrayTransformerInterface.php ├── Builder ├── CallbackDriverFactory.php ├── DefaultDriverFactory.php ├── DocBlockDriverFactory.php └── DriverFactoryInterface.php ├── Construction ├── DoctrineObjectConstructor.php ├── ObjectConstructorInterface.php └── UnserializeObjectConstructor.php ├── Context.php ├── ContextFactory ├── CallableContextFactory.php ├── CallableDeserializationContextFactory.php ├── CallableSerializationContextFactory.php ├── DefaultDeserializationContextFactory.php ├── DefaultSerializationContextFactory.php ├── DeserializationContextFactoryInterface.php └── SerializationContextFactoryInterface.php ├── DeserializationContext.php ├── EventDispatcher ├── Event.php ├── EventDispatcher.php ├── EventDispatcherInterface.php ├── EventSubscriberInterface.php ├── Events.php ├── LazyEventDispatcher.php ├── ObjectEvent.php ├── PreDeserializeEvent.php ├── PreSerializeEvent.php └── Subscriber │ ├── DoctrineProxySubscriber.php │ ├── EnumSubscriber.php │ └── SymfonyValidatorValidatorSubscriber.php ├── Exception ├── CircularReferenceDetectedException.php ├── Exception.php ├── ExcludedClassException.php ├── ExpressionLanguageRequiredException.php ├── InvalidArgumentException.php ├── InvalidMetadataException.php ├── LogicException.php ├── NonCastableTypeException.php ├── NonFloatCastableTypeException.php ├── NonIntCastableTypeException.php ├── NonStringCastableTypeException.php ├── NonVisitableTypeException.php ├── NotAcceptableException.php ├── ObjectConstructionException.php ├── RuntimeException.php ├── SkipHandlerException.php ├── UninitializedPropertyException.php ├── UnsupportedFormatException.php ├── ValidationFailedException.php └── XmlErrorException.php ├── Exclusion ├── DepthExclusionStrategy.php ├── DisjunctExclusionStrategy.php ├── ExclusionStrategyInterface.php ├── ExpressionLanguageExclusionStrategy.php ├── GroupsExclusionStrategy.php └── VersionExclusionStrategy.php ├── Expression ├── CompilableExpressionEvaluatorInterface.php ├── Expression.php ├── ExpressionEvaluator.php └── ExpressionEvaluatorInterface.php ├── Functions.php ├── GraphNavigator.php ├── GraphNavigator ├── DeserializationGraphNavigator.php ├── Factory │ ├── DeserializationGraphNavigatorFactory.php │ ├── GraphNavigatorFactoryInterface.php │ └── SerializationGraphNavigatorFactory.php └── SerializationGraphNavigator.php ├── GraphNavigatorInterface.php ├── Handler ├── ArrayCollectionHandler.php ├── ConstraintViolationHandler.php ├── DateHandler.php ├── EnumHandler.php ├── FormErrorHandler.php ├── HandlerRegistry.php ├── HandlerRegistryInterface.php ├── IteratorHandler.php ├── LazyHandlerRegistry.php ├── StdClassHandler.php ├── SubscribingHandlerInterface.php ├── SymfonyUidHandler.php └── UnionHandler.php ├── JsonDeserializationStrictVisitor.php ├── JsonDeserializationVisitor.php ├── JsonSerializationVisitor.php ├── Metadata ├── ClassMetadata.php ├── Driver │ ├── AbstractDoctrineTypeDriver.php │ ├── AnnotationDriver.php │ ├── AnnotationOrAttributeDriver.php │ ├── AttributeDriver.php │ ├── AttributeDriver │ │ └── AttributeReader.php │ ├── DefaultValuePropertyDriver.php │ ├── DocBlockDriver.php │ ├── DocBlockDriver │ │ └── DocBlockTypeResolver.php │ ├── DoctrinePHPCRTypeDriver.php │ ├── DoctrineTypeDriver.php │ ├── EnumPropertiesDriver.php │ ├── ExpressionMetadataTrait.php │ ├── NullDriver.php │ ├── TypedPropertiesDriver.php │ ├── XmlDriver.php │ └── YamlDriver.php ├── ExpressionPropertyMetadata.php ├── PropertyMetadata.php ├── StaticPropertyMetadata.php └── VirtualPropertyMetadata.php ├── Naming ├── CamelCaseNamingStrategy.php ├── IdenticalPropertyNamingStrategy.php ├── PropertyNamingStrategyInterface.php └── SerializedNameAnnotationStrategy.php ├── NullAwareVisitorInterface.php ├── Ordering ├── AlphabeticalPropertyOrderingStrategy.php ├── CustomPropertyOrderingStrategy.php ├── IdenticalPropertyOrderingStrategy.php └── PropertyOrderingInterface.php ├── SerializationContext.php ├── Serializer.php ├── SerializerBuilder.php ├── SerializerInterface.php ├── Twig ├── SerializerBaseExtension.php ├── SerializerExtension.php ├── SerializerRuntimeExtension.php └── SerializerRuntimeHelper.php ├── Type ├── Exception │ ├── Exception.php │ ├── InvalidNode.php │ └── SyntaxError.php ├── Lexer.php ├── Parser.php ├── ParserInterface.php └── Type.php ├── Visitor ├── DeserializationVisitorInterface.php ├── Factory │ ├── DeserializationVisitorFactory.php │ ├── JsonDeserializationVisitorFactory.php │ ├── JsonSerializationVisitorFactory.php │ ├── SerializationVisitorFactory.php │ ├── XmlDeserializationVisitorFactory.php │ └── XmlSerializationVisitorFactory.php └── SerializationVisitorInterface.php ├── VisitorInterface.php ├── XmlDeserializationVisitor.php └── XmlSerializationVisitor.php /.run/phpunit.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | Thank you for contributing! 4 | 5 | Before we can merge your Pull-Request here are some guidelines that you need to follow. 6 | These guidelines exist not to annoy you, but to keep the code base clean, unified and future proof. 7 | 8 | ## Coding Standard 9 | 10 | This project uses [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) to enforce coding standards. 11 | The coding standard rules are defined in the **phpcs.xml.dist** file (part of this repository). 12 | The project follows a relaxed version of the Doctrine Coding standards v4. 13 | 14 | Your Pull-Request must be compliant with the said standard. 15 | To check your code you can run `vendor/bin/phpcs`. This command will give you a list of violations in your code (if any). 16 | The most common errors can be automatically fixed just by running `vendor/bin/phpcbf`. 17 | 18 | ## Dependencies 19 | 20 | We're using [`composer/composer`](https://github.com/composer/composer) to manage dependencies 21 | 22 | ## Unit-Tests 23 | 24 | Please try to add a test for your pull-request. This project uses [PHPUnit](https://phpunit.de/) as testing framework. 25 | 26 | You can run the unit-tests by calling `vendor/bin/phpunit`. 27 | 28 | New features without tests can't be merged. 29 | 30 | 31 | ## Documentation 32 | 33 | The documentation is stored in the `doc` folder and is written using the [rST](http://docutils.sourceforge.net/rst.html) language. 34 | If you are adding a new feature, you must update the documentation. 35 | 36 | To test doc rendering before submitting your PR, you will need [Sphinx](http://www.sphinx-doc.org/en/stable/). 37 | 38 | To install `Sphinx` just run: 39 | 40 | ```bash 41 | pip install --requirement doc/requirements.txt --user 42 | ``` 43 | 44 | When that is done, just run: 45 | 46 | ```bash 47 | cd doc && sphinx-build -W -b html -d _build/doctrees . _build/html 48 | ``` 49 | 50 | ## CI 51 | 52 | We automatically run your pull request through [Github Actions](https://github.com/schmittjoh/serializer/actions). 53 | If you break the tests, we cannot merge your code, 54 | so please make sure that your code is working before opening up a Pull-Request. 55 | 56 | ## Issues and Bugs 57 | 58 | To create a new issue, you can use the GitHub issue tracking system. 59 | Please try to avoid opening support-related tickets. For support related questions please use more appropriate 60 | channels as Q&A platforms (such as Stackoverflow), Forums, Local PHP user groups. 61 | 62 | If you are a Symfony user, please try to distinguish between issues related to the Bundle and issues related to this 63 | library. 64 | 65 | ## Getting merged 66 | 67 | Please allow us time to review your pull requests. 68 | We will give our best to review everything as fast as possible, but cannot always live up to our own expectations. 69 | 70 | Please, write [commit messages that make 71 | sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), 72 | and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) 73 | before submitting your Pull Request. 74 | 75 | One may ask you to [squash your 76 | commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) 77 | too. This is used to "clean" your Pull Request before merging it (we don't want 78 | commits such as "fix tests", "fix 2", "fix 3", etc.). 79 | 80 | Pull requests without tests most probably will not be merged. 81 | Documentation PRs obviously do not require tests. 82 | 83 | Thank you very much again for your contribution! 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Johannes M. Schmitt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /META.md: -------------------------------------------------------------------------------- 1 | # Generating changelog 2 | 3 | Use: https://github.com/skywinder/Github-Changelog-Generator 4 | 5 | ```bash 6 | github_changelog_generator --user=schmittjoh --project=serializer --pull-requests --no-compare-link -t GITHUB-TOKEN 7 | ``` 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > # UKRAINE NEEDS YOUR HELP NOW! 2 | > 3 | > On 24 February 2022, Russian [President Vladimir Putin ordered an invasion of Ukraine by Russian Armed Forces](https://www.bbc.com/news/world-europe-60504334). 4 | > 5 | > Your support is urgently needed. 6 | > 7 | > - Donate to the volunteers. Here is the volunteer fund helping the Ukrainian army to provide all the necessary equipment: 8 | > https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi or https://savelife.in.ua/en/donate/ 9 | > - Triple-check social media sources. Russian disinformation is attempting to coverup and distort the reality in Ukraine. 10 | > - Help Ukrainian refugees who are fleeing Russian attacks and shellings: https://www.globalcitizen.org/en/content/ways-to-help-ukraine-conflict/ 11 | > - Put pressure on your political representatives to provide help to Ukraine. 12 | > - Believe in the Ukrainian people, they will not surrender, they don't have another Ukraine. 13 | > 14 | > THANK YOU! 15 | ---- 16 | 17 | # jms/serializer 18 | 19 | [![GitHub Actions][GA Image]][GA Link] 20 | [![Packagist][Packagist Image]][Packagist Link] 21 | 22 | ![alt text](doc/logo-small.png) 23 | 24 | ## Introduction 25 | 26 | This library allows you to (de-)serialize data of any complexity. Currently, it supports XML and JSON. 27 | 28 | It also provides you with a rich tool-set to adapt the output to your specific needs. 29 | 30 | Built-in features include: 31 | 32 | - (De-)serialize data of any complexity; circular references and complex exclusion strategies are handled gracefully. 33 | - Supports many built-in PHP types (such as dates, intervals) 34 | - Integrates with Doctrine ORM, et. al. 35 | - Supports versioning, e.g. for APIs 36 | - Configurable via XML, YAML, or Annotations 37 | 38 | 39 | ## Documentation 40 | 41 | Learn more about the serializer in its [documentation](http://jmsyst.com/libs/serializer). 42 | 43 | ## Notes 44 | 45 | You are browsing the code for the 3.x version, if you are interested in the 1.x or 2.x version, 46 | check the [1.x][1.x] and [2.x][2.x] branches. 47 | 48 | The version `3.x` is the supported version (`master` branch). 49 | The `1.x` and `2.x` versions are not supported anymore. 50 | 51 | For the `1.x` and `2.x` branches there will be no additional feature releases. 52 | Security issues will be fixed till the 1st January 2020 and 53 | only critical bugs might receive fixes until the 1st September 2019. 54 | 55 | Instructions on how to upgrade to 3.x are available in the [UPGRADING][UPGRADING] document. 56 | 57 | ## Professional Support 58 | 59 | For eventual paid support please write an email to [goetas@gmail.com](mailto:goetas@gmail.com). 60 | 61 | 62 | [CHANGELOG]: https://github.com/schmittjoh/serializer/blob/master/CHANGELOG.md 63 | [UPGRADING]: https://github.com/schmittjoh/serializer/blob/master/UPGRADING.md 64 | 65 | [GA Image]: https://github.com/schmittjoh/serializer/workflows/CI/badge.svg 66 | 67 | [GA Link]: https://github.com/schmittjoh/serializer/actions?query=workflow%3A%22CI%22+branch%3Amaster 68 | 69 | [Packagist Image]: https://img.shields.io/packagist/v/jms/serializer.svg 70 | 71 | [Packagist Link]: https://packagist.org/packages/jms/serializer 72 | 73 | [1.x]: https://github.com/schmittjoh/serializer/tree/1.x 74 | [2.x]: https://github.com/schmittjoh/serializer/tree/2.x 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jms/serializer", 3 | "type": "library", 4 | "description": "Library for (de-)serializing data of any complexity; supports XML, and JSON.", 5 | "keywords": [ 6 | "serialization", 7 | "deserialization", 8 | "json", 9 | "jaxb", 10 | "xml" 11 | ], 12 | "homepage": "http://jmsyst.com/libs/serializer", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Johannes M. Schmitt", 17 | "email": "schmittjoh@gmail.com" 18 | }, 19 | { 20 | "name": "Asmir Mustafic", 21 | "email": "goetas@gmail.com" 22 | } 23 | ], 24 | "require": { 25 | "php": "^7.4 || ^8.0", 26 | "doctrine/instantiator": "^1.3.1 || ^2.0", 27 | "doctrine/lexer": "^2.0 || ^3.0", 28 | "jms/metadata": "^2.6", 29 | "phpstan/phpdoc-parser": "^1.20 || ^2.0" 30 | }, 31 | "suggest": { 32 | "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", 33 | "symfony/cache": "Required if you like to use cache functionality.", 34 | "symfony/uid": "Required if you'd like to serialize UID objects.", 35 | "symfony/yaml": "Required if you'd like to use the YAML metadata format." 36 | }, 37 | "require-dev": { 38 | "ext-pdo_sqlite": "*", 39 | "doctrine/annotations": "^1.14 || ^2.0", 40 | "slevomat/coding-standard": "dev-master#f2cc4c553eae68772624ffd7dd99022343b69c31 as 8.11.9999", 41 | "doctrine/coding-standard": "^12.0", 42 | "doctrine/orm": "^2.14 || ^3.0", 43 | "doctrine/persistence": "^2.5.2 || ^3.0", 44 | "doctrine/phpcr-odm": "^1.5.2 || ^2.0", 45 | "jackalope/jackalope-doctrine-dbal": "^1.3", 46 | "ocramius/proxy-manager": "^1.0 || ^2.0", 47 | "phpbench/phpbench": "^1.0", 48 | "phpstan/phpstan": "^2.0", 49 | "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0", 50 | "psr/container": "^1.0 || ^2.0", 51 | "rector/rector": "^1.0.0 || ^2.0@dev", 52 | "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", 53 | "symfony/expression-language": "^5.4 || ^6.0 || ^7.0", 54 | "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", 55 | "symfony/form": "^5.4 || ^6.0 || ^7.0", 56 | "symfony/translation": "^5.4 || ^6.0 || ^7.0", 57 | "symfony/uid": "^5.4 || ^6.0 || ^7.0", 58 | "symfony/validator": "^5.4 || ^6.0 || ^7.0", 59 | "symfony/yaml": "^5.4 || ^6.0 || ^7.0", 60 | "twig/twig": "^1.34 || ^2.4 || ^3.0" 61 | }, 62 | "autoload": { 63 | "psr-4": { 64 | "JMS\\Serializer\\": "src/" 65 | } 66 | }, 67 | "autoload-dev": { 68 | "psr-4": { 69 | "JMS\\Serializer\\Tests\\": "tests/" 70 | } 71 | }, 72 | "config": { 73 | "sort-packages": true, 74 | "allow-plugins": { 75 | "composer/package-versions-deprecated": true, 76 | "dealerdirect/phpcodesniffer-composer-installer": true 77 | } 78 | }, 79 | "extra": { 80 | "branch-alias": { 81 | "dev-master": "3.x-dev" 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, os 4 | 5 | # -- General configuration ----------------------------------------------------- 6 | 7 | # Add any Sphinx extension module names here, as strings. They can be extensions 8 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 9 | extensions = ['sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode'] 10 | 11 | # Add any paths that contain templates here, relative to this directory. 12 | templates_path = ['_templates'] 13 | 14 | # The suffix of source filenames. 15 | source_suffix = '.rst' 16 | 17 | # The master toctree document. 18 | master_doc = 'index' 19 | 20 | # List of patterns, relative to source directory, that match files and 21 | # directories to ignore when looking for source files. 22 | exclude_patterns = ['_build'] 23 | 24 | # The name of the Pygments (syntax highlighting) style to use. 25 | pygments_style = 'sphinx' 26 | 27 | # This will be used when using the shorthand notation 28 | highlight_language = 'php' 29 | 30 | # -- Options for HTML output --------------------------------------------------- 31 | import sphinx_rtd_theme 32 | 33 | # The theme to use for HTML and HTML Help pages. See the documentation for 34 | # a list of builtin themes. 35 | html_theme = 'sphinx_rtd_theme' 36 | 37 | # Add any paths that contain custom themes here, relative to this directory. 38 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 39 | 40 | # Output file base name for HTML help builder. 41 | htmlhelp_basename = 'doc' 42 | -------------------------------------------------------------------------------- /doc/cookbook.rst: -------------------------------------------------------------------------------- 1 | Cookbook 2 | ======== 3 | 4 | .. toctree :: 5 | :glob: 6 | 7 | cookbook/* -------------------------------------------------------------------------------- /doc/cookbook/arrays.rst: -------------------------------------------------------------------------------- 1 | Serializing arrays and hashes 2 | ============================= 3 | 4 | Introduction 5 | ------------ 6 | Serializing arrays and hashes (a concept that in PHP has not explicit boundaries) 7 | can be challenging. The serializer offers via ``@Type`` annotation different options 8 | to configure its behavior, but if we try to serialize directly an array 9 | (not as a property of an object), we need to use context information to determine the 10 | array "type" 11 | 12 | Examples 13 | -------- 14 | 15 | In case of a JSON serialization: 16 | 17 | .. code-block :: php 18 | 19 | serialize([1, 2]); // [1, 2] 23 | $serializer->serialize(['a', 'b']); // ['a', 'b'] 24 | $serializer->serialize(['c' => 'd']); // {"c" => "d"} 25 | 26 | // same as default (let the PHP's json_encode function decide) 27 | $serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array')); // [1, 2] 28 | $serializer->serialize([1 => 2], SerializationContext::create()->setInitialType('array')); // {"1": 2} 29 | $serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array')); // ['a', 'b'] 30 | $serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array')); // {"c" => "d"} 31 | 32 | // typehint as strict array, keys will be always discarded 33 | $serializer->serialize([], SerializationContext::create()->setInitialType('array')); // [] 34 | $serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array')); // [1, 2] 35 | $serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array')); // ['a', 'b'] 36 | $serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array')); // ["d"] 37 | 38 | // typehint as hash, keys will be always considered 39 | $serializer->serialize([], SerializationContext::create()->setInitialType('array')); // {} 40 | $serializer->serialize([1, 2], SerializationContext::create()->setInitialType('array')); // {"0" : 1, "1" : 2} 41 | $serializer->serialize(['a', 'b'], SerializationContext::create()->setInitialType('array')); // {"0" : "a", "1" : "b"} 42 | $serializer->serialize(['c' => 'd'], SerializationContext::create()->setInitialType('array')); // {"c" : "d"} 43 | 44 | 45 | .. note :: 46 | 47 | This applies only for the JSON serialization. 48 | -------------------------------------------------------------------------------- /doc/cookbook/object_constructor.rst: -------------------------------------------------------------------------------- 1 | Object constructor 2 | ================== 3 | 4 | Deserialize on existing objects 5 | ------------------------------- 6 | 7 | By default, a brand new instance of target class is created during deserialization. To deserialize into an existing object, you need to perform the following steps. 8 | 9 | 10 | 1. Create new class which implements ObjectConstructorInterface 11 | 12 | .. code-block:: php 13 | 14 | fallbackConstructor = $fallbackConstructor; 32 | } 33 | 34 | public function construct(DeserializationVisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context): ?object 35 | { 36 | if ($context->hasAttribute(self::ATTRIBUTE)) { 37 | return $context->getAttribute(self::ATTRIBUTE); 38 | } 39 | 40 | return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context); 41 | } 42 | } 43 | 44 | 45 | 2. Register ExistingObjectConstructor. 46 | 47 | You should pass ExistingObjectConstructor to DeserializationGraphNavigatorFactory constructor. 48 | 49 | 50 | 3. Add special attribute to DeserializationContext 51 | 52 | .. code-block:: php 53 | 54 | $context = DeserializationContext::create(); 55 | $context->setAttribute('deserialization-constructor-target', $document); 56 | $serializer->deserialize($data, get_class($document), 'json'); 57 | -------------------------------------------------------------------------------- /doc/cookbook/stdclass.rst: -------------------------------------------------------------------------------- 1 | stdClass 2 | ======== 3 | 4 | The serializer offers support for serializing ``stdClass`` objects, however the use of 5 | ``stdClass`` objects is discouraged. 6 | 7 | The current implementation serializes all the properties of a ``stdClass`` object in 8 | the order they appear. 9 | 10 | There are many known limitations when dealing with ``stdClass`` objects. 11 | More in detail, it is not possible to: 12 | 13 | - change serialization order of properties 14 | - apply per-property exclusion policies 15 | - specify any extra serialization information for properties that are part of the ``stdClass`` object, as serialization name, type, xml structure and so on 16 | - deserialize data into ``stdClass`` objects 17 | -------------------------------------------------------------------------------- /doc/event_system.rst: -------------------------------------------------------------------------------- 1 | Event System 2 | ============ 3 | 4 | The serializer dispatches different events during the serialization, and 5 | deserialization process which you can use to hook in and alter the default 6 | behavior. 7 | 8 | Register an Event Listener, or Subscriber 9 | ----------------------------------------- 10 | The difference between listeners, and subscribers is that listener do not know to which events they listen 11 | while subscribers contain that information. Thus, subscribers are easier to share, and re-use. Listeners 12 | on the other hand, can be simple callables and do not require a dedicated class. 13 | 14 | .. code-block :: php 15 | 16 | class MyEventSubscriber implements JMS\Serializer\EventDispatcher\EventSubscriberInterface 17 | { 18 | public static function getSubscribedEvents() 19 | { 20 | return array( 21 | array( 22 | 'event' => 'serializer.pre_serialize', 23 | 'method' => 'onPreSerialize', 24 | 'class' => 'AppBundle\\Entity\\SpecificClass', // if no class, subscribe to every serialization 25 | 'format' => 'json', // optional format 26 | 'priority' => 0, // optional priority 27 | ), 28 | ); 29 | } 30 | 31 | public function onPreSerialize(JMS\Serializer\EventDispatcher\PreSerializeEvent $event) 32 | { 33 | // do something 34 | } 35 | } 36 | 37 | $builder 38 | ->configureListeners(function(JMS\Serializer\EventDispatcher\EventDispatcher $dispatcher) { 39 | $dispatcher->addListener('serializer.pre_serialize', 40 | function(JMS\Serializer\EventDispatcher\PreSerializeEvent $event) { 41 | // do something 42 | } 43 | ); 44 | 45 | $dispatcher->addSubscriber(new MyEventSubscriber()); 46 | }) 47 | ; 48 | 49 | Events 50 | ------ 51 | 52 | serializer.pre_serialize 53 | ~~~~~~~~~~~~~~~~~~~~~~~~ 54 | This is dispatched before a type is visited. You have access to the visitor, 55 | data, and type. Listeners may modify the type that is being used for 56 | serialization. 57 | 58 | **Event Object**: ``JMS\Serializer\EventDispatcher\PreSerializeEvent`` 59 | 60 | serializer.post_serialize 61 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 62 | This is dispatched right before a type is left. You can for example use this 63 | to add additional data for an object that you normally do not save inside 64 | objects such as links. 65 | 66 | **Event Object**: ``JMS\Serializer\EventDispatcher\ObjectEvent`` 67 | 68 | serializer.pre_deserialize 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | 71 | .. versionadded : 0.12 72 | Event was added 73 | 74 | This is dispatched before an object is deserialized. You can use this to 75 | modify submitted data, or modify the type that is being used for deserialization. 76 | 77 | **Event Object**: ``JMS\Serializer\EventDispatcher\PreDeserializeEvent`` 78 | 79 | serializer.post_deserialize 80 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 81 | This is dispatched after a type is processed. You can use it to normalize 82 | submitted data if you require external services for example, or also to 83 | perform validation of the submitted data. 84 | 85 | **Event Object**: ``JMS\Serializer\EventDispatcher\ObjectEvent`` 86 | -------------------------------------------------------------------------------- /doc/handlers.rst: -------------------------------------------------------------------------------- 1 | Handlers 2 | ======== 3 | 4 | Introduction 5 | ------------ 6 | Handlers allow you to change the serialization, or deserialization process 7 | for a single type/format combination. 8 | 9 | Handlers are simple callback which receive three arguments: the visitor, 10 | the data, and the type. 11 | 12 | Simple Callables 13 | ---------------- 14 | You can register simple callables on the builder object:: 15 | 16 | $builder 17 | ->configureHandlers(function(JMS\Serializer\Handler\HandlerRegistry $registry) { 18 | $registry->registerHandler('serialization', 'MyObject', 'json', 19 | function($visitor, MyObject $obj, array $type) { 20 | return $obj->getName(); 21 | } 22 | ); 23 | }) 24 | ; 25 | 26 | .. note :: 27 | 28 | Be aware that when you call `configureHandlers` default handlers (like `DateHandler`) 29 | won't be added and you will have to call `addDefaultHandlers` on the Builder 30 | 31 | Subscribing Handlers 32 | -------------------- 33 | Subscribing handlers contain the configuration themselves which makes them easier to share with other users, 34 | and easier to set-up in general:: 35 | 36 | use JMS\Serializer\Handler\SubscribingHandlerInterface; 37 | use JMS\Serializer\GraphNavigator; 38 | use JMS\Serializer\JsonSerializationVisitor; 39 | use JMS\Serializer\JsonDeserializationVisitor; 40 | use JMS\Serializer\Context; 41 | 42 | class MyHandler implements SubscribingHandlerInterface 43 | { 44 | public static function getSubscribingMethods() 45 | { 46 | return [ 47 | [ 48 | 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 49 | 'format' => 'json', 50 | 'type' => 'DateTime', 51 | 'method' => 'serializeDateTimeToJson', 52 | ], 53 | [ 54 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 55 | 'format' => 'json', 56 | 'type' => 'DateTime', 57 | 'method' => 'deserializeDateTimeToJson', 58 | ], 59 | ]; 60 | } 61 | 62 | public function serializeDateTimeToJson(JsonSerializationVisitor $visitor, \DateTime $date, array $type, Context $context) 63 | { 64 | return $date->format($type['params'][0]); 65 | } 66 | 67 | public function deserializeDateTimeToJson(JsonDeserializationVisitor $visitor, $dateAsString, array $type, Context $context) 68 | { 69 | return new \DateTime($dateAsString); 70 | } 71 | } 72 | 73 | Also, this type of handler is registered via the builder object:: 74 | 75 | $builder 76 | ->configureHandlers(function(JMS\Serializer\Handler\HandlerRegistry $registry) { 77 | $registry->registerSubscribingHandler(new MyHandler()); 78 | }) 79 | ; 80 | 81 | Skippable Subscribing Handlers 82 | ------------------------------- 83 | 84 | In case you need to be able to fall back to the default deserialization behavior instead of using your custom 85 | handler, you can simply throw a `SkipHandlerException` from you custom handler method to do so. 86 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Serializer 2 | ========== 3 | 4 | .. image:: logo-small.png 5 | 6 | Introduction 7 | ------------ 8 | This library allows you to (de-)serialize data of any complexity. Currently, it supports XML and JSON. 9 | 10 | It also provides you with a rich tool-set to adapt the output to your specific needs. 11 | 12 | Built-in features include: 13 | 14 | - (De-)serialize data of any complexity; circular references are handled gracefully. 15 | - Supports many built-in PHP types (such as dates) 16 | - Integrates with Doctrine ORM, et. al. 17 | - Supports versioning, e.g. for APIs 18 | - Configurable via XML, YAML, or Doctrine Annotations 19 | 20 | Installation 21 | ------------ 22 | This library can be easily installed via composer 23 | 24 | .. code-block :: bash 25 | 26 | composer require jms/serializer 27 | 28 | or just add it to your ``composer.json`` file directly. 29 | 30 | Usage 31 | ----- 32 | For standalone projects usage of the provided builder is encouraged:: 33 | 34 | $serializer = JMS\Serializer\SerializerBuilder::create()->build(); 35 | $jsonContent = $serializer->serialize($data, 'json'); 36 | echo $jsonContent; // or return it in a Response 37 | 38 | 39 | Documentation 40 | ------------- 41 | 42 | .. toctree :: 43 | :maxdepth: 2 44 | 45 | configuration 46 | usage 47 | event_system 48 | handlers 49 | reference 50 | cookbook 51 | 52 | License 53 | ------- 54 | 55 | The code is released under the business-friendly `MIT license`_. 56 | 57 | Documentation is subject to the `Attribution-NonCommercial-NoDerivs 3.0 Unported 58 | license`_. 59 | 60 | .. _MIT license: https://opensource.org/licenses/MIT 61 | .. _Attribution-NonCommercial-NoDerivs 3.0 Unported license: http://creativecommons.org/licenses/by-nc-nd/3.0/ 62 | 63 | -------------------------------------------------------------------------------- /doc/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schmittjoh/serializer/410da8bd35d727a9a62e85a3d9698102b145ea36/doc/logo-small.png -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schmittjoh/serializer/410da8bd35d727a9a62e85a3d9698102b145ea36/doc/logo.png -------------------------------------------------------------------------------- /doc/reference.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | .. toctree :: 5 | :glob: 6 | :maxdepth: 1 7 | 8 | reference/* -------------------------------------------------------------------------------- /doc/reference/yml_reference.rst: -------------------------------------------------------------------------------- 1 | YAML Reference 2 | -------------- 3 | 4 | .. code-block :: yaml 5 | 6 | # Vendor\MyBundle\Resources\config\serializer\Model.ClassName.yml 7 | Vendor\MyBundle\Model\ClassName: 8 | exclusion_policy: ALL 9 | xml_root_name: foobar 10 | xml_root_namespace: http://your.default.namespace 11 | exclude: true 12 | exclude_if: expr 13 | read_only: false 14 | access_type: public_method # defaults to property 15 | accessor_order: custom 16 | custom_accessor_order: [propertyName1, propertyName2, ..., propertyNameN] 17 | discriminator: 18 | field_name: type 19 | disabled: false 20 | map: 21 | some-value: ClassName 22 | groups: [foo, bar] 23 | xml_attribute: true 24 | xml_element: 25 | cdata: false 26 | namespace: http://www.w3.org/2005/Atom 27 | virtual_properties: 28 | getSomeProperty: 29 | name: optional-prop-name 30 | serialized_name: foo 31 | type: integer 32 | expression_prop: 33 | name: optional-prop-name 34 | exp: object.getName() 35 | serialized_name: foo 36 | type: integer 37 | xml_namespaces: 38 | "": http://your.default.namespace 39 | atom: http://www.w3.org/2005/Atom 40 | properties: 41 | some-property: 42 | exclude: true 43 | expose: true 44 | exclude_if: expr 45 | expose_if: expr 46 | skip_when_empty: false 47 | access_type: public_method # defaults to property 48 | accessor: # access_type must be set to public_method 49 | getter: getSomeOtherProperty 50 | setter: setSomeOtherProperty 51 | type: string 52 | serialized_name: foo 53 | since_version: 1.0 54 | until_version: 1.1 55 | groups: [foo, bar] 56 | xml_attribute: true 57 | xml_value: true 58 | inline: true 59 | read_only: true 60 | xml_key_value_pairs: true 61 | xml_list: 62 | inline: true 63 | entry_name: foo 64 | namespace: http://www.w3.org/2005/Atom 65 | xml_map: 66 | inline: true 67 | key_attribute_name: foo 68 | entry_name: bar 69 | namespace: http://www.w3.org/2005/Atom 70 | xml_attribute_map: true 71 | xml_element: 72 | cdata: false 73 | namespace: http://www.w3.org/2005/Atom 74 | max_depth: 2 75 | union_discriminator: 76 | filed: foo 77 | map: 78 | a: SomeClassFQCN1 79 | b: SomeClassFQCN2 80 | c: SomeClassFQCN3 81 | 82 | callback_methods: 83 | pre_serialize: [foo, bar] 84 | post_serialize: [foo, bar] 85 | post_deserialize: [foo, bar] 86 | 87 | Constants 88 | --------- 89 | 90 | In some cases, it may be helpful to reference constants in your YAML files. 91 | You can do this by prefixing the constant with the special `!php/const` syntax. 92 | 93 | .. code-block :: yaml 94 | 95 | Vendor\MyBundle\Model\ClassName: 96 | properties: 97 | some-property: 98 | serialized_name: !php/const Vendor\MyBundle\Model\ClassName::SOME_CONSTANT 99 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.8.5 2 | git+https://github.com/fabpot/sphinx-php.git 3 | sphinx_rtd_theme 4 | Jinja2<3.0 5 | MarkupSafe<2.1 6 | alabaster<0.7.14 7 | -------------------------------------------------------------------------------- /doc/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Serializing Objects 5 | ------------------- 6 | Most common usage is probably to serialize objects. This can be achieved 7 | very easily: 8 | 9 | .. configuration-block :: 10 | 11 | .. code-block :: php 12 | 13 | build(); 16 | $serializer->serialize($object, 'json'); 17 | $serializer->serialize($object, 'xml'); 18 | 19 | .. code-block :: jinja 20 | 21 | {{ object | serialize }} {# uses JSON #} 22 | {{ object | serialize('json') }} 23 | {{ object | serialize('xml') }} 24 | 25 | Deserializing Objects 26 | --------------------- 27 | You can also deserialize objects from their XML, or JSON representation. For 28 | example, when accepting data via an API. 29 | 30 | .. code-block :: php 31 | 32 | build(); 35 | $object = $serializer->deserialize($jsonData, \MyNamespace\MyObject::class, 'json'); 36 | 37 | -------------------------------------------------------------------------------- /phpbench.json: -------------------------------------------------------------------------------- 1 | { 2 | "runner.bootstrap": "tests/bootstrap.php", 3 | "runner.iterations": 3, 4 | "runner.revs": 1, 5 | "report.generators": { 6 | "memory": { 7 | "extends": "aggregate", 8 | "cols": [ 9 | "benchmark", 10 | "mem_peak" 11 | ] 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan/ignore-by-php-version.neon.php 3 | parameters: 4 | level: 3 5 | ignoreErrors: 6 | - '~Class Doctrine\\Common\\Persistence\\Proxy not found~' 7 | - '~Class Doctrine\\ODM\\MongoDB\\PersistentCollection not found~' 8 | - '~Class Symfony\\(Contracts|Component)\\Translation\\TranslatorInterface not found~' 9 | - '#Class JMS\\Serializer\\Annotation\\DeprecatedReadOnly extends @final class JMS\\Serializer\\Annotation\\ReadOnlyProperty.#' 10 | - '#^Call to an undefined method Doctrine\\Persistence\\Mapping\\ClassMetadata\\:\:getFieldValue\(\)\.$#' 11 | - '#^Call to an undefined method JMS\\Serializer\\Visitor\\DeserializationVisitorInterface\:\:getCurrentObject\(\)\.$#' 12 | - '#^Call to method trans\(\) on an unknown class Symfony\\Component\\Translation\\TranslatorInterface\.$#' 13 | - '#^Call to method transChoice\(\) on an unknown class Symfony\\Component\\Translation\\TranslatorInterface\.$#' 14 | - '#^Property JMS\\Serializer\\Handler\\FormErrorHandler\:\:\$translator has unknown class Symfony\\Component\\Translation\\TranslatorInterface as its type\.$#' 15 | - '#^Cannot call method appendChild\(\) on null\.$#' 16 | - '#^Call to an undefined method JMS\\Serializer\\VisitorInterface\:\:setData\(\)\.$#' 17 | - '#^Call to method expects\(\) on an unknown class Symfony\\Component\\Translation\\TranslatorInterface\.$#' 18 | - '#^Call to an undefined method JMS\\Serializer\\VisitorInterface\:\:hasData\(\)\.$#' 19 | - '#^Method JMS\\Serializer\\GraphNavigator\\DeserializationGraphNavigator\:\:resolveMetadata\(\) should return JMS\\Serializer\\Metadata\\ClassMetadata\|null#' 20 | - '#^ArrayObject<\*NEVER\*, \*NEVER\*> does not accept list\.#' 21 | - '#^ArrayObject<\*NEVER\*, \*NEVER\*> does not accept array\.#' 22 | paths: 23 | - %currentWorkingDirectory%/src 24 | - %currentWorkingDirectory%/tests 25 | excludePaths: 26 | - tests/Fixtures/* 27 | reportUnmatchedIgnoredErrors: false 28 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 8 | __DIR__ . '/src', 9 | ]) 10 | ->withPhp74Sets(); 11 | -------------------------------------------------------------------------------- /src/AbstractVisitor.php: -------------------------------------------------------------------------------- 1 | navigator = $navigator; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function prepare($data) 33 | { 34 | return $data; 35 | } 36 | 37 | /** 38 | * @param TypeArray $typeArray 39 | */ 40 | protected function getElementType(array $typeArray): ?array 41 | { 42 | if (false === isset($typeArray['params'][0])) { 43 | return null; 44 | } 45 | 46 | if (isset($typeArray['params'][1]) && \is_array($typeArray['params'][1])) { 47 | return $typeArray['params'][1]; 48 | } else { 49 | return $typeArray['params'][0]; 50 | } 51 | } 52 | 53 | /** 54 | * logic according to strval https://www.php.net/manual/en/function.strval.php 55 | * "You cannot use strval() on arrays or on objects that do not implement the __toString() method." 56 | * 57 | * @param mixed $value 58 | */ 59 | protected function assertValueCanBeCastToString($value): void 60 | { 61 | if (is_array($value)) { 62 | throw new NonStringCastableTypeException($value); 63 | } 64 | 65 | if (is_object($value) && !method_exists($value, '__toString')) { 66 | throw new NonStringCastableTypeException($value); 67 | } 68 | } 69 | 70 | /** 71 | * logic according to intval https://www.php.net/manual/en/function.intval.php 72 | * "intval() should not be used on objects, as doing so will emit an E_NOTICE level error and return 1." 73 | * 74 | * @param mixed $value 75 | */ 76 | protected function assertValueCanBeCastToInt($value): void 77 | { 78 | if (is_object($value) && !$value instanceof \SimpleXMLElement) { 79 | throw new NonIntCastableTypeException($value); 80 | } 81 | } 82 | 83 | /** 84 | * logic according to floatval https://www.php.net/manual/en/function.floatval.php 85 | * "floatval() should not be used on objects, as doing so will emit an E_NOTICE level error and return 1." 86 | * 87 | * @param mixed $value 88 | */ 89 | protected function assertValueCanCastToFloat($value): void 90 | { 91 | if (is_object($value) && !$value instanceof \SimpleXMLElement) { 92 | throw new NonFloatCastableTypeException($value); 93 | } 94 | } 95 | 96 | protected function mapRoundMode(?string $roundMode = null): int 97 | { 98 | switch ($roundMode) { 99 | case 'HALF_DOWN': 100 | $roundMode = PHP_ROUND_HALF_DOWN; 101 | break; 102 | case 'HALF_EVEN': 103 | $roundMode = PHP_ROUND_HALF_EVEN; 104 | break; 105 | case 'HALF_ODD': 106 | $roundMode = PHP_ROUND_HALF_ODD; 107 | break; 108 | case 'HALF_UP': 109 | default: 110 | $roundMode = PHP_ROUND_HALF_UP; 111 | } 112 | 113 | return $roundMode; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Accessor/AccessorStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface AccessorStrategyInterface 15 | { 16 | /** 17 | * @return mixed 18 | */ 19 | public function getValue(object $object, PropertyMetadata $metadata, SerializationContext $context); 20 | 21 | /** 22 | * @param mixed $value 23 | */ 24 | public function setValue(object $object, $value, PropertyMetadata $metadata, DeserializationContext $context): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/AccessType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY)] 14 | final class AccessType implements SerializerAttribute 15 | { 16 | use AnnotationUtilsTrait; 17 | 18 | /** 19 | * @Required 20 | * @var string|null 21 | */ 22 | public $type; 23 | 24 | public function __construct(array $values = [], ?string $type = null) 25 | { 26 | $this->loadAnnotationParameters(get_defined_vars()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Annotation/Accessor.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[\Attribute(\Attribute::TARGET_PROPERTY)] 14 | final class Accessor implements SerializerAttribute 15 | { 16 | use AnnotationUtilsTrait; 17 | 18 | /** 19 | * @var string|null 20 | */ 21 | public $getter = null; 22 | 23 | /** 24 | * @var string|null 25 | */ 26 | public $setter = null; 27 | 28 | public function __construct(array $values = [], ?string $getter = null, ?string $setter = null) 29 | { 30 | $this->loadAnnotationParameters(get_defined_vars()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Annotation/AccessorOrder.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | #[\Attribute(\Attribute::TARGET_CLASS)] 16 | final class AccessorOrder implements SerializerAttribute 17 | { 18 | use AnnotationUtilsTrait; 19 | 20 | /** 21 | * @Required 22 | * @var string|null 23 | */ 24 | public $order = null; 25 | 26 | /** 27 | * @var array 28 | */ 29 | public $custom = []; 30 | 31 | public function __construct(array $values = [], ?string $order = null, array $custom = []) 32 | { 33 | $this->loadAnnotationParameters(get_defined_vars()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Annotation/AnnotationUtilsTrait.php: -------------------------------------------------------------------------------- 1 | $vars['values']]; 17 | } else { 18 | $values = $vars['values']; 19 | } 20 | 21 | unset($vars['values']); 22 | 23 | if (array_key_exists('value', $values)) { 24 | $values[key($vars)] = $values['value']; 25 | unset($values['value']); 26 | } 27 | 28 | foreach ($values as $key => $value) { 29 | $vars[$key] = $value; 30 | } 31 | 32 | foreach ($vars as $key => $value) { 33 | if (!property_exists(static::class, $key)) { 34 | throw new InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, static::class)); 35 | } 36 | 37 | $this->{$key} = $value; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Annotation/DeprecatedReadOnly.php: -------------------------------------------------------------------------------- 1 | */ 17 | public $map = []; 18 | 19 | /** @var string */ 20 | public $field = 'type'; 21 | 22 | /** @var bool */ 23 | public $disabled = false; 24 | 25 | /** @var string[] */ 26 | public $groups = []; 27 | 28 | public function __construct(array $values = [], string $field = 'type', array $groups = [], array $map = [], bool $disabled = false) 29 | { 30 | $this->loadAnnotationParameters(get_defined_vars()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Annotation/Exclude.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/ExclusionPolicy.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 29 | 30 | $this->policy = strtoupper($this->policy); 31 | 32 | if (self::NONE !== $this->policy && self::ALL !== $this->policy) { 33 | throw new RuntimeException('Exclusion policy must either be "ALL", or "NONE".'); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Annotation/Expose.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/Groups.php: -------------------------------------------------------------------------------- 1 | @Required */ 17 | public $groups = []; 18 | 19 | public function __construct(array $values = [], array $groups = []) 20 | { 21 | $vars = get_defined_vars(); 22 | /* 23 | if someone wants to set as Groups(['value' => '...']) this check will miserably fail (only one group with 'value' as only key). 24 | That is because doctrine annotations uses for @Groups("abc") the same values content (buy validation will fail since groups has to be an array). 25 | All the other cases should work as expected. 26 | The alternative here is to use the explicit syntax Groups(groups=['value' => '...']) 27 | */ 28 | if (count($values) > 0 && ((!isset($values['value']) && !isset($values['groups'])) || count($values) > 1) && 0 === count($groups)) { 29 | $vars['groups'] = $values; 30 | $vars['values'] = []; 31 | } 32 | 33 | $this->loadAnnotationParameters($vars); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Annotation/Inline.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Annotation/PostDeserialize.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[\Attribute(\Attribute::TARGET_METHOD)] 19 | final class PostDeserialize implements SerializerAttribute 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Annotation/PostSerialize.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | #[\Attribute(\Attribute::TARGET_METHOD)] 20 | final class PreSerialize implements SerializerAttribute 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Annotation/ReadOnly.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Annotation/SerializedName.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/SerializerAttribute.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Annotation/UnionDiscriminator.php: -------------------------------------------------------------------------------- 1 | */ 17 | public $map = []; 18 | 19 | /** @var string */ 20 | public $field = 'type'; 21 | 22 | public function __construct(array $values = [], string $field = 'type', array $map = []) 23 | { 24 | $this->loadAnnotationParameters(get_defined_vars()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Annotation/Until.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Annotation/VirtualProperty.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] 14 | final class VirtualProperty implements SerializerAttribute 15 | { 16 | use AnnotationUtilsTrait; 17 | 18 | /** 19 | * @var string|null 20 | */ 21 | public $exp = null; 22 | 23 | /** 24 | * @var string|null 25 | */ 26 | public $name = null; 27 | 28 | /** 29 | * @var array 30 | */ 31 | public $options = []; 32 | 33 | public function __construct($values = [], ?string $name = null, ?string $exp = null, array $options = []) 34 | { 35 | $vars = get_defined_vars(); 36 | unset($vars['options']); 37 | $this->loadAnnotationParameters($vars); 38 | 39 | if (0 !== count($options)) { 40 | $this->options = $options; 41 | } 42 | 43 | foreach ($options as $option) { 44 | if (is_array($option) && class_exists($option[0])) { 45 | $this->options[] = new $option[0]([], ...$option[1]); 46 | 47 | continue; 48 | } 49 | 50 | $this->options[] = $option; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Annotation/XmlAttribute.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/XmlAttributeMap.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Annotation/XmlDiscriminator.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Annotation/XmlElement.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Annotation/XmlKeyValuePairs.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Annotation/XmlNamespace.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Annotation/XmlRoot.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Annotation/XmlValue.php: -------------------------------------------------------------------------------- 1 | loadAnnotationParameters(get_defined_vars()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ArrayTransformerInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ArrayTransformerInterface 13 | { 14 | /** 15 | * Converts objects to an array structure. 16 | * 17 | * This is useful when the data needs to be passed on to other methods which expect array data. 18 | * 19 | * @param mixed $data anything that converts to an array, typically an object or an array of objects 20 | * 21 | * @return array 22 | */ 23 | public function toArray($data, ?SerializationContext $context = null, ?string $type = null): array; 24 | 25 | /** 26 | * Restores objects from an array structure. 27 | * 28 | * @param array $data 29 | * 30 | * @return mixed this returns whatever the passed type is, typically an object or an array of objects 31 | */ 32 | public function fromArray(array $data, string $type, ?DeserializationContext $context = null); 33 | } 34 | -------------------------------------------------------------------------------- /src/Builder/CallbackDriverFactory.php: -------------------------------------------------------------------------------- 1 | callback = $callable; 25 | } 26 | 27 | public function createDriver(array $metadataDirs, ?Reader $reader = null): DriverInterface 28 | { 29 | $driver = \call_user_func($this->callback, $metadataDirs, $reader); 30 | if (!$driver instanceof DriverInterface) { 31 | throw new LogicException('The callback must return an instance of DriverInterface.'); 32 | } 33 | 34 | return $driver; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Builder/DefaultDriverFactory.php: -------------------------------------------------------------------------------- 1 | typeParser = $typeParser ?: new Parser(); 50 | $this->propertyNamingStrategy = $propertyNamingStrategy; 51 | $this->expressionEvaluator = $expressionEvaluator; 52 | } 53 | 54 | public function enableEnumSupport(bool $enableEnumSupport = true): void 55 | { 56 | $this->enableEnumSupport = $enableEnumSupport; 57 | } 58 | 59 | public function createDriver(array $metadataDirs, ?Reader $annotationReader = null): DriverInterface 60 | { 61 | if (PHP_VERSION_ID < 80000 && empty($metadataDirs) && !interface_exists(Reader::class)) { 62 | throw new RuntimeException(sprintf('To use "%s", either a list of metadata directories must be provided, the "doctrine/annotations" package installed, or use PHP 8.0 or later.', self::class)); 63 | } 64 | 65 | /* 66 | * Build the sorted list of metadata drivers based on the environment. The final order should be: 67 | * 68 | * - YAML Driver 69 | * - XML Driver 70 | * - Annotations/Attributes Driver 71 | * - Null (Fallback) Driver 72 | */ 73 | $metadataDrivers = []; 74 | 75 | if (PHP_VERSION_ID >= 80000 || $annotationReader instanceof Reader) { 76 | $metadataDrivers[] = new AnnotationOrAttributeDriver($this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator, $annotationReader); 77 | } 78 | 79 | if (!empty($metadataDirs)) { 80 | $fileLocator = new FileLocator($metadataDirs); 81 | 82 | array_unshift($metadataDrivers, new XmlDriver($fileLocator, $this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator)); 83 | 84 | if (class_exists(Yaml::class)) { 85 | array_unshift($metadataDrivers, new YamlDriver($fileLocator, $this->propertyNamingStrategy, $this->typeParser, $this->expressionEvaluator)); 86 | } 87 | } 88 | 89 | $driver = new DriverChain($metadataDrivers); 90 | $driver->addDriver(new NullDriver($this->propertyNamingStrategy)); 91 | 92 | if ($this->enableEnumSupport) { 93 | $driver = new EnumPropertiesDriver($driver); 94 | } 95 | 96 | $driver = new TypedPropertiesDriver($driver, $this->typeParser); 97 | 98 | if (PHP_VERSION_ID >= 80000) { 99 | $driver = new DefaultValuePropertyDriver($driver); 100 | } 101 | 102 | return $driver; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Builder/DocBlockDriverFactory.php: -------------------------------------------------------------------------------- 1 | driverFactoryToDecorate = $driverFactoryToDecorate; 26 | $this->typeParser = $typeParser; 27 | } 28 | 29 | public function createDriver(array $metadataDirs, ?Reader $annotationReader = null): DriverInterface 30 | { 31 | $driver = $this->driverFactoryToDecorate->createDriver($metadataDirs, $annotationReader); 32 | 33 | return new DocBlockDriver($driver, $this->typeParser); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Builder/DriverFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @phpstan-import-type TypeArray from Type 18 | */ 19 | interface ObjectConstructorInterface 20 | { 21 | /** 22 | * Constructs a new object. 23 | * 24 | * Implementations could for example create a new object calling "new", use 25 | * "unserialize" techniques, reflection, or other means. 26 | * 27 | * @param mixed $data 28 | * @param TypeArray $type 29 | */ 30 | public function construct(DeserializationVisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context): ?object; 31 | } 32 | -------------------------------------------------------------------------------- /src/Construction/UnserializeObjectConstructor.php: -------------------------------------------------------------------------------- 1 | getInstantiator()->instantiate($metadata->name); 23 | } 24 | 25 | private function getInstantiator(): Instantiator 26 | { 27 | if (null === $this->instantiator) { 28 | $this->instantiator = new Instantiator(); 29 | } 30 | 31 | return $this->instantiator; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ContextFactory/CallableContextFactory.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 23 | } 24 | 25 | public function createDeserializationContext(): DeserializationContext 26 | { 27 | $callable = $this->callable; 28 | 29 | return $callable(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ContextFactory/CallableSerializationContextFactory.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 25 | } 26 | 27 | public function createSerializationContext(): SerializationContext 28 | { 29 | $callable = $this->callable; 30 | 31 | return $callable(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ContextFactory/DefaultDeserializationContextFactory.php: -------------------------------------------------------------------------------- 1 | depth; 29 | } 30 | 31 | public function increaseDepth(): void 32 | { 33 | $this->depth += 1; 34 | } 35 | 36 | public function decreaseDepth(): void 37 | { 38 | if ($this->depth <= 0) { 39 | throw new LogicException('Depth cannot be smaller than zero.'); 40 | } 41 | 42 | $this->depth -= 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EventDispatcher/Event.php: -------------------------------------------------------------------------------- 1 | context = $context; 37 | $this->type = $type; 38 | } 39 | 40 | public function getVisitor(): VisitorInterface 41 | { 42 | return $this->context->getVisitor(); 43 | } 44 | 45 | public function getContext(): Context 46 | { 47 | return $this->context; 48 | } 49 | 50 | public function getType(): array 51 | { 52 | return $this->type; 53 | } 54 | 55 | /** 56 | * Returns whether further event listeners should be triggered. 57 | * 58 | * @see Event::stopPropagation() 59 | * 60 | * @return bool Whether propagation was already stopped for this event 61 | */ 62 | public function isPropagationStopped(): bool 63 | { 64 | return $this->propagationStopped; 65 | } 66 | 67 | /** 68 | * Stops the propagation of the event to further event listeners. 69 | * 70 | * If multiple event listeners are connected to the same event, no 71 | * further event listener will be triggered once any trigger calls 72 | * stopPropagation(). 73 | */ 74 | public function stopPropagation(): void 75 | { 76 | $this->propagationStopped = true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/EventDispatcher/EventDispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 'the-event-name', 'method' => 'onEventName', 'class' => 'some-class', 'format' => 'json'), 15 | * array(...), 16 | * ) 17 | * 18 | * The class may be omitted if the class wants to subscribe to events of all classes. 19 | * Same goes for the format key. 20 | * 21 | * @return array 22 | * 23 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 24 | */ 25 | public static function getSubscribedEvents(); 26 | } 27 | -------------------------------------------------------------------------------- /src/EventDispatcher/Events.php: -------------------------------------------------------------------------------- 1 | container = $container; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function initializeListeners(string $eventName, string $loweredClass, string $format): array 34 | { 35 | $listeners = parent::initializeListeners($eventName, $loweredClass, $format); 36 | 37 | foreach ($listeners as &$listener) { 38 | if (!\is_array($listener[0]) || !\is_string($listener[0][0])) { 39 | continue; 40 | } 41 | 42 | if (!$this->container->has($listener[0][0])) { 43 | continue; 44 | } 45 | 46 | $listener[0][0] = $this->container->get($listener[0][0]); 47 | } 48 | 49 | return $listeners; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/EventDispatcher/ObjectEvent.php: -------------------------------------------------------------------------------- 1 | object = $object; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function getObject() 35 | { 36 | return $this->object; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/EventDispatcher/PreDeserializeEvent.php: -------------------------------------------------------------------------------- 1 | data = $data; 29 | } 30 | 31 | public function setType(string $name, array $params = []): void 32 | { 33 | $this->type = ['name' => $name, 'params' => $params]; 34 | } 35 | 36 | /** 37 | * @return mixed 38 | */ 39 | public function getData() 40 | { 41 | return $this->data; 42 | } 43 | 44 | /** 45 | * @param mixed $data 46 | */ 47 | public function setData($data): void 48 | { 49 | $this->data = $data; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/EventDispatcher/PreSerializeEvent.php: -------------------------------------------------------------------------------- 1 | type = ['name' => $typeName, 'params' => $params]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventDispatcher/Subscriber/EnumSubscriber.php: -------------------------------------------------------------------------------- 1 | getType(); 15 | 16 | if (isset($type['name']) && ('enum' === $type['name'] || !is_a($type['name'], \UnitEnum::class, true))) { 17 | return; 18 | } 19 | 20 | $object = $event->getObject(); 21 | $params = [get_class($object), $object instanceof \BackedEnum ? 'value' : 'name']; 22 | $event->setType('enum', $params); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public static function getSubscribedEvents() 29 | { 30 | return [ 31 | ['event' => 'serializer.pre_serialize', 'method' => 'onPreSerializeEnum', 'interface' => \UnitEnum::class], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/EventDispatcher/Subscriber/SymfonyValidatorValidatorSubscriber.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public static function getSubscribedEvents() 28 | { 29 | return [ 30 | ['event' => 'serializer.post_deserialize', 'method' => 'onPostDeserialize'], 31 | ]; 32 | } 33 | 34 | public function onPostDeserialize(ObjectEvent $event): void 35 | { 36 | $context = $event->getContext(); 37 | 38 | if ($context->getDepth() > 0) { 39 | return; 40 | } 41 | 42 | $validator = $this->validator; 43 | $groups = $context->hasAttribute('validation_groups') ? $context->getAttribute('validation_groups') : null; 44 | 45 | if (!$groups) { 46 | return; 47 | } 48 | 49 | $constraints = $context->hasAttribute('validation_constraints') ? $context->getAttribute('validation_constraints') : null; 50 | 51 | $list = $validator->validate($event->getObject(), $constraints, $groups); 52 | 53 | if ($list->count() > 0) { 54 | throw new ValidationFailedException($list); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Exception/CircularReferenceDetectedException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class CircularReferenceDetectedException extends NotAcceptableException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface Exception extends \Throwable 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Exception/ExcludedClassException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ExcludedClassException extends NotAcceptableException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Exception/ExpressionLanguageRequiredException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ExpressionLanguageRequiredException extends LogicException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class InvalidArgumentException extends \InvalidArgumentException implements Exception 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Exception/InvalidMetadataException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class InvalidMetadataException extends \Exception implements Exception 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class LogicException extends \LogicException implements Exception 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Exception/NonCastableTypeException.php: -------------------------------------------------------------------------------- 1 | value = $value; 20 | 21 | parent::__construct( 22 | sprintf( 23 | 'Cannot convert value of type "%s" to %s', 24 | gettype($value), 25 | $expectedType, 26 | ), 27 | ); 28 | } 29 | 30 | /** 31 | * @return mixed 32 | */ 33 | public function getValue() 34 | { 35 | return $this->value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Exception/NonFloatCastableTypeException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NotAcceptableException extends LogicException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Exception/ObjectConstructionException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ObjectConstructionException extends RuntimeException implements Exception 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RuntimeException extends \RuntimeException implements Exception 13 | { 14 | public static function noMetadataForProperty(string $class, string $prop): self 15 | { 16 | return new RuntimeException(sprintf( 17 | 'You must define a type for %s::$%s.', 18 | $class, 19 | $prop, 20 | )); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Exception/SkipHandlerException.php: -------------------------------------------------------------------------------- 1 | list = $list; 21 | } 22 | 23 | public function getConstraintViolationList(): ConstraintViolationListInterface 24 | { 25 | return $this->list; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Exception/XmlErrorException.php: -------------------------------------------------------------------------------- 1 | level) { 17 | case LIBXML_ERR_WARNING: 18 | $level = 'WARNING'; 19 | break; 20 | 21 | case LIBXML_ERR_FATAL: 22 | $level = 'FATAL'; 23 | break; 24 | 25 | case LIBXML_ERR_ERROR: 26 | $level = 'ERROR'; 27 | break; 28 | 29 | default: 30 | $level = 'UNKNOWN'; 31 | } 32 | 33 | parent::__construct(sprintf('[%s] %s in %s (line: %d, column: %d)', $level, $error->message, $error->file, $error->line, $error->column)); 34 | 35 | $this->xmlError = $error; 36 | } 37 | 38 | public function getXmlError(): \LibXMLError 39 | { 40 | return $this->xmlError; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Exclusion/DepthExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class DepthExclusionStrategy implements ExclusionStrategyInterface 15 | { 16 | public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool 17 | { 18 | return $this->isTooDeep($context); 19 | } 20 | 21 | public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool 22 | { 23 | return $this->isTooDeep($context); 24 | } 25 | 26 | private function isTooDeep(Context $context): bool 27 | { 28 | $relativeDepth = 0; 29 | 30 | foreach ($context->getMetadataStack() as $metadata) { 31 | if (!$metadata instanceof PropertyMetadata) { 32 | continue; 33 | } 34 | 35 | $relativeDepth++; 36 | 37 | if (0 === $metadata->maxDepth && $context->getMetadataStack()->top() === $metadata) { 38 | continue; 39 | } 40 | 41 | if (null !== $metadata->maxDepth && $relativeDepth > $metadata->maxDepth) { 42 | return true; 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Exclusion/DisjunctExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class DisjunctExclusionStrategy implements ExclusionStrategyInterface 19 | { 20 | /** 21 | * @var ExclusionStrategyInterface[] 22 | */ 23 | private $delegates; 24 | 25 | /** 26 | * @param ExclusionStrategyInterface[] $delegates 27 | */ 28 | public function __construct(array $delegates = []) 29 | { 30 | $this->delegates = $delegates; 31 | } 32 | 33 | public function addStrategy(ExclusionStrategyInterface $strategy): void 34 | { 35 | $this->delegates[] = $strategy; 36 | } 37 | 38 | /** 39 | * Whether the class should be skipped. 40 | */ 41 | public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool 42 | { 43 | foreach ($this->delegates as $delegate) { 44 | \assert($delegate instanceof ExclusionStrategyInterface); 45 | if ($delegate->shouldSkipClass($metadata, $context)) { 46 | return true; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Whether the property should be skipped. 55 | */ 56 | public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool 57 | { 58 | foreach ($this->delegates as $delegate) { 59 | \assert($delegate instanceof ExclusionStrategyInterface); 60 | if ($delegate->shouldSkipProperty($property, $context)) { 61 | return true; 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Exclusion/ExclusionStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ExclusionStrategyInterface 17 | { 18 | /** 19 | * Whether the class should be skipped. 20 | */ 21 | public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool; 22 | 23 | /** 24 | * Whether the property should be skipped. 25 | */ 26 | public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool; 27 | } 28 | -------------------------------------------------------------------------------- /src/Exclusion/ExpressionLanguageExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | final class ExpressionLanguageExclusionStrategy 24 | { 25 | /** 26 | * @var ExpressionEvaluatorInterface 27 | */ 28 | private $expressionEvaluator; 29 | 30 | public function __construct(ExpressionEvaluatorInterface $expressionEvaluator) 31 | { 32 | $this->expressionEvaluator = $expressionEvaluator; 33 | } 34 | 35 | public function shouldSkipClass(ClassMetadata $class, Context $navigatorContext): bool 36 | { 37 | if (null === $class->excludeIf) { 38 | return false; 39 | } 40 | 41 | $variables = [ 42 | 'context' => $navigatorContext, 43 | 'class_metadata' => $class, 44 | ]; 45 | if ($navigatorContext instanceof SerializationContext) { 46 | $variables['object'] = $navigatorContext->getObject(); 47 | } else { 48 | $variables['object'] = null; 49 | } 50 | 51 | if (($class->excludeIf instanceof Expression) && ($this->expressionEvaluator instanceof CompilableExpressionEvaluatorInterface)) { 52 | return $this->expressionEvaluator->evaluateParsed($class->excludeIf, $variables); 53 | } 54 | 55 | return $this->expressionEvaluator->evaluate($class->excludeIf, $variables); 56 | } 57 | 58 | public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool 59 | { 60 | if (null === $property->excludeIf) { 61 | return false; 62 | } 63 | 64 | $variables = [ 65 | 'context' => $navigatorContext, 66 | 'property_metadata' => $property, 67 | ]; 68 | if ($navigatorContext instanceof SerializationContext) { 69 | $variables['object'] = $navigatorContext->getObject(); 70 | } else { 71 | $variables['object'] = null; 72 | } 73 | 74 | if (($property->excludeIf instanceof Expression) && ($this->expressionEvaluator instanceof CompilableExpressionEvaluatorInterface)) { 75 | return $this->expressionEvaluator->evaluateParsed($property->excludeIf, $variables); 76 | } 77 | 78 | return $this->expressionEvaluator->evaluate($property->excludeIf, $variables); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Exclusion/GroupsExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | nestedGroups = true; 34 | break; 35 | } 36 | } 37 | 38 | if ($this->nestedGroups) { 39 | $this->groups = $groups; 40 | } else { 41 | foreach ($groups as $group) { 42 | $this->groups[$group] = true; 43 | } 44 | } 45 | } 46 | 47 | public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext): bool 48 | { 49 | return false; 50 | } 51 | 52 | public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool 53 | { 54 | if ($this->nestedGroups) { 55 | $groups = $this->getGroupsFor($navigatorContext); 56 | 57 | if (!$property->groups) { 58 | return !in_array(self::DEFAULT_GROUP, $groups); 59 | } 60 | 61 | return $this->shouldSkipUsingGroups($property, $groups); 62 | } else { 63 | if (!$property->groups) { 64 | return !isset($this->groups[self::DEFAULT_GROUP]); 65 | } 66 | 67 | foreach ($property->groups as $group) { 68 | if (is_scalar($group) && isset($this->groups[$group])) { 69 | return false; 70 | } 71 | } 72 | 73 | return true; 74 | } 75 | } 76 | 77 | private function shouldSkipUsingGroups(PropertyMetadata $property, array $groups): bool 78 | { 79 | foreach ($property->groups as $group) { 80 | if (in_array($group, $groups)) { 81 | return false; 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | 88 | public function getGroupsFor(Context $navigatorContext): array 89 | { 90 | if (!$this->nestedGroups) { 91 | return array_keys($this->groups); 92 | } 93 | 94 | $paths = $navigatorContext->getCurrentPath(); 95 | $groups = $this->groups; 96 | foreach ($paths as $index => $path) { 97 | if (!array_key_exists($path, $groups)) { 98 | if ($index > 0) { 99 | $groups = [self::DEFAULT_GROUP]; 100 | } else { 101 | $groups = array_filter($groups, 'is_string') ?: [self::DEFAULT_GROUP]; 102 | } 103 | 104 | break; 105 | } 106 | 107 | $groups = $groups[$path]; 108 | if (!array_filter($groups, 'is_string')) { 109 | $groups += [self::DEFAULT_GROUP]; 110 | } 111 | } 112 | 113 | return $groups; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Exclusion/VersionExclusionStrategy.php: -------------------------------------------------------------------------------- 1 | version = $version; 21 | } 22 | 23 | public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext): bool 24 | { 25 | return false; 26 | } 27 | 28 | public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool 29 | { 30 | if ((null !== $version = $property->sinceVersion) && version_compare($this->version, $version, '<')) { 31 | return true; 32 | } 33 | 34 | return (null !== $version = $property->untilVersion) && version_compare($this->version, $version, '>'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Expression/CompilableExpressionEvaluatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface CompilableExpressionEvaluatorInterface 11 | { 12 | public function parse(string $expression, array $names = []): Expression; 13 | 14 | /** 15 | * @return mixed 16 | */ 17 | public function evaluateParsed(Expression $expression, array $data = []); 18 | } 19 | -------------------------------------------------------------------------------- /src/Expression/Expression.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Expression implements \Serializable 14 | { 15 | /** 16 | * @var BaseExpression 17 | */ 18 | private $expression; 19 | 20 | public function __construct(BaseExpression $expression) 21 | { 22 | $this->expression = $expression; 23 | } 24 | 25 | public function getExpression(): BaseExpression 26 | { 27 | return $this->expression; 28 | } 29 | 30 | /** 31 | * @return string 32 | * 33 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 34 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation 35 | */ 36 | public function __toString() 37 | { 38 | return (string) $this->expression; 39 | } 40 | 41 | /** 42 | * @return string 43 | * 44 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint 45 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 46 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation 47 | */ 48 | public function serialize() 49 | { 50 | return serialize([(string) $this->expression, serialize($this->expression->getNodes())]); 51 | } 52 | 53 | /** 54 | * @param string $str 55 | * 56 | * @return void 57 | * 58 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint 59 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 60 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation 61 | */ 62 | public function unserialize($str): void 63 | { 64 | $this->expression = new SerializedParsedExpression(...unserialize($str)); 65 | } 66 | 67 | public function __serialize(): array 68 | { 69 | return [(string) $this->expression, $this->expression->getNodes()]; 70 | } 71 | 72 | public function __unserialize(array $data): void 73 | { 74 | [$expression, $nodes] = $data; 75 | $this->expression = new BaseExpression($expression, $nodes); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Expression/ExpressionEvaluator.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ExpressionEvaluator implements CompilableExpressionEvaluatorInterface, ExpressionEvaluatorInterface 13 | { 14 | /** 15 | * @var ExpressionLanguage 16 | */ 17 | private $expressionLanguage; 18 | 19 | /** 20 | * @var array 21 | */ 22 | private $context; 23 | 24 | public function __construct(ExpressionLanguage $expressionLanguage, array $context = []) 25 | { 26 | $this->expressionLanguage = $expressionLanguage; 27 | $this->context = $context; 28 | } 29 | 30 | /** 31 | * @param mixed $value 32 | */ 33 | public function setContextVariable(string $name, $value): void 34 | { 35 | $this->context[$name] = $value; 36 | } 37 | 38 | /** 39 | * @return mixed 40 | */ 41 | public function evaluate(string $expression, array $data = []) 42 | { 43 | return $this->expressionLanguage->evaluate($expression, $data + $this->context); 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function evaluateParsed(Expression $expression, array $data = []) 50 | { 51 | return $this->expressionLanguage->evaluate($expression->getExpression(), $data + $this->context); 52 | } 53 | 54 | public function parse(string $expression, array $names = []): Expression 55 | { 56 | return new Expression($this->expressionLanguage->parse($expression, array_merge(array_keys($this->context), $names))); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expression/ExpressionEvaluatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExpressionEvaluatorInterface 11 | { 12 | /** 13 | * @return mixed 14 | */ 15 | public function evaluate(string $expression, array $data = []); 16 | } 17 | -------------------------------------------------------------------------------- /src/Functions.php: -------------------------------------------------------------------------------- 1 | 18 | * @author Johannes M. Schmitt 19 | */ 20 | abstract class GraphNavigator implements GraphNavigatorInterface 21 | { 22 | /** 23 | * @var VisitorInterface 24 | */ 25 | protected $visitor; 26 | /** 27 | * @var Context 28 | */ 29 | protected $context; 30 | /*** 31 | * @var string 32 | */ 33 | protected $format; 34 | /** 35 | * @var ExclusionStrategyInterface 36 | */ 37 | protected $exclusionStrategy; 38 | 39 | public function initialize(VisitorInterface $visitor, Context $context): void 40 | { 41 | $this->visitor = $visitor; 42 | $this->context = $context; 43 | 44 | // cache value 45 | $this->format = $context->getFormat(); 46 | $this->exclusionStrategy = $context->getExclusionStrategy(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/GraphNavigator/Factory/DeserializationGraphNavigatorFactory.php: -------------------------------------------------------------------------------- 1 | metadataFactory = $metadataFactory; 52 | $this->handlerRegistry = $handlerRegistry; 53 | $this->objectConstructor = $objectConstructor; 54 | $this->accessor = $accessor; 55 | $this->dispatcher = $dispatcher; 56 | $this->expressionEvaluator = $expressionEvaluator; 57 | } 58 | 59 | public function getGraphNavigator(): GraphNavigatorInterface 60 | { 61 | return new DeserializationGraphNavigator($this->metadataFactory, $this->handlerRegistry, $this->objectConstructor, $this->accessor, $this->dispatcher, $this->expressionEvaluator); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/GraphNavigator/Factory/GraphNavigatorFactoryInterface.php: -------------------------------------------------------------------------------- 1 | metadataFactory = $metadataFactory; 48 | $this->handlerRegistry = $handlerRegistry; 49 | $this->accessor = $accessor ?: new DefaultAccessorStrategy(); 50 | $this->dispatcher = $dispatcher ?: new EventDispatcher(); 51 | $this->expressionEvaluator = $expressionEvaluator; 52 | } 53 | 54 | public function getGraphNavigator(): GraphNavigatorInterface 55 | { 56 | return new SerializationGraphNavigator($this->metadataFactory, $this->handlerRegistry, $this->accessor, $this->dispatcher, $this->expressionEvaluator); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/GraphNavigatorInterface.php: -------------------------------------------------------------------------------- 1 | string, "params" => array] 29 | * 30 | * @return mixed the return value depends on the direction, and type of visitor 31 | * 32 | * @throws NotAcceptableException 33 | */ 34 | public function accept($data, ?array $type = null); 35 | } 36 | -------------------------------------------------------------------------------- /src/Handler/ConstraintViolationHandler.php: -------------------------------------------------------------------------------- 1 | 'serializeList', ConstraintViolation::class => 'serializeViolation']; 28 | 29 | foreach ($types as $type => $method) { 30 | foreach ($formats as $format) { 31 | $methods[] = [ 32 | 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 33 | 'type' => $type, 34 | 'format' => $format, 35 | 'method' => $method . 'To' . $format, 36 | ]; 37 | } 38 | } 39 | 40 | return $methods; 41 | } 42 | 43 | public function serializeListToXml(XmlSerializationVisitor $visitor, ConstraintViolationList $list): void 44 | { 45 | $currentNode = $visitor->getCurrentNode(); 46 | if (!$currentNode) { 47 | $visitor->createRoot(); 48 | } 49 | 50 | foreach ($list as $violation) { 51 | $this->serializeViolationToXml($visitor, $violation); 52 | } 53 | } 54 | 55 | /** 56 | * @param TypeArray $type 57 | * 58 | * @return array|\ArrayObject 59 | */ 60 | public function serializeListToJson(SerializationVisitorInterface $visitor, ConstraintViolationList $list, array $type, SerializationContext $context) 61 | { 62 | return $visitor->visitArray(iterator_to_array($list), $type); 63 | } 64 | 65 | public function serializeViolationToXml(XmlSerializationVisitor $visitor, ConstraintViolation $violation): void 66 | { 67 | $violationNode = $visitor->getDocument()->createElement('violation'); 68 | 69 | $parent = $visitor->getCurrentNode(); 70 | if (!$parent) { 71 | $visitor->setCurrentAndRootNode($violationNode); 72 | } else { 73 | $parent->appendChild($violationNode); 74 | } 75 | 76 | $violationNode->setAttribute('property_path', $violation->getPropertyPath()); 77 | $violationNode->appendChild($messageNode = $visitor->getDocument()->createElement('message')); 78 | 79 | $messageNode->appendChild($visitor->getDocument()->createCDATASection($violation->getMessage())); 80 | } 81 | 82 | public function serializeViolationToJson(SerializationVisitorInterface $visitor, ConstraintViolation $violation): array 83 | { 84 | return [ 85 | 'property_path' => $violation->getPropertyPath(), 86 | 'message' => $violation->getMessage(), 87 | ]; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Handler/EnumHandler.php: -------------------------------------------------------------------------------- 1 | 'enum', 30 | 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 31 | 'format' => $format, 32 | 'method' => 'deserializeEnum', 33 | ]; 34 | $methods[] = [ 35 | 'type' => 'enum', 36 | 'format' => $format, 37 | 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 38 | 'method' => 'serializeEnum', 39 | ]; 40 | } 41 | 42 | return $methods; 43 | } 44 | 45 | /** 46 | * @param TypeArray $type 47 | */ 48 | public function serializeEnum( 49 | SerializationVisitorInterface $visitor, 50 | \UnitEnum $enum, 51 | array $type, 52 | SerializationContext $context 53 | ) { 54 | if ((isset($type['params'][1]) && 'value' === $type['params'][1]) || (!isset($type['params'][1]) && $enum instanceof \BackedEnum)) { 55 | if (!$enum instanceof \BackedEnum) { 56 | throw new InvalidMetadataException(sprintf('The type "%s" is not a backed enum, thus you can not use "value" as serialization mode for its value.', get_class($enum))); 57 | } 58 | 59 | $valueType = isset($type['params'][2]) ? ['name' => $type['params'][2]] : null; 60 | 61 | return $context->getNavigator()->accept($enum->value, $valueType); 62 | } else { 63 | return $context->getNavigator()->accept($enum->name); 64 | } 65 | } 66 | 67 | /** 68 | * @param int|string|\SimpleXMLElement $data 69 | * @param TypeArray $type 70 | */ 71 | public function deserializeEnum(DeserializationVisitorInterface $visitor, $data, array $type): ?\UnitEnum 72 | { 73 | $enumType = $type['params'][0]; 74 | if (isset($enumType['name'])) { 75 | $enumType = $enumType['name']; 76 | } else { 77 | trigger_deprecation('jms/serializer', '3.31', "Using enum<'Type'> or similar is deprecated, use enum instead."); 78 | } 79 | 80 | $caseValue = (string) $data; 81 | 82 | $ref = new \ReflectionEnum($enumType); 83 | if (isset($type['params'][1]) && 'value' === $type['params'][1] || (!isset($type['params'][1]) && is_a($enumType, \BackedEnum::class, true))) { 84 | if (!is_a($enumType, \BackedEnum::class, true)) { 85 | throw new InvalidMetadataException(sprintf('The type "%s" is not a backed enum, thus you can not use "value" as serialization mode for its value.', $enumType)); 86 | } 87 | 88 | if ('int' === $ref->getBackingType()->getName()) { 89 | if (!is_numeric($caseValue)) { 90 | throw new RuntimeException(sprintf('"%s" is not a valid backing value for enum "%s"', $caseValue, $enumType)); 91 | } 92 | 93 | $caseValue = (int) $caseValue; 94 | } 95 | 96 | return $enumType::from($caseValue); 97 | } else { 98 | if (!$ref->hasCase($caseValue)) { 99 | throw new InvalidMetadataException(sprintf('The type "%s" does not have the case "%s"', $ref->getName(), $caseValue)); 100 | } 101 | 102 | return $ref->getCase($caseValue)->getValue(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Handler/HandlerRegistry.php: -------------------------------------------------------------------------------- 1 | handlers = $handlers; 39 | } 40 | 41 | public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void 42 | { 43 | foreach ($handler->getSubscribingMethods() as $methodData) { 44 | if (!isset($methodData['type'], $methodData['format'])) { 45 | throw new RuntimeException(sprintf('For each subscribing method a "type" and "format" attribute must be given, but only got "%s" for %s.', implode('" and "', array_keys($methodData)), \get_class($handler))); 46 | } 47 | 48 | $directions = [GraphNavigatorInterface::DIRECTION_DESERIALIZATION, GraphNavigatorInterface::DIRECTION_SERIALIZATION]; 49 | if (isset($methodData['direction'])) { 50 | $directions = [$methodData['direction']]; 51 | } 52 | 53 | foreach ($directions as $direction) { 54 | $method = $methodData['method'] ?? self::getDefaultMethod($direction, $methodData['type'], $methodData['format']); 55 | $this->registerHandler($direction, $methodData['type'], $methodData['format'], [$handler, $method]); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function registerHandler(int $direction, string $typeName, string $format, $handler): void 64 | { 65 | $this->handlers[$direction][$typeName][$format] = $handler; 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function getHandler(int $direction, string $typeName, string $format) 72 | { 73 | if (!isset($this->handlers[$direction][$typeName][$format])) { 74 | return null; 75 | } 76 | 77 | return $this->handlers[$direction][$typeName][$format]; 78 | } 79 | 80 | /** 81 | * @internal Used for profiling 82 | */ 83 | public function getHandlers(): array 84 | { 85 | return $this->handlers; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Handler/HandlerRegistryInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface HandlerRegistryInterface 13 | { 14 | public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void; 15 | 16 | /** 17 | * Registers a handler in the registry. 18 | * 19 | * @param int $direction one of the GraphNavigatorInterface::DIRECTION_??? constants 20 | * @param object|callable $handler function(visitor, mixed $data, array $type): mixed 21 | */ 22 | public function registerHandler(int $direction, string $typeName, string $format, $handler): void; 23 | 24 | /** 25 | * @param int $direction one of the GraphNavigatorInterface::DIRECTION_??? constants 26 | * 27 | * @return callable|object|null 28 | */ 29 | public function getHandler(int $direction, string $typeName, string $format); 30 | } 31 | -------------------------------------------------------------------------------- /src/Handler/IteratorHandler.php: -------------------------------------------------------------------------------- 1 | GraphNavigatorInterface::DIRECTION_SERIALIZATION, 35 | 'type' => Iterator::class, 36 | 'format' => $format, 37 | 'method' => 'serializeIterable', 38 | ]; 39 | 40 | $methods[] = [ 41 | 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 42 | 'type' => Iterator::class, 43 | 'format' => $format, 44 | 'method' => 'deserializeIterator', 45 | ]; 46 | } 47 | 48 | foreach (self::SUPPORTED_FORMATS as $format) { 49 | $methods[] = [ 50 | 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 51 | 'type' => ArrayIterator::class, 52 | 'format' => $format, 53 | 'method' => 'serializeIterable', 54 | ]; 55 | 56 | $methods[] = [ 57 | 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 58 | 'type' => ArrayIterator::class, 59 | 'format' => $format, 60 | 'method' => 'deserializeIterator', 61 | ]; 62 | } 63 | 64 | foreach (self::SUPPORTED_FORMATS as $format) { 65 | $methods[] = [ 66 | 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 67 | 'type' => Generator::class, 68 | 'format' => $format, 69 | 'method' => 'serializeIterable', 70 | ]; 71 | 72 | $methods[] = [ 73 | 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 74 | 'type' => Generator::class, 75 | 'format' => $format, 76 | 'method' => 'deserializeGenerator', 77 | ]; 78 | } 79 | 80 | return $methods; 81 | } 82 | 83 | /** 84 | * @param TypeArray $type 85 | * 86 | * @return array|\ArrayObject|null 87 | */ 88 | public function serializeIterable( 89 | SerializationVisitorInterface $visitor, 90 | iterable $iterable, 91 | array $type, 92 | SerializationContext $context 93 | ): ?iterable { 94 | $type['name'] = 'array'; 95 | 96 | $context->stopVisiting($iterable); 97 | $result = $visitor->visitArray(Functions::iterableToArray($iterable), $type); 98 | $context->startVisiting($iterable); 99 | 100 | return $result; 101 | } 102 | 103 | /** 104 | * @param TypeArray $type 105 | * @param mixed $data 106 | */ 107 | public function deserializeIterator( 108 | DeserializationVisitorInterface $visitor, 109 | $data, 110 | array $type, 111 | DeserializationContext $context 112 | ): \Iterator { 113 | $type['name'] = 'array'; 114 | 115 | return new ArrayIterator($visitor->visitArray($data, $type)); 116 | } 117 | 118 | /** 119 | * @param mixed $data 120 | * @param TypeArray $type 121 | */ 122 | public function deserializeGenerator( 123 | DeserializationVisitorInterface $visitor, 124 | $data, 125 | array $type, 126 | DeserializationContext $context 127 | ): Generator { 128 | return (static function () use (&$visitor, &$data, &$type): Generator { 129 | $type['name'] = 'array'; 130 | yield from $visitor->visitArray($data, $type); 131 | })(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Handler/LazyHandlerRegistry.php: -------------------------------------------------------------------------------- 1 | container = $container; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function registerHandler(int $direction, string $typeName, string $format, $handler): void 42 | { 43 | parent::registerHandler($direction, $typeName, $format, $handler); 44 | 45 | unset($this->initializedHandlers[$direction][$typeName][$format]); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function getHandler(int $direction, string $typeName, string $format) 52 | { 53 | if (isset($this->initializedHandlers[$direction][$typeName][$format])) { 54 | return $this->initializedHandlers[$direction][$typeName][$format]; 55 | } 56 | 57 | if (!isset($this->handlers[$direction][$typeName][$format])) { 58 | return null; 59 | } 60 | 61 | $handler = $this->handlers[$direction][$typeName][$format]; 62 | if (\is_array($handler) && \is_string($handler[0]) && $this->container->has($handler[0])) { 63 | $handler[0] = $this->container->get($handler[0]); 64 | } 65 | 66 | return $this->initializedHandlers[$direction][$typeName][$format] = $handler; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Handler/StdClassHandler.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * @phpstan-import-type TypeArray from Type 17 | */ 18 | final class StdClassHandler implements SubscribingHandlerInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public static function getSubscribingMethods() 24 | { 25 | $methods = []; 26 | $formats = ['json', 'xml']; 27 | 28 | foreach ($formats as $format) { 29 | $methods[] = [ 30 | 'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION, 31 | 'type' => \stdClass::class, 32 | 'format' => $format, 33 | 'method' => 'serializeStdClass', 34 | ]; 35 | } 36 | 37 | return $methods; 38 | } 39 | 40 | /** 41 | * @param TypeArray $type 42 | * 43 | * @return mixed 44 | */ 45 | public function serializeStdClass(SerializationVisitorInterface $visitor, \stdClass $stdClass, array $type, SerializationContext $context) 46 | { 47 | $classMetadata = $context->getMetadataFactory()->getMetadataForClass('stdClass'); 48 | $visitor->startVisitingObject($classMetadata, $stdClass, ['name' => 'stdClass']); 49 | 50 | foreach ((array) $stdClass as $name => $value) { 51 | $metadata = new StaticPropertyMetadata('stdClass', $name, $value); 52 | $visitor->visitProperty($metadata, $value); 53 | } 54 | 55 | return $visitor->endVisitingObject($classMetadata, $stdClass, ['name' => 'stdClass']); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Handler/SubscribingHandlerInterface.php: -------------------------------------------------------------------------------- 1 | GraphNavigatorInterface::DIRECTION_SERIALIZATION, 15 | * 'format' => 'json', 16 | * 'type' => 'DateTime', 17 | * 'method' => 'serializeDateTimeToJson', 18 | * ), 19 | * ) 20 | * 21 | * The direction and method keys can be omitted. 22 | * 23 | * @return array 24 | * 25 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 26 | */ 27 | public static function getSubscribingMethods(); 28 | } 29 | -------------------------------------------------------------------------------- /src/Metadata/Driver/AnnotationDriver.php: -------------------------------------------------------------------------------- 1 | reader = $reader; 27 | } 28 | 29 | /** 30 | * @return list 31 | */ 32 | protected function getClassAnnotations(\ReflectionClass $class): array 33 | { 34 | return $this->reader->getClassAnnotations($class); 35 | } 36 | 37 | /** 38 | * @return list 39 | */ 40 | protected function getMethodAnnotations(\ReflectionMethod $method): array 41 | { 42 | return $this->reader->getMethodAnnotations($method); 43 | } 44 | 45 | /** 46 | * @return list 47 | */ 48 | protected function getPropertyAnnotations(\ReflectionProperty $property): array 49 | { 50 | return $this->reader->getPropertyAnnotations($property); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Metadata/Driver/AttributeDriver.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected function getClassAnnotations(\ReflectionClass $class): array 15 | { 16 | return array_map( 17 | static fn (\ReflectionAttribute $attribute): object => $attribute->newInstance(), 18 | $class->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF), 19 | ); 20 | } 21 | 22 | /** 23 | * @return list 24 | */ 25 | protected function getMethodAnnotations(\ReflectionMethod $method): array 26 | { 27 | return array_map( 28 | static fn (\ReflectionAttribute $attribute): object => $attribute->newInstance(), 29 | $method->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF), 30 | ); 31 | } 32 | 33 | /** 34 | * @return list 35 | */ 36 | protected function getPropertyAnnotations(\ReflectionProperty $property): array 37 | { 38 | return array_map( 39 | static fn (\ReflectionAttribute $attribute): object => $attribute->newInstance(), 40 | $property->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Metadata/Driver/AttributeDriver/AttributeReader.php: -------------------------------------------------------------------------------- 1 | reader = $reader; 26 | } 27 | 28 | public function getClassAnnotations(ReflectionClass $class): array 29 | { 30 | $attributes = $class->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF); 31 | 32 | return array_merge($this->reader->getClassAnnotations($class), $this->buildAnnotations($attributes)); 33 | } 34 | 35 | public function getClassAnnotation(ReflectionClass $class, $annotationName): ?object 36 | { 37 | $attributes = $class->getAttributes($annotationName, \ReflectionAttribute::IS_INSTANCEOF); 38 | 39 | return $this->reader->getClassAnnotation($class, $annotationName) ?? $this->buildAnnotation($attributes); 40 | } 41 | 42 | public function getMethodAnnotations(ReflectionMethod $method): array 43 | { 44 | $attributes = $method->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF); 45 | 46 | return array_merge($this->reader->getMethodAnnotations($method), $this->buildAnnotations($attributes)); 47 | } 48 | 49 | public function getMethodAnnotation(ReflectionMethod $method, $annotationName): ?object 50 | { 51 | $attributes = $method->getAttributes($annotationName, \ReflectionAttribute::IS_INSTANCEOF); 52 | 53 | return $this->reader->getClassAnnotation($method, $annotationName) ?? $this->buildAnnotation($attributes); 54 | } 55 | 56 | public function getPropertyAnnotations(ReflectionProperty $property): array 57 | { 58 | $attributes = $property->getAttributes(SerializerAttribute::class, \ReflectionAttribute::IS_INSTANCEOF); 59 | 60 | return array_merge($this->reader->getPropertyAnnotations($property), $this->buildAnnotations($attributes)); 61 | } 62 | 63 | public function getPropertyAnnotation(ReflectionProperty $property, $annotationName): ?object 64 | { 65 | $attributes = $property->getAttributes($annotationName, \ReflectionAttribute::IS_INSTANCEOF); 66 | 67 | return $this->reader->getClassAnnotation($property, $annotationName) ?? $this->buildAnnotation($attributes); 68 | } 69 | 70 | /** 71 | * @param list<\ReflectionAttribute> $attributes 72 | */ 73 | private function buildAnnotation(array $attributes): ?SerializerAttribute 74 | { 75 | if (!isset($attributes[0])) { 76 | return null; 77 | } 78 | 79 | return $attributes[0]->newInstance(); 80 | } 81 | 82 | /** 83 | * @return list 84 | */ 85 | private function buildAnnotations(array $attributes): array 86 | { 87 | return array_map( 88 | static fn (\ReflectionAttribute $attribute): object => $attribute->newInstance(), 89 | $attributes, 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Metadata/Driver/DefaultValuePropertyDriver.php: -------------------------------------------------------------------------------- 1 | delegate = $delegate; 25 | } 26 | 27 | /** 28 | * @return SerializerClassMetadata|null 29 | */ 30 | public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata 31 | { 32 | $classMetadata = $this->delegate->loadMetadataForClass($class); 33 | 34 | if (null === $classMetadata) { 35 | return null; 36 | } 37 | 38 | \assert($classMetadata instanceof SerializerClassMetadata); 39 | 40 | foreach ($classMetadata->propertyMetadata as $key => $propertyMetadata) { 41 | \assert($propertyMetadata instanceof PropertyMetadata); 42 | if (null !== $propertyMetadata->hasDefault) { 43 | continue; 44 | } 45 | 46 | try { 47 | $propertyReflection = $this->getPropertyReflection($propertyMetadata); 48 | $propertyMetadata->hasDefault = false; 49 | if ($propertyReflection->hasDefaultValue() && $propertyReflection->hasType()) { 50 | $propertyMetadata->hasDefault = true; 51 | $propertyMetadata->defaultValue = $propertyReflection->getDefaultValue(); 52 | } elseif ($propertyReflection->isPromoted()) { 53 | // need to get the parameter in the constructor to check for default values 54 | $classReflection = $this->getClassReflection($propertyMetadata); 55 | $params = $classReflection->getConstructor()->getParameters(); 56 | foreach ($params as $parameter) { 57 | if ($parameter->getName() === $propertyMetadata->name) { 58 | if ($parameter->isDefaultValueAvailable()) { 59 | $propertyMetadata->hasDefault = true; 60 | $propertyMetadata->defaultValue = $parameter->getDefaultValue(); 61 | } 62 | 63 | break; 64 | } 65 | } 66 | } 67 | } catch (ReflectionException $e) { 68 | continue; 69 | } 70 | } 71 | 72 | return $classMetadata; 73 | } 74 | 75 | private function getPropertyReflection(PropertyMetadata $propertyMetadata): ReflectionProperty 76 | { 77 | return new ReflectionProperty($propertyMetadata->class, $propertyMetadata->name); 78 | } 79 | 80 | private function getClassReflection(PropertyMetadata $propertyMetadata): ReflectionClass 81 | { 82 | return new ReflectionClass($propertyMetadata->class); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Metadata/Driver/DocBlockDriver.php: -------------------------------------------------------------------------------- 1 | delegate = $delegate; 41 | $this->typeParser = $typeParser ?: new Parser(); 42 | $this->docBlockTypeResolver = new DocBlockTypeResolver(); 43 | } 44 | 45 | /** 46 | * @return SerializerClassMetadata|null 47 | */ 48 | public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata 49 | { 50 | $classMetadata = $this->delegate->loadMetadataForClass($class); 51 | 52 | if (null === $classMetadata) { 53 | return null; 54 | } 55 | 56 | \assert($classMetadata instanceof SerializerClassMetadata); 57 | 58 | // We base our scan on the internal driver's property list so that we 59 | // respect any internal allow/blocklist like in the AnnotationDriver 60 | foreach ($classMetadata->propertyMetadata as $key => $propertyMetadata) { 61 | // If the inner driver provides a type, don't guess anymore. 62 | if ($propertyMetadata->type) { 63 | continue; 64 | } 65 | 66 | if ($this->isNotSupportedVirtualProperty($propertyMetadata)) { 67 | continue; 68 | } 69 | 70 | try { 71 | if ($propertyMetadata instanceof VirtualPropertyMetadata) { 72 | $type = $this->docBlockTypeResolver->getMethodDocblockTypeHint( 73 | new ReflectionMethod($propertyMetadata->class, $propertyMetadata->getter), 74 | ); 75 | } else { 76 | $type = $this->docBlockTypeResolver->getPropertyDocblockTypeHint( 77 | new ReflectionProperty($propertyMetadata->class, $propertyMetadata->name), 78 | ); 79 | } 80 | 81 | if ($type) { 82 | $propertyMetadata->setType($this->typeParser->parse($type)); 83 | } 84 | } catch (ReflectionException $e) { 85 | continue; 86 | } 87 | } 88 | 89 | return $classMetadata; 90 | } 91 | 92 | private function isNotSupportedVirtualProperty(PropertyMetadata $propertyMetadata): bool 93 | { 94 | return $propertyMetadata instanceof StaticPropertyMetadata 95 | || $propertyMetadata instanceof ExpressionPropertyMetadata; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Metadata/Driver/DoctrinePHPCRTypeDriver.php: -------------------------------------------------------------------------------- 1 | name 24 | || $doctrineMetadata->parentMapping === $propertyMetadata->name 25 | || $doctrineMetadata->node === $propertyMetadata->name; 26 | } 27 | 28 | /** 29 | * @param PHPCRClassMetadata $doctrineMetadata 30 | * @param PropertyMetadata $propertyMetadata 31 | */ 32 | protected function setPropertyType(DoctrineClassMetadata $doctrineMetadata, PropertyMetadata $propertyMetadata): void 33 | { 34 | $propertyName = $propertyMetadata->name; 35 | if ( 36 | $doctrineMetadata->hasField($propertyName) 37 | && ($typeOfFiled = $doctrineMetadata->getTypeOfField($propertyName)) 38 | && ($fieldType = $this->normalizeFieldType($typeOfFiled)) 39 | ) { 40 | $field = $doctrineMetadata->getFieldMapping($propertyName); 41 | if (!empty($field['multivalue'])) { 42 | $fieldType = 'array'; 43 | } 44 | 45 | $propertyMetadata->setType($this->typeParser->parse($fieldType)); 46 | } elseif ($doctrineMetadata->hasAssociation($propertyName)) { 47 | try { 48 | $targetEntity = $doctrineMetadata->getAssociationTargetClass($propertyName); 49 | } catch (\Throwable $e) { 50 | return; 51 | } 52 | 53 | if (null === $this->tryLoadingDoctrineMetadata($targetEntity)) { 54 | return; 55 | } 56 | 57 | if (!$doctrineMetadata->isSingleValuedAssociation($propertyName)) { 58 | $targetEntity = sprintf('ArrayCollection<%s>', $targetEntity); 59 | } 60 | 61 | $propertyMetadata->setType($this->typeParser->parse($targetEntity)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Metadata/Driver/DoctrineTypeDriver.php: -------------------------------------------------------------------------------- 1 | discriminatorMap) && !$classMetadata->discriminatorDisabled 25 | && !empty($doctrineMetadata->discriminatorMap) && $doctrineMetadata->isRootEntity() 26 | ) { 27 | if ($doctrineMetadata->discriminatorColumn instanceof DiscriminatorColumnMapping) { 28 | // Doctrine 3.1+ 29 | $classMetadata->setDiscriminator( 30 | $doctrineMetadata->discriminatorColumn->name, 31 | $doctrineMetadata->discriminatorMap, 32 | ); 33 | } else { 34 | // Doctrine up to 3.1 35 | $classMetadata->setDiscriminator( 36 | $doctrineMetadata->discriminatorColumn['name'], 37 | $doctrineMetadata->discriminatorMap, 38 | ); 39 | } 40 | } 41 | } 42 | 43 | protected function setPropertyType(DoctrineClassMetadata $doctrineMetadata, PropertyMetadata $propertyMetadata): void 44 | { 45 | $propertyName = $propertyMetadata->name; 46 | if ( 47 | $doctrineMetadata->hasField($propertyName) 48 | && ($typeOfFiled = $doctrineMetadata->getTypeOfField($propertyName)) 49 | && ($fieldType = $this->normalizeFieldType($typeOfFiled)) 50 | ) { 51 | $propertyMetadata->setType($this->typeParser->parse($fieldType)); 52 | } elseif ($doctrineMetadata->hasAssociation($propertyName)) { 53 | $targetEntity = $doctrineMetadata->getAssociationTargetClass($propertyName); 54 | 55 | if (null === $targetMetadata = $this->tryLoadingDoctrineMetadata($targetEntity)) { 56 | return; 57 | } 58 | 59 | // For inheritance schemes, we cannot add any type as we would only add the super-type of the hierarchy. 60 | // On serialization, this would lead to only the supertype being serialized, and properties of subtypes 61 | // being ignored. 62 | if ($targetMetadata instanceof ODMClassMetadata && !$targetMetadata->isInheritanceTypeNone()) { 63 | return; 64 | } 65 | 66 | if (!$doctrineMetadata->isSingleValuedAssociation($propertyName)) { 67 | $targetEntity = sprintf('ArrayCollection<%s>', $targetEntity); 68 | } 69 | 70 | $propertyMetadata->setType($this->typeParser->parse($targetEntity)); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Metadata/Driver/EnumPropertiesDriver.php: -------------------------------------------------------------------------------- 1 | delegate = $delegate; 28 | } 29 | 30 | public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata 31 | { 32 | $classMetadata = $this->delegate->loadMetadataForClass($class); 33 | 34 | if (null === $classMetadata) { 35 | return null; 36 | } 37 | 38 | \assert($classMetadata instanceof SerializerClassMetadata); 39 | 40 | // We base our scan on the internal driver's property list so that we 41 | // respect any internal allow/blocklist like in the AnnotationDriver 42 | foreach ($classMetadata->propertyMetadata as $propertyMetadata) { 43 | // If the inner driver provides a type, don't guess anymore. 44 | if ($propertyMetadata->type || $this->isVirtualProperty($propertyMetadata)) { 45 | continue; 46 | } 47 | 48 | try { 49 | $propertyReflection = $this->getReflection($propertyMetadata); 50 | if ($enum = $this->getEnumReflection($propertyReflection)) { 51 | $serializerType = [ 52 | 'name' => 'enum', 53 | 'params' => [ 54 | ['name' => $enum->getName(), 'params' => []], 55 | $enum->isBacked() ? 'value' : 'name', 56 | ], 57 | ]; 58 | $propertyMetadata->setType($serializerType); 59 | } 60 | } catch (ReflectionException $e) { 61 | continue; 62 | } 63 | } 64 | 65 | return $classMetadata; 66 | } 67 | 68 | private function isVirtualProperty(PropertyMetadata $propertyMetadata): bool 69 | { 70 | return $propertyMetadata instanceof VirtualPropertyMetadata 71 | || $propertyMetadata instanceof StaticPropertyMetadata 72 | || $propertyMetadata instanceof ExpressionPropertyMetadata; 73 | } 74 | 75 | private function getReflection(PropertyMetadata $propertyMetadata): ReflectionProperty 76 | { 77 | return new ReflectionProperty($propertyMetadata->class, $propertyMetadata->name); 78 | } 79 | 80 | private function getEnumReflection(ReflectionProperty $propertyReflection): ?\ReflectionEnum 81 | { 82 | $reflectionType = $propertyReflection->getType(); 83 | 84 | if (!($reflectionType instanceof \ReflectionNamedType)) { 85 | return null; 86 | } 87 | 88 | return enum_exists($reflectionType->getName()) ? new \ReflectionEnum($reflectionType->getName()) : null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Metadata/Driver/ExpressionMetadataTrait.php: -------------------------------------------------------------------------------- 1 | expressionEvaluator) { 26 | return $expression; 27 | } 28 | 29 | try { 30 | return $this->expressionEvaluator->parse($expression, array_merge(['context', 'property_metadata', 'object'], $names)); 31 | } catch (\LogicException $e) { 32 | throw new InvalidMetadataException(sprintf('Can not parse the expression "%s"', $expression), 0, $e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Metadata/Driver/NullDriver.php: -------------------------------------------------------------------------------- 1 | namingStrategy = $namingStrategy; 23 | } 24 | 25 | public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata 26 | { 27 | $classMetadata = new ClassMetadata($name = $class->name); 28 | $fileResource = $class->getFilename(); 29 | if (false !== $fileResource) { 30 | $classMetadata->fileResources[] = $fileResource; 31 | } 32 | 33 | foreach ($class->getProperties() as $property) { 34 | if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) { 35 | continue; 36 | } 37 | 38 | $propertyMetadata = new PropertyMetadata($name, $property->getName()); 39 | 40 | if (!$propertyMetadata->serializedName) { 41 | $propertyMetadata->serializedName = $this->namingStrategy->translateName($propertyMetadata); 42 | } 43 | 44 | $classMetadata->addPropertyMetadata($propertyMetadata); 45 | } 46 | 47 | return $classMetadata; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Metadata/ExpressionPropertyMetadata.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | #[\Attribute(\Attribute::TARGET_METHOD)] 16 | class ExpressionPropertyMetadata extends PropertyMetadata 17 | { 18 | /** 19 | * @var string|Expression 20 | */ 21 | public $expression; 22 | 23 | /** 24 | * @param string|Expression $expression 25 | */ 26 | public function __construct(string $class, string $fieldName, $expression) 27 | { 28 | $this->class = $class; 29 | $this->name = $fieldName; 30 | $this->expression = $expression; 31 | $this->readOnly = true; 32 | } 33 | 34 | public function setAccessor(string $type, ?string $getter = null, ?string $setter = null): void 35 | { 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | protected function serializeToArray(): array 42 | { 43 | return [ 44 | $this->expression, 45 | parent::serializeToArray(), 46 | ]; 47 | } 48 | 49 | protected function unserializeFromArray(array $data): void 50 | { 51 | [ 52 | $this->expression, 53 | $parentData, 54 | ] = $data; 55 | 56 | parent::unserializeFromArray($parentData); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Metadata/StaticPropertyMetadata.php: -------------------------------------------------------------------------------- 1 | class = $className; 23 | $this->name = $fieldName; 24 | $this->serializedName = $fieldName; 25 | $this->value = $fieldValue; 26 | $this->readOnly = true; 27 | $this->groups = $groups; 28 | } 29 | 30 | /** 31 | * @return mixed 32 | */ 33 | public function getValue() 34 | { 35 | return $this->value; 36 | } 37 | 38 | public function setAccessor(string $type, ?string $getter = null, ?string $setter = null): void 39 | { 40 | } 41 | 42 | protected function serializeToArray(): array 43 | { 44 | return [ 45 | $this->value, 46 | parent::serializeToArray(), 47 | ]; 48 | } 49 | 50 | protected function unserializeFromArray(array $data): void 51 | { 52 | [ 53 | $this->value, 54 | $parentData, 55 | ] = $data; 56 | 57 | parent::unserializeFromArray($parentData); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Metadata/VirtualPropertyMetadata.php: -------------------------------------------------------------------------------- 1 | class = $class; 18 | $this->name = $fieldName; 19 | $this->getter = $methodName; 20 | $this->readOnly = true; 21 | } 22 | 23 | public function setAccessor(string $type, ?string $getter = null, ?string $setter = null): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Naming/CamelCaseNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class CamelCaseNamingStrategy implements PropertyNamingStrategyInterface 15 | { 16 | /** 17 | * @var string 18 | */ 19 | private $separator; 20 | 21 | /** 22 | * @var bool 23 | */ 24 | private $lowerCase; 25 | 26 | public function __construct(string $separator = '_', bool $lowerCase = true) 27 | { 28 | $this->separator = $separator; 29 | $this->lowerCase = $lowerCase; 30 | } 31 | 32 | public function translateName(PropertyMetadata $property): string 33 | { 34 | $name = preg_replace('/[A-Z]+/', $this->separator . '\\0', $property->name); 35 | 36 | if ($this->lowerCase) { 37 | return strtolower($name); 38 | } 39 | 40 | return ucfirst($name); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Naming/IdenticalPropertyNamingStrategy.php: -------------------------------------------------------------------------------- 1 | name; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Naming/PropertyNamingStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface PropertyNamingStrategyInterface 18 | { 19 | /** 20 | * Translates the name of the property to the serialized version. 21 | */ 22 | public function translateName(PropertyMetadata $property): string; 23 | } 24 | -------------------------------------------------------------------------------- /src/Naming/SerializedNameAnnotationStrategy.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class SerializedNameAnnotationStrategy implements PropertyNamingStrategyInterface 15 | { 16 | /** 17 | * @var PropertyNamingStrategyInterface 18 | */ 19 | private $delegate; 20 | 21 | public function __construct(PropertyNamingStrategyInterface $namingStrategy) 22 | { 23 | $this->delegate = $namingStrategy; 24 | } 25 | 26 | public function translateName(PropertyMetadata $property): string 27 | { 28 | if (null !== $name = $property->serializedName) { 29 | return $name; 30 | } 31 | 32 | return $this->delegate->translateName($property); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NullAwareVisitorInterface.php: -------------------------------------------------------------------------------- 1 | strcmp($a->name, $b->name), 19 | ); 20 | 21 | return $properties; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ordering/CustomPropertyOrderingStrategy.php: -------------------------------------------------------------------------------- 1 | weight */ 10 | private $ordering; 11 | 12 | /** 13 | * @param int[] $ordering property => weight 14 | */ 15 | public function __construct(array $ordering) 16 | { 17 | $this->ordering = $ordering; 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function order(array $properties): array 24 | { 25 | $currentSorting = $properties ? array_combine(array_keys($properties), range(1, \count($properties))) : []; 26 | 27 | uksort($properties, function ($a, $b) use ($currentSorting) { 28 | $existsA = isset($this->ordering[$a]); 29 | $existsB = isset($this->ordering[$b]); 30 | 31 | if (!$existsA && !$existsB) { 32 | return $currentSorting[$a] - $currentSorting[$b]; 33 | } 34 | 35 | if (!$existsA) { 36 | return 1; 37 | } 38 | 39 | if (!$existsB) { 40 | return -1; 41 | } 42 | 43 | return $this->ordering[$a] < $this->ordering[$b] ? -1 : 1; 44 | }); 45 | 46 | return $properties; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ordering/IdenticalPropertyOrderingStrategy.php: -------------------------------------------------------------------------------- 1 | property 13 | * 14 | * @return PropertyMetadata[] name => property 15 | */ 16 | public function order(array $properties): array; 17 | } 18 | -------------------------------------------------------------------------------- /src/SerializationContext.php: -------------------------------------------------------------------------------- 1 | visitingSet = new \SplObjectStorage(); 38 | $this->visitingStack = new \SplStack(); 39 | } 40 | 41 | /** 42 | * Set if NULLs should be serialized (TRUE) ot not (FALSE) 43 | */ 44 | public function setSerializeNull(bool $bool): self 45 | { 46 | $this->assertMutable(); 47 | 48 | $this->serializeNull = $bool; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * Returns TRUE when NULLs should be serialized 55 | * Returns FALSE when NULLs should not be serialized 56 | */ 57 | public function shouldSerializeNull(): bool 58 | { 59 | return $this->serializeNull; 60 | } 61 | 62 | /** 63 | * @param mixed $object 64 | */ 65 | public function startVisiting($object): void 66 | { 67 | if (!\is_object($object)) { 68 | return; 69 | } 70 | 71 | $this->visitingSet->attach($object); 72 | $this->visitingStack->push($object); 73 | } 74 | 75 | /** 76 | * @param mixed $object 77 | */ 78 | public function stopVisiting($object): void 79 | { 80 | if (!\is_object($object)) { 81 | return; 82 | } 83 | 84 | $this->visitingSet->detach($object); 85 | $poppedObject = $this->visitingStack->pop(); 86 | 87 | if ($object !== $poppedObject) { 88 | throw new RuntimeException('Context visitingStack not working well'); 89 | } 90 | } 91 | 92 | /** 93 | * @param mixed $object 94 | */ 95 | public function isVisiting($object): bool 96 | { 97 | if (!\is_object($object)) { 98 | return false; 99 | } 100 | 101 | return $this->visitingSet->contains($object); 102 | } 103 | 104 | public function getPath(): ?string 105 | { 106 | $path = []; 107 | foreach ($this->visitingStack as $obj) { 108 | $path[] = \get_class($obj); 109 | } 110 | 111 | if (!$path) { 112 | return null; 113 | } 114 | 115 | return implode(' -> ', $path); 116 | } 117 | 118 | public function getDirection(): int 119 | { 120 | return GraphNavigatorInterface::DIRECTION_SERIALIZATION; 121 | } 122 | 123 | public function getDepth(): int 124 | { 125 | return $this->visitingStack->count(); 126 | } 127 | 128 | public function getObject(): ?object 129 | { 130 | return !$this->visitingStack->isEmpty() ? $this->visitingStack->top() : null; 131 | } 132 | 133 | public function getVisitingStack(): \SplStack 134 | { 135 | return $this->visitingStack; 136 | } 137 | 138 | public function getVisitingSet(): \SplObjectStorage 139 | { 140 | return $this->visitingSet; 141 | } 142 | 143 | /** 144 | * @return $this 145 | */ 146 | public function setInitialType(string $type): self 147 | { 148 | $this->assertMutable(); 149 | 150 | $this->initialType = $type; 151 | $this->setAttribute('initial_type', $type); 152 | 153 | return $this; 154 | } 155 | 156 | public function getInitialType(): ?string 157 | { 158 | return $this->initialType ?: ($this->hasAttribute('initial_type') ? $this->getAttribute('initial_type') : null); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface SerializerInterface 15 | { 16 | /** 17 | * Serializes the given data to the specified output format. 18 | * 19 | * @param mixed $data 20 | * 21 | * @throws RuntimeException 22 | */ 23 | public function serialize($data, string $format, ?SerializationContext $context = null, ?string $type = null): string; 24 | 25 | /** 26 | * Deserializes the given data to the specified type. 27 | * 28 | * @return mixed 29 | * 30 | * @throws RuntimeException 31 | */ 32 | public function deserialize(string $data, string $type, string $format, ?DeserializationContext $context = null); 33 | } 34 | -------------------------------------------------------------------------------- /src/Twig/SerializerBaseExtension.php: -------------------------------------------------------------------------------- 1 | serializationFunctionsPrefix = $serializationFunctionsPrefix; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Twig/SerializerExtension.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 27 | 28 | parent::__construct($serializationFunctionsPrefix); 29 | } 30 | 31 | /** 32 | * @return TwigFilter[] 33 | * 34 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 35 | */ 36 | public function getFilters() 37 | { 38 | return [ 39 | new TwigFilter($this->serializationFunctionsPrefix . 'serialize', [$this, 'serialize']), 40 | ]; 41 | } 42 | 43 | /** 44 | * @return TwigFunction[] 45 | * 46 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 47 | */ 48 | public function getFunctions() 49 | { 50 | return [ 51 | new TwigFunction($this->serializationFunctionsPrefix . 'serialization_context', '\JMS\Serializer\SerializationContext::create'), 52 | ]; 53 | } 54 | 55 | public function serialize(object $object, string $type = 'json', ?SerializationContext $context = null): string 56 | { 57 | return $this->serializer->serialize($object, $type, $context); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Twig/SerializerRuntimeExtension.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class SerializerRuntimeExtension extends SerializerBaseExtension 14 | { 15 | /** 16 | * @return TwigFilter[] 17 | * 18 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 19 | */ 20 | public function getFilters() 21 | { 22 | return [ 23 | new TwigFilter($this->serializationFunctionsPrefix . 'serialize', [SerializerRuntimeHelper::class, 'serialize']), 24 | ]; 25 | } 26 | 27 | /** 28 | * @return TwigFunction[] 29 | * 30 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 31 | */ 32 | public function getFunctions() 33 | { 34 | return [ 35 | new TwigFunction($this->serializationFunctionsPrefix . 'serialization_context', '\JMS\Serializer\SerializationContext::create'), 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Twig/SerializerRuntimeHelper.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class SerializerRuntimeHelper 14 | { 15 | /** 16 | * @var SerializerInterface 17 | */ 18 | protected $serializer; 19 | 20 | public function __construct(SerializerInterface $serializer) 21 | { 22 | $this->serializer = $serializer; 23 | } 24 | 25 | /** 26 | * @param mixed $object 27 | */ 28 | public function serialize($object, string $type = 'json', ?SerializationContext $context = null): string 29 | { 30 | return $this->serializer->serialize($object, $type, $context); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Type/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | getType($type); 31 | } catch (\Throwable $e) { 32 | throw new SyntaxError($e->getMessage(), 0, $e); 33 | } 34 | } 35 | 36 | protected function getCatchablePatterns(): array 37 | { 38 | return [ 39 | '[a-z][a-z_\\\\0-9]*', // identifier or qualified name 40 | "'(?:[^']|'')*'", // single quoted strings 41 | '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers 42 | '"(?:[^"]|"")*"', // double quoted strings 43 | '<', 44 | '>', 45 | '\\[', 46 | '\\]', 47 | ]; 48 | } 49 | 50 | protected function getNonCatchablePatterns(): array 51 | { 52 | return ['\s+']; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | * 58 | * @return int|string|null 59 | */ 60 | protected function getType(&$value) 61 | { 62 | $type = self::T_UNKNOWN; 63 | 64 | switch (true) { 65 | // Recognize numeric values 66 | case is_numeric($value): 67 | if (false !== strpos($value, '.') || false !== stripos($value, 'e')) { 68 | return self::T_FLOAT; 69 | } 70 | 71 | return self::T_INTEGER; 72 | 73 | // Recognize quoted strings 74 | case "'" === $value[0]: 75 | $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); 76 | 77 | return self::T_STRING; 78 | 79 | case '"' === $value[0]: 80 | $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); 81 | 82 | return self::T_STRING; 83 | 84 | case 'null' === $value: 85 | return self::T_NULL; 86 | 87 | // Recognize identifiers, aliased or qualified names 88 | case ctype_alpha($value[0]) || '\\' === $value[0]: 89 | return self::T_IDENTIFIER; 90 | 91 | case ',' === $value: 92 | return self::T_COMMA; 93 | 94 | case '>' === $value: 95 | return self::T_TYPE_END; 96 | 97 | case '<' === $value: 98 | return self::T_TYPE_START; 99 | 100 | case ']' === $value: 101 | return self::T_ARRAY_END; 102 | 103 | case '[' === $value: 104 | return self::T_ARRAY_START; 105 | 106 | // Default 107 | default: 108 | // Do nothing 109 | } 110 | 111 | return $type; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Type/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 14 | * @author Asmir Mustafic 15 | * 16 | * @phpstan-import-type TypeArray from Type 17 | */ 18 | interface DeserializationVisitorInterface extends VisitorInterface 19 | { 20 | /** 21 | * @param mixed $data 22 | * @param TypeArray $type 23 | * 24 | * @return null 25 | */ 26 | public function visitNull($data, array $type); 27 | 28 | /** 29 | * @param mixed $data 30 | * @param TypeArray $type 31 | */ 32 | public function visitString($data, array $type): ?string; 33 | 34 | /** 35 | * @param mixed $data 36 | * @param TypeArray $type 37 | */ 38 | public function visitBoolean($data, array $type): ?bool; 39 | 40 | /** 41 | * @param mixed $data 42 | * @param TypeArray $type 43 | */ 44 | public function visitDouble($data, array $type): ?float; 45 | 46 | /** 47 | * @param mixed $data 48 | * @param TypeArray $type 49 | */ 50 | public function visitInteger($data, array $type): ?int; 51 | 52 | /** 53 | * Returns the class name based on the type of the discriminator map value 54 | * 55 | * @param mixed $data 56 | */ 57 | public function visitDiscriminatorMapProperty($data, ClassMetadata $metadata): string; 58 | 59 | /** 60 | * @param mixed $data 61 | * @param TypeArray $type 62 | * 63 | * @return array 64 | */ 65 | public function visitArray($data, array $type): array; 66 | 67 | /** 68 | * Called before the properties of the object are being visited. 69 | * 70 | * @param TypeArray $type 71 | */ 72 | public function startVisitingObject(ClassMetadata $metadata, object $data, array $type): void; 73 | 74 | /** 75 | * @param mixed $data 76 | * 77 | * @return mixed 78 | */ 79 | public function visitProperty(PropertyMetadata $metadata, $data); 80 | 81 | /** 82 | * Called after all properties of the object have been visited. 83 | * 84 | * @param mixed $data 85 | * @param TypeArray $type 86 | */ 87 | public function endVisitingObject(ClassMetadata $metadata, $data, array $type): object; 88 | 89 | /** 90 | * @param mixed $data 91 | * 92 | * @return mixed 93 | */ 94 | public function getResult($data); 95 | } 96 | -------------------------------------------------------------------------------- /src/Visitor/Factory/DeserializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface DeserializationVisitorFactory 13 | { 14 | public function getVisitor(): DeserializationVisitorInterface; 15 | } 16 | -------------------------------------------------------------------------------- /src/Visitor/Factory/JsonDeserializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class JsonDeserializationVisitorFactory implements DeserializationVisitorFactory 15 | { 16 | /** 17 | * @var int 18 | */ 19 | private $options = 0; 20 | 21 | /** 22 | * @var int 23 | */ 24 | private $depth = 512; 25 | 26 | /** 27 | * @var bool 28 | */ 29 | private $strict; 30 | 31 | public function __construct(bool $strict = false) 32 | { 33 | $this->strict = $strict; 34 | } 35 | 36 | public function getVisitor(): DeserializationVisitorInterface 37 | { 38 | if ($this->strict) { 39 | return new JsonDeserializationStrictVisitor($this->options, $this->depth); 40 | } 41 | 42 | return new JsonDeserializationVisitor($this->options, $this->depth); 43 | } 44 | 45 | public function setOptions(int $options): self 46 | { 47 | $this->options = $options; 48 | 49 | return $this; 50 | } 51 | 52 | public function setDepth(int $depth): self 53 | { 54 | $this->depth = $depth; 55 | 56 | return $this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Visitor/Factory/JsonSerializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class JsonSerializationVisitorFactory implements SerializationVisitorFactory 14 | { 15 | /** 16 | * @var int 17 | */ 18 | private $options = JSON_PRESERVE_ZERO_FRACTION; 19 | 20 | public function getVisitor(): SerializationVisitorInterface 21 | { 22 | return new JsonSerializationVisitor($this->options); 23 | } 24 | 25 | public function setOptions(int $options): self 26 | { 27 | $this->options = $options; 28 | 29 | return $this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Visitor/Factory/SerializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface SerializationVisitorFactory 13 | { 14 | public function getVisitor(): SerializationVisitorInterface; 15 | } 16 | -------------------------------------------------------------------------------- /src/Visitor/Factory/XmlDeserializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class XmlDeserializationVisitorFactory implements DeserializationVisitorFactory 14 | { 15 | /** 16 | * @var bool 17 | */ 18 | private $disableExternalEntities = true; 19 | 20 | /** 21 | * @var string[] 22 | */ 23 | private $doctypeWhitelist = []; 24 | 25 | /** 26 | * @var int 27 | */ 28 | private $options = 0; 29 | 30 | /** 31 | * @return XmlDeserializationVisitor 32 | */ 33 | public function getVisitor(): DeserializationVisitorInterface 34 | { 35 | return new XmlDeserializationVisitor($this->disableExternalEntities, $this->doctypeWhitelist, $this->options); 36 | } 37 | 38 | public function enableExternalEntities(bool $enable = true): self 39 | { 40 | $this->disableExternalEntities = !$enable; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @param string[] $doctypeWhitelist 47 | */ 48 | public function setDoctypeWhitelist(array $doctypeWhitelist): self 49 | { 50 | $this->doctypeWhitelist = $doctypeWhitelist; 51 | 52 | return $this; 53 | } 54 | 55 | public function setOptions(int $options): self 56 | { 57 | $this->options = $options; 58 | 59 | return $this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Visitor/Factory/XmlSerializationVisitorFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class XmlSerializationVisitorFactory implements SerializationVisitorFactory 14 | { 15 | /** 16 | * @var string 17 | */ 18 | private $defaultRootName = 'result'; 19 | 20 | /** 21 | * @var string 22 | */ 23 | private $defaultVersion = '1.0'; 24 | 25 | /** 26 | * @var string 27 | */ 28 | private $defaultEncoding = 'UTF-8'; 29 | 30 | /** 31 | * @var bool 32 | */ 33 | private $formatOutput = true; 34 | 35 | /** 36 | * @var string|null 37 | */ 38 | private $defaultRootNamespace; 39 | 40 | public function getVisitor(): SerializationVisitorInterface 41 | { 42 | return new XmlSerializationVisitor( 43 | $this->formatOutput, 44 | $this->defaultEncoding, 45 | $this->defaultVersion, 46 | $this->defaultRootName, 47 | $this->defaultRootNamespace, 48 | ); 49 | } 50 | 51 | public function setDefaultRootName(string $name, ?string $namespace = null): self 52 | { 53 | $this->defaultRootName = $name; 54 | $this->defaultRootNamespace = $namespace; 55 | 56 | return $this; 57 | } 58 | 59 | public function setDefaultVersion(string $version): self 60 | { 61 | $this->defaultVersion = $version; 62 | 63 | return $this; 64 | } 65 | 66 | public function setDefaultEncoding(string $encoding): self 67 | { 68 | $this->defaultEncoding = $encoding; 69 | 70 | return $this; 71 | } 72 | 73 | public function setFormatOutput(bool $formatOutput): self 74 | { 75 | $this->formatOutput = (bool) $formatOutput; 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Visitor/SerializationVisitorInterface.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Asmir Mustafic 17 | * 18 | * @phpstan-import-type TypeArray from Type 19 | */ 20 | interface SerializationVisitorInterface extends VisitorInterface 21 | { 22 | /** 23 | * @param mixed $data 24 | * @param TypeArray $type 25 | * 26 | * @return mixed 27 | */ 28 | public function visitNull($data, array $type); 29 | 30 | /** 31 | * @param TypeArray $type 32 | * 33 | * @return mixed 34 | */ 35 | public function visitString(string $data, array $type); 36 | 37 | /** 38 | * @param TypeArray $type 39 | * 40 | * @return mixed 41 | */ 42 | public function visitBoolean(bool $data, array $type); 43 | 44 | /** 45 | * @param TypeArray $type 46 | * 47 | * @return mixed 48 | */ 49 | public function visitDouble(float $data, array $type); 50 | 51 | /** 52 | * @param TypeArray $type 53 | * 54 | * @return mixed 55 | */ 56 | public function visitInteger(int $data, array $type); 57 | 58 | /** 59 | * @param TypeArray $type 60 | * 61 | * @return array|\ArrayObject|void 62 | */ 63 | public function visitArray(array $data, array $type); 64 | 65 | /** 66 | * @param TypeArray $type 67 | * Called before the properties of the object are being visited. 68 | */ 69 | public function startVisitingObject(ClassMetadata $metadata, object $data, array $type): void; 70 | 71 | /** 72 | * @param mixed $data 73 | */ 74 | public function visitProperty(PropertyMetadata $metadata, $data): void; 75 | 76 | /** 77 | * @param TypeArray $type 78 | * Called after all properties of the object have been visited. 79 | * 80 | * @return array|\ArrayObject|void 81 | */ 82 | public function endVisitingObject(ClassMetadata $metadata, object $data, array $type); 83 | } 84 | -------------------------------------------------------------------------------- /src/VisitorInterface.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Asmir Mustafic 17 | */ 18 | interface VisitorInterface 19 | { 20 | /** 21 | * Allows visitors to convert the input data to a different representation 22 | * before the actual serialization/deserialization process starts. 23 | * 24 | * @param mixed $data 25 | * 26 | * @return mixed 27 | */ 28 | public function prepare($data); 29 | 30 | /** 31 | * Called before serialization/deserialization starts. 32 | */ 33 | public function setNavigator(GraphNavigatorInterface $navigator): void; 34 | 35 | /** 36 | * Get the result of the serialization/deserialization process. 37 | * 38 | * @param mixed $data 39 | * 40 | * @return mixed 41 | */ 42 | public function getResult($data); 43 | } 44 | --------------------------------------------------------------------------------