ldc-volena/app/Traits/Enums/BaseEnum.php
2026-04-24 15:06:55 +02:00

283 lines
7.9 KiB
PHP

<?php
namespace App\Traits\Enums;
use Illuminate\Support\Str;
/**
* Provides rich functionality to PHP backed enums.
*/
trait BaseEnum
{
// * GENERIC METHODS.
/**
* Get enum value.
*
* @return string|integer
*/
public function value(): string|int
{
return self::toValues()[$this->name];
}
/**
* Get enum label.
*
* @return string
*/
public function label(): string
{
return self::toLabels()[$this->name];
}
/**
* Get enum name.
*
* @return string
*/
public function name(): string
{
return $this->name;
}
// * STATIC FETCH METHODS.
/**
* Get enum labels.
*
* @return array<string, string>
* @throws \Error If missing definition label.
* @phpstan-ignore missingType.iterableValue
*/
public static function toLabels(): array
{
if (empty(self::getCache('labels'))) {
$labels = self::getLabelsConstant();
self::setCache('labels', \collect(self::cases())
->mapWithKeys(function ($enum) use ($labels) {
if (!\is_null($labels) and !isset($labels[$enum->name])) {
throw new \Error('Missing definition label ' . self::class . " for {$enum->name}");
}
return [$enum->name => \trans(
!\is_null($labels) ? $labels[$enum->name] : Str::ucfirst($enum->name)
)];
})->all());
}
return self::getCache('labels');
}
/**
* Get enum values.
*
* @return array<string, integer|string>
* @phpstan-ignore missingType.iterableValue
*/
public static function toValues(): array
{
if (empty(self::getCache('values'))) {
self::setCache('values', \collect(self::cases())
->mapWithKeys(fn ($enum) => [$enum->name => $enum->value])->all());
}
return self::getCache('values');
}
/**
* Get enums.
*
* @return list<static>
* @phpstan-ignore missingType.iterableValue
*/
public static function all(): array
{
return self::cases();
}
/**
* Get enums.
*
* @return array<string, object>
* @phpstan-ignore missingType.iterableValue
*/
public static function toArray(): array
{
if (empty(self::getCache('enums'))) {
$definitions = self::getCustomDefinitions();
self::setCache('enums', \collect(self::cases())
->mapWithKeys(function ($enum) use ($definitions) {
$baseArray = [
'name' => $enum->name,
'label' => $enum->label(),
'value' => $enum->value,
];
foreach ($definitions as $index => $elements) {
$index = Str::lower(Str::singular($index));
if (!isset($elements[$enum->name])) {
throw new \Error("Missing definition {$index} " . self::class . " for {$enum->name}");
}
$baseArray[$index] = $elements[$enum->name];
}
return [$enum->name => (object) $baseArray];
})->all());
}
return self::getCache('enums');
}
/**
* Fetch enum using value.
*
* @param self|integer|string $value
* @param string $field
* @return static|null
*/
public static function make(self|int|string $value, string $field = 'value'): self|null
{
if ($field === "label") {
$enumLabels = array_change_key_case(
array_flip(array_map(fn($value): string => Str::slug($value), self::toLabels())),
CASE_LOWER
);
$searchValue = $value instanceof self ? (string) $value->value : (string) $value;
$slugged = Str::slug($searchValue);
$value = $enumLabels[$slugged] ?? false;
if ($value === false) {
return null;
}
$field = 'name';
}
/** @phpstan-ignore-next-line */
return !\is_object($value) ? \collect(self::cases())->keyBy($field)->get($value) : $value;
}
/**
* Test if 2 enums are equals.
*
* @param object ...$others
* @return boolean
* @throws \Error If provided element are not enums that extends BaseEnum.
*/
public function equals(object ...$others): bool
{
foreach ($others as $other) {
if (!\in_array(BaseEnum::class, \class_uses_recursive($other))) {
throw new \Error(__METHOD__ . ' requires $enum to use ' . BaseEnum::class);
}
if (
get_class($this) === get_class($other)
&& $this->value === $other->value // @phpstan-ignore-line
) {
return true;
}
}
return false;
}
/**
* Serialize to json.
*
* @return mixed
*/
public function jsonSerialize(): mixed
{
return $this->value;
}
// * CACHE.
/**
* Get cache array.
*
* @param string $index
* @return array<mixed>
* @phpstan-ignore missingType.iterableValue
*/
private static function getCache(string $index): array
{
self::initCache();
if (!isset($GLOBALS[self::class]['props'][$index])) {
$GLOBALS[self::class]['props'][$index] = [];
}
return $GLOBALS[self::class]['props'][$index];
}
/**
* Set cache array.
*
* @param string $index
* @param array<mixed> $items
* @return void
* @phpstan-ignore missingType.iterableValue
*/
private static function setCache(string $index, array $items): void
{
self::initCache();
if (!isset($GLOBALS[self::class]['props'][$index])) {
$GLOBALS[self::class]['props'][$index] = [];
}
$GLOBALS[self::class]['props'][$index] = $items;
}
/**
* Init cache if needed.
*
* @return void
*/
private static function initCache(): void
{
if (!isset($GLOBALS[self::class])) {
$GLOBALS[self::class] = [
'props' => [
'enums' => [],
'values' => [],
'labels' => []
],
'customProps' => null
];
}
}
// * PRIVATE.
/**
* Fetch the LABELS constant if exists.
*
* @return array<string, string>|null
* @phpstan-ignore missingType.iterableValue
*/
private static function getLabelsConstant(): array|null
{
$class_name = self::class;
try {
$constant_reflex = new \ReflectionClassConstant($class_name, 'LABELS');
return $constant_reflex->getValue();
} catch (\ReflectionException $e) {
return null;
}
}
/**
* Get custom constants.
*
* @return array<string, array<mixed>>
* @phpstan-ignore missingType.iterableValue
*/
private static function getCustomDefinitions(): array
{
if (!isset($GLOBALS[self::class]) or \is_null($GLOBALS[self::class]['customProps'])) {
$names = \collect(self::cases())->keyBy('name');
$GLOBALS[self::class]['customProps'] = \collect((new \ReflectionClass(self::class))->getConstants())
->filter(function (mixed $constant, string $key) use ($names): bool {
if ($key === 'LABELS' or !\is_array($constant)) {
return false;
}
$count = \collect($constant)->keys()
->filter(fn (string $name) => $names->has($name))
->count();
return $count > 0;
})->all();
}
return $GLOBALS[self::class]['customProps'];
}
}