henzeb/enumhancer

View on GitHub
src/Contracts/Mapper.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

namespace Henzeb\Enumhancer\Contracts;

use UnitEnum;
use function strtolower;
use function trigger_error;

/**
 * @method static string|null map(string|UnitEnum $key, string $prefix = null)
 * @method static string|null defined(string|UnitEnum $key, string $prefix = null)
 * @method static static flip(string $prefix = null)
 * @method static array keys(string $prefix = null)
 */
abstract class Mapper
{
    private bool $flip = false;

    private ?array $flipped = null;
    private ?string $flipPrefix = null;

    /**
     * @return array<string|int,string|int|UnitEnum|array<string|int,string|int|UnitEnum>>
     */
    abstract protected function mappable(): array;

    public function makeFlipped(string $prefix = null): self
    {
        $this->flip = true;
        $this->flipped = null;
        $this->flipPrefix = $prefix;

        return $this;
    }

    private function flipMethod(string $prefix = null): self
    {
        return (clone $this)->makeFlipped($prefix);
    }

    private function parseValue(mixed $value): string|int|null
    {
        if (null === $value) {
            $value = null;
        }

        if (empty($value)) {
            $value = null;
        }

        if ($value instanceof UnitEnum) {
            $value = $value->name;
        }

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

        return $value;
    }

    private function getMapWithPrefix(string $prefix = null): array
    {
        /**
         * @var array $mappable
         */
        $mappable = $this->mappable();
        return array_change_key_case($mappable[$prefix] ?? []);
    }

    private function getMap(string $prefix = null): array
    {
        if ($this->flip) {
            return $this->flipped ?? $this->flipped = $this->flipMappable($prefix);
        }

        return array_change_key_case($this->mappable());
    }

    private function mapMethod(string|UnitEnum $key, string $prefix = null): string|int|null
    {
        $key = $this->parseValue($key);

        if (is_string($key)) {
            $key = strtolower($key);
        }

        return $this->parseValue(
            ($this->flip ? null : $this->getMapWithPrefix($prefix)[$key] ?? null)
            ??
            $this->getMap($prefix)[$key]
            ?? null
        );
    }

    private function definedMethod(string|UnitEnum $key, string $prefix = null): bool
    {
        return (bool)$this->map($key, $prefix);
    }

    private function keysMethod(string $prefix = null): array
    {
        if (!$prefix || $this->flip) {
            $mappable = $this->getMap($prefix);
        }

        if (!isset($mappable)) {
            $mappable = [...$this->getMap(), ...$this->getMapWithPrefix($prefix)];
        }

        return array_unique(
            array_merge(
                array_keys(
                    array_filter(
                        $mappable,
                        function ($value) {
                            return !is_array($value);
                        }
                    ),
                ),
                is_array($mappable[$prefix] ?? null) ? array_keys($mappable[$prefix]) : []
            )
        );
    }

    private function flipMappable(string $prefix = null): array
    {
        return array_change_key_case(
            array_flip(
                array_filter(
                    array_map(
                        fn($value) => is_array($value) ? null : $this->parseValue($value),
                        $this->getMapWithPrefix($prefix ?? $this->flipPrefix) ?: $this->mappable()
                    )
                )
            )
        );
    }

    public function __call(string $name, array $arguments): mixed
    {
        return match ($name) {
            'map' => $this->mapMethod(...$arguments),
            'defined' => $this->definedMethod(...$arguments),
            'keys' => $this->keysMethod(...$arguments),
            'flip' => $this->flipMethod(...$arguments),
            default => $this->triggerError($name)
        };
    }

    public static function __callStatic(string $name, array $arguments): mixed
    {
        return self::newInstance()->$name(...$arguments);
    }

    private function triggerError(string $name): bool
    {
        return trigger_error(
            sprintf(
                'Uncaught Error: Call to undefined method %s::%s()',
                static::class,
                $name
            ),
            E_USER_ERROR
        );
    }

    public static function newInstance(mixed ...$parameters): static
    {
        return new static(...$parameters);
    }
}