Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Annotations Pack Laravel Package

symfony/annotations-pack

Symfony annotations-pack is a Composer metapackage that pulls in the libraries needed to use annotations in Symfony applications, chiefly doctrine/annotations. It simplified dependency setup during the Symfony 3.4 to 4.x transition.

View on GitHub
Deep Wiki
Context7

Getting Started

symfony/annotations-pack is a Composer metapackage — it contains no PHP code, no namespace, no services. It exists so that composer require annotations (via Symfony Flex) pulls in a working, cache-backed annotation stack in one step. Its only job is to require:

  • doctrine/annotations (the actual Reader implementation)
  • symfony/cache (the PSR-6 pool used to cache parsed docblocks)

Install

# If symfony/flex is present, the alias resolves to this pack
composer require annotations

# Otherwise, install the two things the pack would have installed
composer require doctrine/annotations symfony/cache

Where to look first

  1. vendor/symfony/annotations-pack/composer.json — confirms there is nothing else in here.
  2. vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ — AnnotationReader, PsrCachedReader, AnnotationRegistry.
  3. In a Symfony app: bin/console debug:container annotations.cached_reader and debug:container cache.annotations.

First use case

Drop an annotation on something that already supports one — a route, a validator constraint, a Doctrine mapping — and confirm it is read through the cached reader rather than the raw one:

use Symfony\Component\Routing\Annotation\Route;

class HealthController
{
    /**
     * @Route("/health", name="health")
     */
    public function check(): Response
    {
        return new Response('ok');
    }
}

Then verify the cache is actually wired:

bin/console debug:container annotations.cached_reader --show-arguments
# expect: Doctrine\Common\Annotations\PsrCachedReader
#         arguments: [@annotations.reader, @cache.annotations, %kernel.debug%]

If that service is missing or is a plain AnnotationReader, the pack was never installed (or you are on Symfony 7, where it is gone — see Gotchas).

Implementation Patterns

Symfony app: do nothing, just annotate

FrameworkBundle wires the whole graph. The useful service IDs:

Service ID Class
annotations.reader Doctrine\Common\Annotations\AnnotationReader
annotations.cached_reader Doctrine\Common\Annotations\PsrCachedReader
Doctrine\Common\Annotations\Reader (alias) the cached reader

Inject the interface, never the concrete class, so tests can swap it:

use Doctrine\Common\Annotations\Reader;

final class EndpointDescriber
{
    public function __construct(private Reader $reader) {}

    public function describe(string $class, string $method): array
    {
        $ref = new \ReflectionMethod($class, $method);

        return [
            'route'  => $this->reader->getMethodAnnotation($ref, Route::class),
            'class'  => $this->reader->getClassAnnotation(
                new \ReflectionClass($class),
                SomeMarker::class
            ),
            'props'  => $this->reader->getPropertyAnnotations(
                new \ReflectionProperty($class, 'id')
            ),
            'all'    => $this->reader->getMethodAnnotations($ref),
        ];
    }
}

Standalone / non-Symfony wiring

This is the pattern the pack is really shorthand for. Build the decorator yourself:

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\PsrCachedReader;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$pool   = new FilesystemAdapter('annotations', 0, __DIR__ . '/var/cache');
$reader = new PsrCachedReader(new AnnotationReader(), $pool, $debug = false);

$route = $reader->getClassAnnotation(
    new ReflectionClass(SomeController::class),
    Route::class
);

PsrCachedReader is a straight decorator: cache key = class/method/property name + annotation class, so you never invalidate by hand in production.

Custom annotation classes

namespace App\Annotation;

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"CLASS", "METHOD"})
 */
final class Audited
{
    /** @var string */
    public $channel = 'default';

    public function __construct(array $values = [])
    {
        $this->channel = $values['channel'] ?? $values['value'] ?? 'default';
    }
}
/**
 * @Audited(channel="payments")
 */
final class ChargeCustomer { /* ... */ }

Two hard requirements: the @Annotation docblock tag on the annotation class, and a use statement at the usage site (use App\Annotation\Audited;). Both failure modes are silent-ish — see Gotchas.

Cache strategy per environment

# config/packages/framework.yaml
framework:
    annotations:
        cache: cache.annotations   # pool name or service id
        debug: '%kernel.debug%'

Prod: back cache.annotations with APCu or Redis and warm it during deploy (just run the app's routing/DI warmup — annotation entries are populated lazily on first reflect, so a cache:warmup plus one request per route set is the practical pattern). Dev: debug: true is what saves you from stale entries; don't disable it locally to "speed things up".

Tests

Annotations are resolved at runtime against docblocks, so tests that boot the kernel inherit whatever pool the environment uses. In config/packages/test/framework.yaml:

framework:
    annotations:
        cache: cache.adapter.array

This prevents filesystem cache entries from a previous run leaking into a test that asserts on a changed annotation.

Coexistence with attributes

There is no conflict — doctrine/annotations only reads docblocks; PHP reads #[...] natively. During migration you can read both from your own code:

$route = $ref->getAttributes(Route::class)[0]?->newInstance()
      ?? $this->reader->getMethodAnnotation($ref, Route::class);

Prefer attributes in new code; annotations only where a third-party bundle still requires them.

Gotchas and Tips

The pack is abandoned and its scope shrank with every Symfony release. Symfony 6.4 deprecated the framework.annotations config node and the annotation loaders for routing/serializer; Symfony 7 removed them. Doctrine ORM 3 removed its annotation driver entirely. If you are starting fresh on Symfony 7 / ORM 3, this pack is a dead end — require doctrine/annotations directly only if a specific third-party bundle still needs a Reader. Check composer why doctrine/annotations before adding anything.

"Package annotations not found." composer require annotations is a Flex alias; without symfony/flex installed Composer looks for a literal package named annotations on Packagist and fails. Install the two real packages instead.

@Annotation is not optional. A custom annotation class without the @Annotation docblock tag is treated as a plain class and simply never matches — getClassAnnotation() returns null with no error. This is the single most common "my annotation does nothing" bug.

Missing use statements produce confusing errors. With AnnotationReader in strict mode you get [Semantical Error] The annotation "@Audited" in ... was never imported. Did you maybe forget to add a "use" statement?. Note the reader resolves docblock names against the imports of the annotated file, not the annotation file — copy-pasting a docblock between files is a classic way to break it.

Stale cache in dev is a real time sink. If %kernel.debug% is false — common when someone sets APP_DEBUG=0 locally or copies a prod .env for a quick test — PsrCachedReader skips the mtime checks and you will edit an annotation for twenty minutes wondering why nothing changes. Symptom: bin/console cache:clear "fixes" it. Always confirm debug: true in dev before debugging anything else.

AnnotationRegistry::registerLoader() is gone in doctrine/annotations 2.0. Old snippets still float around; in 2.x, annotation classes are autoloaded normally. If you follow a StackOverflow answer that calls AnnotationRegistry, you are on 1.x or reading stale advice.

PsrCachedReader caches by (class, member, annotationClass) — not by file. Editing the annotation class itself (adding a property, changing its constructor) does not invalidate existing entries under debug: false. Clear the pool on deploy, not just application code.

Do not put the annotation pool on cache.app. It gets cleared by unrelated application cache operations and, worse, shares a namespace with data you may flush intentionally. cache.annotations (backed by cache.system) or a dedicated pool is correct.

Watch reflection cost in hot paths. $reader->getMethodAnnotations() materialises every annotation on a member, instantiating each one. If you only need one, use getMethodAnnotation($ref, Foo::class). In tight loops, resolve once at container-build time (a compiler pass) rather than per request.

Annotation targets are enforced, but late. @Target({"METHOD"}) violations throw AnnotationException at read time, not compile time — meaning a bad annotation on an unexercised code path stays hidden until that path runs. A cheap guard: a small test that reflects over your src/ annotation consumers.

Extending the reader is the supported escape hatch. AnnotationReader is final in 2.0 in most respects, but Reader is an interface — decorate annotations.cached_reader (Symfony's service decoration) if you need to log, normalise, or layer a second source of metadata. Keep the decorator outside the cached one, or you will re-parse docblocks on every call.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect