Completed
Push — master ( 47d055...8fe3a5 )
by Hong
02:35
created

ReferenceTrait::deReference()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 4
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
            $this->checkReferenceLoop($loop++, $matched[2]); // avoid looping
122
            $val = $this->resolveReference($matched[2]);
123
            if (is_string($val)) {
124
                $subject = str_replace($matched[1], $val, $subject);
125
            } else {
126
                return $this->checkValue($val, $subject, $matched[1]);
127
            }
128
        }
129
        return $subject;
130
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135
    public function deReferenceArray(&$dataArray)
136
    {
137
        // @since 2.1.0
138
        if (!$this->ref_enabled) {
139
            return;
140
        }
141
142
        if (is_string($dataArray)) {
143
            $dataArray = $this->deReference($dataArray);
144
        }
145
146
        if (!is_array($dataArray)) {
147
            return;
148
        }
149
150
        foreach ($dataArray as &$data) {
151
            $this->dereferenceArray($data);
152
        }
153
    }
154
155
    /**
156
     * {@inheritDoc}
157
     *
158
     * @since  2.1.0
159
     */
160
    public function enableDeReference(/*# bool */ $flag = true)
161
    {
162
        $this->ref_enabled = (bool) $flag;
163
        return $this;
164
    }
165
166
    /**
167
     * Check dereferenced value
168
     *
169
     * @param  mixed $value
170
     * @param  string $subject the subject to dereference
171
     * @param  string $reference the matched whole reference
172
     * @return mixed
173
     * @throws RuntimeException if $subject malformed, like mix string & array
174
     * @access protected
175
     */
176
    protected function checkValue(
177
        $value,
178
        /*# string */ $subject,
179
        /*# string */ $reference
180
    ) {
181
        // unknown reference found, leave it alone
182
        if (is_null($value)) {
183
            // exception thrown in resolveUnknown() already if wanted to
184
            return $subject;
185
186
        // malformed partial match, partial string, partial non-scalar
187
        } elseif ($subject != $reference) {
188
            throw new RuntimeException(
189
                Message::get(Message::MSG_REF_MALFORMED, $reference),
190
                Message::MSG_REF_MALFORMED
191
            );
192
193
        // full match, array or object
194
        } else {
195
            return $value;
196
        }
197
    }
198
199
    /**
200
     * Resolve the reference $name
201
     *
202
     * @param  string $name
203
     * @return mixed
204
     * @throws RuntimeException if reference unknown
205
     * @access protected
206
     * @since  2.0.8 added localCache support
207
     * @since  2.0.13 removed localCache support
208
     */
209
    protected function resolveReference(/*# string */ $name)
210
    {
211
        // lookup the reference
212
        $val = $this->referenceLookup($name);
213
214
        // dealing with unknown reference
215
        if (is_null($val)) {
216
            $val = $this->resolveUnknown($name);
217
        }
218
        return $val;
219
    }
220
221
    /**
222
     * Throw exception if looped
223
     *
224
     * @param  int $loop loop counter
225
     * @param  string $name reference name
226
     * @throws RuntimeException if loop found
227
     * @access protected
228
     * @since  2.0.6
229
     */
230
    protected function checkReferenceLoop(
231
        /*# int */ $loop,
232
        /*# string */ $name
233
    ) {
234
        if ($loop > 20) {
235
            throw new RuntimeException(
236
                Message::get(Message::MSG_REF_LOOP, $name),
237
                Message::MSG_REF_LOOP
238
            );
239
        }
240
    }
241
242
    /**
243
     * Lookup reference
244
     *
245
     * @param  string $name
246
     * @return mixed
247
     * @access protected
248
     * @since  2.0.15 removed delegator support, moved to individual library
249
     */
250
    protected function referenceLookup(/*# string */ $name)
251
    {
252
        return $this->getReference($name);
253
    }
254
255
    /**
256
     * For unknown reference $name, normally returns NULL
257
     *
258
     * @param  string $name
259
     * @return mixed
260
     * @throws \Exception if implementor WANTS TO !!
261
     * @access protected
262
     */
263
    abstract protected function resolveUnknown(/*# string */ $name);
264
265
    /**
266
     * The REAL resolving method. return NULL for unknown reference
267
     *
268
     * @param  string $name
269
     * @return mixed
270
     * @access protected
271
     */
272
    abstract protected function getReference(/*# string */ $name);
273
}
274