Skip to content

Commit

Permalink
Memory management improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonferens committed Nov 4, 2024
1 parent 6ea3fe1 commit 90deca6
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 85 deletions.
27 changes: 8 additions & 19 deletions src/Commands/GenerateEnumsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use ReflectionEnum;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use UnitEnum;

#[AsCommand(name: 'paragon:enum:generate', description: 'Generate Typescript versions of existing PHP enums')]
class GenerateEnumsCommand extends Command
Expand All @@ -30,7 +29,7 @@ public function handle(): int
$builder = $this->builder();

$generatedEnums = $this->enums()
->map(fn (string $enum) => app(EnumGenerator::class, ['enum' => $enum, 'builder' => $builder])())
->map(fn (ReflectionEnum $enum) => app(EnumGenerator::class, ['enum' => $enum, 'builder' => $builder])())
->filter();

$this->components->info("{$generatedEnums->count()} enums have been (re)generated.");
Expand All @@ -45,36 +44,26 @@ public function handle(): int
/**
* Gather all enum namespaces for searching.
*
* @return Collection<int,class-string<UnitEnum>>
* @return Collection<int, ReflectionEnum>
*/
protected function enums(): Collection
{
/** @var string */
$phpPath = config('paragon.enums.paths.php');

return DiscoverEnums::within(app_path($phpPath))
->reject(function (string $enum) {
if (! enum_exists($enum)) {
return true;
}

$reflector = new ReflectionEnum($enum);

$pathsToIgnore = Arr::map(
return DiscoverEnums::within(app_path(config()->string('paragon.enums.paths.php')))
->reject(function (ReflectionEnum $enum) {
$paths = Arr::map(
Arr::wrap(config('paragon.enums.paths.ignore')),
fn (string $path): string => Str::finish(app_path($path), '/'),
);

return $reflector->getAttributes(IgnoreParagon::class)
|| Str::startsWith((string) $reflector->getFileName(), $pathsToIgnore);
return $enum->getAttributes(IgnoreParagon::class)
|| Str::startsWith((string) $enum->getFileName(), $paths);
})
->values();
}

protected function builder(): EnumBuilder
{
/** @var string */
$generateAs = config('paragon.generate-as');
$generateAs = config()->string('paragon.generate-as');

$builder = match (true) {
$this->option('javascript') => EnumJsBuilder::class,
Expand Down
71 changes: 43 additions & 28 deletions src/Concerns/DiscoverEnums.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Kirschbaum\Paragon\Concerns;

use Illuminate\Support\Collection;
use ReflectionClass;
use ReflectionEnum;
use ReflectionException;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
Expand All @@ -16,7 +16,7 @@ class DiscoverEnums
*
* @param array<int, string>|string $path
*
* @return Collection<class-string<UnitEnum>, class-string<UnitEnum>>
* @return Collection<int, ReflectionEnum>
*/
public static function within(array|string $path): Collection
{
Expand All @@ -28,7 +28,7 @@ public static function within(array|string $path): Collection
*
* @param Finder<string, SplFileInfo> $files
*
* @return Collection<class-string<UnitEnum>, class-string<UnitEnum>>
* @return Collection<int, ReflectionEnum>
*/
protected static function getEnums(Finder $files): Collection
{
Expand All @@ -38,43 +38,58 @@ protected static function getEnums(Finder $files): Collection
$fileCollection = collect($files);

return $fileCollection
->mapWithKeys(function (SplFileInfo $file) {
->map(function (SplFileInfo $file) {
try {
if (! class_exists($enum = static::classFromFile($file))) {
return [];
}

$reflector = new ReflectionClass($enum);
return static::classFromFile($file);
} catch (ReflectionException) {
return [];
return false;
}

return $reflector->isEnum()
? [$enum => $enum]
: [];
})
->filter();
->filter(fn ($enum) => $enum instanceof ReflectionEnum);
}

/**
* Extract the class name from the given file path.
*
* @return class-string<UnitEnum>
* @throws ReflectionException
*/
protected static function classFromFile(SplFileInfo $file): string
protected static function classFromFile(SplFileInfo $file): ReflectionEnum|false
{
$handle = fopen($file->getRealPath(), 'r');

if (! $handle) {
return false;
}

$namespace = null;
$enumClass = null;

while (($line = fgets($handle)) !== false) {
if (preg_match('/^namespace\s+([^;]+);/', $line, $matches)) {
$namespace = $matches[1];
}

if (preg_match('/^enum\s+(\w+)(?:\s*:\s*\w+)?/', $line, $matches)) {
$enumClass = $matches[1];
}

if (
($namespace && $enumClass)
|| preg_match('/\b(class|trait|interface)\b/', $line)
) {
break;
}
}

fclose($handle);

/**
* @var class-string<UnitEnum>
* @var class-string<UnitEnum>|false $enum
*/
return str($file->getRealPath())
->replaceFirst(base_path(), '')
->trim(DIRECTORY_SEPARATOR)
->replaceLast('.php', '')
->ucfirst()
->replace(
search: [DIRECTORY_SEPARATOR, ucfirst(basename(app()->path())) . '\\'],
replace: ['\\', app()->getNamespace()]
)
->toString();
$enum = $namespace && $enumClass
? "{$namespace}\\{$enumClass}"
: false;

return $enum ? new ReflectionEnum($enum) : false;
}
}
61 changes: 23 additions & 38 deletions src/Generators/EnumGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Fluent;
use Illuminate\Support\Stringable;
use Kirschbaum\Paragon\Concerns\Builders\EnumBuilder;
use Kirschbaum\Paragon\Concerns\IgnoreParagon;
use ReflectionEnum;
use ReflectionEnumBackedCase;
use ReflectionEnumUnitCase;
use ReflectionMethod;
use UnitEnum;

class EnumGenerator
{
Expand All @@ -26,27 +24,16 @@ class EnumGenerator

/**
* Create new EnumGenerator instance.
*
* @param class-string<UnitEnum> $enum
*/
public function __construct(protected string $enum, protected EnumBuilder $builder)
public function __construct(protected ReflectionEnum $enum, protected EnumBuilder $builder)
{
/** @var string */
$generatedPath = config('paragon.enums.paths.generated');

$this->files = Storage::createLocalDriver([
'root' => resource_path($generatedPath),
'root' => resource_path(config()->string('paragon.enums.paths.generated')),
]);

$this->cache = Storage::createLocalDriver([
'root' => storage_path('framework/cache/paragon'),
]);

if (! enum_exists($this->enum)) {
return;
}

$this->reflector = new ReflectionEnum($this->enum);
}

public function __invoke(): bool
Expand All @@ -68,13 +55,11 @@ public function __invoke(): bool
protected function contents(): string
{
$code = $this->prepareEnumCode();
/** @var string */
$abstractClass = config('paragon.enums.abstract-class');

return str(file_get_contents($this->builder->stubPath()) ?: null)
->replace('{{ Path }}', $this->relativePath())
->replace('{{ Enum }}', class_basename($this->enum))
->replace('{{ Abstract }}', $abstractClass)
->replace('{{ Enum }}', $this->enum->getShortName())
->replace('{{ Abstract }}', config()->string('paragon.enums.abstract-class'))
->replace('{{ TypeDefinition }}', $code->get('type') ?? '')
->replace('{{ Cases }}', $code->get('cases') ?? '')
->replace('{{ Getters }}', $code->get('getters') ?? '');
Expand All @@ -87,7 +72,7 @@ protected function contents(): string
*/
protected function prepareEnumCode(): Fluent
{
$cases = collect($this->reflector->getCases());
$cases = collect($this->enum->getCases());

return new Fluent([
'type' => $this->buildTypeDefinition(),
Expand All @@ -101,7 +86,7 @@ protected function prepareEnumCode(): Fluent
*/
protected function relativePath(): string
{
$depth = str($this->enum)->after('App\\Enums\\')->explode('\\')->count() - 1;
$depth = str($this->enum->getName())->after('App\\Enums\\')->explode('\\')->count() - 1;

return $depth
? collect(range(1, $depth))->map(fn () => '../')->join('')
Expand All @@ -114,11 +99,11 @@ protected function relativePath(): string
protected function buildTypeDefinition(): string
{
return $this->methods()
->map(fn (ReflectionMethod $method) => PHP_EOL . " {$method->getName()}();")
->map(fn ($method) => PHP_EOL . " {$method->getName()}();")
->sortDesc()
->when(
$this->reflector->isBacked(),
fn (Collection $collection) => $collection->push(PHP_EOL . " value: {$this->valueReturnType()};")
$this->enum->isBacked(),
fn ($collection) => $collection->push(PHP_EOL . " value: {$this->valueReturnType()};")
)
->reverse()
->join('');
Expand All @@ -131,7 +116,7 @@ protected function buildTypeDefinition(): string
*/
protected function methods(): Collection
{
return collect($this->reflector->getMethods(ReflectionMethod::IS_PUBLIC))
return collect($this->enum->getMethods(ReflectionMethod::IS_PUBLIC))
->reject(function (ReflectionMethod $method) {
return $method->isStatic() || $method->getAttributes(IgnoreParagon::class);
})
Expand All @@ -143,18 +128,18 @@ protected function methods(): Collection
*/
protected function valueReturnType(): string
{
return $this->reflector->getBackingType()?->getName() === 'int' ? 'number' : 'string';
return $this->enum->getBackingType()?->getName() === 'int' ? 'number' : 'string';
}

/**
* Build all the case objects.
*
* @param Collection<int, ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
* @param Collection<int,ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
*/
protected function buildCases(Collection $cases): string
{
return $cases
->map(function (ReflectionEnumUnitCase|ReflectionEnumBackedCase $case) {
->map(function (ReflectionEnumUnitCase $case) {
$value = $this->caseValueProperty($case);

$methodValues = $this->methods()
Expand All @@ -170,15 +155,15 @@ protected function buildCases(Collection $cases): string
*/
protected function caseValueProperty(ReflectionEnumUnitCase|ReflectionEnumBackedCase $case): string
{
if ($this->reflector->isBacked()) {
if ($this->enum->isBacked()) {
return str('value: ')
->prepend(PHP_EOL . ' ')
->when(
$this->reflector->getBackingType()->getName() === 'int',
fn (Stringable $string) => $case->getValue() instanceof BackedEnum
$this->enum->getBackingType()->getName() === 'int',
fn ($string) => $case->getValue() instanceof BackedEnum
? $string->append("{$case->getValue()->value}")
: $string,
fn (Stringable $string) => $case->getValue() instanceof BackedEnum
fn ($string) => $case->getValue() instanceof BackedEnum
? $string->append("'{$case->getValue()->value}'")
: $string,
)
Expand All @@ -191,7 +176,7 @@ protected function caseValueProperty(ReflectionEnumUnitCase|ReflectionEnumBacked
/**
* Assemble the actual enum case object code including the name, value if needed, and any public methods.
*
* @param Collection<int, string> $methodValues
* @param Collection<int,string> $methodValues
*/
protected function assembleCaseObject(
ReflectionEnumUnitCase|ReflectionEnumBackedCase $case,
Expand All @@ -210,12 +195,12 @@ protected function assembleCaseObject(
/**
* Build all case object getter methods.
*
* @param Collection<int, ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
* @param Collection<int,ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
*/
protected function buildGetters(Collection $cases): string
{
return $cases
->map(fn (ReflectionEnumUnitCase|ReflectionEnumBackedCase $case) => $this->builder->assembleCaseGetter($case))
->map(fn ($case) => $this->builder->assembleCaseGetter($case))
->join(PHP_EOL . PHP_EOL);
}

Expand All @@ -224,7 +209,7 @@ protected function buildGetters(Collection $cases): string
*/
protected function path(): string
{
return str($this->enum)
return str($this->enum->getName())
->after('App\\Enums\\')
->replace('\\', '/')
->finish($this->builder->fileExtension());
Expand All @@ -248,12 +233,12 @@ protected function cached(): bool

protected function hashFilename(): string
{
return md5((string) $this->reflector->getFileName());
return md5((string) $this->enum->getFileName());
}

protected function hashFile(): string
{
return (string) md5_file((string) $this->reflector->getFileName());
return (string) md5_file((string) $this->enum->getFileName());
}

protected function cacheEnum(): void
Expand Down

0 comments on commit 90deca6

Please sign in to comment.