Completed
Pull Request — 2.x (#349)
by Alexander
02:20
created

ClassFieldAccess   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 3

Test Coverage

Coverage 16.66%

Importance

Changes 0
Metric Value
wmc 23
lcom 4
cbo 3
dl 0
loc 217
ccs 10
cts 60
cp 0.1666
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getAccessType() 0 4 1
A getField() 0 4 1
A getValue() 0 6 1
A getValueToSet() 0 6 1
B ensureScopeRule() 0 16 5
A proceed() 0 8 2
B __invoke() 0 31 4
A getThis() 0 4 1
A getStaticPart() 0 4 1
A __toString() 0 10 4
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2011, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Aop\Framework;
13
14
use Go\Aop\AspectException;
15
use Go\Aop\Intercept\FieldAccess;
16
use Go\Aop\Support\AnnotatedReflectionProperty;
17
use ReflectionProperty;
18
19
/**
20
 * Represents a field access joinpoint
21
 */
22
class ClassFieldAccess extends AbstractJoinpoint implements FieldAccess
23
{
24
25
    /**
26
     * Instance of object for accessing
27
     *
28
     * @var object
29
     */
30
    protected $instance;
31
32
    /**
33
     * Instance of reflection property
34
     *
35
     * @var ReflectionProperty
36
     */
37
    protected $reflectionProperty;
38
39
    /**
40
     * New value to set
41
     *
42
     * @var mixed
43
     */
44
    protected $newValue;
45
46
    /**
47
     * Access type for field access
48
     *
49
     * @var integer
50
     */
51
    private $accessType;
52
53
    /**
54
     * Copy of the original value of property
55
     *
56
     * @var mixed
57
     */
58
    private $value;
59
60
    /**
61
     * Constructor for field access
62
     *
63
     * @param string $className Class name
64
     * @param string $fieldName Field name
65
     * @param $advices array List of advices for this invocation
66
     */
67 3
    public function __construct(string $className, string $fieldName, array $advices)
68
    {
69 3
        parent::__construct($advices);
70
71 3
        $this->reflectionProperty = $reflectionProperty = new AnnotatedReflectionProperty($className, $fieldName);
72
        // Give an access to protected field
73 3
        if ($reflectionProperty->isProtected()) {
74 3
            $reflectionProperty->setAccessible(true);
75
        }
76 3
    }
77
78
    /**
79
     * Returns the access type.
80
     */
81
    public function getAccessType(): int
82
    {
83
        return $this->accessType;
84
    }
85
86
    /**
87
     * Gets the field being accessed.
88
     *
89
     * @return ReflectionProperty|AnnotatedReflectionProperty the property which is being accessed.
90
     */
91 3
    public function getField(): ReflectionProperty
92
    {
93 3
        return $this->reflectionProperty;
94
    }
95
96
    /**
97
     * Gets the current value of property
98
     *
99
     * @return mixed
100
     */
101
    public function &getValue()
102
    {
103
        $value = &$this->value;
104
105
        return $value;
106
    }
107
108
    /**
109
     * Gets the value that must be set to the field.
110
     *
111
     * @return mixed
112
     */
113
    public function &getValueToSet()
114
    {
115
        $newValue = &$this->newValue;
116
117
        return $newValue;
118
    }
119
120
    /**
121
     * Checks scope rules for accessing property
122
     *
123
     * @param int $stackLevel Stack level for check
124
     */
125
    public function ensureScopeRule($stackLevel = 2)
126
    {
127
        $property = $this->reflectionProperty;
128
129
        if ($property->isProtected()) {
130
            $backTrace     = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $stackLevel+1);
131
            $accessor      = $backTrace[$stackLevel] ?? [];
132
            $propertyClass = $property->class;
133
            if (isset($accessor['class'])) {
134
                if ($accessor['class'] === $propertyClass || is_subclass_of($accessor['class'], $propertyClass)) {
135
                    return;
136
                }
137
            }
138
            throw new AspectException("Cannot access protected property {$propertyClass}::{$property->name}");
139
        }
140
    }
141
142
    /**
143
     * Proceed to the next interceptor in the Chain
144
     *
145
     * Typically this method is called inside previous closure, as instance of Joinpoint is passed to callback
146
     * Do not call this method directly, only inside callback closures.
147
     *
148
     * @return void For field interceptor there is no return values
149
     */
150
    final public function proceed()
151
    {
152
        if (isset($this->advices[$this->current])) {
153
            $currentInterceptor = $this->advices[$this->current++];
154
155
            $currentInterceptor->invoke($this);
156
        }
157
    }
158
159
    /**
160
     * Invokes current field access with all interceptors
161
     *
162
     * @param object $instance Instance of object
163
     * @param integer $accessType Type of access: READ or WRITE
164
     * @param mixed $originalValue Original value of property
165
     * @param mixed $newValue New value to set
166
     *
167
     * @return mixed
168
     */
169
    final public function &__invoke($instance, $accessType, &$originalValue, $newValue = NAN)
170
    {
171
        if ($this->level > 0) {
172
            $this->stackFrames[] = [$this->instance, $this->accessType, &$this->value, &$this->newValue];
173
        }
174
175
        ++$this->level;
176
177
        $this->current    = 0;
178
        $this->instance   = $instance;
179
        $this->accessType = $accessType;
180
        $this->value      = &$originalValue;
181
        $this->newValue   = $newValue;
182
183
        $this->proceed();
184
185
        --$this->level;
186
187
        if ($this->level > 0) {
188
            list($this->instance, $this->accessType, $this->value, $this->newValue) = array_pop($this->stackFrames);
189
        }
190
191
        if ($accessType === self::READ) {
192
            $result = &$this->value;
193
        } else {
194
            $result = &$this->newValue;
195
        }
196
197
        return $result;
198
199
    }
200
201
    /**
202
     * Returns the object that holds the current joinpoint's static
203
     * part.
204
     *
205
     * @return object|null the object (can be null if the accessible object is
206
     * static).
207
     */
208
    final public function getThis()
209
    {
210
        return $this->instance;
211
    }
212
213
    /**
214
     * Returns the static part of this joinpoint.
215
     *
216
     * @return object
217
     */
218 1
    final public function getStaticPart()
219
    {
220 1
        return $this->getField();
221
    }
222
223
    /**
224
     * Returns a friendly description of current joinpoint
225
     *
226
     * @return string
227
     */
228
    final public function __toString()
229
    {
230
        return sprintf(
231
            '%s(%s%s%s)',
232
            $this->accessType === self::READ ? 'get' : 'set',
233
            is_object($this->instance) ? get_class($this->instance) : $this->instance,
234
            $this->reflectionProperty->isStatic() ? '::' : '->',
235
            $this->reflectionProperty->name
236
        );
237
    }
238
}
239