terminal42/service-annotation-bundle
A Symfony bundle that lets you define and configure dependency-injection services using PHP annotations, reducing boilerplate YAML or XML. Annotate classes to register services, tags, and dependencies automatically.
This is not a Laravel package — it is a Symfony bundle (terminal42/service-annotation-bundle) that adds an attribute/annotation-driven service registration compiler pass to the Symfony DI container. There is no ServiceProvider, no config/*.php to publish, and no illuminate/* dependency. If you composer require it into a Laravel app you will pull Symfony DI/HttpKernel components in and get nothing usable. A Laravel developer will only meet this package when working inside a Symfony app (or a Symfony-based distribution) that already depends on it.
Assuming a Symfony host application:
composer require terminal42/service-annotation-bundle (1.1.6 now accepts symfony/*: ^7.0, so it will install cleanly on Symfony 6.x and 7.x).config/bundles.php:
Terminal42\ServiceAnnotationBundle\ServiceAnnotationBundle::class => ['all' => true],
config/packages/service_annotation.yaml:
terminal42_service_annotation:
annotations:
App\Attribute\AsFoo:
tags:
- { name: 'app.foo' }
# public / autowire / autoconfigure as supported by your version
TARGET_CLASS):
#[Attribute(Attribute::TARGET_CLASS)]
final class AsFoo {}
#[AsFoo]
final class PaymentHandler {}
Where to look first: the README (it is short and is the real documentation), then DependencyInjection/Configuration.php for the exact keys the config tree accepts, and the bundle's compiler pass to understand which classes get scanned. First use case: replacing a hand-maintained list of services.yaml entries + tags with a single attribute on each class, then consuming them via a tagged iterator.
#[AsExporter], #[AsPaymentHandler]) mapped once to a tag in YAML. The individual classes stay annotation-free apart from the marker, and the tag name lives in exactly one place.!tagged_iterator:
App\Export\ExporterCollection:
arguments:
- !tagged_iterator app.exporter
Now dropping #[AsExporter] on a new class is the entire registration step — no YAML edit, no constructor wiring._defaults deliberately. Auto-registered services do not necessarily inherit your services.yaml _defaults (autowire/autoconfigure/public). If a tagged service is injected by id anywhere, set public: true or autowire: true in the annotation's config block; otherwise expect "service not found" or "cannot autowire" at compile time.#[AutoconfigureTag], #[AsTaggedItem], #[AsEventListener] and #[AsCommand] cover the majority of cases with zero config. Reach for this bundle only for your own domain markers that core autoconfiguration cannot express.services.yaml definition with #[AsAlias]-style indirection instead.src/ so they are autoloadable at container-compile time. A dev-only or test-only autoload path will break cache:warmup in prod.bin/console cache:clear → bin/console debug:container --tag=app.foo to confirm → delete the old services.yaml entry → run the test suite. Doing it one class at a time keeps the diff reviewable.symfony/* constraint. No API changes, no deprecations, no behavioural differences — upgrading from 1.1.5 is safe. The practical takeaway: if this package was the last thing blocking your Symfony 7 upgrade, that blocker is gone; re-run composer update terminal42/service-annotation-bundle -W and check composer why-not symfony/framework-bundle ^7.0 again.doctrine/annotations is deprecated and gone from most modern stacks. Use #[Attribute] classes exclusively; if you inherit a codebase using @Foo docblocks, budget for that migration before touching anything else.bin/console cache:clear (or rm -rf var/cache/*) is the first debugging step for "my new tagged service isn't there".service_annotation.yaml, or an attribute not matching the configured class exactly, produces no error — the class simply never becomes a service. Verify with bin/console debug:container --tag=<tag> and, if needed, debug:container --show-private.composer.json for symfony/dependency-injection before adding. If you want this pattern in Laravel, do not use this package — use olvlvl/composer-attribute-collector plus a small ServiceProvider that reflects over app/ and calls $this->app->tag(...), or just bind an explicit interface → implementation map.CompilerPassInterface registered in your Kernel::build() gives you full control and is usually cleaner than stretching the YAML annotations mapping beyond tags/public/autowire.^1.1.6 is a reasonable floor now that Symfony 7 is supported; consumers on Symfony 6.4 are unaffected.How can I help you explore Laravel packages today?