Completed
Push — master ( 558df7...329d64 )
by Hong
03:22
created

ReferenceTrait   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 18
Bugs 1 Features 3
Metric Value
wmc 24
c 18
b 1
f 3
lcom 1
cbo 2
dl 0
loc 241
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A hasReference() 0 12 4
A setReferencePattern() 0 16 1
B deReference() 0 24 3
A deReferenceArray() 0 14 4
A checkValue() 0 22 3
B resolveReference() 0 26 5
A checkReferenceLoop() 0 11 2
A delegatedReference() 0 13 2
resolveUnknown() 0 1 ?
getReference() 0 1 ?
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Shared
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Shared\Reference;
16
17
use Phossa2\Shared\Message\Message;
18
use Phossa2\Shared\Cache\LocalCacheTrait;
19
use Phossa2\Shared\Exception\RuntimeException;
20
21
/**
22
 * ReferenceTrait
23
 *
24
 * Provides reference & dereference methods
25
 *
26
 * @package Phossa2\Shared
27
 * @author  Hong Zhang <[email protected]>
28
 * @see     ReferenceInterface
29
 * @version 2.0.8
30
 * @since   2.0.4 added
31
 * @since   2.0.6 added reference cache support
32
 * @since   2.0.8 added delegator support, changed to LocaclCache
33
 */
34
trait ReferenceTrait
35
{
36
    use LocalCacheTrait;
37
38
    /**
39
     * refernece start chars
40
     *
41
     * @var    string
42
     * @access private
43
     */
44
    private $ref_start = '${';
45
46
    /**
47
     * reference ending chars
48
     *
49
     * @var    string
50
     * @access private
51
     */
52
    private $ref_end = '}';
53
54
    /**
55
     * cached pattern to match
56
     *
57
     * @var    string
58
     * @access private
59
     */
60
    private $ref_pattern = '~(\$\{((?:(?!\$\{|\}).)+?)\})~';
61
62
    /**
63
     * {@inheritDoc}
64
     */
65
    public function setReferencePattern(
66
        /*# string */ $start,
67
        /*# string */ $end
68
    ) {
69
        $this->ref_start = $start;
70
        $this->ref_end = $end;
71
72
        // build pattern on the fly
73
        $s = preg_quote($start);
74
        $e = preg_quote($end);
75
        $this->ref_pattern = sprintf(
76
            "~(%s((?:(?!%s|%s).)+?)%s)~", $s, $s, $e, $e
77
        );
78
79
        return $this->clearLocalCache();
80
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85
    public function hasReference(
86
        /*# string */ $subject,
87
        array &$matched
88
    )/*# : bool */ {
89
        if (is_string($subject) &&
90
            false !== strpos($subject, $this->ref_start) &&
91
            preg_match($this->ref_pattern, $subject, $matched)
92
        ) {
93
            return true;
94
        }
95
        return false;
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101
    public function deReference(/*# string */ $subject)
102
    {
103
        $loop = 0;
104
        $matched = [];
105
106
        // recursively dereference in the string $subject
107
        while ($this->hasReference($subject, $matched)) {
108
            // avoid looping
109
            $this->checkReferenceLoop($loop++, $matched[2]);
110
111
            // resolve the reference to a value
112
            $val = $this->resolveReference($matched[2]);
113
114
            // value is another string
115
            if (is_string($val)) {
116
                $subject = str_replace($matched[1], $val, $subject);
117
118
            // value is an array, object, null etc.
119
            } else {
120
                return $this->checkValue($val, $subject, $matched[1]);
121
            }
122
        }
123
        return $subject;
124
    }
125
126
    /**
127
     * {@inheritDoc}
128
     */
129
    public function deReferenceArray(&$dataArray)
130
    {
131
        if (is_string($dataArray)) {
132
            $dataArray = $this->deReference($dataArray);
133
        }
134
135
        if (!is_array($dataArray)) {
136
            return;
137
        }
138
139
        foreach ($dataArray as &$data) {
140
            $this->dereferenceArray($data);
141
        }
142
    }
143
144
    /**
145
     * Check dereferenced value
146
     *
147
     * @param  mixed $value
148
     * @param  string $subject the subject to dereference
149
     * @param  string $reference the matched whole reference
150
     * @return mixed
151
     * @throws RuntimeException if $subject malformed, like mix string & array
152
     * @access private
153
     */
154
    private function checkValue(
155
        $value,
156
        /*# string */ $subject,
157
        /*# string */ $reference
158
    ) {
159
        // unknown reference found, leave it alone
160
        if (is_null($value)) {
161
            // exception thrown in resolveUnknown() already if wanted to
162
            return $subject;
163
164
        // malformed partial match, partial string, partial non-scalar
165
        } elseif ($subject != $reference) {
166
            throw new RuntimeException(
167
                Message::get(Message::MSG_REF_MALFORMED, $reference),
168
                Message::MSG_REF_MALFORMED
169
            );
170
171
        // full match, array or object
172
        } else {
173
            return $value;
174
        }
175
    }
176
177
    /**
178
     * Resolve the reference $name
179
     *
180
     * @param  string $name
181
     * @return mixed
182
     * @throws RuntimeException if reference unknown
183
     * @access private
184
     * @since  2.0.6 added cache support
185
     * @since  2.0.8 added delegatorAware support
186
     */
187
    private function resolveReference(/*# string */ $name)
188
    {
189
        // try reference cache first
190
        if ($this->hasLocalCache($name)) {
191
            return $this->getLocalCache($name);
192
        }
193
194
        // lookup the reference
195
        if ($this instanceof DelegatorAwareInterface &&
196
            $this->hasDelegator()
197
        ) {
198
            $val = $this->delegatedReference($name);
0 ignored issues
show
Bug introduced by
The method delegatedReference() does not seem to exist on object<Phossa2\Shared\Re...elegatorAwareInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
        } else {
200
            $val = $this->getReference($name);
201
        }
202
203
        // dealing with unknown reference
204
        if (is_null($val)) {
205
            $val = $this->resolveUnknown($name);
206
        }
207
208
        // cache deref result
209
        $this->setLocalCache($name, $val);
210
211
        return $val;
212
    }
213
214
    /**
215
     * Throw exception if looped
216
     *
217
     * @param  int $loop loop counter
218
     * @param  string $name reference name
219
     * @throws RuntimeException if loop found
220
     * @access private
221
     * @since  2.0.6
222
     */
223
    private function checkReferenceLoop(
224
        /*# int */ $loop,
225
        /*# string */ $name
226
    ) {
227
        if ($loop > 20) {
228
            throw new RuntimeException(
229
                Message::get(Message::MSG_REF_LOOP, $name),
230
                Message::MSG_REF_LOOP
231
            );
232
        }
233
    }
234
235
    /**
236
     * Lookup reference with delegator
237
     *
238
     * @param  string $name
239
     * @return mixed
240
     * @access private
241
     */
242
    private function delegatedReference(/*# string */ $name)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
243
    {
244
        /* @var $delegator DelegatorInterface */
245
        $delegator = $this->getDelegator();
0 ignored issues
show
Bug introduced by
It seems like getDelegator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
246
247
        if ($delegator->hasInLookup($name)) {
248
            $val = $delegator->getFromLookup($name);
249
        } else {
250
            $val = null;
251
        }
252
253
        return $val;
254
    }
255
256
    /**
257
     * For unknown reference $name, normally returns NULL
258
     *
259
     * @param  string $name
260
     * @return mixed
261
     * @throws \Exception if implementor WANTS TO !!
262
     * @access protected
263
     */
264
    abstract protected function resolveUnknown(/*# string */ $name);
265
266
    /**
267
     * The REAL resolving method. return NULL for unknown reference
268
     *
269
     * @param  string $name
270
     * @return mixed
271
     * @access protected
272
     */
273
    abstract protected function getReference(/*# string */ $name);
274
}
275