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

Laravel Decorator Laravel Package

mindtwo/laravel-decorator

A lightweight decorator pattern implementation for Laravel. Wrap objects and services with reusable decorators to add behavior without modifying the original class, keep concerns separated, and resolve decorated instances cleanly through the container.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mindtwo/laravel-decorator

The service provider is auto-discovered (Laravel package discovery); there is nothing to register for a default setup. If you keep a providers array in config/app.php for a legacy app, add Mindtwo\LaravelDecorator\LaravelDecoratorServiceProvider manually.

Where to look first

  1. README.md in the package — the attribute-based example is the canonical usage and is the fastest path to a working decorator.
  2. src/Attributes/ — the decoration attribute(s) you place on the class you want to wrap.
  3. src/ — the base decorator class/trait that provides the passthrough behaviour and access to the wrapped instance.
  4. Published config (if you use the config-driven variant) — php artisan vendor:publish and pick the package's provider/tag from the printed list rather than guessing the tag name.

First use case

You have a service that is already injected in ten places and you want to add caching/logging/metrics without editing it or touching every call site. Decorate it:

namespace App\Services;

use Mindtwo\LaravelDecorator\Attributes\Decorate;

#[Decorate(ReportServiceDecorator::class)]
class ReportService
{
    public function daily(): array { /* expensive */ }
}
namespace App\Services;

use Mindtwo\LaravelDecorator\Decorator; // base class/trait shipped by the package

class ReportServiceDecorator extends Decorator
{
    public function __construct(protected ReportService $decorated) {}

    public function daily(): array
    {
        return cache()->remember('reports.daily', 300, fn () => $this->decorated->daily());
    }
}

Then resolve normally — app(ReportService::class) or constructor injection returns the decorator. This is the whole value proposition: container-level wrapping with zero changes to consumers.

Implementation Patterns

Keep the decorator a substitute, not a subclass of convenience. The decorator must satisfy the same contract consumers type-hint against (interface, or the concrete class where the package permits it). If ReportService has a ReportServiceInterface, bind the interface in a service provider and let the decorator implement the same interface — this keeps static analysis honest and makes the wrapper swappable.

Constructor-inject the inner instance explicitly. Do not call app(ReportService::class) from inside the decorator's constructor or methods; the container will hand you the decorator again and you get a circular dependency or infinite recursion. Declare the decorated type as a constructor argument and let the package wire it.

Order matters when stacking. Multiple decorators applied to one class compose in declaration/config order. Decide deliberately: caching should sit outside logging if you don't want cache hits logged, and inside metrics if you want to measure origin latency. Write the ordering down in a comment next to the attribute — it is the thing future maintainers get wrong.

Environment-scoped decoration. Register decorators conditionally in a service provider (if (app()->isProduction())) for things like profiling or tracing wrappers, so local stack traces and Xdebug stay usable. The attribute is static, so environment gating belongs in the provider/config layer, not in the class.

Prefer decorating at the boundary. Decorators are a good fit for repository/service classes resolved from the container: caching, retry, rate-limiting, audit logging, feature-flagged implementations. They are a poor fit for Eloquent models, controllers, and anything that is frequently new'd or serialized — the container never sees those instances.

Testing. Because the decorator is just a class, unit-test it by passing a mock of the inner instance to its constructor. For integration assertions, resolve the type from the container and assert assertInstanceOf(SomeDecorator::class, app(SomeService::class)) in a feature test — this catches a silently broken registration (wrong namespace, cached config, attribute typo) that unit tests never will.

Registering explicitly when attributes are impractical. For third-party or vendor classes you cannot annotate, use the package's config/provider registration path instead and map the abstraction to the decorator. Keep all such mappings in one provider method so the container graph is discoverable in a single file.

Gotchas and Tips

  • Circular resolution is the #1 failure mode. If you see a stack overflow or "maximum function nesting level" error, a decorator is resolving its own decorated type through the container. Always take the inner instance via constructor injection.
  • Attributes only apply on container resolution. new ReportService() bypasses decoration entirely — as does anything the framework instantiates itself (controllers, middleware, form requests). If your decorator "doesn't run", check how the object is being created before suspecting the package.
  • Clear caches after changing decoration. php artisan config:clear and php artisan optimize:clear after editing the config or provider. In production with config:cache/optimize in the deploy pipeline, a stale config cache will quietly keep the old mapping — re-run php artisan optimize as part of deploy.
  • Final classes and private constructors cannot be extended or wrapped cleanly. Check the target's declaration before promising a decorator to a teammate.
  • Serialization. Decorated objects pushed onto queues or cached with serialize() will carry the decorator wrapper; keep decorators stateless (no request-scoped data in properties) or you will get surprises on unserialize.
  • Debugging the graph: dd(get_class(app(SomeService::class))) or app()->make() in tinker is the quickest confirmation of which implementation is live. If it returns the raw class, the registration never happened.
  • Release note for 3.0.4: this is a housekeeping release — the committed composer.lock was removed and Laravel/PHP compatibility constraints were refreshed. No API changes, no deprecations, so upgrading from 3.0.3 is a routine composer update. Two things to check when you bump: (a) your composer.json Laravel constraint now has to intersect with the package's refreshed constraint, and (b) because the package no longer ships a lock file it resolves against your dependency graph — if you were implicitly relying on the versions the package had pinned, run your test suite before deploying.
  • Extension point: the cleanest customisation is your own service provider that rebinds abstractions to decorators, layered on top of the package's resolution. Reach for that before forking the package internals.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator