Search by

floriankraemer / php-aop-extension

floriankraemer

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

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-10-29 01:09 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 (needs aop.interception=hook).
  • Plain functions and internal functions, such as str_repeat().
  • call() and execution(), withincode() and within().
  • cflow() and cflowbelow() 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, percflowbelow and pertypewithin.
  • 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 with new.
  • Per-object advice with Weaver::advised().
  • Exception handlers with handler().
  • Field access with get() and set(), and constructor-less initialization(); both need aop.interception=hook, which turns the JIT off.
  • Introductions with #[DeclareParents]: a class gets an interface and a default implementation, at compile time.

Documentation

Use cases

Realistic examples, each one a runnable script in examples/.

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 woven with 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()) or initialization(); those need aop.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=1 changes 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.