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

Psalm Plugin Laravel Package

php-static-analysis/psalm-plugin

A Psalm plugin for PHP static analysis that extends Psalm with additional checks and integrations, helping teams catch issues earlier and enforce consistent code quality in their projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package collects PHP attributes off your classes, methods, and properties and makes their constructor arguments readable directly — no ReflectionAttribute::newInstance() round-trip at the call site.

composer require <vendor>/<package>   # verify name/constraint against the repo

First places to look:

  1. src/ — the collector/registry class (the thing that walks your codebase and stores attribute instances) and the attribute value object.
  2. tests/Fixtures — the fixture attributes show the exact expected shape of stored parameters faster than the README does.
  3. The config file, if one is published (php artisan vendor:publish), which typically holds the scanned paths.

First use case. Declare a domain attribute with promoted constructor parameters and read it back without reflection:

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
final class RateLimit
{
    public function __construct(
        public int $attempts = 60,
        public int $decaySeconds = 60,
    ) {}
}
#[RateLimit(attempts: 30, decaySeconds: 60)]
final class WebhookController
{
    #[RateLimit(attempts: 5)]
    public function store() {}
}

Before 0.5.0 you had to instantiate the attribute yourself to get at attempts:

$reflection = new ReflectionMethod($controller, 'store');
$limit = $reflection->getAttributes(RateLimit::class)[0]->newInstance()->attempts;

From 0.5.0 the parameters are stored on the collected attribute, so the reflection step disappears and you read the parameters off the collected object directly (check the collector's accessor name — arguments(), parameters(), or a public property — in src/; don't assume).

Implementation Patterns

Read parameters, don't re-instantiate. This is the whole point of 0.5.0. Audit for ->newInstance() and getArguments() calls in your codebase and replace them with the stored-parameter accessor. If you have a helper that wraps newInstance(), delete it — it is now the slow path.

Middleware reading controller attributes. Resolve the route action, pull the method-level attribute off the collector, fall back to the class-level one:

public function handle(Request $request, Closure $next)
{
    $action = $request->route()->getActionName(); // "Controller@method"

    $limit = $this->attributes->forMethod($action, RateLimit::class)
        ?? $this->attributes->forClass($action, RateLimit::class);

    if ($limit && $this->limiter->tooManyAttempts($key, $limit->attempts)) {
        abort(429);
    }
}

Note the class-level fallback — it's the pattern that makes #[RateLimit(attempts: 30)] on the class act as a default for every method.

Collect once, in a service provider. Scanning is the expensive part; reading parameters is now nearly free. Warm the collection in boot() (or behind a cache) rather than calling the collector inside a request-scoped loop:

$this->app->singleton(AttributeCollection::class, fn () => AttributeCollection::fromPaths([
    app_path('Http/Controllers'),
    app_path('Console/Commands'),
]));

Treat attributes as declarative config. Once parameters are readable, they compete with config/*.php for the same job. Use attributes for per-class/per-method behaviour (rate limits, feature flags, audit logging, queue settings); keep cross-cutting environment values in config and let the attribute reference them by name.

Repeatable attributes. If your attribute is Attribute::IS_REPEATABLE, design your collector for a list and decide the ordering contract explicitly — don't rely on declaration order surviving a cache round-trip.

Testing. Assert on the attribute's parameters, not on the reflection call. A unit test against the attribute class plus one integration test asserting the collector returned the right stored parameters covers most regressions.

Integration tips. Attributes survive into queued jobs, console commands, and Eloquent models equally well — anywhere you can reach a ReflectionClass, you can reach the collector. Prefer resolving the collector from the container so tests can swap it with a hand-built instance instead of scanning the fixture directory.

Gotchas and Tips

  • 0.5.0 is a 0.x minor. SemVer does not protect you here. Pin the version, read the changelog, and re-run your suite on upgrade. The parameter-storage change is additive in intent but touches the shape of the collected object — any code doing ->newInstance() should be re-verified, not assumed equivalent.
  • Storing parameters ≠ running the constructor. If you previously relied on newInstance() to execute constructor-side validation, normalisation, or side effects, that no longer happens implicitly. Validate where you consume the attribute, or add an explicit newInstance() only on the paths that need it.
  • Attribute arguments must be constant expressions. No objects except nested attributes (PHP 8.1+) and enums; no config() calls, no function calls. If you need runtime values, store a config key string and resolve it at the consumer.
  • Enums are objects. They serialise fine to most caches but not all — if you cache the collection, verify enum parameters round-trip, and check whether your cache driver mangles class-string keys.
  • Named arguments mean a keyed array. Access parameters by name ($attr->attempts), never by position. Optional parameters left out at the declaration site may be absent or defaulted depending on the collector — confirm which, because isset() vs ?? matters.
  • #[Attribute] flags bite. A TARGET_METHOD-only attribute on a class silently doesn't appear. If an attribute "vanishes", check TARGET_* first, then whether the path is inside the configured scan roots.
  • Debugging: dump the collected attribute object rather than the raw reflection — dd($collection->forClass(MyController::class)) shows exactly what was stored, which distinguishes "not collected" from "collected with wrong args" immediately.
  • Extension points: if the collector supports custom resolvers or per-type handlers, register them in a service provider rather than subclassing; custom handlers are the clean place for your own normalisation now that the constructor isn't doing it.
  • Nested/private scope. The collector stores what it discovered; it won't reach closure parameters, promoted property attributes on nested private classes, or attributes on vendor code outside the scan roots. Drop to reflection for those and accept the cost.
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.
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle