update readme

This commit is contained in:
icefox 2026-01-05 10:40:49 -03:00
parent ff7602dd48
commit 2c0ba488d8
No known key found for this signature in database
2 changed files with 67 additions and 160 deletions

208
README.md
View file

@ -1,189 +1,115 @@
# PHP AOP - Proof of Concept # PHP AOP
A minimalistic Aspect-Oriented Programming (AOP) implementation for PHP that uses attributes and Composer's autoloading system to weave aspects into classes at runtime. A minimalistic Aspect-Oriented Programming (AOP) implementation for PHP that uses attributes and Composer's autoloading system to weave aspects into classes at runtime.
## Features
- **Attribute-based AOP**: Use custom attributes to mark methods for aspect weaving
- **Builder Pattern Configuration**: Fluent API for configuring aspects and classes
- **Selective Class Loading**: Only process specified classes, avoiding vendor code
- **Multiple Aspect Support**: Register multiple aspect attributes and control which ones are processed
- **Reflection-based Weaving**: Uses native PHP Reflection for robust type handling
- **Complex Type Support**: Union types, nullable, variadic, by-reference parameters
- **Automatic proxy generation**: Hooks into Composer's class loading to generate proxy classes
- **Before/After execution**: Methods marked with aspects will execute interceptor code
- **Works with public and protected methods**: Full support for method visibility
- **Smart Caching**: Generated proxies cached for performance
## How It Works
### Two-Pass Reflection Architecture
1. **Configuration**: Use builder pattern to specify which aspects and classes to process
2. **Class Loading Hook**: Custom autoloader intercepts class loading for registered classes
3. **Use Statement Detection**: Checks if source file imports registered aspect attributes
4. **Two-Pass Loading**:
- **Pass 1 (String + eval)**: Rename class, load into memory for reflection analysis
- **Pass 2 (Reflection)**: Use native PHP Reflection to inspect method signatures and attributes
5. **Proxy Generation**: Create self-contained proxy file with:
- Original class with renamed identifier
- Proxy class extending original with aspect methods overridden
6. **Caching**: Generated proxies cached to `/tmp/php-aop-cache` for performance
## Installation ## Installation
```bash ```bash
composer install composer install
``` ```
## Configuration ## Usage
Configure the AspectLoader using the builder pattern in `src/bootstrap.php`:
### Create an Aspect class:
```php ```php
<?php namespace Tests\Aspects;
use Fnzr\Aop\AspectLoader; use Attribute;
use Fnzr\Aop\Aspect;
(new AspectLoader()) #[Attribute(Attribute::TARGET_METHOD)]
->withAspects([Aspect::class]) // Which aspect attributes to process class MyAspect
->withClasses([ // Which classes to weave (avoids vendor code) {
'Example\Calculator', public function before(object|string $target, mixed ...$args): void
'Example\ComplexExample', {
]) // $target is the class (for static methods) or object (for instance methods)
// Access all parameters before the wrapped function is called
var_dump($args);
}
public function after(object|string $target, mixed $result): mixed
{
// Access the returned value with $result
var_dump($result);
// modify the return value, or simply return the unmodified value
return $result;
}
}
```
### Register all Aspect classes on application bootstrap:
```
```
$cacheDir =
$useCache = false;
$weaver = new AspectWeaver(
[MyAspect::class],
// provide a cache directory to avoid reprocessing class
sys_get_temp_dir() . '/cache/php-aop-cache';
// whether to use cache. Disable in dev, enable in production
false,
// PSR compatible log, for debugging.
new NullLogger(),
);
// Provide with classes or namespaces will be evaluated for Aspects
// trying to evaluate all vendor classes will result in a bad time
// at minimum, use withNamespaces to filter your application
// classes will be evaluated if they match the list of classes OR belong to the namespace
$loader = AspectBuilder::begin()
// list of class names
->withClasses([ MyClass::class ])
// namespace prefix
->withNamespaces([ "MyApplication\" ])
->build($weaver)
->register(); ->register();
``` ```
## Usage ### Create a class and mark a method with your Aspect class:
### 1. Mark methods with aspect attributes
```php ```php
<?php <?php
namespace Example; class MyClass
use Fnzr\Aop\Aspect;
class Calculator
{ {
#[Aspect] #[MyAspect]
public function add(int $a, int $b): int public function add(int $a, int $b): int
{ {
echo "Adding {$a} + {$b}\n"; echo "Adding {$a} + {$b}\n";
return $a + $b; return $a + $b;
} }
#[Aspect]
protected function divide(float $a, float $b): float
{
echo "Dividing {$a} / {$b}\n";
return $a / $b;
}
// This method will NOT have aspect behavior
public function subtract(int $a, int $b): int
{
return $a - $b;
}
} }
``` ```
### 2. Use the class normally ### Use the class normally
```php ```php
<?php <?php
require_once __DIR__ . '/vendor/autoload.php'; $object = new MyClass();
use Example\Calculator;
$calc = new Calculator();
// This will output:
// before
// Adding 5 + 3
// after
$result = $calc->add(5, 3); $result = $calc->add(5, 3);
/// MyAspect@before will var_dump([5, 3])
/// MyClass@add will echo "Adding 5 + 3\n"
/// MyAspect@after will var_dump(8)
``` ```
## Running the Demo ## Proxy Generation Strategy
```bash
php example/demo.php
```
Expected output:
```
=== PHP AOP Demo ===
1. Calling add() with Aspect attribute:
before
Adding 5 + 3
after
Result: 8
2. Calling multiply() with Aspect attribute:
before
Multiplying 4 * 7
after
Result: 28
3. Calling subtract() WITHOUT Aspect attribute:
Subtracting 10 - 3
Result: 7
4. Calling protected divide() method (with Aspect) via public wrapper:
before
Dividing 20 / 4
after
Result: 5
=== Demo Complete ===
```
## Architecture
### Components
- **`Aspect.php`**: The attribute class that marks methods for aspect weaving
- **`AspectWeaver.php`**: Parses source files and generates proxy classes with aspect behavior
- **`AspectLoader.php`**: Hooks into Composer's autoloader to intercept class loading
- **`bootstrap.php`**: Initializes the AspectLoader when the autoloader is included
### Proxy Generation Strategy
The weaver uses a two-class approach: The weaver uses a two-class approach:
1. **Original Class (renamed)**: The original class is included in the proxy file with a modified name (e.g., `__AopOriginal_Calculator`) 1. **Original Class (renamed)**: The original class is included in the proxy file with a modified name (e.g., `__AopOriginal_Calculator`)
2. **Proxy Class**: A new class with the original name that extends the renamed original class and overrides methods marked with `#[Aspect]` 2. **Proxy Class**: A new class with the original name that extends the renamed original class and overrides methods marked with `#[Aspect]`
This approach ensures: ## TODO
- No modification of source files
- Full type safety and IDE support
- Debuggability
- No runtime performance overhead (after initial proxy generation)
## Limitations (POC) Features that may or may not exist in the future
This is a proof of concept with the following limitations: 1. **Around Advice**: Allow aspects to control method execution
2. Demonstrate exception handling
- Only supports `before` and `after` behavior (no around, exception handling, etc.) 3. Demonstrate Aspect constructor arguments
- Simple regex-based parsing (not a full PHP parser)
- Cache clearing requires manual deletion of `/tmp/php-aop-cache`
- No support for static methods with static variables
- No support for complex method signatures (references, variadic parameters may have issues)
## Extending
To extend this POC to support more advanced AOP features:
1. **Custom Aspects**: Create different attribute classes with parameters
2. **Joinpoint Information**: Pass method name, arguments, and execution context to aspect code
3. **Around Advice**: Allow aspects to control method execution
4. **Exception Handling**: Add support for after-throwing and after-returning advice
5. **Performance**: Use a proper PHP parser (like nikic/php-parser) for more robust code analysis
## References ## References

View file

@ -1,19 +0,0 @@
<?php
namespace IceFox\Aspect;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Aspect
{
public function before(mixed ...$args): void
{
echo "before\n";
}
public function after(mixed $result): void
{
echo "after\n";
}
}