Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
- "highest"
- "locked"
php-version:
- "7.4"
- "8.0"
- "8.1"
operating-system:
Expand Down
82 changes: 82 additions & 0 deletions src/Generator/AttributeGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator;

use Laminas\Code\Generator\AttributeGenerator\AttributeAssembler;
use Laminas\Code\Generator\AttributeGenerator\AttributePrototype;
use Laminas\Code\Generator\AttributeGenerator\AttributeWithArgumentsAssembler;
use Laminas\Code\Generator\AttributeGenerator\SimpleAttributeAssembler;
use ReflectionAttribute;
use ReflectionClass;

final class AttributeGenerator implements GeneratorInterface
{
private array $assemblers;

private function __construct(AttributeAssembler ...$assembler)
{
$this->assemblers = $assembler;
}

public function generate(): string
{
$generatedAttributes = array_map(fn(AttributeAssembler $attributeAssembler) => $attributeAssembler->assemble(),
$this->assemblers,
);

return implode(AbstractGenerator::LINE_FEED, $generatedAttributes);
}

public static function fromPrototype(AttributePrototype ...$attributePrototype): self
{
$assemblers = [];

foreach ($attributePrototype as $prototype) {
$assemblers[] = self::negotiateAssembler($prototype);
}

return new self(...$assemblers);
}

public static function fromReflection(ReflectionClass $reflectionClass): self
{
$attributes = $reflectionClass->getAttributes();
$assemblers = [];

foreach ($attributes as $attribute) {
$assembler = self::negotiateAssembler($attribute);

$assemblers[] = $assembler;
}

return new self(...$assemblers);
}

public static function fromArray(array $definitions): self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to drop all ::fromArray() methods as per #153

This API will be dropped once I have the green light on #153

{
$assemblers = [];

foreach ($definitions as $definition) {
@list($attributeName, $attributeArguments) = $definition;

$prototype = new AttributePrototype($attributeName, $attributeArguments ?? []);

$assemblers[] = self::negotiateAssembler($prototype);
}

return new self(...$assemblers);
}

private static function negotiateAssembler(ReflectionAttribute|AttributePrototype $reflectionPrototype): AttributeAssembler
{
$hasArguments = !empty($reflectionPrototype->getArguments());

if ($hasArguments) {
return new AttributeWithArgumentsAssembler($reflectionPrototype);
}

return new SimpleAttributeAssembler($reflectionPrototype);
}
}
24 changes: 24 additions & 0 deletions src/Generator/AttributeGenerator/AbstractAttributeAssembler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

use ReflectionAttribute;

abstract class AbstractAttributeAssembler implements AttributeAssembler
{
public function __construct(private ReflectionAttribute $attributePrototype)
{
}

protected function getName(): string
{
return $this->attributePrototype->getName();
}

protected function getArguments(): array
{
return $this->attributePrototype->getArguments();
}
}
10 changes: 10 additions & 0 deletions src/Generator/AttributeGenerator/AttributeAssembler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

interface AttributeAssembler
{
public function assemble(): string;
}
21 changes: 21 additions & 0 deletions src/Generator/AttributeGenerator/AttributePart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

//TODO Enum in PHP8.1
/**
* @internal
*/
final class AttributePart
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mark all internally used symbols as @internal, to allow refactoring/rewrite as soon as we are allowed to.

{
public const T_ATTR_START = '#[';
public const T_ATTR_END = ']';

public const T_ATTR_ARGUMENTS_LIST_START = '(';
public const T_ATTR_ARGUMENTS_LIST_END = ')';

public const T_ATTR_ARGUMENTS_LIST_ASSIGN_OPERAND = ': ';
public const T_ATTR_ARGUMENTS_LIST_SEPARATOR = ', ';
}
24 changes: 24 additions & 0 deletions src/Generator/AttributeGenerator/AttributePrototype.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

use ReflectionAttribute;

final class AttributePrototype extends ReflectionAttribute
{
public function __construct(private string $attributeName, private array $arguments = [])
{
}

public function getName(): string
{
return $this->attributeName;
}

public function getArguments(): array
{
return $this->arguments;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

use Laminas\Code\Generator\AttributeGenerator\Exception\NestedAttributesAreNotSupportedException;

final class AttributeWithArgumentsAssembler extends AbstractAttributeAssembler
{
public function assemble(): string
{
$attributeName = $this->getName();

$attributeDefinition = AttributePart::T_ATTR_START . $attributeName . AttributePart::T_ATTR_ARGUMENTS_LIST_START;

$this->generateArguments($attributeDefinition);

return $attributeDefinition . AttributePart::T_ATTR_END;
}

private function generateArguments(string &$output): void
{
$argumentsList = [];

foreach ($this->getArguments() as $argumentName => $argumentValue) {
$argumentsList[] = $argumentName . AttributePart::T_ATTR_ARGUMENTS_LIST_ASSIGN_OPERAND . $this->formatArgumentValue($argumentValue);
}

$output .= implode(AttributePart::T_ATTR_ARGUMENTS_LIST_SEPARATOR, $argumentsList);
$output .= AttributePart::T_ATTR_ARGUMENTS_LIST_END;
}

private function formatArgumentValue(mixed $argument): mixed
{
switch (true) {
case is_string($argument):
return "'$argument'";
case is_bool($argument):
return $argument ? 'true' : 'false';
case is_array($argument):
throw NestedAttributesAreNotSupportedException::create();
default:
return $argument;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator\Exception;

use Laminas\Code\Generator\Exception\RuntimeException;

final class NestedAttributesAreNotSupportedException extends RuntimeException
{
public static function create(): self
{
return new self('Nested attributes are not supported yet');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator\Exception;

use Laminas\Code\Generator\Exception\RuntimeException;

class NoSuitableArgumentAssemblerFoundException extends RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator\Exception;

use Laminas\Code\Generator\Exception\RuntimeException;

final class NotEmptyArgumentListException extends RuntimeException
{
}
34 changes: 34 additions & 0 deletions src/Generator/AttributeGenerator/SimpleAttributeAssembler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Laminas\Code\Generator\AttributeGenerator;

use Laminas\Code\Generator\AttributeGenerator\Exception\NotEmptyArgumentListException;
use ReflectionAttribute;

final class SimpleAttributeAssembler extends AbstractAttributeAssembler
{
public function __construct(ReflectionAttribute $attributePrototype)
{
parent::__construct($attributePrototype);

$this->assertAttributeWithoutArguments();
}

public function assemble(): string
{
$attributeName = $this->getName();

return AttributePart::T_ATTR_START . $attributeName . AttributePart::T_ATTR_END;
}

private function assertAttributeWithoutArguments(): void
{
$arguments = $this->getArguments();

if (!empty($arguments)) {
throw new NotEmptyArgumentListException('Argument list has to be empty');
}
}
}
32 changes: 29 additions & 3 deletions src/Generator/ClassGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laminas\Code\Generator;

use Laminas\Code\Generator\AttributeGenerator\AttributeBuilder;
use Laminas\Code\Reflection\ClassReflection;

use function array_diff;
Expand Down Expand Up @@ -68,6 +69,8 @@ class ClassGenerator extends AbstractGenerator implements TraitUsageInterface
/** @var TraitUsageGenerator Object to encapsulate trait usage logic */
protected TraitUsageGenerator $traitUsageGenerator;

private ?AttributeGenerator $attributeGenerator = null;

/**
* Build a Code Generation Php Object from a Class Reflection
*
Expand Down Expand Up @@ -198,6 +201,10 @@ public static function fromArray(array $array)
$docBlock = $value instanceof DocBlockGenerator ? $value : DocBlockGenerator::fromArray($value);
$cg->setDocBlock($docBlock);
break;
case 'attribute':
$generator = $value instanceof AttributeGenerator ? $value : AttributeGenerator::fromArray($value);
$cg->setAttributes($generator);
break;
case 'flags':
$cg->setFlags($value);
break;
Expand Down Expand Up @@ -228,17 +235,17 @@ public static function fromArray(array $array)
* @psalm-param array<class-string> $interfaces
* @param PropertyGenerator[]|string[]|array[] $properties
* @param MethodGenerator[]|string[]|array[] $methods
* @param DocBlockGenerator $docBlock
*/
public function __construct(
$name = null,
string $name = null,
$namespaceName = null,
$flags = null,
$extends = null,
array $interfaces = [],
array $properties = [],
array $methods = [],
$docBlock = null
DocBlockGenerator $docBlock = null,
AttributeGenerator $attributeGenerator = null,
) {
$this->traitUsageGenerator = new TraitUsageGenerator($this);

Expand Down Expand Up @@ -266,6 +273,9 @@ public function __construct(
if ($docBlock !== null) {
$this->setDocBlock($docBlock);
}
if ($attributeGenerator) {
$this->setAttributes($attributeGenerator);
}
}

/**
Expand Down Expand Up @@ -336,6 +346,13 @@ public function setDocBlock(DocBlockGenerator $docBlock)
return $this;
}

public function setAttributes(AttributeGenerator $attributeGenerator): self
{
$this->attributeGenerator = $attributeGenerator;

return $this;
}

/**
* @return ?DocBlockGenerator
*/
Expand All @@ -344,6 +361,11 @@ public function getDocBlock()
return $this->docBlock;
}

public function getAttributes(): ?AttributeGenerator
{
return $this->attributeGenerator;
}

/**
* @param int[]|int $flags
* @return static
Expand Down Expand Up @@ -1063,6 +1085,10 @@ public function generate()
$output .= $docBlock->generate();
}

if ($attributeGenerator = $this->getAttributes()) {
$output .= $attributeGenerator->generate() . self::LINE_FEED;
}

if ($this->isAbstract()) {
$output .= 'abstract ';
} elseif ($this->isFinal()) {
Expand Down
Loading