AspectContext

This commit is contained in:
icefox 2026-01-06 11:41:55 -03:00
parent 6050e3bb72
commit d35c3df06d
No known key found for this signature in database
8 changed files with 70 additions and 19 deletions

13
src/AspectContext.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace IceFox\Aspect;
readonly class AspectContext
{
public function __construct(
public string $class,
public string $function,
public ?object $self,
) {
}
}

View file

@ -243,6 +243,14 @@ class AspectWeaver
$code = " {$visibility} {$static}function {$name}({$params}){$returnType}\n"; $code = " {$visibility} {$static}function {$name}({$params}){$returnType}\n";
$code .= " {\n"; $code .= " {\n";
// Create AspectContext for all aspects
$selfArg = $isStatic ? 'null' : '$this';
$code .= " \$context = new \\IceFox\\Aspect\\AspectContext(\n";
$code .= " class: '{$className}',\n";
$code .= " function: '{$name}',\n";
$code .= " self: {$selfArg}\n";
$code .= " );\n\n";
// Instantiate all aspects using their constructor arguments from attributes // Instantiate all aspects using their constructor arguments from attributes
foreach ($aspects as $index => $aspectMetadata) { foreach ($aspects as $index => $aspectMetadata) {
$aspectClass = $aspectMetadata->aspectClass; $aspectClass = $aspectMetadata->aspectClass;
@ -252,12 +260,11 @@ class AspectWeaver
$args = $attribute->getArguments(); $args = $attribute->getArguments();
$aspectConstructorArguments = $this->buildConstructorArguments($args); $aspectConstructorArguments = $this->buildConstructorArguments($args);
$code .= " \$aspect{$index} = new \\{$aspectClass}({$aspectConstructorArguments});\n"; // Pass context as first parameter before other constructor arguments
$constructorParams = $aspectConstructorArguments ? "\$context, {$aspectConstructorArguments}" : "\$context";
$code .= " \$aspect{$index} = new \\{$aspectClass}({$constructorParams});\n";
} }
// Prepare target argument for aspect methods
$targetArg = $isStatic ? "static::class" : "\$this";
// Call all before methods in a single try-catch // Call all before methods in a single try-catch
$code .= " \$currentAspect = '';\n"; $code .= " \$currentAspect = '';\n";
$code .= " try {\n"; $code .= " try {\n";
@ -266,8 +273,7 @@ class AspectWeaver
$aspectReflection = new ReflectionClass($aspectClass); $aspectReflection = new ReflectionClass($aspectClass);
if ($aspectReflection->hasMethod('before')) { if ($aspectReflection->hasMethod('before')) {
$code .= " \$currentAspect = \\{$aspectClass}::class;\n"; $code .= " \$currentAspect = \\{$aspectClass}::class;\n";
$beforeParams = $paramNames ? "{$targetArg}, {$paramNames}" : $targetArg; $code .= " \$aspect{$index}->before({$paramNames});\n";
$code .= " \$aspect{$index}->before({$beforeParams});\n";
} }
} }
$code .= " } catch (\\Throwable \$e) {\n"; $code .= " } catch (\\Throwable \$e) {\n";
@ -291,7 +297,7 @@ class AspectWeaver
$aspectReflection = new ReflectionClass($aspectClass); $aspectReflection = new ReflectionClass($aspectClass);
if ($aspectReflection->hasMethod('after')) { if ($aspectReflection->hasMethod('after')) {
$code .= " \$currentAspect = \\{$aspectClass}::class;\n"; $code .= " \$currentAspect = \\{$aspectClass}::class;\n";
$code .= " \$result = \$aspect{$i}->after({$targetArg}, \$result);\n"; $code .= " \$result = \$aspect{$i}->after(\$result);\n";
} }
} }
$code .= " } catch (\\Throwable \$e) {\n"; $code .= " } catch (\\Throwable \$e) {\n";
@ -315,7 +321,7 @@ class AspectWeaver
$aspectReflection = new ReflectionClass($aspectClass); $aspectReflection = new ReflectionClass($aspectClass);
if ($aspectReflection->hasMethod('after')) { if ($aspectReflection->hasMethod('after')) {
$code .= " \$currentAspect = \\{$aspectClass}::class;\n"; $code .= " \$currentAspect = \\{$aspectClass}::class;\n";
$code .= " \$aspect{$i}->after({$targetArg}, null);\n"; $code .= " \$aspect{$i}->after(null);\n";
} }
} }
$code .= " } catch (\\Throwable \$e) {\n"; $code .= " } catch (\\Throwable \$e) {\n";

View file

@ -3,19 +3,25 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
class BasicAspect class BasicAspect
{ {
public ?object $object = null; public ?object $object = null;
public function before(object|string $target, mixed ...$args): void public function __construct(
private readonly AspectContext $context,
) {
}
public function before(mixed ...$args): void
{ {
$this->object = end($args); $this->object = end($args);
$this->object->before = true; $this->object->before = true;
} }
public function after(object|string $target, mixed $return): mixed public function after(mixed $return): mixed
{ {
$this->object->after = true; $this->object->after = true;
return $return; return $return;

View file

@ -3,6 +3,7 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
class ConfigurableAspect class ConfigurableAspect
@ -10,13 +11,14 @@ class ConfigurableAspect
public static array $executionLog = []; public static array $executionLog = [];
public function __construct( public function __construct(
private readonly AspectContext $context,
public readonly string $prefix = 'default', public readonly string $prefix = 'default',
public readonly int $multiplier = 1, public readonly int $multiplier = 1,
public readonly bool $enabled = true, public readonly bool $enabled = true,
) { ) {
} }
public function before(object|string $target, mixed ...$args): void public function before(mixed ...$args): void
{ {
if ($this->enabled) { if ($this->enabled) {
self::$executionLog[] = [ self::$executionLog[] = [
@ -28,7 +30,7 @@ class ConfigurableAspect
} }
} }
public function after(object|string $target, mixed $result): mixed public function after(mixed $result): mixed
{ {
if (!$this->enabled) { if (!$this->enabled) {
return $result; return $result;

View file

@ -3,18 +3,24 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
class LoggingAspect class LoggingAspect
{ {
public static array $logs = []; public static array $logs = [];
public function before(object|string $target, mixed ...$args): void public function __construct(
private readonly AspectContext $context,
) {
}
public function before(mixed ...$args): void
{ {
self::$logs[] = ['type' => 'logging_before', 'args' => $args]; self::$logs[] = ['type' => 'logging_before', 'args' => $args];
} }
public function after(object|string $target, mixed $result): mixed public function after(mixed $result): mixed
{ {
self::$logs[] = ['type' => 'logging_after', 'result' => $result]; self::$logs[] = ['type' => 'logging_after', 'result' => $result];
return $result; return $result;

View file

@ -3,13 +3,19 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
class ModifyingAspect class ModifyingAspect
{ {
public static mixed $modifier = null; public static mixed $modifier = null;
public function after(object|string $target, mixed $result): mixed public function __construct(
private readonly AspectContext $context,
) {
}
public function after(mixed $result): mixed
{ {
if (self::$modifier !== null) { if (self::$modifier !== null) {
return (self::$modifier)($result); return (self::$modifier)($result);

View file

@ -3,6 +3,7 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
use RuntimeException; use RuntimeException;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
@ -11,14 +12,19 @@ class ThrowingAspect
public static bool $throwInBefore = false; public static bool $throwInBefore = false;
public static bool $throwInAfter = false; public static bool $throwInAfter = false;
public function before(object|string $target, mixed ...$args): void public function __construct(
private readonly AspectContext $context,
) {
}
public function before(mixed ...$args): void
{ {
if (self::$throwInBefore) { if (self::$throwInBefore) {
throw new RuntimeException('Exception thrown in before()'); throw new RuntimeException('Exception thrown in before()');
} }
} }
public function after(object|string $target, mixed $result): mixed public function after(mixed $result): mixed
{ {
if (self::$throwInAfter) { if (self::$throwInAfter) {
throw new RuntimeException('Exception thrown in after()'); throw new RuntimeException('Exception thrown in after()');

View file

@ -3,18 +3,24 @@
namespace Tests\Aspects; namespace Tests\Aspects;
use Attribute; use Attribute;
use IceFox\Aspect\AspectContext;
#[Attribute(Attribute::TARGET_METHOD)] #[Attribute(Attribute::TARGET_METHOD)]
class TrackingAspect class TrackingAspect
{ {
public static array $calls = []; public static array $calls = [];
public function before(object|string $target, mixed ...$args): void public function __construct(
private readonly AspectContext $context,
) {
}
public function before(mixed ...$args): void
{ {
self::$calls[] = ['event' => 'before', 'args' => $args]; self::$calls[] = ['event' => 'before', 'args' => $args];
} }
public function after(object|string $target, mixed $result): mixed public function after(mixed $result): mixed
{ {
self::$calls[] = ['event' => 'after', 'result' => $result]; self::$calls[] = ['event' => 'after', 'result' => $result];
return $result; return $result;