-
-
Notifications
You must be signed in to change notification settings - Fork 163
/
Copy pathAbstractInterceptor.php
153 lines (135 loc) · 5.01 KB
/
AbstractInterceptor.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php
declare(strict_types=1);
/*
* Go! AOP framework
*
* @copyright Copyright 2011, Lisachenko Alexander <[email protected]>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/
namespace Go\Aop\Framework;
use Closure;
use Go\Aop\AspectException;
use Go\Aop\Intercept\Interceptor;
use Go\Aop\OrderedAdvice;
use Go\Core\AspectKernel;
use ReflectionFunction;
use ReflectionMethod;
/**
* Base class for all framework interceptor implementations
*
* This class describe an action taken by the interceptor at a particular joinpoint.
* Different types of interceptors include "around", "before" and "after" advices.
*
* Around interceptor is an advice that surrounds a joinpoint such as a method invocation. This is the most powerful
* kind of advice. Around advices will perform custom behavior before and after the method invocation. They are
* responsible for choosing whether to proceed to the joinpoint or to shortcut executing by returning their own return
* value or throwing an exception.
*
* After and before interceptors are simple closures that will be invoked after and before main invocation.
*
* Framework models an interceptor as an PHP {@see Closure}, maintaining a chain of interceptors "around" the joinpoint:
* <pre>
* public function (Joinpoint $joinPoint)
* {
* echo 'Before action';
* // call chain here with Joinpoint->proceed() method
* $result = $joinPoint->proceed();
* echo 'After action';
*
* return $result;
* }
* </pre>
*/
abstract class AbstractInterceptor implements Interceptor, OrderedAdvice
{
/**
* @var (array&array<string, Closure>) Local hashmap of advices for faster unserialization
*/
private static array $localAdvicesCache = [];
/**
* Default constructor for interceptor
*/
public function __construct(
protected readonly Closure $adviceMethod,
private readonly int $adviceOrder = 0,
protected readonly string $pointcutExpression = ''
) {}
/**
* Serializes advice closure into array
*
* @return array{name: string, class?: string}
*/
public static function serializeAdvice(Closure $adviceMethod): array
{
$reflectionAdvice = new ReflectionFunction($adviceMethod);
$scopeReflectionClass = $reflectionAdvice->getClosureScopeClass();
$packedAdvice = ['name' => $reflectionAdvice->name];
if (!isset($scopeReflectionClass)) {
throw new AspectException('Could not pack an interceptor without aspect name');
}
$packedAdvice['class'] = $scopeReflectionClass->name;
return $packedAdvice;
}
/**
* Unserialize an advice
*
* @param array{name: string, class?: string} $adviceData Information about advice
*/
public static function unserializeAdvice(array $adviceData): Closure
{
// General unpacking supports only aspect's advices
if (!isset($adviceData['class'])) {
throw new AspectException('Could not unpack an interceptor without aspect name');
}
$aspectName = $adviceData['class'];
$methodName = $adviceData['name'];
// With aspect name and method name, we can restore back a closure for it
if (!isset(self::$localAdvicesCache["$aspectName->$methodName"])) {
$aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName);
$advice = (new ReflectionMethod($aspectName, $methodName))->getClosure($aspect);
assert(isset($advice), 'getClosure() can not be null on modern PHP versions');
self::$localAdvicesCache["$aspectName->$methodName"] = $advice;
}
return self::$localAdvicesCache["$aspectName->$methodName"];
}
public function getAdviceOrder(): int
{
return $this->adviceOrder;
}
/**
* Getter for extracting the advice closure from Interceptor
*
* @internal
*/
public function getRawAdvice(): Closure
{
return $this->adviceMethod;
}
/**
* Serializes an interceptor into it's array shape representation
*
* @return non-empty-array<string, mixed>
*/
final public function __serialize(): array
{
// Compressing state representation to avoid default values, eg pointcutExpression = '' or adviceOrder = 0
$state = array_filter(get_object_vars($this));
// Override closure with array representation to enable serialization
$state['adviceMethod'] = static::serializeAdvice($this->adviceMethod);
return $state;
}
/**
* Un-serializes an interceptor from it's stored state
*
* @param array{adviceMethod: array{name: string, class?: string}} $state The stored representation of the interceptor.
*/
final public function __unserialize(array $state): void
{
$state['adviceMethod'] = static::unserializeAdvice($state['adviceMethod']);
foreach ($state as $key => $value) {
$this->$key = $value;
}
}
}