-
-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for PHP 8.0 class attributes generation #145
base: 4.8.x
Are you sure you want to change the base?
Changes from 2 commits
94ab27f
f0c043f
e606216
2e07cf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,6 @@ jobs: | |
- "highest" | ||
- "locked" | ||
php-version: | ||
- "7.4" | ||
- "8.0" | ||
- "8.1" | ||
operating-system: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator; | ||
|
||
use Laminas\Code\Generator\AttributeGenerator\AttributeAssembler; | ||
use Laminas\Code\Generator\AttributeGenerator\AttributeAssemblerBag; | ||
use Laminas\Code\Generator\AttributeGenerator\AttributeAssemblerFactory; | ||
use Laminas\Code\Generator\AttributeGenerator\AttributeBuilder; | ||
use ReflectionClass; | ||
|
||
class AttributeGenerator extends AbstractGenerator | ||
{ | ||
private function __construct(private AttributeAssemblerBag $attributeAssemblerBag) | ||
{ | ||
} | ||
|
||
public function generate(): string | ||
{ | ||
$generatedAttributes = array_map(fn(AttributeAssembler $attributeAssembler) => $attributeAssembler->__toString(), | ||
$this->attributeAssemblerBag->toArray(), | ||
); | ||
|
||
return implode(self::LINE_FEED, $generatedAttributes); | ||
} | ||
|
||
public static function fromReflection(ReflectionClass $reflectionClass): self | ||
{ | ||
return new self(AttributeAssemblerFactory::createForClassByReflection($reflectionClass)); | ||
} | ||
|
||
public static function fromBuilder(AttributeBuilder $attributeBuilder): self | ||
{ | ||
return new self(AttributeAssemblerFactory::createForClassFromBuilder($attributeBuilder)); | ||
} | ||
|
||
public static function fromArray(array $definitions): self | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
$builder = new AttributeBuilder(); | ||
|
||
foreach ($definitions as $definition) { | ||
@list($attributeName, $attributeArguments) = $definition; | ||
|
||
if (!isset($attributeArguments)) { | ||
$attributeArguments = []; | ||
} | ||
|
||
$builder->add($attributeName, $attributeArguments); | ||
} | ||
|
||
return self::fromBuilder($builder); | ||
} | ||
} |
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(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator\AttributeGenerator; | ||
|
||
use Stringable; | ||
|
||
interface AttributeAssembler extends Stringable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not make this See also ShittySoft/symfony-live-berlin-2018-doctrine-tutorial#3 (comment) for more clarity |
||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator\AttributeGenerator; | ||
|
||
final class AttributeAssemblerBag | ||
jakublabno marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
/** | ||
* @var AttributeAssembler[] | ||
*/ | ||
private array $assemblers = []; | ||
|
||
public function add(AttributeAssembler $assembler): void | ||
{ | ||
$this->assemblers[] = $assembler; | ||
} | ||
|
||
public function toArray(): array | ||
{ | ||
return $this->assemblers; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator\AttributeGenerator; | ||
|
||
use ReflectionAttribute; | ||
use ReflectionClass; | ||
|
||
final class AttributeAssemblerFactory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code can all be inlined into the |
||
{ | ||
public static function createForClassByReflection(ReflectionClass $reflectionClass): AttributeAssemblerBag | ||
{ | ||
$attributes = $reflectionClass->getAttributes(); | ||
$assemblyBag = new AttributeAssemblerBag(); | ||
|
||
foreach ($attributes as $attribute) { | ||
$assembler = self::negotiateAssembler($attribute); | ||
|
||
$assemblyBag->add($assembler); | ||
} | ||
|
||
return $assemblyBag; | ||
} | ||
|
||
public static function createForClassFromBuilder(AttributeBuilder $attributeBuilder): AttributeAssemblerBag | ||
{ | ||
$attributes = $attributeBuilder->build(); | ||
$assemblyBag = new AttributeAssemblerBag(); | ||
|
||
foreach ($attributes as $attribute) { | ||
$assembler = self::negotiateAssembler($attribute); | ||
|
||
$assemblyBag->add($assembler); | ||
} | ||
|
||
return $assemblyBag; | ||
} | ||
|
||
private static function negotiateAssembler(ReflectionAttribute|AttributePrototype $reflectionPrototype): AttributeAssembler | ||
{ | ||
$hasArguments = !empty($reflectionPrototype->getArguments()); | ||
|
||
if ($hasArguments) { | ||
return new AttributeWithArgumentsAssembler($reflectionPrototype); | ||
} | ||
|
||
return new SimpleAttributeAssembler($reflectionPrototype); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator\AttributeGenerator; | ||
|
||
final class AttributeBuilder | ||
{ | ||
private array $definitions = []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type is to be refined |
||
|
||
public function add(string $name, array $arguments = []): self | ||
{ | ||
$this->definitions[] = [$name, $arguments]; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @return AttributePrototype[] | ||
*/ | ||
public function build(): array | ||
{ | ||
return array_map(function (array $definition): AttributePrototype { | ||
list($name, $arguments) = $definition; | ||
|
||
return new AttributePrototype($name, $arguments); | ||
}, $this->definitions); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Code\Generator\AttributeGenerator; | ||
|
||
//TODO Enum in PHP8.1 | ||
final class AttributePart | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's mark all internally used symbols as |
||
{ | ||
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 = ', '; | ||
} |
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 __toString(): 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 . ': ' . $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 | ||
{ | ||
} |
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 __toString(): 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'); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably make this
final
, since it's a new symbol, and inheritance abuse is strong :-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I don't know why I extended it, maybe in the beginning when I didn't know the code, I'll stay on with interface
GeneratorInterface
:)