PHPackages                             sarkis-sh/spray-aop - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. sarkis-sh/spray-aop

ActiveLibrary

sarkis-sh/spray-aop
===================

A lightweight, high-performance Aspect-Oriented Programming (AOP) framework for Laravel using PHP 8.2+ Attributes.

v1.0.1(2d ago)22↓100%MITPHPPHP ^8.2

Since Apr 5Pushed 2d agoCompare

[ Source](https://github.com/sarkis-sh/spray-aop)[ Packagist](https://packagist.org/packages/sarkis-sh/spray-aop)[ RSS](/packages/sarkis-sh-spray-aop/feed)WikiDiscussions master Synced today

READMEChangelog (2)Dependencies (2)Versions (3)Used By (0)

Spray AOP
=========

[](#spray-aop)

[![Latest Version](https://camo.githubusercontent.com/0f63fc2bf7d12a948957c1a93f8669a5fdee986ac0dcf4a2a246dd70764fb5bf/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7361726b69732d73682f73707261792d616f702e737667)](https://packagist.org/packages/sarkis-sh/spray-aop)[![Laravel](https://camo.githubusercontent.com/8dde7e2689bbc7740aacca68a93c74d45735877f1b4ef8943360e299c9ae0586/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302f31312f31322f31332d4646324432302e737667)](https://laravel.com)[![PHP](https://camo.githubusercontent.com/eebfeb80482c8b896dff0a49d47a9960936961bba1a8aad1d948a028a7c7d413/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545382e322d3737374242342e737667)](https://www.php.net/)[![License](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)

A lightweight, high-performance Aspect-Oriented Programming (AOP) package for Laravel, built specifically for PHP 8.2+ Attributes.

Spray AOP enables you to apply cross-cutting concerns to your application classes using native PHP Attributes and dynamically generated proxy classes. It hooks into the Laravel Service Container to automatically resolve proxied versions of your classes whenever aspects are detected, ensuring clean separation of logic with zero manual boilerplate.

Table of Contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Publish Configuration](#publish-configuration)
- [Configuration](#configuration)
- [Usage](#usage)
    - [Create an Aspect and Attribute](#create-an-aspect-and-attribute)
    - [Example Aspect Attribute](#example-aspect-attribute)
    - [Example Aspect Handler](#example-aspect-handler)
    - [Full Examples of Generated Aspects](#full-examples-of-generated-aspects)
    - [Apply the Attribute](#apply-the-attribute)
    - [How It Works](#how-it-works)
- [Artisan Commands](#artisan-commands)
- [Production](#production)
- [Notes](#notes)
- [License](#license)

Features
--------

[](#features)

- Modern AOP: Leverages native PHP 8.2+ Attributes for declarative interception.
- Smart Proxy Engine: Automatic proxy generation with full method signature compatibility.
- Production-Ready: High-performance proxy caching to eliminate runtime I/O overhead.
- Seamless Integration: Automatically swaps target classes within the Laravel Service Container.
- Lifecycle Hooks: Simplified logic through Spray\\Aop\\Aspects\\BaseAspect (Around, Before, After, and Exception hooks).
- Developer Experience: Powerful Artisan commands for proxy management (cache, clear, rebuild).
- Rapid Scaffolding: Built-in generator to create Aspects and Attributes in seconds (spray:make-aspect).

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- Laravel 10.0 or higher (compatible with 11.0, 12.0, 13.0)
- Composer for dependency management

Installation
------------

[](#installation)

```
composer require sarkis-sh/spray-aop
```

Laravel will auto-discover the provider via `Spray\Aop\Providers\AopServiceProvider`.

Publish Configuration
---------------------

[](#publish-configuration)

```
php artisan vendor:publish --tag=spray-aop-config
```

This publishes the configuration file to `config/spray-aop.php`.

Configuration
-------------

[](#configuration)

The published config supports:

- `enabled` - Enable or disable the AOP engine entirely
- `storage_path` - Relative storage path for generated proxy classes
- `scan_paths` - Directories scanned for classes with AOP attributes
- `auto_generate` - Allow on-the-fly proxy generation when a proxy is missing

Default config values:

```
return [
    'enabled' => env('SPRAY_AOP_ENABLED', true),
    'storage_path' => 'framework/aop/proxies',
    'scan_paths' => [
        app_path(),
    ],
    'auto_generate' => env('SPRAY_AOP_AUTO_GEN', true),
];
```

Usage
-----

[](#usage)

### Create an Aspect and Attribute

[](#create-an-aspect-and-attribute)

You can create both using the built-in generator:

```
php artisan spray:make-aspect AuditLog
```

The generated files include:

- an Aspect handler class under `app/Aspects`
- a matching Attribute class under `app/Attributes`

#### Command Options

[](#command-options)

Customize your Aspect and Attribute generation with the following options:

OptionShortDescription`--before``-b`Include the `before()` hook in the generated Aspect`--after``-a`Include the `after()` hook in the generated Aspect`--around``-x`Include the `handle()` (around) method for full control over method execution`--exception``-e`Include the `onException()` hook for exception handling logic`--class``-c`Allow the Attribute to target classes`--method``-m`Allow the Attribute to target methods`--repeatable``-r`Make the Attribute repeatable (allows multiple instances on the same target)`--force`-Force creation even if the Aspect or Attribute already exists#### Examples

[](#examples)

Generate with before and after hooks on methods:

```
php artisan spray:make-aspect Logging -b -a -m
```

Generate with exception handling on classes:

```
php artisan spray:make-aspect ErrorTracker -e -c
```

Generate a full-featured repeatable Aspect:

```
php artisan spray:make-aspect PerformanceMonitor -b -a -x -e -r
```

### Example Aspect Attribute

[](#example-aspect-attribute)

```
namespace App\Attributes;

use Spray\Aop\Attributes\AspectAttribute;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AuditLog extends AspectAttribute
{
    public function __construct(string $action)
    {
        parent::__construct(\App\Aspects\AuditLogAspect::class, ['action' => $action]);
    }
}
```

### Example Aspect Handler

[](#example-aspect-handler)

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;

class AuditLogAspect extends BaseAspect
{
    protected function before(array $invocation): void
    {
        logger()->info('Audit start', [
            'method' => $invocation['method'],
            'args' => $invocation['args'],
            'options' => $invocation['options'],
        ]);
    }

    protected function after(array $invocation, mixed $result): mixed
    {
        logger()->info('Audit completed', ['result' => $result]);
        return $result;
    }
}
```

The `$invocation` array contains the following keys:

- `'instance'`: The object instance being intercepted (the proxied class instance).
- `'method'`: The name of the method being called (e.g., `'create'`).
- `'args'`: An array of arguments passed to the method.
- `'options'`: The configuration options defined in the attribute (e.g., `['action' => 'user.created']`).

### Full Examples of Generated Aspects

[](#full-examples-of-generated-aspects)

Here are complete examples of aspects generated with different options using `php artisan spray:make-aspect`. Each example shows the Aspect class and its corresponding Attribute.

#### Before Hook Only (`--before`)

[](#before-hook-only---before)

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;

class LoggingAspect extends BaseAspect
{
    protected function before(array $invocation): void
    {
        logger()->info('Method called', [
            'class' => get_class($invocation['instance']),
            'method' => $invocation['method'],
            'args' => $invocation['args'],
        ]);
    }
}
```

Corresponding Attribute:

```
namespace App\Attributes;

use Spray\Aop\Attributes\AspectAttribute;

#[\Attribute(\Attribute::TARGET_METHOD)]
class Logging extends AspectAttribute
{
    public function __construct()
    {
        parent::__construct(\App\Aspects\LoggingAspect::class, []);
    }
}
```

#### After Hook Only (`--after`)

[](#after-hook-only---after)

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;

class CachingAspect extends BaseAspect
{
    protected function after(array $invocation, mixed $result): mixed
    {
        // Cache the result
        cache()->put($invocation['method'], $result, 3600);
        return $result;
    }
}
```

#### Exception Hook Only (`--exception`)

[](#exception-hook-only---exception)

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;
use Throwable;

class ErrorTrackingAspect extends BaseAspect
{
    protected function onException(array $invocation, Throwable $e): mixed
    {
        // Log the error
        logger()->error('Exception in method', [
            'method' => $invocation['method'],
            'exception' => $e->getMessage(),
        ]);
        throw $e; // Re-throw the exception
    }
}
```

#### Around Hook (`--around`)

[](#around-hook---around)

For full control, use the `handle` method (around advice):

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;
use Closure;

class PerformanceAspect extends BaseAspect
{
    public function handle(array $invocation, Closure $next): mixed
    {
        $start = microtime(true);

        try {
            $result = $next($invocation); // Execute the original method

            $duration = microtime(true) - $start;
            logger()->info('Method performance', [
                'method' => $invocation['method'],
                'duration' => $duration,
            ]);

            return $result;
        } catch (Throwable $e) {
            $duration = microtime(true) - $start;
            logger()->error('Method failed', [
                'method' => $invocation['method'],
                'duration' => $duration,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }
}
```

#### Combined Hooks (`--before --after --exception`)

[](#combined-hooks---before---after---exception)

```
namespace App\Aspects;

use Spray\Aop\Aspects\BaseAspect;
use Throwable;

class AuditAspect extends BaseAspect
{
    protected function before(array $invocation): void
    {
        logger()->info('Audit: Method starting', [
            'method' => $invocation['method'],
            'args' => $invocation['args'],
        ]);
    }

    protected function after(array $invocation, mixed $result): mixed
    {
        logger()->info('Audit: Method completed', [
            'method' => $invocation['method'],
            'result' => $result,
        ]);
        return $result;
    }

    protected function onException(array $invocation, Throwable $e): mixed
    {
        logger()->error('Audit: Method failed', [
            'method' => $invocation['method'],
            'error' => $e->getMessage(),
        ]);
        throw $e;
    }
}
```

### Apply the Attribute

[](#apply-the-attribute)

```
use App\Attributes\AuditLog;

#[AuditLog('user.created')]
class UserService
{
    public function create(array $data)
    {
        // business logic
    }
}
```

### How It Works

[](#how-it-works)

- Laravel resolves a service from the container
- Spray AOP inspects the class for `AspectAttribute` usage
- If aspects exist, it generates or loads a proxy class
- Proxied methods execute through the `Spray\Aop\Engines\Pipeline`
- Each aspect is executed in order, with support for `before`, `after`, and exception handling
- Proxies are generated once and cached as plain PHP files, ensuring near-native execution speed

Artisan Commands
----------------

[](#artisan-commands)

- `php artisan spray:aop-cache` - Pre-generate all proxy classes for production
- `php artisan spray:aop-clear` - Remove all generated proxy classes
- `php artisan spray:aop-rebuild` - Clear and regenerate proxies immediately
- `php artisan spray:make-aspect` - Generate a new Aspect handler and Attribute

Production
----------

[](#production)

For production deployments, disable runtime generation and cache proxies ahead of time:

```
SPRAY_AOP_AUTO_GEN=false
```

Then run:

```
php artisan spray:aop-cache
```

Notes
-----

[](#notes)

- Spray AOP only intercepts public, non-static, non-final, non-constructor methods.
- It ignores Laravel internals and proxy classes to avoid bootstrap recursion.
- The package uses `storage/framework/aop/proxies` by default for generated PHP files.
- If you change the `storage_path` in the configuration, ensure the new directory is writable by the web server.

License
-------

[](#license)

MIT — see [LICENSE](LICENSE)

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance99

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

2d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4e0d66ce5a98104bbaa52c763332623c1cd667c1f580c8771cfc45ddb1b9d942?d=identicon)[sarkis-sh](/maintainers/sarkis-sh)

---

Top Contributors

[![sarkis-sh](https://avatars.githubusercontent.com/u/103902380?v=4)](https://github.com/sarkis-sh "sarkis-sh (2 commits)")

---

Tags

aopaspect-oriented-programmingattributesclean-architecturecode-generationinterceptorlaravellaravel-packagephpphp-8proxy-patternspraylaravelaopproxyaspectattributesspray

### Embed Badge

![Health badge](/badges/sarkis-sh-spray-aop/health.svg)

```
[![Health](https://phpackages.com/badges/sarkis-sh-spray-aop/health.svg)](https://phpackages.com/packages/sarkis-sh-spray-aop)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.1k](/packages/larastan-larastan)[laravel/folio

Page based routing for Laravel.

608453.9k27](/packages/laravel-folio)[laragear/preload

Effortlessly make a Preload script for your Laravel application.

119363.5k](/packages/laragear-preload)[goaop/goaop-laravel-bridge

Integration bridge for Go! AOP framework

9420.6k](/packages/goaop-goaop-laravel-bridge)[vantage/authorized-attributes

Authorized Model Attributes for Laravel

2324.7k](/packages/vantage-authorized-attributes)[spescina/imgproxy

An image proxy for Laravel

201.3k](/packages/spescina-imgproxy)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
