apie / object-access-normalizer
Object Access + Symfony normalizers to map setters/getters to an object.
Requires
- php: >=7.3
- phpdocumentor/reflection-docblock: ^4.3|5.*
- psr/cache: ^1.0
- symfony/http-kernel: ^4.3|^5.0
- symfony/property-info: ^4.3|^5.0
- symfony/serializer: ^4.3|^5.0
Requires (Dev)
- apie/uuid-plugin: dev-main
- apie/value-object-plugin: dev-main
- doctrine/annotations: ^1.6
- phpstan/phpstan: ^0.12.3
- phpunit/phpunit: ^9.5
- ramsey/uuid: ^3.8
- symfony/cache: ^4.3|5.*
This package is auto-updated.
Last update: 2022-06-12 09:55:17 UTC
README
This package is part of the Apie library. The code is maintained in a monorepo, so PR's need to be sent to the monorepo
Documentation
Usage with Symfony Serializer
The simplest usage is adding ApieObjectAccessNormalizer to the constructor of the Symfony serializer.
<?php use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Serializer; use Apie\ObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer; use Apie\ObjectAccessNormalizer\ObjectAccess\ObjectAccess; $serializer = new Serializer( [ new DateTimeNormalizer(), new ApieObjectAccessNormalizer(), new ArrayDenormalizer(), ], [new JsonEncoder()] ); class Example { private $number; private $stringValue = '<no value set>'; public function __construct(int $number) { $this->number = $number; } public function setStringValue(string $stringValue) { $this->stringValue = $stringValue; } public function getNumber(): int { return $this->number; } public function getStringValue(): string { return $this->stringValue; } } $instance = new Example(12); // returns array['number' => 12, 'stringValue' => '<no value set>'] var_dump($serializer->serialize($instance, 'json')); // returns new Example(12) var_dump($serializer->deserialize(['number' => 12], Example::class, 'json')); // throws validation error with errors => ['number' =>' must be one of "int" ("invalid" given)'] $serializer->deserialize(['number' => 'invalid'], Example::class, 'json'); // calls setStringValue("blah") on $instance $serializer->deserialize(['stringValue' => 'text'], Example::class, 'json', ['object_to_populate' => $instance]); // use a different object access on $instance to set private properties that have no public setter. $serializer->deserialize(['number' => '15'], Example::class, 'json', ['object_to_populate' => $instance, 'object_access' => new ObjectAccess(false)]);
Unless the context option 'object_to_populate' is called it will first try to create a new object by reading the constructor arguments. Afterwards it will check all setters. If a setter throws an error the error is considered a validation error and will return a validation exception with errors structure.
ObjectAccess is also able to read property typehints in PHP 7.4+ and php docblocks with composer package phpdocumentor/reflection-docblock.
Camel case keys
By default the property name is the same as the key. We can override this in the ApieObjectAccessNormalizer class by providing a class implementing Symfony\Component\Serializer\NameConverter\NameConverterInterface.
<?php use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Serializer; use Apie\ObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer; use Apie\ObjectAccessNormalizer\ObjectAccess\ObjectAccess; $serializer = new Serializer( [ new DateTimeNormalizer(), new ApieObjectAccessNormalizer(new ObjectAccess(), new CamelCaseToSnakeCaseNameConverter()), new ArrayDenormalizer(), ], [new JsonEncoder()] ); $instance = new Example(12); // returns array['number' => 12, 'string_value' => '<no value set>'] var_dump($serializer->serialize($instance, 'json'));
Advanced usages
In many cases you wan to use ObjectAccess and only use a different ObjectAccessInterface implementation for a specific class or interface. For that we created GroupedObjectAccess.
<?php use Illuminate\Database\Eloquent\Model; use W2w\Laravel\LaravelApie\ObjectAccess\EloquentModelAccess; use Apie\ObjectAccessNormalizer\ObjectAccess\GroupedObjectAccess; use Apie\ObjectAccessNormalizer\ObjectAccess\ObjectAccess; use Apie\ObjectAccessNormalizer\ObjectAccess\SelfObjectAccess; use Apie\ObjectAccessNormalizer\ObjectAccess\SelfObjectAccessInterface; $objectAccess = new GroupedObjectAccess( new ObjectAccess, [ // For SomeClass we can read private properties/getters SomeClass::class => new ObjectAccess(false, true), // for any class that implements SelfobjectAccessInterface we use SelfObjectAccess SelfObjectAccessInterface::class => new SelfObjectAccess(), // does not exist in this package, just an example. Eloquent models are notorious for the amount of magic. Model::class => new EloquentModelObjectAccess(), ] );
Available object access implementations
- CachedObjectAccess: decorator to cache the results for performance reasons.
- FilteredObjectAccess: Filter the fields you can actually use. Another decorator
- GroupedObjectAccess: see Advanced usages. Can be used to use different Object Acces instances dependening on the class
- ObjectAccess: Default object access. Checks public properties and public setters and getters.
- SelfObjectAccess: Works for classes that implementSelfObjectAccessInterface, so the class can tell itself what it can access.
- LocalizationAwareObjectAccess: Can be used on objects with localization aware fields.
Localization
Since version 2 we add localization support. In your simple object add a setter like this and you have a localized field:
<?php namespace Wrwr; class ObjectWithLocalization { private $pizzas = []; public function setPizza(string $locale, string $preference) { $this->pizzas[$locale] = $preference; } public function getPizza(string $locale) { return $this->pizzas[$locale]; } }
This will result in a setter and getter for field name 'pizza' with support of localization.
in Symfony framework
If you want to use it in the Symfony framework all you need to do is register class Apie\ObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer as a service and tag it with 'serializer.normalizer' to add it to the symfony serializer.