Completed
Push — master ( 329d64...ee80e5 )
by Hong
02:31
created

ReferenceTrait::resolveReference()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 12
Bugs 1 Features 3
Metric Value
c 12
b 1
f 3
dl 0
loc 20
rs 9.4285
cc 3
eloc 8
nc 3
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\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
        while ($this->hasReference($subject, $matched)) {
106
            // avoid looping
107
            $this->checkReferenceLoop($loop++, $matched[2]);
108
109
            // resolve the reference to a value
110
            $val = $this->resolveReference($matched[2]);
111
112
            // value is another string
113
            if (is_string($val)) {
114
                $subject = str_replace($matched[1], $val, $subject);
115
            } else {
116
                return $this->checkValue($val, $subject, $matched[1]);
117
            }
118
        }
119
        return $subject;
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     */
125
    public function deReferenceArray(&$dataArray)
126
    {
127
        if (is_string($dataArray)) {
128
            $dataArray = $this->deReference($dataArray);
129
        }
130
131
        if (!is_array($dataArray)) {
132
            return;
133
        }
134
135
        foreach ($dataArray as &$data) {
136
            $this->dereferenceArray($data);
137
        }
138
    }
139
140
    /**
141
     * Check dereferenced value
142
     *
143
     * @param  mixed $value
144
     * @param  string $subject the subject to dereference
145
     * @param  string $reference the matched whole reference
146
     * @return mixed
147
     * @throws RuntimeException if $subject malformed, like mix string & array
148
     * @access private
149
     */
150
    private function checkValue(
151
        $value,
152
        /*# string */ $subject,
153
        /*# string */ $reference
154
    ) {
155
        // unknown reference found, leave it alone
156
        if (is_null($value)) {
157
            // exception thrown in resolveUnknown() already if wanted to
158
            return $subject;
159
160
        // malformed partial match, partial string, partial non-scalar
161
        } elseif ($subject != $reference) {
162
            throw new RuntimeException(
163
                Message::get(Message::MSG_REF_MALFORMED, $reference),
164
                Message::MSG_REF_MALFORMED
165
            );
166
167
        // full match, array or object
168
        } else {
169
            return $value;
170
        }
171
    }
172
173
    /**
174
     * Resolve the reference $name
175
     *
176
     * @param  string $name
177
     * @return mixed
178
     * @throws RuntimeException if reference unknown
179
     * @access private
180
     * @since  2.0.8 added localCache support
181
     */
182
    private function resolveReference(/*# string */ $name)
183
    {
184
        // try reference cache first
185
        if ($this->hasLocalCache($name)) {
186
            return $this->getLocalCache($name);
187
        }
188
189
        // lookup the reference
190
        $val = $this->referenceLookup($name);
191
192
        // dealing with unknown reference
193
        if (is_null($val)) {
194
            $val = $this->resolveUnknown($name);
195
        }
196
197
        // cache deref result
198
        $this->setLocalCache($name, $val);
199
200
        return $val;
201
    }
202
203
    /**
204
     * Throw exception if looped
205
     *
206
     * @param  int $loop loop counter
207
     * @param  string $name reference name
208
     * @throws RuntimeException if loop found
209
     * @access private
210
     * @since  2.0.6
211
     */
212
    private function checkReferenceLoop(
213
        /*# int */ $loop,
214
        /*# string */ $name
215
    ) {
216
        if ($loop > 20) {
217
            throw new RuntimeException(
218
                Message::get(Message::MSG_REF_LOOP, $name),
219
                Message::MSG_REF_LOOP
220
            );
221
        }
222
    }
223
224
    /**
225
     * Lookup reference with delegator
226
     *
227
     * @param  string $name
228
     * @return mixed
229
     * @access private
230
     */
231
    private function referenceLookup(/*# string */ $name)
232
    {
233
        if ($this instanceof DelegatorAwareInterface &&
234
            $this->hasDelegator()
235
        ) {
236
            /* @var $delegator DelegatorInterface */
237
            $delegator = $this->getDelegator();
238
            $val = $delegator->getFromLookup($name);
239
        } else {
240
            $val = $this->getReference($name);
241
        }
242
        return $val;
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