Completed
Push — master ( c0ad3e...980fd0 )
by Hong
04:11
created

ReferenceTrait::hasReference()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 12
rs 9.2
cc 4
eloc 8
nc 2
nop 2
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.0.4
29
 * @since   2.0.4 added
30
 * @since   2.0.6 added reference cache support
31
 */
32
trait ReferenceTrait
33
{
34
    /**
35
     * refernece start chars
36
     *
37
     * @var    string
38
     * @access protected
39
     */
40
    protected $ref_start = '${';
41
42
    /**
43
     * reference ending chars
44
     *
45
     * @var    string
46
     * @access protected
47
     */
48
    protected $ref_end = '}';
49
50
    /**
51
     * cached pattern to match
52
     *
53
     * @var    string
54
     * @access protected
55
     */
56
    protected $ref_pattern = '~(\$\{((?:(?!\$\{|\}).)+?)\})~';
57
58
    /**
59
     * cached references
60
     *
61
     * @var    array
62
     * @access protected
63
     * @since  2.0.6
64
     */
65
    protected $ref_cache = [];
66
67
    /**
68
     * {@inheritDoc}
69
     */
70
    public function setReference(
71
        /*# string */ $start,
72
        /*# string */ $end
73
    ) {
74
        $this->ref_start = $start;
75
        $this->ref_end = $end;
76
77
        // build pattern
78
        $s = preg_quote($start);
79
        $e = preg_quote($end);
80
        $this->ref_pattern = sprintf(
81
            "~(%s((?:(?!%s|%s).)+?)%s)~", $s, $s, $e, $e
82
        );
83
84
        return $this;
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     */
90
    public function hasReference(
91
        /*# string */ $subject,
92
        array &$matched
93
    )/*# : bool */ {
94
        if (is_string($subject) &&
95
            false !== strpos($subject, $this->ref_start) &&
96
            preg_match($this->ref_pattern, $subject, $matched)
97
        ) {
98
            return true;
99
        }
100
        return false;
101
    }
102
103
    /**
104
     * {@inheritDoc}
105
     */
106
    public function deReference(/*# string */ $subject)
107
    {
108
        $loop = 0;
109
        $matched = [];
110
111
        while ($this->hasReference($subject, $matched)) {
112
            // check loop
113
            $this->checkReferenceLoop($loop++, $matched[2]);
114
115
            // resolve the reference
116
            $val = $this->resolveReference($matched[2]);
117
118
            // resolved to another string
119
            if (is_string($val)) {
120
                $subject = str_replace($matched[1], $val, $subject);
121
122
            // resolved to array, object, null etc.
123
            } else {
124
                return $this->checkValue($val, $subject, $matched[1]);
125
            }
126
        }
127
        return $subject;
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133
    public function deReferenceArray(&$dataArray)
134
    {
135
        if (is_string($dataArray)) {
136
            $dataArray = $this->deReference($dataArray);
137
        }
138
139
        if (!is_array($dataArray)) {
140
            return;
141
        }
142
143
        foreach ($dataArray as &$data) {
144
            $this->dereferenceArray($data);
145
        }
146
    }
147
148
    /**
149
     * Check dereferenced value
150
     *
151
     * @param  mixed $value
152
     * @param  string $subject
153
     * @param  string $reference
154
     * @return mixed
155
     * @throws RuntimeException if $subject malformed, like mix string & array
156
     * @access private
157
     */
158
    private function checkValue(
159
        $value,
160
        /*# string */ $subject,
161
        /*# string */ $reference
162
    ) {
163
        // unknown reference found, leave it alone
164
        if (is_null($value)) {
165
            // exception thrown in resolveUnknown() already if wanted to
166
            return $subject;
167
168
        // malformed partial match
169
        } elseif ($subject != $reference) {
170
            throw new RuntimeException(
171
                Message::get(Message::MSG_REF_MALFORMED, $reference),
172
                Message::MSG_REF_MALFORMED
173
            );
174
175
        // full match, array or object
176
        } else {
177
            return $value;
178
        }
179
    }
180
181
    /**
182
     * Resolve the reference $name
183
     *
184
     * @param  string $name
185
     * @return mixed
186
     * @throws RuntimeException if reference unknown
187
     * @access private
188
     * @since  2.0.6 added cache support
189
     */
190
    private function resolveReference(/*# string */ $name)
191
    {
192
        // try reference cache first
193
        if (isset($this->ref_cache[$name])) {
194
            return $this->ref_cache[$name];
195
        }
196
197
        // get referenced value
198
        $val = $this->getReference($name);
199
200
        // unknown ref found
201
        if (is_null($val)) {
202
            $val = $this->resolveUnknown($name);
203
        }
204
205
        // cache deref result
206
        $this->ref_cache[$name] = $val;
207
208
        return $val;
209
    }
210
211
    /**
212
     * Throw exception if looped
213
     *
214
     * @param  int $loop loop counter
215
     * @param  string $name reference name
216
     * @throws RuntimeException if loop found
217
     * @access private
218
     * @since  2.0.6
219
     */
220
    private function checkReferenceLoop(
221
        /*# int */ $loop,
222
        /*# string */ $name
223
    ) {
224
        if ($loop > 20) {
225
            throw new RuntimeException(
226
                Message::get(Message::MSG_REF_LOOP, $name),
227
                Message::MSG_REF_LOOP
228
            );
229
        }
230
    }
231
232
    /**
233
     * Clear reference cache
234
     *
235
     * @return $this
236
     * @access protected
237
     * @since  2.0.6 added
238
     */
239
    protected function clearReferenceCache()
240
    {
241
        $this->ref_cache = [];
242
        return $this;
243
    }
244
245
    /**
246
     * For unknown reference $name, normally returns NULL
247
     *
248
     * @param  string $name
249
     * @return mixed
250
     * @throws \Exception if implementor WANTS TO !!
251
     * @access protected
252
     */
253
    abstract protected function resolveUnknown(/*# string */ $name);
254
255
    /**
256
     * The real resolving method. return NULL for unknown reference
257
     *
258
     * @param  string $name
259
     * @return mixed
260
     * @access protected
261
     */
262
    abstract protected function getReference(/*# string */ $name);
263
}
264