w2w / apie-object-access-normalizer
Symfony normalizers used internally by Apie
Installs: 4 512
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 3
Requires
- php: ^7.2
- 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)
- phpstan/phpstan: ^0.12.3
- phpunit/phpunit: ^8.5
- ramsey/uuid: ^3.8
- w2w/apie: ^3.2|dev-master|v4.x-dev
README
Object access normalizer used internally by apie. It can be used outside Apie with the symfony serializer where it can replace the default object normalizers that are used there.
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 W2w\Lib\ApieObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer; use W2w\Lib\ApieObjectAccessNormalizer\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 W2w\Lib\ApieObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer; use W2w\Lib\ApieObjectAccessNormalizer\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 W2w\Lib\ApieObjectAccessNormalizer\ObjectAccess\GroupedObjectAccess; use W2w\Lib\ApieObjectAccessNormalizer\ObjectAccess\ObjectAccess; use W2w\Lib\ApieObjectAccessNormalizer\ObjectAccess\SelfObjectAccess; use W2w\Lib\ApieObjectAccessNormalizer\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 W2w\Lib\ApieObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer as a service and tag it with 'serializer.normalizer' to add it to the symfony serializer.