Aspect declarations, method weaving, join points, and interceptor dispatch with startup-activated class loading. Mimic supplies file proxies and Stream fallback.
- Composer package:
super-kernel/aop. - Source:
src/. - Production namespace:
SuperKernel\Aop\. - Test namespace:
SuperKernel\Aop\Tests\. - Runtime: PHP 8.4.27 or later in the 8.4.x series with Reflection, PHP-Parser
^5.9.0, and the Annotator, Attribute, Contract, Event, and Mimic components. - The application supplies
SuperKernel\Contract\AutoLoaderInterface; the Autoloader component provides its default binding.
SuperKernel\Aop\Attribute\Aspect marks a class that handles aspect behavior.
The native Attribute is placed on that handler class; its arguments describe
the classes, methods, and property hooks that the handler targets.
It is a final, readonly, non-repeatable native PHP Attribute targeting classes only. One declaration can contain multiple pointcuts in each of three public readonly arrays:
classes: a list of target class names.methods: target class names mapped to non-empty lists of method names.propertyHooks: target class names mapped to property names, each containing a non-empty list ofgetand/orsethook names. Property names omit$.
use SuperKernel\Aop\Attribute\Aspect;
use SuperKernel\Aop\Contract\AspectInterface;
use SuperKernel\Aop\Contract\ProceedingJoinPointInterface;
#[Aspect(
classes: [
App\Service\OrderService::class,
App\Service\InvoiceService::class,
],
methods: [
App\Service\UserService::class => [
'create',
'update',
],
],
)]
final class ExampleAspect implements AspectInterface
{
public function process(ProceedingJoinPointInterface $point): mixed
{
return $point->proceed();
}
}All three arguments default to empty arrays for manual self-weaving. Names, ordering, and duplicate list
entries are preserved. Class names may have a leading namespace separator;
wildcards and selector strings such as Class::method are not accepted in these
structured declarations.
Construction checks identifier syntax, list shapes, and the supported hook names,
throwing InvalidArgumentException for malformed declarations. It does not load
target classes or check whether their members exist, so metadata collection can
leave targets unloaded for later weaving.
Handlers with class or method pointcuts implement Contract\AspectInterface.
The container supplies their dependencies, and process(ProceedingJoinPointInterface): mixed
provides around advice through the Contract\ProceedingJoinPointInterface contract.
Call proceed() to invoke the original method, preserving
its result or deliberately supplying a replacement. Use try/finally for after
logic that must also run when the original method throws.
The AOP autoloader resolves matching handlers and registers their advice when a
declared target class is loaded. Parameter-free declarations select self-weaving: their declaring class is selected for weaving and callbacks may be
registered manually without implementing AspectInterface.
Property hook metadata remains supported by the Attribute, but hook bodies are not
yet woven. Loading a declared property hook target raises LogicException before
the target is declared. Unloaded targets do not prevent startup or unrelated class
loads.
SuperKernel\Aop\Collector\AspectCollector is a final, readonly service. It receives
the registered AnnotationCollectorInterface through constructor injection and
builds its declaration map from collected metadata.
getClasses() returns original Aspect instances keyed by declaring class name,
preserving discovery order and the shared collector's package ownership.
Collection does not reflect or autoload annotated target classes, instantiate their annotations again, or run another scan. The query returns an array by value. Resolve this service through DI when aspect declarations are needed.
isTargetClass() checks the collected class, method, and property hook targets
without loading them. It ignores class-name case and a leading namespace separator.
An explicit handler is not itself a target unless one of its pointcuts selects it.
SuperKernel\Aop\Listener\BeforeApplicationStartListener implements the shared
ListenerInterface, subscribes exclusively to
SuperKernel\Event\BeforeApplicationStart, and declares
#[Listener(PHP_INT_MAX)].
The existing event dispatcher discovers the listener and places it in the highest
numeric priority group, before lower-priority listeners. Equal priorities retain
discovery order, including other listeners using PHP_INT_MAX.
The listener receives AspectCollector and the shared PSR-11 container. During
process(), an empty aspect map returns immediately. The AOP loader and its
runtime dependencies are resolved only after this check, so an empty collection
does not activate the shared loader or initialize Mimic.
For a non-empty collection, the listener resolves SuperKernel\Aop\AutoLoader
and registers it at the front of the SPL stack. Repeated startup dispatch reuses
the container's instance and does not add duplicate callbacks. Resolving the
default shared loader first performs the Autoloader component's documented SPL
takeover; AOP then prepends its own callback and preserves the resulting stack.
The listener does not resolve handlers, validate their contracts, or register interceptors. Repeated startup only ensures loader registration and leaves existing advice callbacks unchanged.
AutoLoader receives AspectCollector, AutoLoaderInterface, MimicFactory,
the shared ContainerInterface, and Runtime\SourceMetadata through constructor
injection. Direct callers supply the container as the fourth argument and source
metadata as the fifth. For each SPL class request it:
- Checks the collected target map without reflecting or loading the target.
- Returns for an already loaded symbol or a class absent from the declared pointcuts and self-weaving markers.
- Reads the selected class's pending path from
AutoLoaderInterface::getClassMap(). - Returns if the path is absent.
- Asks Mimic to prepare a cached proxy or a fallback
mimic://URI. - Resolves the original file through
SourceMetadataand the injected package manager, binding file and directory constants before proxy evaluation. - Finds declarations that select this target, rejects its unsupported property
hook pointcuts, and resolves matching class or method handlers through the
injected container. Each handler must implement
AspectInterface. - Executes the prepared source and verifies that the requested symbol was declared.
- Registers matching class and method selectors with
Runtime\Dispatcherusing each handler'sprocess()callable, before returning to the caller.
Unrelated declarations do not resolve their handlers or register their selectors. If several handlers select the same case-insensitive selector, the last one in discovery order replaces earlier registrations. Repeated requests for an already loaded target do not replace its callbacks.
Handler resolution happens before declaring the target, so a resolution or validation failure leaves it unloaded and releases the loader's recursion guard for a retry. A handler constructor must not request the target currently being loaded, directly or through another dependency. Such a dependency is circular. Advice is published only after the prepared source successfully declares the requested target.
AOP owns the actual replacement load, including recursion checks and the SPL callback. Mimic prepares source using the AOP mutator supplied through its generic contract.
Mutation\Mutator declares #[Provider(MutatorInterface::class)], binding
SuperKernel\Mimic\Contract\MutatorInterface directly to its shared instance.
The container constructs MimicFactory
from injected FileDriver and StreamDriver services. The file driver also receives
PackageManagerInterface for class names. It writes
<VendorDirInterface>/.cache/mimic/Src.Service.TestService.tpl,
replacing namespace separators with dots and using no hash subdirectory.
A first-line SHA-256 fingerprint validates the original path, source content,
and transformation key, so changes regenerate the same class-named proxy.
Mimic's Source\ProxyFile owns proxy paths and header reads and writes. AOP loads
the returned address directly, without reconstructing filenames or suffixes.
Publication uses temporary files and atomic rename without .lock sidecars.
Cache creation or replacement failures fall back to Stream. Mimic registers no
SPL callback, and construction initializes neither cache files nor the protocol.
AOP's Mutation\Mutator and Mutation\NodeVisitor use PHP-Parser to weave
Runtime\Dispatcher and Runtime\ProceedingJoinPoint into concrete methods.
Register callbacks through the AOP runtime before invoking selected methods:
use App\Service;
use SuperKernel\Aop\Runtime\Dispatcher;
use SuperKernel\Aop\Runtime\ProceedingJoinPoint;
Dispatcher::intercept([
Service::class,
'calculate',
], static function (ProceedingJoinPoint $point): mixed {
return $point->proceed();
});Dispatcher::intercept() accepts a class name, Class::method, or [Class, method].
Selectors are case-insensitive. Method selectors override class selectors, and
re-registering a selector replaces its callback. Matching uses the runtime class,
including the called class for static methods, without ancestor or interface
expansion. Without a callback, dispatch proceeds into the original body.
Runtime\ProceedingJoinPoint implements Contract\ProceedingJoinPointInterface.
Aspect implementations accept the interface, allowing advice to work with other
implementations of the same call contract. Existing AspectInterface implementers
must update their process() parameter from the concrete class to the interface.
ProceedingJoinPointInterface::getInstance() returns an object for an instance method or
a class-string for a static method. getArguments(), getClassReflection(),
getMethodReflection(), and getShortMethodName() describe the original call.
proceed() invokes its body. A callback can bypass that body by returning without
proceeding; the enclosing method still enforces its return type. Constructors,
destructors, and void methods discard callback results. PHP performs constructor
promotion before interception. A never interceptor must not return normally.
Registrations and Runtime\Tracker are process-global. Loading a selected target
installs its advice for that process; forked workers inherit callbacks already
registered in their parent. Advice discovery supports class and method pointcuts
through AspectInterface. There is no separate Pointcut Attribute.
- Use trusted, declaration-oriented files. All concrete methods in a selected
file are woven; abstract methods remain unchanged. Final modifiers and private
visibility are preserved. AOP's
Mutation\Modifieris an optional PHP-Parser visitor that relaxes final modifiers and private visibility; the default mutator does not install it. - Original parameter values and promoted default objects are preserved.
__FILE__and__DIR__refer to the original local source, including inside class constants, property initializers, and parameter defaults. Generated code references namespaced source constants instead of embedding absolute paths.Runtime\SourceMetadatareceivesPackageManagerInterfaceand binds these constants from the original class map before loading. Direct Mimic integrations using the AOP mutator must also callSourceMetadata::register($class)on their injected instance before executing the prepared source. Constant bindings remain process-local. - Reference returns, reference or variadic parameters, hooked promoted
parameters, generators, static locals, and
__FUNCTION__,__METHOD__, or__LINE__in method bodies are rejected before loading. This conservative check includes nested closures. - Exceptions from transformed bodies retain their object and receive their original file and last entered top-level statement line. Nested statements may report the enclosing statement. Callback and external failures retain their own locations; nested dispatch restores the caller's diagnostic state.
- Stack frames and reflection line numbers still describe generated code. Diagnostic lines are not isolated across suspended fibers or Swoole coroutines. Normal initialization does not replace the global exception handler.
- Parser and transformation failures propagate without falling through to untransformed loading. Unmarked classes and missing map entries continue through other SPL loaders. Already loaded symbols cannot be rewritten.
AOP owns Runtime\Dispatcher, ProceedingJoinPoint, Tracker, and the Mutation classes.
Runtime\Dispatcher::intercept() registers callbacks and returns void. Mimic supplies generic preparation drivers
and MutatorInterface; dependencies run from AOP to Mimic. PHP-Parser is an AOP production dependency.
Mutator::getCacheKey() identifies generated-code semantics. Change it with weaving semantics to invalidate file and
Stream identities. Historical files remain untouched. Rebuild annotation and Phar snapshots after declaration changes,
and restart workers to load rebuilt code.
The migrated source derives from the authorized Mimic port; provenance and authorization are retained in NOTICE.
Use a PHP 8.4 Composer in this package's checkout:
composer install
composer validate --strict
composer test
composer format:check
composer formatThe component is licensed under the MIT License, as designated by wheakerd.