Skip to content

Commit

Permalink
Docs (#19)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergei Predvoditelev <sergei@predvoditelev.ru>
  • Loading branch information
samdark and vjik committed May 30, 2023
1 parent bce6ee0 commit 1a49a47
Show file tree
Hide file tree
Showing 31 changed files with 728 additions and 27 deletions.
207 changes: 205 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,210 @@
[![type-coverage](https://shepherd.dev/github/yiisoft/hydrator/coverage.svg)](https://shepherd.dev/github/yiisoft/hydrator)
[![psalm-level](https://shepherd.dev/github/yiisoft/hydrator/level.svg)](https://shepherd.dev/github/yiisoft/hydrator)

The package ...
The package provides a way to create or hydrate objects from a set of raw data.

Features are:

- supports properties of any visibility;
- uses constructor arguments to create objects;
- resolves dependencies when creating objects using [PSR-11](http://www.php-fig.org/psr/psr-11/) compatible DI container
provided;
- supports nested objects;
- supports mapping;
- allows fine-tuning hydration via PHP attributes.

## Basic usage

To hydrate existing object:

```php
use Yiisoft\Hydrator\Hydrator;

$hydrator = new Hydrator($container);
$hydrator->hydrate($object, $data);
```

To create a new object and fill it with the data:

```php
use Yiisoft\Hydrator\Hydrator;

$hydrator = new Hydrator($container);
$object = $hydrator->create(MyClass::class, $data);
```

To pass arguments to the constructor of a nested object, use nested array or dot-notation:

```php
final class Engine
{
public function __construct(
private string $name,
) {}
}

final class Car
{
public function __construct(
private string $name,
private Engine $engine,
) {}
}

// nested array
$object = $hydrator->create(Car::class, [
'name' => 'Ferrari',
'engine' => [
'name' => 'V8',
]
]);

// or dot-notation
$object = $hydrator->create(Car::class, [
'name' => 'Ferrari',
'engine.name' => 'V8',
]);
```

That would pass the `name` constructor argument of the `Car` object and create a new `Engine` object for `engine`
argument passing `V8` as the `name` argument to its constructor.

## Configuration with PHP attributes

You can configure how the hydrator creates or hydrates a specific class using attributes.

### Mapping

To map attributes to specific data keys, use `Map` attribute:

```php
use \Yiisoft\Hydrator\Attribute\Data\Map;

#[Map([
'firstName' => 'first_name',
'lastName' => 'last_name',
])]
final class Person
{
public function __construct(
private string $firstName,
private string $lastName,
) {}
}

$person = $hydrator->create(Person::class, [
'first_name' => 'John',
'last_name' => 'Doe',
]);
```

When using the `Map`, you can set `strict` argument to `true`. That instructs the hydrator that all data should be
mapped explicitly.

Alternatively you can map each property using `Data` attribute:

```php
use \Yiisoft\Hydrator\Attribute\Parameter\Data;

final class Person
{
public function __construct(
#[Data('first_name')]
private string $firstName,
#[Data('last_name')]
private string $lastName,
) {}
}

$person = $hydrator->create(Person::class, [
'first_name' => 'John',
'last_name' => 'Doe',
]);
```

### Casting value to string

To cast a value to string, use `ToString` attribute:

```php
use \Yiisoft\Hydrator\Attribute\Parameter\ToString;

class Money
{
public function __construct(
#[ToString]
private string $value,
private string $currency,
) {}
}

$money = $hydrator->create(Money::class, [
'value' => 4200,
'currency' => 'AMD',
]);
```

### Skipping hydration

To skip hydration of a specific property, use `SkipHydration` attribute:

```php
use \Yiisoft\Hydrator\Attribute\SkipHydration;

class MyClass
{
#[SkipHydration]
private $property;
}
```

### Resolving dependencies

To resolve dependencies by specific ID using DI container, use `Di` attribute:

```php
ues \Yiisoft\Hydrator\Attribute\Parameter\Di;

class MyClass
{
public function __construct(
#[Di(id: 'importConnection')]
private ConnectionInterface $connection,
) {}
}
```

The annotation will instruct hydrator to get `$connection` from DI container by `importConnection` ID.

### Your own attributes

There are two main parts:

- Attribute class.
It only stores configuration options and a reference to its handler.
- Attribute resolver.
Given an attribute reflection and extra data, it resolves an attribute.

Besides responsibilities' separation,
this approach allows the package to automatically resolve dependencies for attribute resolver.

#### Data attributes

You apply data attributes to a whole class.
The main goal is getting data from external sources such as from request.
Additionally, it's possible to specify how external source attributes map to hydrated class.

Data attribute class should implement `DataAttributeInterface` and the corresponding data attribute resolver should
implement `DataAttributeResolverInterface`.

#### Parameter attributes

You apply parameter attributes to class properties and constructor parameters.
You use these attributes for getting value for specific parameter or for preparing the value
(for example, by type casting).

Parameter attribute class should implement `ParameterAttributeInterface` and the corresponding parameter attribute
resolver should implement `ParameterAttributeResolverInterface`.

## Requirements

Expand Down Expand Up @@ -74,7 +277,7 @@ Use [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker)

## License

The Yii Hydrator is free software. It is released under the terms of the BSD License.
The Yii Hydrator is free software. It's released under the terms of the BSD License.
Please see [`LICENSE`](./LICENSE.md) for more information.

Maintained by [Yii Software](https://www.yiiframework.com/).
Expand Down
5 changes: 5 additions & 0 deletions src/Attribute/Data/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
use Yiisoft\Hydrator\UnexpectedAttributeException;

/**
* Override mapping of object property names to keys in the data array in hydrator.
*
* @psalm-import-type MapType from HydratorInterface
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Map implements DataAttributeInterface, DataAttributeResolverInterface
{
/**
* @param array $map Object property names mapped to keys in the data array.
* @param bool|null $strict Whether to hydrate properties from the map only.
*
* @psalm-param MapType $map
*/
public function __construct(
Expand Down
5 changes: 4 additions & 1 deletion src/Attribute/Parameter/Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
use Yiisoft\Hydrator\Result;
use Yiisoft\Hydrator\UnexpectedAttributeException;

/**
* Resolve value from the data array used for object hydration by key specified.
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
final class Data implements ParameterAttributeInterface, ParameterAttributeResolverInterface
{
/**
* @param string|string[] $key
* @param string|string[] $key The data array key.
*/
public function __construct(
private array|string $key,
Expand Down
11 changes: 11 additions & 0 deletions src/Attribute/Parameter/Di.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@
use Attribute;
use Yiisoft\Hydrator\ParameterAttributeInterface;

/**
* Resolve value as instance obtained from container by the specified ID or autoresolvied ID by PHP type.
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
final class Di implements ParameterAttributeInterface
{
/**
* @param string|null $id Container ID to obtain instance from.
*/
public function __construct(
private ?string $id = null
) {
}

/**
* Get container ID to obtain instance from.
*
* @return string|null Container ID to obtain instance from.
*/
public function getId(): ?string
{
return $this->id;
Expand Down
7 changes: 7 additions & 0 deletions src/Attribute/Parameter/DiNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@
use RuntimeException;
use Throwable;

/**
* Exception that thrown into {@see DiResolver} when object not found or object ID autoresolving is fail.
*/
final class DiNotFoundException extends RuntimeException implements NotFoundExceptionInterface
{
/**
* @param ReflectionParameter|ReflectionProperty $reflection Parameter or property reflection.
* @param Throwable|null $previous The previous throwable used for the exception chaining.
*/
public function __construct(ReflectionParameter|ReflectionProperty $reflection, ?Throwable $previous = null)
{
/**
Expand Down
8 changes: 7 additions & 1 deletion src/Attribute/Parameter/DiResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@
use Yiisoft\Hydrator\Result;
use Yiisoft\Hydrator\UnexpectedAttributeException;

/**
* Resolver for {@see Di} attribute. Obtains dependency from container by ID specified or autoresolved ID by PHP type.
*/
final class DiResolver implements ParameterAttributeResolverInterface
{
/**
* @param ContainerInterface $container Container to obtain dependency from.
*/
public function __construct(
private ContainerInterface $container,
) {
}

/**
* @throws ContainerExceptionInterface
* @throws DiNotFoundException
* @throws DiNotFoundException When object not found in container or object ID autoresolving is fail.
*/
public function getParameterValue(ParameterAttributeInterface $attribute, Context $context): Result
{
Expand Down
3 changes: 3 additions & 0 deletions src/Attribute/Parameter/ToString.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use Yiisoft\Hydrator\Result;
use Yiisoft\Hydrator\UnexpectedAttributeException;

/**
* Converts the resolved value to string. Non-resolved values is skip.
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class ToString implements ParameterAttributeInterface, ParameterAttributeResolverInterface
{
Expand Down
3 changes: 3 additions & 0 deletions src/Attribute/SkipHydration.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use Attribute;

/**
* Attribute that mark a class property or constructor parameter for as non-used on hydration.
*/
#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)]
final class SkipHydration
{
Expand Down

0 comments on commit 1a49a47

Please sign in to comment.