floriankraemer / php-aop-extension
Native aspect-oriented programming for PHP: a Zend engine extension that weaves Spring/AspectJ-style aspects into private, protected, static and final methods, constructors and functions without proxies.
Package info
github.com/floriankraemer/php-aop-extension
Type:php-ext
Ext name:ext-aop
pkg:composer/floriankraemer/php-aop-extension
Requires
- php: >=8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.35.8
- phpbench/phpbench: ^1.7
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.0
Suggests
- ext-aop: The engine part; without it aspects are read but nothing is woven.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-29 10:19:26 UTC
README
Aspect-oriented programming for PHP, woven inside the Zend engine. It brings Spring's and AspectJ's model to PHP: aspects, pointcuts, advice, the Spring AOP API and AspectJ's join points. There are no proxies and no generated classes. The engine decides at call time whether a call is advised.
Because nothing is proxied, the things a proxy cannot reach work too: private, protected, static and final methods, final classes, self-invocation, constructors and plain functions.
The package floriankraemer/php-aop-extension has two parts.
The C extension aop does the weaving.
The PHP package in src/, namespace AOP\, holds the model, the pointcut language and the Spring API.
Highlights
- Advice on private, protected, static and final methods, and on final classes.
- Self-invocation is advised: a method calling its own private method goes through the advice.
- Constructors, with
initialization()for the outermost one (needsaop.interception=hook). - Plain functions and internal functions, such as
str_repeat(). call()andexecution(),withincode()andwithin().cflow()andcflowbelow()for control flow.if()for a runtime condition.- The five advice types, binding,
Ordered,#[Order]and#[DeclarePrecedence]. #[DeclareWarning]and#[DeclareError].- Instantiation models: singleton,
perthis,pertarget,percflow,percflowbelowandpertypewithin. - The Spring AOP API: programmatic pointcuts, advice interfaces, advisors, custom advice types through an adapter registry, and target sources (hot-swappable, pooling, prototype and fiber-local).
- Aspects without attributes: a configuration array in the shape of Spring's XML schema.
#[Configurable]for objects created withnew.- Per-object advice with
Weaver::advised(). - Exception handlers with
handler(). - Field access with
get()andset(), and constructor-lessinitialization(); both needaop.interception=hook, which turns the JIT off. - Introductions with
#[DeclareParents]: a class gets an interface and a default implementation, at compile time.
Documentation
- Getting started: requirements, install and a quick start.
- php.ini settings: the four INI settings.
- Docs index
- Concepts
- Pointcut expressions
- Advice
- Use cases
- Weaver
- Spring API
- Target sources
- Configuration
- Spring and AspectJ mapping
- Limitations
- Building, testing and releasing
Use cases
Realistic examples, each one a runnable script in examples/.
- Audit and logging: a service-boundary audit that masks sensitive parameters.
- Transactions: a
#[Transactional]attribute where nested calls join the outer transaction. - Caching: cache and evict by attribute, on a private method of a final class.
- Authorization: role checks from a class-level or method-level attribute.
- Retry and circuit breaker: two ordered around advice around a flaky gateway.
- Tracing and metrics: a span tree and call counters, including an internal function.
- Legacy code and architecture rules: a feature flag on a final vendor class, a test seam, and layering rules.
- The combined order service: authorization, audit and a transaction on one service, nested by
#[Order].
The page Use cases explains the pointcut and the advice type of each one.
Compared with proxy-based AOP
The table compares this extension with phauthentic/aop, which weaves with generated proxy classes. The proxy column describes what that library documents about its proxies.
| Topic | phauthentic/aop (proxies) | This extension (engine) |
|---|---|---|
| How advice is attached | A generated subclass or interface proxy is used in place of the object. | The engine intercepts the call. There are no generated classes. |
| Private methods | Not advised. A subclass cannot override them. | Advised. |
| Protected methods | Advised with the subclass strategy, never with the interface strategy. | Advised. |
| Static methods | Not advised. | Advised. |
| Final methods | Skipped by the subclass strategy. | Advised. |
| Final classes | Need the interface strategy, so the proxy is not an instance of the class. | Advised. The object stays an instance of its class. |
Self-invocation ($this->method()) |
Advised with the subclass strategy, not with the interface strategy. | Advised. |
| Constructors | Never advised. | Advised, with initialization() for the outermost one (needs aop.interception=hook). |
| Plain functions | Not supported. | Advised. |
| Internal functions and methods | Not supported. | Advised, with the limits described in limitations. |
call() join points |
Not supported. | Supported. |
cflow(), cflowbelow(), if() |
Not supported. | Supported. |
initialization() |
Not supported. | Supported, for constructors and for classes without a constructor. Needs aop.interception=hook. |
| Requirements | PHP only. | PHP 8.3 to 8.5, and the extension. |
| Install | Composer. | Composer and the extension (PIE or a prebuilt archive). |
| Spring AOP API and aspect attributes | Supported. | Supported. |
Limitations
The full list is in docs/limitations.md. The most relevant points:
- The overhead of each backend is measured in docs/benchmarks.md: about 12 ns per call on top of plain PHP for
wovenwith nothing registered, and no measurable cost for code without calls. - The default backend (
aop.interception=woven) runs with the JIT, but it cannot intercept field access (get(),set()) orinitialization(); those needaop.interception=hook, which turns the JIT off. - Under
woven, advice sees arguments after parameter coercion, and advice on a generator function runs at the first resume. - Opcache inlining of functions that only return a constant is prevented by default (
aop.disable_inlining=1). - Calls compiled to dedicated opcodes, such as
strlen()in the global namespace, are not intercepted. - Functions that inspect their caller's frame, such as
compact(), fail when advised. - Functions that return by reference, closures and arrow functions are not advised.
- The
AOP\namespace is never woven. - Class loading is not woven by default (
aop.weave_autoload=1changes that). - On PHP 8.4+, a hooked property access is one field join point around the whole hooked access; lazy objects created by
newLazyGhost()/newLazyProxy()are not covered.
Development
make help # every target make ext # build the extension make test # unit tests, then end-to-end tests with the extension make test-ext # the phpt suite in ext/tests make check # php-cs-fixer, phpstan, phpt and phpunit make mutation # Infection on the critical src files (build/infection/infection.log) make coverage-php # line coverage of src/, fails below 85% (make coverage-c: ext/*.c) make test-all # in Docker: PHP 8.3, 8.4 and 8.5, NTS and ZTS make dist # in Docker: the release archives into dist/ make bench # the benchmark matrix, see docs/benchmarks.md (make bench-docker: PHP 8.3, 8.4, 8.5)
docs/building.md describes the Docker matrix, the variables and the release process.
License
MIT. See LICENSE.