Completed
Push — 1.x ( e42f79...4120cd )
by Alexander
02:50
created

BaseAdvice::fromAspectReflection()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 15
ccs 0
cts 7
cp 0
rs 9.4285
c 1
b 0
f 0
cc 3
eloc 9
nc 4
nop 2
crap 12
1
<?php
2
/*
3
 * Go! AOP framework
4
 *
5
 * @copyright Copyright 2011, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\Aop\Framework;
12
13
use Go\Aop\Features;
14
use ReflectionFunction;
15
use ReflectionMethod;
16
use Go\Aop\Aspect;
17
use Go\Aop\Intercept\Joinpoint;
18
use Go\Core\AspectKernel;
19
20
/**
21
 * Base class for all framework advices implementations
22
 *
23
 *  This class describe an action taken by the AOP framework at a particular
24
 * joinpoint. Different types of advice include "around", "before" and "after"
25
 * advices.
26
 *
27
 *  Around advice is an advice that surrounds a joinpoint such as a method
28
 * invocation. This is the most powerful kind of advice. Around advices will
29
 * perform custom behavior before and after the method invocation. They are
30
 * responsible for choosing whether to proceed to the joinpoint or to shortcut
31
 * executing by returning their own return value or throwing an exception.
32
 *  After and before advices are simple closures that will be invoked after and
33
 * before main invocation.
34
 *  Framework model an advice as an PHP-closure interceptor, maintaining a
35
 * chain of interceptors "around" the joinpoint:
36
 *   function (Joinpoint $joinPoint) {
37
 *      echo 'Before action';
38
 *      // call chain here with Joinpoint->proceed() method
39
 *      $result = $joinPoint->proceed();
40
 *      echo 'After action';
41
 *      return $result;
42
 *   }
43
 */
44
abstract class BaseAdvice implements OrderedAdvice
45
{
46
    /**
47
     * Advice order
48
     *
49
     * @var int
50
     */
51
    protected $order = 0;
52
53
    /**
54
     * Local cache of advices for faster unserialization on big projects
55
     *
56
     * @var array
57
     */
58
    protected static $localAdvicesCache = [];
59
60
    /**
61
     * Returns the advice order
62
     *
63
     * @return int
64
     */
65
    public function getAdviceOrder()
66
    {
67
        return $this->order;
68
    }
69
70
    /**
71
     * Serialize advice method into array
72
     *
73
     * @param callable|\Closure $adviceMethod An advice for aspect
74
     *
75
     * @return array
76
     */
77
    public static function serializeAdvice($adviceMethod)
78
    {
79
        $refAdvice    = new ReflectionFunction($adviceMethod);
80
        $refVariables = $refAdvice->getStaticVariables();
81
        $scope        = 'aspect';
82
        if (isset($refVariables['scope'])) {
83
            $scope     = $refVariables['scope'];
84
            $refAdvice = new ReflectionFunction($refVariables['adviceCallback']);
85
        }
86
87
        return array(
88
            'scope'  => $scope,
89
            'method' => $refAdvice->name,
90
            'aspect' => get_class($refAdvice->getClosureThis())
91
        );
92
    }
93
94
    /**
95
     * Unserialize an advice
96
     *
97
     * @param array $adviceData Information about advice
98
     *
99
     * @return callable|\Closure
100
     */
101
    public static function unserializeAdvice($adviceData)
102
    {
103
        $aspectName = $adviceData['aspect'];
104
        $methodName = $adviceData['method'];
105
        $scope      = $adviceData['scope'];
106
107
        if (!isset(static::$localAdvicesCache["$aspectName->$methodName"]['aspect'])) {
108
            $refMethod = new ReflectionMethod($aspectName, $methodName);
109
            $aspect    = AspectKernel::getInstance()->getContainer()->getAspect($aspectName);
110
            $advice    = $refMethod->getClosure($aspect);
111
            static::$localAdvicesCache["$aspectName->$methodName"]['aspect'] = $advice;
112
        }
113
114
        if ($scope !== 'aspect' && !isset(static::$localAdvicesCache["$aspectName->$methodName"][$scope])) {
115
            $aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName);
116
            $advice = static::$localAdvicesCache["$aspectName->$methodName"]['aspect'];
117
            $advice = static::createScopeCallback($aspect, $advice, $scope);
118
            static::$localAdvicesCache["$aspectName->$methodName"][$scope] = $advice;
119
        }
120
121
        return static::$localAdvicesCache["$aspectName->$methodName"][$scope];
122
    }
123
124
    /**
125
     * Creates an advice with respect to the desired scope
126
     *
127
     * @param Aspect $aspect
128
     * @param \Closure $adviceCallback Advice to call
129
     * @param string $scope Scope for callback
130
     *
131
     * @throws \InvalidArgumentException is scope is not supported
132
     * @return callable
133
     */
134
    public static function createScopeCallback(Aspect $aspect, \Closure $adviceCallback, $scope)
135
    {
136
        switch ($scope) {
137
            case 'aspect':
138
                return $adviceCallback;
139
140
            case 'proxy':
141
                return function(Joinpoint $joinpoint) use ($aspect, $adviceCallback) {
142
                    $instance    = $joinpoint->getThis();
143
                    $isNotObject = $instance !== (object) $instance;
144
                    $target      = $isNotObject ? $instance : get_class($instance);
145
                    $callback    = $adviceCallback->bindTo($aspect, $target);
146
147
                    return $callback($joinpoint);
148
                };
149
150
            case 'target':
151
                return function(Joinpoint $joinpoint) use ($aspect, $adviceCallback) {
152
                    $instance    = $joinpoint->getThis();
153
                    $isNotObject = $instance !== (object) $instance;
154
                    $target      = $isNotObject ? $instance : get_parent_class($instance);
155
                    $callback    = $adviceCallback->bindTo($aspect, $target);
156
157
                    return $callback($joinpoint);
158
                };
159
160
            default:
161
                return function(Joinpoint $joinpoint) use ($aspect, $adviceCallback, $scope) {
162
                    $callback = $adviceCallback->bindTo($aspect, $scope);
163
164
                    return $callback($joinpoint);
165
                };
166
        }
167
    }
168
}
169