Completed
Push — master ( d8fcc0...cc3169 )
by Hong
02:33
created

ReferenceTrait::deReferenceArray()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
rs 8.8571
cc 5
eloc 9
nc 7
nop 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\Exception\RuntimeException;
19
20
/**
21
 * ReferenceTrait
22
 *
23
 * Provides reference & dereference methods
24
 *
25
 * @package Phossa2\Shared
26
 * @author  Hong Zhang <[email protected]>
27
 * @see     ReferenceInterface
28
 * @version 2.1.0
29
 * @since   2.0.4 added
30
 * @since   2.0.6 added reference cache support
31
 * @since   2.0.8 added delegator support, changed to LocaclCache
32
 * @since   2.0.12 removed LocalCache
33
 * @since   2.0.15 removed delegator support, moved to individual library
34
 * @since   2.1.0 added `enableDeRefence()`
35
 */
36
trait ReferenceTrait
37
{
38
    /**
39
     * refernece start chars
40
     *
41
     * @var    string
42
     * @access protected
43
     */
44
    protected $ref_start = '${';
45
46
    /**
47
     * reference ending chars
48
     *
49
     * @var    string
50
     * @access protected
51
     */
52
    protected $ref_end = '}';
53
54
    /**
55
     * cached pattern to match
56
     *
57
     * @var    string
58
     * @access protected
59
     */
60
    protected $ref_pattern = '~(\$\{((?:(?!\$\{|\}).)+?)\})~';
61
62
    /**
63
     * dereference enabled
64
     *
65
     * @var    bool
66
     * @access protected
67
     */
68
    protected $ref_enabled = true;
69
70
    /**
71
     * {@inheritDoc}
72
     */
73
    public function setReferencePattern(
74
        /*# string */ $start,
75
        /*# string */ $end
76
    ) {
77
        $this->ref_start = $start;
78
        $this->ref_end = $end;
79
80
        // build pattern on the fly
81
        $s = preg_quote($start);
82
        $e = preg_quote($end);
83
        $this->ref_pattern = sprintf(
84
            "~(%s((?:(?!%s|%s).)+?)%s)~",
85
            $s,
86
            $s,
87
            $e,
88
            $e
89
        );
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95
    public function hasReference(
96
        /*# string */ $subject,
97
        array &$matched
98
    )/*# : bool */ {
99
        if (is_string($subject) &&
100
            false !== strpos($subject, $this->ref_start) &&
101
            preg_match($this->ref_pattern, $subject, $matched)
102
        ) {
103
            return true;
104
        }
105
        return false;
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111
    public function deReference(/*# string */ $subject)
112
    {
113
        // @since 2.1.0
114
        if (!$this->ref_enabled) {
115
            return $subject;
116
        }
117
118
        $loop = 0;
119
        $matched = [];
120
        while ($this->hasReference($subject, $matched)) {
121
            // avoid looping
122
            $this->checkReferenceLoop($loop++, $matched[2]);
123
124
            // resolve the reference to a value
125
            $val = $this->resolveReference($matched[2]);
126
127
            // value is another string
128
            if (is_string($val)) {
129
                $subject = str_replace($matched[1], $val, $subject);
130
            } else {
131
                return $this->checkValue($val, $subject, $matched[1]);
132
            }
133
        }
134
        return $subject;
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140
    public function deReferenceArray(&$dataArray)
141
    {
142
        // @since 2.1.0
143
        if (!$this->ref_enabled) {
144
            return;
145
        }
146
147
        if (is_string($dataArray)) {
148
            $dataArray = $this->deReference($dataArray);
149
        }
150
151
        if (!is_array($dataArray)) {
152
            return;
153
        }
154
155
        foreach ($dataArray as &$data) {
156
            $this->dereferenceArray($data);
157
        }
158
    }
159
160
    /**
161
     * {@inheritDoc}
162
     *
163
     * @since  2.1.0
164
     */
165
    public function enableDeReference(/*# bool */ $flag = true)
166
    {
167
        $this->ref_enabled = (bool) $flag;
168
        return $this;
169
    }
170
171
    /**
172
     * Check dereferenced value
173
     *
174
     * @param  mixed $value
175
     * @param  string $subject the subject to dereference
176
     * @param  string $reference the matched whole reference
177
     * @return mixed
178
     * @throws RuntimeException if $subject malformed, like mix string & array
179
     * @access protected
180
     */
181
    protected function checkValue(
182
        $value,
183
        /*# string */ $subject,
184
        /*# string */ $reference
185
    ) {
186
        // unknown reference found, leave it alone
187
        if (is_null($value)) {
188
            // exception thrown in resolveUnknown() already if wanted to
189
            return $subject;
190
191
        // malformed partial match, partial string, partial non-scalar
192
        } elseif ($subject != $reference) {
193
            throw new RuntimeException(
194
                Message::get(Message::MSG_REF_MALFORMED, $reference),
195
                Message::MSG_REF_MALFORMED
196
            );
197
198
        // full match, array or object
199
        } else {
200
            return $value;
201
        }
202
    }
203
204
    /**
205
     * Resolve the reference $name
206
     *
207
     * @param  string $name
208
     * @return mixed
209
     * @throws RuntimeException if reference unknown
210
     * @access protected
211
     * @since  2.0.8 added localCache support
212
     * @since  2.0.13 removed localCache support
213
     */
214
    protected function resolveReference(/*# string */ $name)
215
    {
216
        // lookup the reference
217
        $val = $this->referenceLookup($name);
218
219
        // dealing with unknown reference
220
        if (is_null($val)) {
221
            $val = $this->resolveUnknown($name);
222
        }
223
        return $val;
224
    }
225
226
    /**
227
     * Throw exception if looped
228
     *
229
     * @param  int $loop loop counter
230
     * @param  string $name reference name
231
     * @throws RuntimeException if loop found
232
     * @access protected
233
     * @since  2.0.6
234
     */
235
    protected function checkReferenceLoop(
236
        /*# int */ $loop,
237
        /*# string */ $name
238
    ) {
239
        if ($loop > 20) {
240
            throw new RuntimeException(
241
                Message::get(Message::MSG_REF_LOOP, $name),
242
                Message::MSG_REF_LOOP
243
            );
244
        }
245
    }
246
247
    /**
248
     * Lookup reference
249
     *
250
     * @param  string $name
251
     * @return mixed
252
     * @access protected
253
     * @since  2.0.15 removed delegator support, moved to individual library
254
     */
255
    protected function referenceLookup(/*# string */ $name)
256
    {
257
        return $this->getReference($name);
258
    }
259
260
    /**
261
     * For unknown reference $name, normally returns NULL
262
     *
263
     * @param  string $name
264
     * @return mixed
265
     * @throws \Exception if implementor WANTS TO !!
266
     * @access protected
267
     */
268
    abstract protected function resolveUnknown(/*# string */ $name);
269
270
    /**
271
     * The REAL resolving method. return NULL for unknown reference
272
     *
273
     * @param  string $name
274
     * @return mixed
275
     * @access protected
276
     */
277
    abstract protected function getReference(/*# string */ $name);
278
}
279