These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * \AppserverIo\Doppelgaenger\StreamFilters\SkeletonFilter |
||
5 | * |
||
6 | * NOTICE OF LICENSE |
||
7 | * |
||
8 | * This source file is subject to the Open Software License (OSL 3.0) |
||
9 | * that is available through the world-wide-web at this URL: |
||
10 | * http://opensource.org/licenses/osl-3.0.php |
||
11 | * |
||
12 | * PHP version 5 |
||
13 | * |
||
14 | * @author Bernhard Wick <[email protected]> |
||
15 | * @copyright 2015 TechDivision GmbH - <[email protected]> |
||
16 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
||
17 | * @link https://github.com/appserver-io/doppelgaenger |
||
18 | * @link http://www.appserver.io/ |
||
19 | */ |
||
20 | |||
21 | namespace AppserverIo\Doppelgaenger\StreamFilters; |
||
22 | |||
23 | use AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition; |
||
24 | use AppserverIo\Doppelgaenger\Exceptions\GeneratorException; |
||
25 | use AppserverIo\Doppelgaenger\Dictionaries\Placeholders; |
||
26 | use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords; |
||
27 | use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface; |
||
28 | use AppserverIo\Doppelgaenger\Utils\Parser; |
||
29 | use AppserverIo\Doppelgaenger\Entities\Definitions\ClassDefinition; |
||
30 | use AppserverIo\Doppelgaenger\Entities\Definitions\InterfaceDefinition; |
||
31 | use AppserverIo\Doppelgaenger\Entities\Definitions\TraitDefinition; |
||
32 | |||
33 | /** |
||
34 | * This filter is the most important one! |
||
35 | * It will analyze the need to act upon the content we get and prepare placeholder for coming filters so they |
||
36 | * do not have to do the analyzing part again. |
||
37 | * This placeholder system also makes them highly optional, configur- and interchangeable. |
||
38 | * |
||
39 | * @author Bernhard Wick <[email protected]> |
||
40 | * @copyright 2015 TechDivision GmbH - <[email protected]> |
||
41 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
||
42 | * @link https://github.com/appserver-io/doppelgaenger |
||
43 | * @link http://www.appserver.io/ |
||
44 | */ |
||
45 | class SkeletonFilter extends AbstractFilter |
||
46 | { |
||
47 | |||
48 | /** |
||
49 | * Order number if filters are used as a stack, higher means below others |
||
50 | * |
||
51 | * @const integer FILTER_ORDER |
||
52 | */ |
||
53 | const FILTER_ORDER = 0; |
||
54 | |||
55 | /** |
||
56 | * Will filter portions of incoming stream content. |
||
57 | * Will always contain false to enforce buffering of all buckets. |
||
58 | * |
||
59 | * @param string $content The content to be filtered |
||
60 | * |
||
61 | * @return boolean |
||
62 | */ |
||
63 | public function filterContent($content) |
||
64 | { |
||
65 | return false; |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * Preparation hook which is intended to be called at the start of the first filter() iteration. |
||
70 | * We will inject the original path hint here |
||
71 | * |
||
72 | * @param string $bucketData Payload of the first filtered bucket |
||
73 | * |
||
74 | * @return void |
||
75 | */ |
||
76 | public function firstBucket(&$bucketData) |
||
77 | { |
||
78 | $this->injectOriginalPathHint($bucketData, $this->structureDefinition->getPath()); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Will find the index of the last character within a structure |
||
83 | * |
||
84 | * @param string $code The structure code to search in |
||
85 | * @param string $structureName Name of the structure to get the last index for |
||
86 | * @param string $structureType Type of the structure in question |
||
87 | * |
||
88 | * @return integer |
||
89 | */ |
||
90 | protected function findLastStructureIndex($code, $structureName, $structureType) |
||
91 | { |
||
92 | // determine which keyword we should search for |
||
93 | switch ($structureType) { |
||
94 | case InterfaceDefinition::TYPE: |
||
95 | $structureKeyword = 'interface'; |
||
96 | break; |
||
97 | |||
98 | case TraitDefinition::TYPE: |
||
99 | $structureKeyword = 'trait'; |
||
100 | break; |
||
101 | |||
102 | default: |
||
103 | $structureKeyword = 'class'; |
||
104 | break; |
||
105 | } |
||
106 | |||
107 | // cut everything in front of the first bracket so we have a better start |
||
108 | $matches = array(); |
||
109 | preg_match('/.*' . $structureKeyword . '\s+' . $structureName . '.+?{/s', $code, $matches); |
||
110 | if (count($matches) != 1) { |
||
111 | throw new GeneratorException(sprintf('Could not find last index for stucture %s. Cannot generate proxy skeleton.', $structureName)); |
||
112 | } |
||
113 | |||
114 | $offset = (strlen(reset($matches)) - 1); |
||
115 | // get a parser util and get the bracket span |
||
116 | $parserUtil = new Parser(); |
||
117 | $structureSpan = $parserUtil->getBracketSpan($code, '{', $offset); |
||
118 | |||
119 | return (($structureSpan + $offset) - 1); |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Preparation hook which is intended to be called at the start of the first filter() iteration. |
||
124 | * We will inject the original path hint here |
||
125 | * |
||
126 | * @return void |
||
127 | */ |
||
128 | public function finish() |
||
129 | { |
||
130 | // we have to substitute magic __DIR__ and __FILE__ constants |
||
131 | $this->substituteLocationConstants($this->bucketBuffer, $this->structureDefinition->getPath()); |
||
132 | |||
133 | // substitute the original function declarations for the renamed ones |
||
134 | $this->substituteFunctionHeaders($this->bucketBuffer, $this->structureDefinition); |
||
135 | |||
136 | // mark the end of the structure as this is an important hook for other things to be woven |
||
137 | $lastIndex = $this->findLastStructureIndex($this->bucketBuffer, $this->structureDefinition->getName(), $this->structureDefinition->getType()); |
||
138 | $this->bucketBuffer = substr_replace($this->bucketBuffer, Placeholders::STRUCTURE_END, $lastIndex, 0); |
||
139 | |||
140 | // inject the code for the function skeletons |
||
141 | $this->injectFunctionSkeletons($this->bucketBuffer, $this->structureDefinition, true); |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * The main filter method. |
||
146 | * Implemented according to \php_user_filter class. Will loop over all stream buckets, buffer them and perform |
||
147 | * the needed actions. |
||
148 | * |
||
149 | * @param resource $in Incoming bucket brigade we need to filter |
||
150 | * @param resource $out Outgoing bucket brigade with already filtered content |
||
151 | * @param integer $consumed The count of altered characters as buckets pass the filter |
||
152 | * @param boolean $closing Is the stream about to close? |
||
153 | * |
||
154 | * @throws \AppserverIo\Doppelgaenger\Exceptions\GeneratorException |
||
155 | * |
||
156 | * @return integer |
||
157 | * |
||
158 | * @link http://www.php.net/manual/en/php-user-filter.filter.php |
||
159 | */ |
||
160 | public function filter($in, $out, &$consumed, $closing) |
||
161 | { |
||
162 | // make the params more prominent |
||
163 | $this->structureDefinition = $this->params; |
||
164 | |||
165 | // use the parent filter method to allow for proper hook usage |
||
166 | return parent::filter($in, $out, $consumed, $closing); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Will inject condition checking code in front and behind the functions body. |
||
171 | * |
||
172 | * @param string $bucketData Payload of the currently filtered bucket |
||
173 | * @param \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface $structureDefinition The original path we have to place as our constants |
||
174 | * @param boolean $beautify Whether or not the injected code should be beautified first |
||
175 | * |
||
176 | * @return boolean |
||
177 | */ |
||
178 | protected function injectFunctionSkeletons(& $bucketData, StructureDefinitionInterface $structureDefinition, $beautify = false) |
||
179 | { |
||
180 | |||
181 | // generate the skeleton code for all known functions |
||
182 | $functionSkeletonsCode = ''; |
||
183 | foreach ($structureDefinition->getFunctionDefinitions() as $functionDefinition) { |
||
184 | // we do not have to act on abstract methods |
||
185 | if ($functionDefinition->isAbstract()) { |
||
186 | continue; |
||
187 | } |
||
188 | |||
189 | // __get and __set need some special steps so we can inject our own logic into them |
||
190 | $injectNeeded = false; |
||
191 | if ($functionDefinition->getName() === '__get' || $functionDefinition->getName() === '__set') { |
||
192 | $injectNeeded = true; |
||
193 | } |
||
194 | |||
195 | // get the code used before the original body |
||
196 | $functionSkeletonsCode .= $this->generateSkeletonCode($injectNeeded, $functionDefinition); |
||
197 | } |
||
198 | |||
199 | // inject the new code at the end of the original structure body |
||
200 | $bucketData = str_replace(Placeholders::STRUCTURE_END, Placeholders::STRUCTURE_END . $functionSkeletonsCode, $bucketData); |
||
201 | |||
202 | // if we are still here we seem to have succeeded |
||
203 | return true; |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * Will generate the skeleton code for the passed function definition. |
||
208 | * Will result in a string resembling the following example: |
||
209 | * |
||
210 | * <FUNCTION_DOCBLOCK> |
||
211 | * <FUNCTION_MODIFIERS> function <FUNCTION_NAME>(<FUNCTION_PARAMS>) |
||
212 | * { |
||
213 | * $dgStartLine = <FUNCTION_START_LINE>; |
||
214 | * $dgEndLine = <FUNCTION_END_LINE>; |
||
215 | * / DOPPELGAENGER_FUNCTION_BEGIN_PLACEHOLDER <FUNCTION_NAME> / |
||
216 | * / DOPPELGAENGER_BEFORE_JOINPOINT <FUNCTION_NAME> / |
||
217 | * $dgOngoingContract = \AppserverIo\Doppelgaenger\ContractContext::open(); |
||
218 | * / DOPPELGAENGER_INVARIANT_PLACEHOLDER / |
||
219 | * / DOPPELGAENGER_PRECONDITION_PLACEHOLDER <FUNCTION_NAME> / |
||
220 | * / DOPPELGAENGER_OLD_SETUP_PLACEHOLDER <FUNCTION_NAME> / |
||
221 | * $dgResult = null; |
||
222 | * try { |
||
223 | * / DOPPELGAENGER_AROUND_JOINPOINT <FUNCTION_NAME> / |
||
224 | * |
||
225 | * } catch (\Exception $dgThrownExceptionObject) { |
||
226 | * / DOPPELGAENGER_AFTERTHROWING_JOINPOINT <FUNCTION_NAME> / |
||
227 | * |
||
228 | * // rethrow the exception |
||
229 | * throw $dgThrownExceptionObject; |
||
230 | * |
||
231 | * } finally { |
||
232 | * / DOPPELGAENGER_AFTER_JOINPOINT <FUNCTION_NAME> / |
||
233 | * |
||
234 | * } |
||
235 | * / DOPPELGAENGER_POSTCONDITION_PLACEHOLDER <FUNCTION_NAME> / |
||
236 | * / DOPPELGAENGER_INVARIANT_PLACEHOLDER / |
||
237 | * if ($dgOngoingContract) { |
||
238 | * \AppserverIo\Doppelgaenger\ContractContext::close(); |
||
239 | * } / DOPPELGAENGER_AFTERRETURNING_JOINPOINT <FUNCTION_NAME> / |
||
240 | * |
||
241 | * return $dgResult; |
||
242 | * } |
||
243 | * |
||
244 | * @param boolean $injectNeeded Determine if we have to use a try...catch block |
||
245 | * @param FunctionDefinition $functionDefinition The function definition object |
||
246 | * |
||
247 | * @return string |
||
248 | */ |
||
249 | protected function generateSkeletonCode($injectNeeded, FunctionDefinition $functionDefinition) |
||
250 | { |
||
251 | |||
252 | // first of all: the docblock |
||
253 | $code = ' |
||
254 | ' . $functionDefinition->getDocBlock() . ' |
||
255 | ' . $functionDefinition->getHeader('definition') . ' |
||
256 | { |
||
257 | ' . ReservedKeywords::START_LINE_VARIABLE . ' = ' . (integer) $functionDefinition->getStartLine() . '; |
||
258 | ' . ReservedKeywords::END_LINE_VARIABLE . ' = ' . (integer) $functionDefinition->getEndLine() . '; |
||
259 | ' . Placeholders::FUNCTION_BEGIN . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
260 | '; |
||
261 | |||
262 | // right after: the "before" join-point |
||
263 | $code .= Placeholders::BEFORE_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
264 | '; |
||
265 | |||
266 | // open the contract context so we are able to avoid endless recursion |
||
267 | $code .= ReservedKeywords::CONTRACT_CONTEXT . ' = \AppserverIo\Doppelgaenger\ContractContext::open(); |
||
268 | '; |
||
269 | |||
270 | // Invariant is not needed in private or static functions. |
||
271 | // Also make sure that there is none in front of the constructor check |
||
272 | View Code Duplication | if ($functionDefinition->getVisibility() !== 'private' && |
|
273 | !$functionDefinition->isStatic() && $functionDefinition->getName() !== '__construct' |
||
274 | ) { |
||
275 | $code .= Placeholders::INVARIANT_CALL_START . ' |
||
276 | '; |
||
277 | } |
||
278 | |||
279 | $code .= Placeholders::PRECONDITION . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
280 | ' . Placeholders::OLD_SETUP . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE. ' |
||
281 | '; |
||
282 | |||
283 | // we will wrap code execution in order to provide a "finally" and "after throwing" placeholder hook. |
||
284 | // we will also predefine the result as NULL to avoid warnings |
||
285 | $code .= ReservedKeywords::RESULT . ' = null; |
||
286 | try { |
||
287 | ' . Placeholders::AROUND_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
288 | '; |
||
289 | |||
290 | // add the second part of the try/catch/finally block |
||
291 | $code .= ' |
||
292 | } catch (\Exception ' . ReservedKeywords::THROWN_EXCEPTION_OBJECT . ') { |
||
293 | ' . Placeholders::AFTERTHROWING_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
294 | // rethrow the exception |
||
295 | throw ' . ReservedKeywords::THROWN_EXCEPTION_OBJECT . '; |
||
296 | } finally { |
||
297 | '; |
||
298 | |||
299 | // if we have to inject additional code, we might do so here |
||
300 | if ($injectNeeded === true) { |
||
301 | $code .= Placeholders::METHOD_INJECT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE; |
||
302 | } |
||
303 | |||
304 | // finish of the block |
||
305 | $code .= ' ' . Placeholders::AFTER_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
306 | } |
||
307 | '; |
||
308 | |||
309 | // now just place all the other placeholder for other filters to come |
||
310 | $code .= ' ' . Placeholders::POSTCONDITION . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE; |
||
311 | |||
312 | // Invariant is not needed in private or static functions |
||
313 | View Code Duplication | if ($functionDefinition->getVisibility() !== 'private' && !$functionDefinition->isStatic()) { |
|
314 | $code .= ' |
||
315 | ' . Placeholders::INVARIANT_CALL_END . ' |
||
316 | '; |
||
317 | } |
||
318 | |||
319 | // close of the contract context |
||
320 | $code .= 'if (' . ReservedKeywords::CONTRACT_CONTEXT . ') { |
||
321 | \AppserverIo\Doppelgaenger\ContractContext::close(); |
||
322 | } |
||
323 | '; |
||
324 | |||
325 | // last of all: the "after returning" join-point and the final return from the proxy |
||
326 | $code .= ' ' . Placeholders::AFTERRETURNING_JOINPOINT . $functionDefinition->getName() . Placeholders::PLACEHOLDER_CLOSE . ' |
||
327 | return ' . ReservedKeywords::RESULT . '; |
||
328 | } |
||
329 | '; |
||
330 | |||
331 | return $code; |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * Will substitute all function headers (we know about) with function headers indicating an original implementation by appending |
||
336 | * a specific suffix |
||
337 | * |
||
338 | * @param string $bucketData Payload of the currently filtered bucket |
||
339 | * @param \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface $structureDefinition The original path we have to place as our constants |
||
340 | * |
||
341 | * @return boolean |
||
342 | */ |
||
343 | protected function substituteFunctionHeaders(& $bucketData, StructureDefinitionInterface $structureDefinition) |
||
344 | { |
||
345 | // is there event anything to substitute? |
||
346 | if ($structureDefinition->getFunctionDefinitions()->count() <= 0) { |
||
347 | return true; |
||
348 | } |
||
349 | |||
350 | // first of all we have to collect all functions we have to substitute |
||
351 | $functionSubstitutes = array(); |
||
352 | $functionPatterns = array(); |
||
353 | foreach ($structureDefinition->getFunctionDefinitions() as $functionDefinition) { |
||
0 ignored issues
–
show
|
|||
354 | // we do not have to act on abstract methods |
||
355 | if ($functionDefinition->isAbstract()) { |
||
356 | continue; |
||
357 | } |
||
358 | |||
359 | $functionPatterns[] = '/function\s' . $functionDefinition->getName() . '\s*\(/'; |
||
360 | $functionSubstitutes[] = 'function ' . $functionDefinition->getName() . ReservedKeywords::ORIGINAL_FUNCTION_SUFFIX . '('; |
||
361 | } |
||
362 | |||
363 | // do the actual replacing and propagate the result in success |
||
364 | $result = preg_replace($functionPatterns, $functionSubstitutes, $bucketData); |
||
365 | if (!is_null($result)) { |
||
366 | $bucketData = $result; |
||
367 | return true; |
||
368 | } |
||
369 | |||
370 | // still here? That seems to be wrong |
||
371 | return false; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Will substitute all magic __DIR__ and __FILE__ constants with our prepared substitutes to |
||
376 | * emulate original original filesystem context when in cache folder. |
||
377 | * |
||
378 | * @param string $bucketData Payload of the currently filtered bucket |
||
379 | * @param string $file The original path we have to place as our constants |
||
380 | * |
||
381 | * @return boolean |
||
382 | */ |
||
383 | protected function substituteLocationConstants(& $bucketData, $file) |
||
384 | { |
||
385 | $dir = dirname($file); |
||
386 | // Inject the code |
||
387 | $bucketData = str_replace( |
||
388 | array('__DIR__', '__FILE__'), |
||
389 | array('\'' . $dir . '\'', '\'' . $file . '\''), |
||
390 | $bucketData |
||
391 | ); |
||
392 | |||
393 | // Still here? Success then |
||
394 | return true; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Will inject a placeholder that is used to store metadata about the original file |
||
399 | * |
||
400 | * @param string $bucketData Payload of the currently filtered bucket |
||
401 | * @param string $file The original file path we have to inject |
||
402 | * |
||
403 | * @return boolean |
||
404 | */ |
||
405 | protected function injectOriginalPathHint(& $bucketData, $file) |
||
406 | { |
||
407 | // Do need to do this? |
||
408 | if (strpos($bucketData, '<?php') === false) { |
||
409 | return false; |
||
410 | } |
||
411 | |||
412 | // Build up the needed code for our hint |
||
413 | $code = ' ' . Placeholders::PLACEHOLDER_OPEN . Placeholders::ORIGINAL_PATH_HINT . $file . '#' . |
||
414 | filemtime( |
||
415 | $file |
||
416 | ) . Placeholders::ORIGINAL_PATH_HINT . Placeholders::PLACEHOLDER_CLOSE; |
||
417 | |||
418 | // Inject the code |
||
419 | $index = strpos($bucketData, '<?php'); |
||
420 | $bucketData = substr_replace($bucketData, $code, $index + 5, 0); |
||
421 | |||
422 | // Still here? Success then. |
||
423 | return true; |
||
424 | } |
||
425 | } |
||
426 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.