Boost PHP Performance with Webman AOP Plugin – Runtime Proxy, No Files
This article introduces a high‑performance, high‑availability AOP plugin for the Webman framework that works on PHP 8, explains its features, installation, configuration, usage examples, custom aspect creation methods, notification types, execution order, JoinPoint API, and provides complete code samples for caching and transaction aspects.
Overview
This high‑performance, high‑availability AOP (Aspect‑Oriented Programming) plugin is built for the Webman framework, runs on PHP 8, uses runtime proxy without generating files, and is released under the MIT license.
Features
Standard AOP types : supports Before, After, Around, AfterReturning, AfterThrowing.
Runtime proxy : high‑performance dynamic proxy implementation, no file generation.
High availability : comprehensive error handling and logging.
Webman compliance : uses Bootstrap interface and middleware mechanism.
PHP 8 attributes : native attribute syntax for marking aspects.
Priority support : multiple aspects execute according to priority.
Class‑level and method‑level aspects : both levels are supported.
Abstract base class : provides AbstractAspect to simplify aspect development.
Installation
composer require x2nx/webman-aopConfiguration
Edit config/plugin/x2nx/webman-aop/app.php:
<?php
return [
// Enable AOP
'enable' => true,
// Directories to scan
'scan_dirs' => [
app_path(),
],
// Directories to exclude
'exclude_dirs' => [
'vendor',
'runtime',
'config',
'public',
],
];Usage
Method‑level aspect
<?php
namespace app\controller\v1;
use X2nx\WebmanAop\Attribute\Aspect;
use X2nx\WebmanAop\Aspect\LogAspect;
class IndexController {
#[Aspect(LogAspect::class)]
public function index() {
// method logic
}
}Class‑level aspect
<?php
namespace app\controller\v1;
use X2nx\WebmanAop\Attribute\Aspect;
use X2nx\WebmanAop\Aspect\LogAspect;
#[Aspect(LogAspect::class)]
class IndexController {
public function index() {}
public function detail() {}
}Multiple aspects with priority
<?php
use X2nx\WebmanAop\Attribute\Aspect;
use app\aspect\CacheAspect;
#[Aspect(LogAspect::class, [], 100)] // priority 100, executed later
#[Aspect(CacheAspect::class, [], 50)] // priority 50, executed earlier
class IndexController {
// CacheAspect runs before LogAspect
}Creating Custom Aspects
Option 1 – Implement AspectInterface
<?php
namespace app\aspect;
use X2nx\WebmanAop\Contract\AspectInterface;
use X2nx\WebmanAop\JoinPoint;
class CacheAspect implements AspectInterface {
public function before(JoinPoint $joinPoint): void { /* ... */ }
public function after(JoinPoint $joinPoint): void { /* ... */ }
public function around(JoinPoint $joinPoint, callable $proceed): mixed { /* ... */ }
public function afterReturning(JoinPoint $joinPoint, mixed $result): void { /* ... */ }
public function afterThrowing(JoinPoint $joinPoint, \Throwable $exception): void { /* ... */ }
}Option 2 – Extend AbstractAspect (recommended)
<?php
namespace app\aspect;
use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Cache;
class CacheAspect extends AbstractAspect {
public function before(JoinPoint $joinPoint): void {
// check cache
$cacheKey = $this->getCacheKey($joinPoint);
if ($cached = Cache::get($cacheKey)) {
$joinPoint->setReturnValue($cached);
$joinPoint->setData('from_cache', true);
}
}
public function around(JoinPoint $joinPoint, callable $proceed): mixed {
if ($joinPoint->getData('from_cache')) {
return $joinPoint->getReturnValue();
}
$result = $proceed();
$cacheKey = $this->getCacheKey($joinPoint);
Cache::set($cacheKey, $result, 3600);
return $result;
}
protected function getCacheKey(JoinPoint $joinPoint): string {
$method = $joinPoint->getFullMethodName();
$args = $joinPoint->getArgs();
return 'aop_cache:' . md5($method . serialize($args));
}
}AOP Notification Types
Before
Executed before the target method; typical uses include logging, parameter validation, permission checks, and cache checks.
public function before(JoinPoint $joinPoint): void {
Log::info('Before method execution');
}After
Executed after the target method regardless of outcome; typical uses include resource cleanup, timing, and lock release.
public function after(JoinPoint $joinPoint): void {
Log::info('After method execution');
}Around
Wraps the target method, allowing full control over execution, parameter modification, return value alteration, or even skipping the method.
public function around(JoinPoint $joinPoint, callable $proceed): mixed {
$this->beforeLogic();
$result = $proceed();
return $this->afterLogic($result);
}AfterReturning
Runs only when the method returns normally; useful for logging return values, processing results, or sending notifications.
public function afterReturning(JoinPoint $joinPoint, mixed $result): void {
Log::info('Method returned', ['result' => $result]);
}AfterThrowing
Runs only when the method throws an exception; useful for error logging, handling, or alerting.
public function afterThrowing(JoinPoint $joinPoint, \Throwable $exception): void {
Log::error('Method threw exception', [
'exception' => get_class($exception),
'message' => $exception->getMessage(),
]);
}Notification Execution Order
All aspects' Before notifications (ordered by priority).
All aspects' Around notifications (ordered by priority, forming a chain).
The target method execution.
All aspects' AfterReturning notifications (if the method returned normally).
All aspects' AfterThrowing notifications (if an exception was thrown).
All aspects' After notifications (always executed).
JoinPoint API
// Get the target object
$target = $joinPoint->getTarget();
// Class and method names
$className = $joinPoint->getClassName();
$methodName = $joinPoint->getMethodName();
$fullMethodName = $joinPoint->getFullMethodName();
// Arguments
$args = $joinPoint->getArgs();
$joinPoint->setArgs($newArgs);
$arg = $joinPoint->getArg(0);
$joinPoint->setArg(0, $value);
// Execute original method
$result = $joinPoint->proceed();
// Return value and exception
$returnValue = $joinPoint->getReturnValue();
$exception = $joinPoint->getException();
// Data storage for communication between aspects
$joinPoint->setData('key', $value);
$value = $joinPoint->getData('key', $default);
$allData = $joinPoint->getAllData();Full Examples
Cache Aspect
<?php
namespace app\aspect;
use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Cache;
class CacheAspect extends AbstractAspect {
public function before(JoinPoint $joinPoint): void {
$cacheKey = $this->getCacheKey($joinPoint);
if ($cached = Cache::get($cacheKey)) {
$joinPoint->setReturnValue($cached);
$joinPoint->setData('from_cache', true);
}
}
public function around(JoinPoint $joinPoint, callable $proceed): mixed {
if ($joinPoint->getData('from_cache')) {
return $joinPoint->getReturnValue();
}
$result = $proceed();
$cacheKey = $this->getCacheKey($joinPoint);
Cache::set($cacheKey, $result, 3600);
return $result;
}
protected function getCacheKey(JoinPoint $joinPoint): string {
return 'aop_cache:' . md5($joinPoint->getFullMethodName() . serialize($joinPoint->getArgs()));
}
}Transaction Aspect
<?php
namespace app\aspect;
use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Db;
class TransactionAspect extends AbstractAspect {
public function around(JoinPoint $joinPoint, callable $proceed): mixed {
Db::beginTransaction();
try {
$result = $proceed();
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollBack();
throw $e;
}
}
}Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
