Repository navigation
Go! AOP 4.0 is the biggest release in the framework's recent history. The weaving engine, the code generator, the caches and the container have all been rewritten for PHP 8.4+. Aspects can now reach code that was out of bounds before: private methods, properties through native property hooks, and enums. In production, woven classes load like any other class: Composer serves them from its class map, OPcache caches them, and a request makes zero source-file checks.
✨ Highlights
A new trait-based weaving engine
The original class body becomes a trait (FooOriginalTrait), and the proxy class uses it instead of extending a renamed copy. Woven classes keep their real inheritance chain, self:: resolves naturally, and the proxy calls original bodies through first-class callables ($this->doSomethingOriginalAlias(...)) without Closure::bind. Proxies are now built by the framework's own AST-based generator instead of laminas-code.
Intercept what was impossible before
- Private methods, both instance and static. The old engine could not intercept them because PHP does not allow overriding a private method.
- Properties via PHP 8.4 property hooks.
access(...)pointcuts intercept reads and writes of public, protected and private properties, including constructor-promoted and trait properties. Asymmetric visibility (private(set)) and attributes are preserved, and arrays are passed by reference so indirect modification still works. - Enums, pure and backed: instance and static methods can be advised.
readonlyclasses with properties can now be woven.
First-class callable advices
Generated code wires advices as first-class callables, which is clear to read and fast to run:
Interceptor::before(The::aspect(MonitorAspect::class)->beforeMethodExecution(...))Compiled advisor caches use a lazy static form (Interceptor::before(MonitorAspect::class, 'beforeMethodExecution')). Including a cache file creates no aspects and no closures. Weaving never creates aspects: an aspect is constructed the first time one of its advices runs.
Designed for production performance
- Composer-native class map.
_include.cachemaps woven classes to their cached files and is handed toClassLoader::addClassMap(). Untransformed classes are served untouched, and known includes skipphp://filter. - A lazy pipeline. The parser, the stream filter and the transformers are only built on a cache miss, never on a warm request. Container services are real factories, and joinpoints create reflection only when an advice asks for it.
- Advisor cache compiled to plain PHP, cached by OPcache, instead of
serialize()blobs. PREBUILT_CACHEtrusts the cache completely: no freshness probes at runtime. A cache miss fails fast with a clear message instead of writing into a read-only release.- Debug mode got faster too: warm-cache requests skip parsing (27–40 % faster), and OPcache caches untransformed files.
Tooling for deployments and containers
cache:warmup:aop --workers=N: parallel weaving with a progress bar, bounded memory (--cached-files) and--fail-fast. It returns a failure exit code if any file fails, so a broken cache is never shipped.cache:warmup:aop --cache-dir=DIR: builds the next release's cache next to the running one, then you swap it in.cache:warmup:aop --preload: writes anopcache.preloadscript for the whole AOP cache.- New
cache:warmup:opcache: primesopcache.file_cacheat image build time and checks your OPcache settings. - Commands find the running kernel themselves, so framework integrations can register them in their own console unchanged.
- Every cache file is written atomically, so a concurrent request never includes a half-written proxy.
See the new performance guide for the production checklist, OPcache tuning, and Docker/Kubernetes recipes.
Plays well with your application
- PSR-11 container.
AspectContaineris aContainerInterface, andsetDelegate()lets aspects pull their dependencies from your application's container. - Aspects are ordinary services: register them with
addLazyService()oradd(). - Rewritten Doctrine bridge (ORM 3.6+):
WovenEntityClassLocatormakes Doctrine map woven entities. - Static analysis built in. PhpStan: level 10. Joinpoint interfaces carry
@templategenerics, so PHPStan and IDEs know the types ofgetThis()andproceed()inside advices. - Attributes are copied to proxies, so runtime attribute inspection of a woven class returns the original attributes.
Safer by default
- An aspect whose own creation triggers its advices is reported with the full call path (
AspectCircularReferenceException), instead of running on a half-built object. #[\SensitiveParameter]values no longer appear in framework stack frames.- Clear
WeavingExceptions with hints for classes whose ancestors Composer cannot locate. Opt in toFeatures::PARTIAL_REFLECTIONto weave such classes anyway. - Every exception implements
Go\Aop\Exception\ExceptionInterfaceand belongs to a group by phase: configuration, aspect definition, weaving or runtime. - Pointcut name patterns now follow the documented grammar exactly, and regex errors raise
PointcutSyntaxExceptioninstead of silently matching nothing.
PHP 8.4 and 8.5
Tested on PHP 8.4 and 8.5, including PHP 8.5 first-class callable default values ($cb = strlen(...)). Known limitations are documented in docs/php84-limitations.md and docs/php85-limitations.md.
⚠️ Breaking changes — the essentials
- PHP 8.4+ is required, with updated dependencies (see
composer.json);psr/containeris new andlaminas/laminas-codeis no longer used. - Advice methods must be
public. registerAspect()is removed. Use$container->addLazyService(MyAspect::class, static fn() => new MyAspect()).AspectContainer::getValue()is nowget()(PSR-11).- Pointcuts: the
?wildcard is removed (use*; a leading?now means nullable),dynamic(...)pointcuts andDeclareErrorare removed, and modifier and|semantics are stricter. - Features flags removed:
INTERCEPT_FUNCTIONSandINTERCEPT_INITIALIZATIONSturn on automatically from your pointcuts, andPARAMETER_WIDENINGis gone. - Every cache format changed. Delete the cache directory and run
bin/aspect cache:warmup:aop. AdviceBefore/AdviceAfter/AdviceAroundare replaced byAdviceTypeEnum. Many framework classes are nowfinalor@internal.
The full list, with before/after examples, is in UPGRADE-4.0.md and CHANGELOG.md.
Upgrading from 3.x? Start with the UPGRADE-4.0 guide. Most applications only need its six-step quick checklist.
🧪 Quality
PHPUnit 13 in strict mode, PHPStan 2 at level 10 over src/ and tests/, PER-CS coding style, and a transformation performance benchmark run in CI on every pull request that touches the weaving pipeline.
🙏 Thanks
Thank you to everyone who reported issues, tested development builds and kept using Go! AOP over the years. Your feedback shaped this release. Enjoy 4.0!
Full diff: 3.1.1...4.0.0