Skip to content

Commit

Permalink
Merge pull request #95 from kynx/backed-enum-strategy
Browse files Browse the repository at this point in the history
Add BackedEnumStrategy
  • Loading branch information
Ocramius committed Oct 24, 2022
2 parents fcaa066 + 03a5fcb commit eae638f
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 9 deletions.
103 changes: 103 additions & 0 deletions docs/book/v4/strategies/backed-enum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# BackedEnum

INFO: **New Feature**
Available since version 4.8.0

MISSING: **Installation Requirements**
[Enumerations](https://www.php.net/manual/language.enumerations.overview.php) require PHP version 8.1 or higher.

The `BackedEnumStrategy` provides **bidirectional conversion between strings
or integers and [Backed Enums](https://www.php.net/manual/en/language.enumerations.backed.php)**.

The code examples below will use the following backed enum, representing a
genre of music:

```php
enum Genre: string
{
case Pop = 'pop';
case Blues = 'blues';
case Jazz = 'jazz';
}
```

## Basic Usage

The following code example shows standalone usage without adding the strategy
to a hydrator.

### Create and Configure Strategy

Create the strategy passing the class name of the enum it will hydrate and extract:

```php
$strategy = new Laminas\Hydrator\Strategy\BackedEnumStrategy(Genre::class);
```

### Hydrate Data

```php
$hydrated = $strategy->hydrate('blues', null);
var_dump($hydrated); // enum Genre::Blues : string("blues");
```

### Extract Data

```php
$extracted = $strategy->extract(Genre::Pop);
var_dump($extracted); // string(3) "pop"
```

## Example

The following example demonstrates hydration for a class with a property.

An example class which represents an album with a music genre:

```php
class Album
{
private ?Genre $genre;

public function __construct(?Genre $genre = null)
{
$this->genre = $genre;
}

public function getGenre() : Genre
{
return $this->genre;
}
}
```

### Create Hydrator and Add Atrategy

Create a hydrator and add the `BackedEnumStrategy` as a strategy:

```php
$hydrator = new Laminas\Hydrator\ReflectionHydrator();
$hydrator->addStrategy(
'genre',
new Laminas\Hydrator\Strategy\BackedEnumStrategy(Genre::class)
);
```

### Hydrate Data

Create an instance of the example class and hydrate data:

```php
$album = new Album();
$hydrator->hydrate(['genre' => 'jazz'], $album);

echo $album->getGenre()->value; // "jazz"
```

### Extract Data

```php
$extracted = $hydrator->extract($album);

echo $extracted['genre']; // "jazz"
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ nav:
- "Filters": v4/filter.md
- "Strategies":
- Introduction: v4/strategy.md
- BackedEnum: v4/strategies/backed-enum.md
- Collection: v4/strategies/collection.md
- DateTimeImmutableFormatter: v4/strategies/datetime-immutable-formatter-strategy.md
- Hydrator: v4/strategies/hydrator.md
Expand Down
69 changes: 60 additions & 9 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,17 @@
<MixedInferredReturnType occurrences="1">
<code>HydratorInterface</code>
</MixedInferredReturnType>
<MixedReturnStatement occurrences="1"/>
<MixedReturnStatement occurrences="1">
<code>$this-&gt;hydrators-&gt;get($object::class)</code>
</MixedReturnStatement>
</file>
<file src="src/DelegatingHydratorFactory.php">
<MixedInferredReturnType occurrences="1">
<code>ContainerInterface</code>
</MixedInferredReturnType>
<MixedReturnStatement occurrences="3">
<code>$container-&gt;get('HydratorManager')</code>
<code>$container-&gt;get('Zend\Hydrator\HydratorPluginManager')</code>
<code>$container-&gt;get(HydratorPluginManager::class)</code>
</MixedReturnStatement>
</file>
Expand Down Expand Up @@ -115,7 +118,9 @@
<MixedAssignment occurrences="1">
<code>$currentValue</code>
</MixedAssignment>
<MixedMethodCall occurrences="1"/>
<MixedMethodCall occurrences="1">
<code>new $prototype()</code>
</MixedMethodCall>
</file>
<file src="src/Module.php">
<MixedAssignment occurrences="2">
Expand Down Expand Up @@ -220,6 +225,26 @@
<code>new $class()</code>
</InvalidStringClass>
</file>
<file src="src/Strategy/BackedEnumStrategy.php">
<MixedInferredReturnType occurrences="1">
<code>T</code>
</MixedInferredReturnType>
<MixedReturnStatement occurrences="1">
<code>$this-&gt;enumClass::from($value)</code>
</MixedReturnStatement>
<PropertyNotSetInConstructor occurrences="1">
<code>$enumClass</code>
</PropertyNotSetInConstructor>
<UndefinedClass occurrences="1">
<code>$this-&gt;enumClass</code>
</UndefinedClass>
<UndefinedDocblockClass occurrences="4">
<code>$value-&gt;value</code>
<code>T</code>
<code>class-string&lt;T&gt;</code>
<code>string</code>
</UndefinedDocblockClass>
</file>
<file src="src/Strategy/BooleanStrategy.php">
<DocblockTypeContradiction occurrences="4">
<code>! is_int($falseValue) &amp;&amp; ! is_string($falseValue)</code>
Expand Down Expand Up @@ -358,7 +383,6 @@
<code>$andFilters</code>
<code>$orFilters</code>
<code>$value</code>
<code>$value</code>
</MixedAssignment>
</file>
<file src="test/HydratorAwareTraitTest.php">
Expand Down Expand Up @@ -410,7 +434,6 @@
<file src="test/HydratorObjectPropertyTest.php">
<MissingClosureParamType occurrences="1">
<code>$property</code>
<code>$property</code>
</MissingClosureParamType>
</file>
<file src="test/HydratorStrategyTest.php">
Expand Down Expand Up @@ -530,8 +553,40 @@
<code>$instance</code>
</MixedAssignment>
</file>
<file src="test/Strategy/BackedEnumStrategyTest.php">
<MixedArgument occurrences="6">
<code>TestBackedEnum::class</code>
<code>TestBackedEnum::class</code>
<code>TestBackedEnum::class</code>
<code>TestBackedEnum::class</code>
<code>TestBackedEnum::class</code>
<code>TestBackedEnum::class</code>
</MixedArgument>
<MixedAssignment occurrences="1">
<code>$expected</code>
</MixedAssignment>
<MixedOperand occurrences="1">
<code>TestBackedEnum::class</code>
</MixedOperand>
<UndefinedClass occurrences="12">
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestBackedEnum</code>
<code>TestUnitEnum</code>
</UndefinedClass>
</file>
<file src="test/Strategy/BooleanStrategyTest.php">
<InvalidArgument occurrences="1"/>
<InvalidArgument occurrences="1">
<code>false</code>
</InvalidArgument>
<InvalidScalarArgument occurrences="2">
<code>5</code>
<code>true</code>
Expand All @@ -553,19 +608,15 @@
</file>
<file src="test/Strategy/DateTimeFormatterStrategyTest.php">
<MixedAssignment occurrences="5">
<code>$date</code>
<code>$date</code>
<code>$date</code>
<code>$extracted</code>
<code>$extracted</code>
<code>$hydrated</code>
</MixedAssignment>
<MixedMethodCall occurrences="3">
<code>format</code>
<code>format</code>
<code>getName</code>
<code>getName</code>
<code>getTimezone</code>
<code>getTimezone</code>
</MixedMethodCall>
</file>
Expand Down
2 changes: 2 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
<ignoreFiles>
<directory name="test/TestAsset"/>
<directory name="vendor"/>
<!-- can be removed once PHP8.0 support dropped -->
<directory name="test/Strategy/TestAsset"/>
</ignoreFiles>
</projectFiles>

Expand Down
75 changes: 75 additions & 0 deletions src/Strategy/BackedEnumStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Laminas\Hydrator\Strategy;

use BackedEnum;
use Laminas\Hydrator\Strategy\Exception\InvalidArgumentException;
use ValueError;

use function get_debug_type;
use function is_int;
use function is_string;
use function sprintf;

/**
* @template T of BackedEnum
*/
final class BackedEnumStrategy implements StrategyInterface
{
/** @var class-string<T> */
private string $enumClass;

/**
* @param class-string<T> $enumClass
*/
public function __construct(string $enumClass)
{
$this->enumClass = $enumClass;
}

/**
* @inheritDoc
*/
public function extract($value, ?object $object = null)
{
if (! $value instanceof $this->enumClass) {
throw new InvalidArgumentException(sprintf(
"Value must be a %s; %s provided",
$this->enumClass,
get_debug_type($value)
));
}

return $value->value;
}

/**
* @param mixed $value
* @return T
*/
public function hydrate($value, ?array $data)
{
if ($value instanceof $this->enumClass) {
return $value;
}

if (! (is_int($value) || is_string($value))) {
throw new InvalidArgumentException(sprintf(
"Value must be string or int; %s provided",
get_debug_type($value)
));
}

try {
return $this->enumClass::from($value);
} catch (ValueError $error) {
throw new InvalidArgumentException(sprintf(
"Value '%s' is not a valid scalar value for %s",
(string) $value,
$this->enumClass
), 0, $error);
}
}
}
Loading

0 comments on commit eae638f

Please sign in to comment.