Skip to content

Commit

Permalink
Merge pull request #20 from sunrise-php/release/v2.6.0
Browse files Browse the repository at this point in the history
v2.6.0
  • Loading branch information
fenric authored Aug 17, 2022
2 parents 80f234d + f5f31a4 commit 435c548
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 27 deletions.
19 changes: 16 additions & 3 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
build:
image: default-bionic
environment:
php:
version: '8.1'
nodes:
analysis:
environment:
php: 8.1
tests:
override:
- php-scrutinizer-run
coverage:
environment:
php: 8.1
tests:
override:
- command: XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-clover coverage.xml
coverage:
file: coverage.xml
format: clover
php80:
environment:
php: 8.0
tests:
override:
- command: php vendor/bin/phpunit
php74:
environment:
php: 7.4
tests:
override:
- command: php vendor/bin/phpunit
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,47 @@ public readonly SomeEnum $value;
['value' => '1']
```

## Enum for PHP < 8.1

Accepts only values that exist in an enum.

```php
use Sunrise\Hydrator\Enum;

final class SomeEnum extends Enum {
public const foo = 0;
public const bar = 1;
}
```

```php
public SomeEnum $value;
```

```php
['value' => 0]
['value' => '1']
```

#### Useful to know

```php
// returns all cases of the enum
SomeEnum::cases();

// initializes the enum by the case's name
$case = SomeEnum::foo();

// initializes the enum by the case's value
$case = SomeEnum::tryFrom(0);

// gets the name of the enum's case
$case->name()

// gets the value of the enum's case
$case->value()
```

## Association

Accepts a valid structure for an association
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
],
"require": {
"php": "^7.4|^8.0"
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "~9.5.0",
Expand Down
171 changes: 171 additions & 0 deletions src/Enum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php declare(strict_types=1);

/**
* It's free open-source software released under the MIT License.
*
* @author Anatoly Fenric <[email protected]>
* @copyright Copyright (c) 2021, Anatoly Fenric
* @license https://github.com/sunrise-php/hydrator/blob/master/LICENSE
* @link https://github.com/sunrise-php/hydrator
*/

namespace Sunrise\Hydrator;

/**
* Import classes
*/
use JsonSerializable;
use ReflectionClass;
use RuntimeException;

/**
* Import functions
*/
use function is_int;
use function is_string;
use function sprintf;

/**
* Abstract enum
*
* @since 2.6.0
*/
abstract class Enum implements JsonSerializable
{

/**
* Cached cases of the enum
*
* @var array<class-string<static>, list<static>>
*/
private static array $cases = [];

/**
* The name of the enum's case
*
* @var string
*
* @readonly
*/
private string $name;

/**
* The value of the enum's case
*
* @var int|string
*
* @readonly
*/
private $value;

/**
* Constructor of the class
*
* @param string $name
* @param int|string $value
*/
final protected function __construct(string $name, $value)
{
$this->name = $name;
$this->value = $value;
}

/**
* Gets the name of the enum's case
*
* @return string
*/
final public function name(): string
{
return $this->name;
}

/**
* Gets the value of the enum's case
*
* @return int|string
*/
final public function value()
{
return $this->value;
}

/**
* Gets all cases of the enum
*
* @return list<static>
*/
final public static function cases(): array
{
if (isset(self::$cases[static::class])) {
return self::$cases[static::class];
}

$class = new ReflectionClass(static::class);
$constants = $class->getReflectionConstants();
foreach ($constants as $constant) {
$owner = $constant->getDeclaringClass();
if ($owner->getName() === self::class) {
continue;
}

$name = $constant->getName();
$value = $constant->getValue();

if (!is_int($value) && !is_string($value)) {
continue;
}

self::$cases[static::class][] = new static($name, $value);
}

return self::$cases[static::class];
}

/**
* Tries to initialize the enum from the given case's value
*
* @param int|string $value
*
* @return static|null
*/
final public static function tryFrom($value)
{
foreach (self::cases() as $case) {
if ($case->value == $value) {
return $case;
}
}

return null;
}

/**
* Tries to initialize the enum from the given case's name
*
* @param string $name
*
* @return static
*
* @throws RuntimeException
*/
final public static function __callStatic(string $name, array $arguments = [])
{
foreach (self::cases() as $case) {
if ($case->name === $name) {
return $case;
}
}

throw new RuntimeException(sprintf('Enum case %1$s::%2$s not found', static::class, $name));
}

/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->value;
}
}
69 changes: 56 additions & 13 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ public function aliasSupport(bool $enabled) : self
* Enables support for annotations
*
* @return self
*
* @codeCoverageIgnoreStart
*/
public function useAnnotations() : self
{
// @codeCoverageIgnoreStart
if (isset($this->annotationReader)) {
return $this;
}
// @codeCoverageIgnoreEnd

if (class_exists(SimpleAnnotationReader::class)) {
$this->annotationReader = /** @scrutinizer ignore-deprecated */ new SimpleAnnotationReader();
Expand All @@ -119,12 +119,12 @@ public function useAnnotations() : self
*
* @return T
*
* @throws Exception\HydrationException
* If the object cannot be hydrated.
*
* @throws InvalidArgumentException
* If the data isn't valid.
*
* @throws Exception\HydrationException
* If the object cannot be hydrated.
*
* @throws Exception\UntypedPropertyException
* If one of the object properties isn't typed.
*
Expand Down Expand Up @@ -215,20 +215,16 @@ public function hydrate($object, $data) : object
*
* @return T
*
* @throws Exception\HydrationException
* If the object cannot be hydrated.
*
* @throws InvalidArgumentException
* If the JSON cannot be decoded.
*
* @throws Exception\HydrationException
* If the object cannot be hydrated.
*
* @template T
*/
public function hydrateWithJson($object, string $json, ?int $flags = null) : object
public function hydrateWithJson($object, string $json, ?int $flags = JSON_OBJECT_AS_ARRAY) : object
{
if (null === $flags) {
$flags = JSON_OBJECT_AS_ARRAY;
}

json_decode('');
$data = json_decode($json, null, 512, $flags);
if (JSON_ERROR_NONE <> json_last_error()) {
Expand Down Expand Up @@ -389,6 +385,11 @@ private function hydrateProperty(
return;
}

if (is_subclass_of($type->getName(), Enum::class)) {
$this->hydratePropertyWithEnum($object, $class, $property, $type, $value);
return;
}

if (is_subclass_of($type->getName(), ObjectCollectionInterface::class)) {
$this->hydratePropertyWithManyAssociations($object, $class, $property, $type, $value);
return;
Expand Down Expand Up @@ -805,6 +806,48 @@ private function hydratePropertyWithBackedEnum(
$property->setValue($object, $enum);
}

/**
* Hydrates the given property with the given enum
*
* @param object $object
* @param ReflectionClass $class
* @param ReflectionProperty $property
* @param ReflectionNamedType $type
* @param mixed $value
*
* @return void
*
* @throws Exception\InvalidValueException
* If the given value isn't valid.
*/
private function hydratePropertyWithEnum(
object $object,
ReflectionClass $class,
ReflectionProperty $property,
ReflectionNamedType $type,
$value
) : void {
/** @var class-string<Enum> */
$enumName = $type->getName();

$enum = $enumName::tryFrom($value);
if (!isset($enum)) {
$allowedCases = [];
foreach ($enumName::cases() as $case) {
$allowedCases[] = $case->value();
}

throw new Exception\InvalidValueException($property, sprintf(
'The %s.%s property expects one of the following values: %s.',
$class->getShortName(),
$property->getName(),
implode(', ', $allowedCases)
));
}

$property->setValue($object, $enum);
}

/**
* Hydrates the given property with the given one association
*
Expand Down
Loading

0 comments on commit 435c548

Please sign in to comment.