Skip to content

Commit

Permalink
resolves #2
Browse files Browse the repository at this point in the history
  • Loading branch information
henzeb committed Jun 4, 2022
1 parent 47496cc commit e190efd
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 11 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to `Enumhancer` will be documented in this file

## 1.6.0 - 2022-06-04
- added Eloquent Casting support for basic enumerations.

## 1.5.0 - 2022-05-31
- added `Extractor` to extract enums from a string mentioned by value
- some documentation repairs
Expand All @@ -23,11 +26,11 @@ All notable changes to `Enumhancer` will be documented in this file

## 1.2.0 - 2022-02-26

- added Value (for use with unit enums)
- added Value (for use with basic enums)

## 1.1.0 - 2022-02-25

- added From. Useful for situations where you need them with non-backed enums
- added From. Useful for situations where you need them with basic enums

## 1.0.2 - 2022-02-16

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ This package currently supports the following:
- Properties
- Reporting (Logging)
- Subset
- Value (for unit enums)
- Value (for basic enums)

Laravel specific features:
- Casting (Laravel Specific)

Note: While functionality that also exists in Spatie's PHP Enum is made backwards
compatible to allow for an easy migration to PHP native enums, currently this is
not the case for the PHPUnit assertions or Faker Provider.

While functionality that also exists in Spatie's PHP Enum is made backwards
compatible to allow for an easy migration to PHP native enums, currently this is
Expand Down Expand Up @@ -57,7 +64,7 @@ for that functionality.
Note: all traits can be used next to each other, except for `Mappers`, which has
implemented the methods of `Makers`, `Extractor` and `Reporters`.

### Functionality
### Features
- [Constructor](docs/constructor.md)
- [Comparison](docs/comparison.md)
- [Extractor](docs/extractor.md)
Expand All @@ -70,7 +77,11 @@ implemented the methods of `Makers`, `Extractor` and `Reporters`.
- [Subset](docs/subset.md)
- [Value](docs/value.md)

### Laravel
### Laravel specific Features

- [Casting](docs/casting.md)

### Laravel's auto-discovery
When you are installing this package into a laravel project, Enumhancer will
automatically set the global `Reporter` for the `makeOrReport` methods, so that
it will use Laravel's `Log` facade.
Expand Down
28 changes: 28 additions & 0 deletions docs/casting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Eloquent Attribute Casting

Laravel supports casting backed enums out of the box, but what if you don't want to
use backed enums? This is where `CastsBasicEnumerations` comes in.

## Usage

```php
enum yourEnum {
case MY_ENUM;
}

use Illuminate\Database\Eloquent\Model;
use Henzeb\Enumhancer\Laravel\Concerns\CastsBasicEnumerations;

class YourModel extends Model
{
use CastsBasicEnumerations;

$casts = [
'column' => YourEnum::class
];
}

```
### Lowercase values
By default, it will use of the basic enumeration as the value. If you want the
lowercase variant, you can add the `$keepEnumCase` property and set it to false.
2 changes: 1 addition & 1 deletion docs/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Just like [Spatie's PHP Enum](https://github.com/spatie/enum), you can add labels to
your enums. This is largely backwards compatible with their package, except that it
also works for UnitEnum's in which case it returns the name if not specified.
also works for basic enums in which case it returns the name if not specified.

## Usage

Expand Down
3 changes: 1 addition & 2 deletions docs/subset.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ YourEnum::of(
```

### values
This method returns an array of values of the specified subset.
This uses the [Value](value.md) trait, when enum is a `UnitEnum`.
This method returns an array of values of the specified subset.

```php
YourEnum::of(
Expand Down
5 changes: 3 additions & 2 deletions docs/value.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Value

When you have a UnitEnum you have no value. You have to use the `name` value. But what if you
want to have the lowercase version of that name? That's where `Value` comes in.
When you have a basic enum, you have no value. You have to use the `name` value. But
what if you want to have the lowercase version of that name? That's where `Value`
comes in.

## Usage

Expand Down
4 changes: 3 additions & 1 deletion src/Concerns/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Henzeb\Enumhancer\Concerns;

use Henzeb\Enumhancer\Helpers\EnumValue;

trait Value
{
final public function value(): string|int
{
return $this->value ?? strtolower($this->name);
return EnumValue::value($this);
}
}
14 changes: 14 additions & 0 deletions src/Helpers/EnumValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Enumhancer\Helpers;

use UnitEnum;
use BackedEnum;

class EnumValue
{
public static function value(BackedEnum|UnitEnum $enum, bool $keepCase = false): string|int
{
return $enum->value ?? ($keepCase ? $enum->name : strtolower($enum->name));
}
}
86 changes: 86 additions & 0 deletions src/Laravel/Concerns/CastsBasicEnumerations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Henzeb\Enumhancer\Laravel\Concerns;

use UnitEnum;
use BackedEnum;
use Illuminate\Database\Eloquent\Model;
use Henzeb\Enumhancer\Helpers\EnumValue;
use Henzeb\Enumhancer\Helpers\EnumMakers;

/**
* @mixin Model
* @property bool $keepEnumCase Change public to private/protected if needed.
*/
trait CastsBasicEnumerations
{
protected function getEnumCastableAttributeValue($key, $value)
{
if (is_null($value)) {
return null;
}

$castType = $this->getCasts()[$key];

/**
* @TODO: remove when fixed: https://github.com/laravel/framework/issues/42658
*/
if ($this->shouldUseWorkaround($castType)) {
return $this->enumToArrayWorkaround($value);
}

if ($value instanceof $castType) {
return $value;
}

return EnumMakers::make($castType, $value);
}

protected function setEnumCastableAttribute($key, $value)
{
$enumClass = $this->getCasts()[$key];

$keepEnumCase = property_exists($this, 'keepEnumCase') ? $this->keepEnumCase : true;

if (!isset($value)) {
$this->attributes[$key] = null;
} elseif ($value instanceof $enumClass) {
$this->attributes[$key] = EnumValue::value($value, $keepEnumCase);
} else {

if ($value instanceof UnitEnum) {
$value = EnumValue::value($value);
}

$this->attributes[$key] = EnumValue::value(
EnumMakers::make($enumClass, $value),
$keepEnumCase
);
}
}

/**
* @TODO: remove when fixed: https://github.com/laravel/framework/issues/42658
* @param $value
* @return object
*/
private function enumToArrayWorkaround(string|int $value): object
{
return new class($value) {
public function __construct(public readonly string|int $value)
{
}
};
}

/**
* @TODO: remove when fixed: https://github.com/laravel/framework/issues/42658
* @param string $enumClass
* @return bool
*/
private function shouldUseWorkaround(string $enumClass): bool
{
return (!is_subclass_of($enumClass, BackedEnum::class, true))
&& 'toArray' === (debug_backtrace(2)[5]['function'] ?? null);
}
}
19 changes: 19 additions & 0 deletions tests/Fixtures/CastsBasicEnumsLowerCaseModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Henzeb\Enumhancer\Tests\Fixtures;

use Illuminate\Database\Eloquent\Model;
use Henzeb\Enumhancer\Laravel\Concerns\CastsBasicEnumerations;

class CastsBasicEnumsLowerCaseModel extends Model
{
use CastsBasicEnumerations;

protected $keepEnumCase = false;

protected $casts = [
'unitEnum' => SubsetUnitEnum::class,
'intBackedEnum' => IntBackedEnum::class,
'stringBackedEnum' => StringBackedMakersEnum::class
];
}
17 changes: 17 additions & 0 deletions tests/Fixtures/CastsBasicEnumsModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Henzeb\Enumhancer\Tests\Fixtures;

use Illuminate\Database\Eloquent\Model;
use Henzeb\Enumhancer\Laravel\Concerns\CastsBasicEnumerations;

class CastsBasicEnumsModel extends Model
{
use CastsBasicEnumerations;

protected $casts = [
'unitEnum' => SubsetUnitEnum::class,
'intBackedEnum' => IntBackedEnum::class,
'stringBackedEnum' => StringBackedMakersEnum::class
];
}
107 changes: 107 additions & 0 deletions tests/Unit/Laravel/Concerns/CastsBasicEnumsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace Unit\Laravel\Concerns;


use UnitEnum;
use ValueError;
use Orchestra\Testbench\TestCase;
use Henzeb\Enumhancer\Helpers\EnumValue;
use Henzeb\Enumhancer\Tests\Fixtures\IntBackedEnum;
use Henzeb\Enumhancer\Tests\Fixtures\SubsetUnitEnum;
use Henzeb\Enumhancer\Tests\Fixtures\CastsBasicEnumsModel;
use Henzeb\Enumhancer\Tests\Fixtures\StringBackedMakersEnum;
use Henzeb\Enumhancer\Tests\Fixtures\CastsBasicEnumsLowerCaseModel;

class CastsBasicEnumsTest extends TestCase
{
public function providesEnums()
{
return [
[SubsetUnitEnum::ENUM, 'unitEnum'],
[IntBackedEnum::TEST, 'intBackedEnum'],
[StringBackedMakersEnum::TEST, 'stringBackedEnum'],

[SubsetUnitEnum::ENUM, 'unitEnum', false],
[IntBackedEnum::TEST, 'intBackedEnum', false],
[StringBackedMakersEnum::TEST, 'stringBackedEnum', false],
];
}

/**
* @return void
*
* @dataProvider providesEnums
*/
public function testShouldCastCorrectlyFromString(UnitEnum $enum, string $key, bool $keepCase = true)
{
$model = $keepCase ? new CastsBasicEnumsModel() : new CastsBasicEnumsLowerCaseModel();
$model->setRawAttributes([
$key => EnumValue::value($enum, $keepCase)
]);

$this->assertEquals(
$enum,
$model->$key,
);
}

/**
* @return void
*
* @dataProvider providesEnums
*/
public function testShouldCastCorrectlyToString(UnitEnum $enum, string $key, bool $keepCase = true)
{
$model = $keepCase ? new CastsBasicEnumsModel() : new CastsBasicEnumsLowerCaseModel();
$model->$key = $enum;

$this->assertEquals(
EnumValue::value($enum, $keepCase),
$model->toArray()[$key],
);
}

public function testShouldHandleNull() {
$model = new CastsBasicEnumsModel();
$model->unitEnum = null;

$this->assertEquals(null, $model->unitEnum);
}

public function testShouldHandleObjectInAttribute() {
$model = new CastsBasicEnumsModel();
$model->setRawAttributes(['unitEnum'=>SubsetUnitEnum::ENUM]);

$this->assertEquals(SubsetUnitEnum::ENUM, $model->unitEnum);
}

public function testShouldHandleStringValue() {
$model = new CastsBasicEnumsModel();
$model->unitEnum = 'enum';

$this->assertEquals('ENUM', $model->getAttributes()['unitEnum']);

$this->assertEquals(SubsetUnitEnum::ENUM, $model->unitEnum);
}

public function testShouldHandleStringValueLowerCase() {
$model = new CastsBasicEnumsLowerCaseModel();
$model->unitEnum = 'ENUM';

$this->assertEquals('enum', $model->getAttributes()['unitEnum']);
}

public function testShouldFailIfStringIsNotValid() {
$this->expectException(ValueError::class);
$model = new CastsBasicEnumsModel();
$model->unitEnum = 'NotAnEnum';
}

public function testShouldFailIfEnumIsNotValid() {
$this->expectException(ValueError::class);

$model = new CastsBasicEnumsModel();
$model->unitEnum = IntBackedEnum::TEST;
}
}

0 comments on commit e190efd

Please sign in to comment.