Skip to content

Add AttributeGenerator and class-level attribute support - #218

Open
armenio wants to merge 1 commit into
laminas:4.18.xfrom
armenio:feature/attribute-generator
Open

Add AttributeGenerator and class-level attribute support#218
armenio wants to merge 1 commit into
laminas:4.18.xfrom
armenio:feature/attribute-generator

Conversation

@armenio

@armenio armenio commented Aug 18, 2026

Copy link
Copy Markdown
Q A
Documentation no
Bugfix no
BC Break no
New Feature yes
RFC no
QA no

Description

Laminas\Code\Generator currently has no way to emit PHP attributes, so any class generated
through this library loses them. This is a long-standing gap: attributes have been part of the
language since PHP 8.0, and consumers such as proxy generators and ORM/DI tooling need to emit
them. See #144, and the earlier attempt in #145.

This PR adds Laminas\Code\Generator\AttributeGenerator plus attribute support on
ClassGenerator (and, by inheritance, InterfaceGenerator and TraitGenerator). It targets
4.18.x as a new feature, and is fully backwards compatible: no existing behaviour changes, and
no existing output changes.

How the code is used

$class = new ClassGenerator('MyEntity');
$class->setAttributes([
    AttributeGenerator::fromName(Entity::class),
    AttributeGenerator::fromName(Table::class, ['name' => 'my_entity']),
]);
$class->addAttribute(AttributeGenerator::fromName('My\Attributes\Deprecated', ['since 1.2.0']));

echo $class->generate();
#[\Doctrine\ORM\Mapping\Entity]
#[\Doctrine\ORM\Mapping\Table(name: 'my_entity')]
#[\My\Attributes\Deprecated('since 1.2.0')]
class MyEntity
{
}

Attributes on existing symbols can be read back through reflection:

// list<AttributeGenerator>
$attributes = AttributeGenerator::fromReflector(new ReflectionClass(MyEntity::class));

Public API

  • AttributeGenerator::fromName(string $name, array $arguments = []): self
  • AttributeGenerator::fromReflectionAttribute(ReflectionAttribute $attribute): self
  • AttributeGenerator::fromReflector(ReflectionClass|ReflectionClassConstant|ReflectionFunction|ReflectionMethod|ReflectionParameter|ReflectionProperty $reflector): list<self>
  • AttributeGenerator#generate(): string, #getName(), #getArguments()
  • ClassGenerator#setAttributes(list<AttributeGenerator>), #addAttribute(), #getAttributes()

Everything else is private. The class is final, @psalm-immutable, implements
GeneratorInterface only, and is not Stringable.

Design notes

This is a fresh implementation rather than a rebase of #145, written against the design direction
requested there by @Ocramius: a single class with a thin public API and no separate
assembler/builder/factory layer.

  • No ::fromArray(), per Deprecating all ::fromArray() methods in in the package #153.
  • No AttributeBuilder / AttributeAssembler / AttributeAssemblerFactory / AttributePart.
    Since the assembler layer is gone, the open "should AttributePart become an enum" question from
    Support for PHP 8.0 class attributes generation #145 no longer applies: there is no part type left to model.
  • Names are always emitted fully qualified (#[\Foo\Bar]), so output is valid regardless of the
    surrounding use statements. A leading \ in the input is accepted and normalised away.
  • Argument values are rendered by a private renderer rather than ValueGenerator. Attribute
    arguments are constant expressions with different rules than property defaults, and
    ValueGenerator renders 1.0 as 1, silently changing the argument's type. Supported values:
    null, bool, int, float, string, array (recursive, list and hash) and enum cases.
    Anything else throws InvalidArgumentException.
  • Invalid input fails at construction time where practical: malformed attribute names,
    malformed argument names, and positional arguments following named ones are all rejected by
    fromName().
  • ClassGenerator::fromReflection() deliberately does not populate attributes. Doing so would
    silently change the output for every existing consumer that generates classes from reflection —
    proxy generators in particular. Attributes are opt-in via AttributeGenerator::fromReflector().

Scope

Class-level attributes only, matching the issue. Attributes on methods, properties, constants and
parameters are a natural follow-up and reuse AttributeGenerator unchanged — happy to add them in
a separate PR once the shape of this one is agreed.

Documentation is included in docs/book/generator/examples.md. composer cs-check,
composer test and composer static-analysis all pass locally.

Fixes #144

Signed-off-by: Rafael Armenio <rafael.armenio@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add setAttributes to ClassGenerator

1 participant