-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a Javascript Converter for Enum
- Loading branch information
Showing
3 changed files
with
593 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
} | ||
} |
Oops, something went wrong.