1 | <?php |
||
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( |
||
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 |