13
13
namespace Go \Aop \Framework ;
14
14
15
15
use Closure ;
16
+ use Go \Aop \AspectException ;
16
17
use Go \Aop \Intercept \Interceptor ;
18
+ use Go \Aop \OrderedAdvice ;
17
19
use Go \Core \AspectKernel ;
18
20
use ReflectionFunction ;
19
21
use ReflectionMethod ;
31
33
*
32
34
* After and before interceptors are simple closures that will be invoked after and before main invocation.
33
35
*
34
- * Framework models an interceptor as an PHP-closure, maintaining a chain of interceptors "around" the joinpoint:
36
+ * Framework models an interceptor as an PHP {@see Closure}, maintaining a chain of interceptors "around" the joinpoint:
37
+ * <pre>
35
38
* public function (Joinpoint $joinPoint)
36
39
* {
37
40
* echo 'Before action';
41
44
*
42
45
* return $result;
43
46
* }
47
+ * </pre>
44
48
*/
45
49
abstract class AbstractInterceptor implements Interceptor, OrderedAdvice
46
50
{
47
51
/**
48
- * Local cache of advices for faster unserialization on big projects
49
- *
50
- * @var array<Closure>
51
- */
52
- protected static array $ localAdvicesCache = [];
53
-
54
- /**
55
- * Pointcut expression string which was used for this interceptor
56
- */
57
- protected string $ pointcutExpression ;
58
-
59
- /**
60
- * Closure to call
61
- */
62
- protected Closure $ adviceMethod ;
63
-
64
- /**
65
- * Advice order
52
+ * @var (array&array<string, Closure>) Local hashmap of advices for faster unserialization
66
53
*/
67
- private int $ adviceOrder ;
54
+ private static array $ localAdvicesCache = [] ;
68
55
69
56
/**
70
57
* Default constructor for interceptor
71
58
*/
72
- public function __construct (Closure $ adviceMethod , int $ adviceOrder = 0 , string $ pointcutExpression = '' )
73
- {
74
- $ this ->adviceMethod = $ adviceMethod ;
75
- $ this ->adviceOrder = $ adviceOrder ;
76
- $ this ->pointcutExpression = $ pointcutExpression ;
77
- }
59
+ public function __construct (
60
+ protected readonly Closure $ adviceMethod ,
61
+ private readonly int $ adviceOrder = 0 ,
62
+ protected readonly string $ pointcutExpression = ''
63
+ ) {}
78
64
79
65
/**
80
- * Serialize advice method into array
66
+ * Serializes advice closure into array
67
+ *
68
+ * @return array{name: string, class?: string}
81
69
*/
82
70
public static function serializeAdvice (Closure $ adviceMethod ): array
83
71
{
84
- $ refAdvice = new ReflectionFunction ($ adviceMethod );
72
+ $ reflectionAdvice = new ReflectionFunction ($ adviceMethod );
73
+ $ scopeReflectionClass = $ reflectionAdvice ->getClosureScopeClass ();
74
+
75
+ $ packedAdvice = ['name ' => $ reflectionAdvice ->name ];
76
+ if (!isset ($ scopeReflectionClass )) {
77
+ throw new AspectException ('Could not pack an interceptor without aspect name ' );
78
+ }
79
+ $ packedAdvice ['class ' ] = $ scopeReflectionClass ->name ;
85
80
86
- return [
87
- 'method ' => $ refAdvice ->name ,
88
- 'class ' => $ refAdvice ->getClosureScopeClass ()->name
89
- ];
81
+ return $ packedAdvice ;
90
82
}
91
83
92
84
/**
93
85
* Unserialize an advice
94
86
*
95
- * @param array $adviceData Information about advice
87
+ * @param array{name: string, class?: string} $adviceData Information about advice
96
88
*/
97
89
public static function unserializeAdvice (array $ adviceData ): Closure
98
90
{
91
+ // General unpacking supports only aspect's advices
92
+ if (!isset ($ adviceData ['class ' ])) {
93
+ throw new AspectException ('Could not unpack an interceptor without aspect name ' );
94
+ }
99
95
$ aspectName = $ adviceData ['class ' ];
100
- $ methodName = $ adviceData ['method ' ];
96
+ $ methodName = $ adviceData ['name ' ];
101
97
102
- if (! isset ( static :: $ localAdvicesCache [ " $ aspectName -> $ methodName " ])) {
103
- $ aspect = AspectKernel:: getInstance ()-> getContainer ()-> getAspect ( $ aspectName );
104
- $ refMethod = new ReflectionMethod ( $ aspectName, $ methodName );
105
- $ advice = $ refMethod ->getClosure ($ aspect );
98
+ // With aspect name and method name, we can restore back a closure for it
99
+ if (! isset ( self :: $ localAdvicesCache [ " $ aspectName -> $ methodName " ])) {
100
+ $ aspect = AspectKernel:: getInstance ()-> getContainer ()-> getAspect ( $ aspectName );
101
+ $ advice = ( new ReflectionMethod ( $ aspectName , $ methodName )) ->getClosure ($ aspect );
106
102
107
- static ::$ localAdvicesCache ["$ aspectName-> $ methodName " ] = $ advice ;
103
+ assert (isset ($ advice ), 'getClosure() can not be null on modern PHP versions ' );
104
+ self ::$ localAdvicesCache ["$ aspectName-> $ methodName " ] = $ advice ;
108
105
}
109
106
110
- return static ::$ localAdvicesCache ["$ aspectName-> $ methodName " ];
107
+ return self ::$ localAdvicesCache ["$ aspectName-> $ methodName " ];
111
108
}
112
109
113
- /**
114
- * Returns the advice order
115
- */
116
110
public function getAdviceOrder (): int
117
111
{
118
112
return $ this ->adviceOrder ;
119
113
}
120
114
121
115
/**
122
116
* Getter for extracting the advice closure from Interceptor
117
+ *
118
+ * @internal
123
119
*/
124
120
public function getRawAdvice (): Closure
125
121
{
126
122
return $ this ->adviceMethod ;
127
123
}
128
124
129
125
/**
130
- * Serializes an interceptor into it's representation
126
+ * Serializes an interceptor into it's array shape representation
127
+ *
128
+ * @return non-empty-array<string, mixed>
131
129
*/
132
130
final public function __serialize (): array
133
131
{
132
+ // Compressing state representation to avoid default values, eg pointcutExpression = '' or adviceOrder = 0
134
133
$ state = array_filter (get_object_vars ($ this ));
135
134
135
+ // Override closure with array representation to enable serialization
136
136
$ state ['adviceMethod ' ] = static ::serializeAdvice ($ this ->adviceMethod );
137
137
138
138
return $ state ;
@@ -141,7 +141,7 @@ final public function __serialize(): array
141
141
/**
142
142
* Un-serializes an interceptor from it's stored state
143
143
*
144
- * @param array $state The stored representation of the interceptor.
144
+ * @param array{adviceMethod: array{name: string, class?: string}} $state The stored representation of the interceptor.
145
145
*/
146
146
final public function __unserialize (array $ state ): void
147
147
{
0 commit comments