Completed
Push — 1.x ( c183a4...05b51a )
by Alexander
8s
created

BaseAdvice   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 125
wmc 13
lcom 1
cbo 1
ccs 0
cts 36
cp 0
rs 10

4 Methods

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