Skip to content

Commit

Permalink
Adding a Javascript Converter for Enum
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jan 20, 2024
1 parent 14dc807 commit 965e17b
Show file tree
Hide file tree
Showing 3 changed files with 593 additions and 0 deletions.
286 changes: 286 additions & 0 deletions src/Enum/JavascriptConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
<?php

declare(strict_types=1);

namespace Bakame\Aide\Enum;

use BackedEnum;
use Closure;
use ReflectionEnum;
use ValueError;

final class JavascriptConverter
{
private const EXPORT_NONE = '';
private const EXPORT_SIMPLE = 'export ';
private const EXPORT_DEFAULT = 'export default ';

private function __construct(
private readonly bool $useSymbol,
private readonly bool $useImmutability,
private readonly ?Closure $propertyNameCasing,
private readonly int $indentSize,
private readonly string $export,
) {
}

public static function new(): self
{
return new self(
useSymbol: false,
useImmutability: true,
propertyNameCasing: null,
indentSize: 2,
export: self::EXPORT_NONE
);
}

public function propertyNameCase(Closure $casing = null): self
{
return new self(
$this->useSymbol,
$this->useImmutability,
$casing,
$this->indentSize,
$this->export,
);
}

public function useImmutability(): self
{
return match ($this->useImmutability) {
true => $this,
default => new self(
$this->useSymbol,
true,
$this->propertyNameCasing,
$this->indentSize,
$this->export,
),
};
}

public function ignoreImmutability(): self
{
return match ($this->useImmutability) {
false => $this,
default => new self(
$this->useSymbol,
false,
$this->propertyNameCasing,
$this->indentSize,
$this->export,
),
};
}

public function useSymbol(): self
{
return match ($this->useSymbol) {
true => $this,
default => new self(
true,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
$this->export,
),
};
}

public function ignoreSymbol(): self
{
return match ($this->useSymbol) {
false => $this,
default => new self(
false,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
$this->export,
),
};
}

public function useExportDefault(): self
{
return match ($this->export) {
self::EXPORT_DEFAULT => $this,
default => new self(
$this->useSymbol,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT_DEFAULT,
),
};
}

public function useExport(): self
{
return match ($this->export) {
self::EXPORT_SIMPLE => $this,
default => new self(
$this->useSymbol,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT_SIMPLE,
),
};
}

public function ignoreExport(): self
{
return match ($this->export) {
self::EXPORT_NONE => $this,
default => new self(
$this->useSymbol,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT_NONE,
),
};
}

public function intendSize(int $indentSize): self
{
return match (true) {
$indentSize < 0 => throw new ValueError('indentation size can no be negative.'),
$indentSize === $this->indentSize => $this,
default => new self(
$this->useSymbol,
$this->useImmutability,
$this->propertyNameCasing,
$indentSize,
$this->export,
),
};
}

/**
* Converts the Enum into a Javascript object.
*
* <ul>
* <li>If the object name is null the object is not assign to const variable</li>
* <li>If the object name is the empty string the PHP namespaced class name will be used</li>
* <li>If the object name is a non-empty string, it will be used as is as the const variable name</li>
* </ul>
*
* @param class-string<BackedEnum> $enumClass
*/
public function convertToObject(string $enumClass, ?string $objectName = null): string
{
$this->filterBackedEnum($enumClass);

$space = '';
$eol = '';
if (0 < $this->indentSize) {
$space = str_repeat(' ', $this->indentSize);
$eol = "\n";
}

$output = array_reduce(
$enumClass::cases(),
fn (string $output, BackedEnum $enum): string => $output.$space.$this->formatPropertyName($enum).': '.$this->formatPropertyValue($enum).','.$eol,
''
);

$output = '{'.$eol.$output.'}';
if ($this->useImmutability) {
$output = "Object.freeze($output)";
}

$objectName = $this->sanitizeName($objectName, $enumClass);
if (null !== $objectName) {
$output = "const $objectName = $output";
if (self::EXPORT_DEFAULT === $this->export) {
return $output.';'.$eol.$this->export.$objectName.';'.$eol;
}
}

return $this->export.$output.$eol;
}

/**
* Converts the Enum into a Javascript class.
*
* <ul>
* <li>If the class name is the empty string the PHP namespaced class name will be used</li>
* <li>If the class name is a non-empty string, it will be used as is as the class name</li>
* </ul>
*
* @param class-string<BackedEnum> $enumClass
*/
public function convertToClass(string $enumClass, string $className = ''): string
{
$this->filterBackedEnum($enumClass);

$space = '';
$eol = '';
if (0 < $this->indentSize) {
$space = str_repeat(' ', $this->indentSize);
$eol = "\n";
}

$className = $this->sanitizeName($className, $enumClass);
$output = array_reduce(
$enumClass::cases(),
fn (string $output, BackedEnum $enum): string => $output.$space."static {$this->formatPropertyName($enum)} = new $className({$this->formatPropertyValue($enum)})$eol",
''
);

$output = 'class '.$className.' {'.$eol.$output.$eol.$space.'constructor(name) {'.$eol.$space.$space.'this.name = name'.$eol.$space.'}'.$eol.'}';

return $this->export.$output.$eol;
;
}

/**
* @param class-string<BackedEnum> $enumClass
*
* @throws ValueError If the given string does not represent a Backed Enum class
*/
private function filterBackedEnum(string $enumClass): void
{
if (!enum_exists($enumClass)) {
throw new ValueError($enumClass.' is not a valid PHP Enum.');
}

$reflection = new ReflectionEnum($enumClass);
if (!$reflection->isBacked()) {
throw new ValueError($enumClass.' is not a PHP backed enum.');
}
}

private function sanitizeName(?string $className, string $enumClass): ?string
{
if ('' !== $className) {
return $className;
}

$parts = explode('\\', $enumClass);

return (string) array_pop($parts);
}

private function formatPropertyName(BackedEnum $enum): string
{
return match ($this->propertyNameCasing) {
null => $enum->name,
default => ($this->propertyNameCasing)($enum->name),
};
}

private function formatPropertyValue(BackedEnum $enum): string|int
{
$value = $enum->value;
$value = is_string($value) ? '"'.$value.'"' : $value;

return match ($this->useSymbol) {
true => 'Symbol('.$value.')',
default => $value,
};
}
}
Loading

0 comments on commit 965e17b

Please sign in to comment.