Check for mismatching type of a variable passed in as a parameter
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | /** |
||
5 | * This file is part of phpDocumentor. |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | * |
||
10 | * @author Mike van Riel <[email protected]> |
||
11 | * @copyright 2010-2018 Mike van Riel / Naenius (http://www.naenius.com) |
||
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT |
||
13 | * @link http://phpdoc.org |
||
14 | */ |
||
15 | |||
16 | namespace phpDocumentor\Transformer\Writer; |
||
17 | |||
18 | use InvalidArgumentException; |
||
19 | use phpDocumentor\Descriptor\DescriptorAbstract; |
||
20 | use phpDocumentor\Descriptor\ProjectDescriptor; |
||
21 | use phpDocumentor\Transformer\Writer\Twig\Extension; |
||
22 | use phpDocumentor\Transformer\Router\ForFileProxy; |
||
23 | use phpDocumentor\Transformer\Router\Queue; |
||
24 | use phpDocumentor\Transformer\Transformation; |
||
25 | use Twig_Environment; |
||
26 | use Twig_Extension_Debug; |
||
27 | use Twig_Loader_Filesystem; |
||
28 | use UnexpectedValueException; |
||
29 | |||
30 | /** |
||
31 | * A specialized writer which uses the Twig templating engine to convert |
||
32 | * templates to HTML output. |
||
33 | * |
||
34 | * This writer support the Query attribute of a Transformation to generate |
||
35 | * multiple templates in one transformation. |
||
36 | * |
||
37 | * The Query attribute supports a simplified version of Twig queries and will |
||
38 | * use each individual result as the 'node' global variable in the Twig template. |
||
39 | * |
||
40 | * Example: |
||
41 | * |
||
42 | * Suppose a Query `indexes.classes` is given then this writer will be |
||
43 | * invoked as many times as there are classes in the project and the |
||
44 | * 'node' global variable in twig will be filled with each individual |
||
45 | * class entry. |
||
46 | * |
||
47 | * When using the Query attribute in the transformation it is important to |
||
48 | * use a variable in the Artifact attribute as well (otherwise the same file |
||
49 | * would be overwritten several times). |
||
50 | * |
||
51 | * A simple example transformation line could be: |
||
52 | * |
||
53 | * ``` |
||
54 | * <transformation |
||
55 | * writer="twig" |
||
56 | * source="templates/twig/index.twig" |
||
57 | * artifact="index.html"/> |
||
58 | * ``` |
||
59 | * |
||
60 | * This example transformation would use this writer to transform the |
||
61 | * index.twig template file in the twig template folder into index.html at |
||
62 | * the destination location. |
||
63 | * Since no Query is provided the 'node' global variable will contain |
||
64 | * the Project Descriptor of the Object Graph. |
||
65 | * |
||
66 | * A complex example transformation line could be: |
||
67 | * |
||
68 | * ``` |
||
69 | * <transformation |
||
70 | * query="indexes.classes" |
||
71 | * writer="twig" |
||
72 | * source="templates/twig/class.twig" |
||
73 | * artifact="{{name}}.html"/> |
||
74 | * ``` |
||
75 | * |
||
76 | * This example transformation would use this writer to transform the |
||
77 | * class.twig template file in the twig template folder into a file with |
||
78 | * the 'name' property for an individual class inside the Object Graph. |
||
79 | * Since a Query *is* provided will the 'node' global variable contain a |
||
80 | * specific instance of a class applicable to the current iteration. |
||
81 | * |
||
82 | * @see self::getDestinationPath() for more information about variables in the |
||
83 | * Artifact attribute. |
||
84 | */ |
||
85 | class Twig extends WriterAbstract implements Routable |
||
86 | { |
||
87 | /** @var Queue $routers */ |
||
88 | protected $routers; |
||
89 | |||
90 | /** @var Twig_Environment $twig */ |
||
91 | private $twig; |
||
92 | |||
93 | public function __construct(Twig_Environment $twig) |
||
94 | { |
||
95 | $this->twig = $twig; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * This method combines the ProjectDescriptor and the given target template |
||
100 | * and creates a static html page at the artifact location. |
||
101 | * |
||
102 | * @param ProjectDescriptor $project Document containing the structure. |
||
103 | * @param Transformation $transformation Transformation to execute. |
||
104 | */ |
||
105 | public function transform(ProjectDescriptor $project, Transformation $transformation): void |
||
106 | { |
||
107 | $template_path = $this->getTemplatePath($transformation); |
||
108 | |||
109 | $finder = new Pathfinder(); |
||
110 | $nodes = $finder->find($project, $transformation->getQuery()); |
||
111 | |||
112 | foreach ($nodes as $node) { |
||
113 | if (!$node) { |
||
114 | continue; |
||
115 | } |
||
116 | |||
117 | $destination = $this->getDestinationPath($node, $transformation); |
||
118 | if ($destination === false) { |
||
119 | continue; |
||
120 | } |
||
121 | |||
122 | $environment = $this->initializeEnvironment($project, $transformation, $destination); |
||
123 | $environment->addGlobal('node', $node); |
||
124 | |||
125 | $html = $environment->render(substr($transformation->getSource(), strlen($template_path))); |
||
126 | file_put_contents($destination, $html); |
||
127 | } |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Initializes the Twig environment with the template, base extension and additionally defined extensions. |
||
132 | */ |
||
133 | protected function initializeEnvironment( |
||
134 | ProjectDescriptor $project, |
||
135 | Transformation $transformation, |
||
136 | string $destination |
||
137 | ): Twig_Environment { |
||
138 | $callingTemplatePath = $this->getTemplatePath($transformation); |
||
139 | |||
140 | $baseTemplatesPath = $transformation->getTransformer()->getTemplates()->getTemplatesPath(); |
||
141 | |||
142 | $templateFolders = [ |
||
143 | $baseTemplatesPath . '/..' . DIRECTORY_SEPARATOR . $callingTemplatePath, |
||
144 | // http://twig.sensiolabs.org/doc/recipes.html#overriding-a-template-that-also-extends-itself |
||
145 | $baseTemplatesPath, |
||
146 | ]; |
||
147 | |||
148 | // get all invoked template paths, they overrule the calling template path |
||
149 | /** @var \phpDocumentor\Transformer\Template $template */ |
||
150 | foreach ($transformation->getTransformer()->getTemplates() as $template) { |
||
151 | $path = $baseTemplatesPath . DIRECTORY_SEPARATOR . $template->getName(); |
||
152 | array_unshift($templateFolders, $path); |
||
153 | } |
||
154 | |||
155 | // Clone twig because otherwise we cannot re-set the extensions on this twig environment on every run of this |
||
156 | // writer |
||
157 | $env = clone $this->twig; |
||
158 | $env->setLoader(new Twig_Loader_Filesystem($templateFolders)); |
||
159 | |||
160 | $this->addPhpDocumentorExtension($project, $transformation, $destination, $env); |
||
161 | $this->addExtensionsFromTemplateConfiguration($transformation, $project, $env); |
||
162 | |||
163 | return $env; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Adds the phpDocumentor base extension to the Twig Environment. |
||
168 | */ |
||
169 | protected function addPhpDocumentorExtension( |
||
170 | ProjectDescriptor $project, |
||
171 | Transformation $transformation, |
||
172 | string $destination, |
||
173 | Twig_Environment $twigEnvironment |
||
174 | ): void { |
||
175 | $base_extension = new Extension($project, $transformation); |
||
176 | $base_extension->setDestination( |
||
177 | substr($destination, strlen($transformation->getTransformer()->getTarget()) + 1) |
||
178 | ); |
||
179 | $base_extension->setRouters($this->routers); |
||
180 | $twigEnvironment->addExtension($base_extension); |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * Tries to add any custom extensions that have been defined in the template or the transformation's configuration. |
||
185 | * |
||
186 | * This method will read the `twig-extension` parameter of the transformation (which inherits the template's |
||
187 | * parameter set) and try to add those extensions to the environment. |
||
188 | * |
||
189 | * @throws InvalidArgumentException if a twig-extension should be loaded but it could not be found. |
||
190 | */ |
||
191 | protected function addExtensionsFromTemplateConfiguration( |
||
192 | Transformation $transformation, |
||
193 | ProjectDescriptor $project, |
||
194 | Twig_Environment $twigEnvironment |
||
195 | ): void { |
||
196 | $isDebug = $transformation->getParameter('twig-debug') |
||
197 | ? $transformation->getParameter('twig-debug')->getValue() |
||
198 | : false; |
||
199 | if ($isDebug === 'true') { |
||
200 | $twigEnvironment->enableDebug(); |
||
201 | $twigEnvironment->enableAutoReload(); |
||
202 | $twigEnvironment->addExtension(new Twig_Extension_Debug()); |
||
203 | } |
||
204 | |||
205 | /** @var \phpDocumentor\Transformer\Template\Parameter $extension */ |
||
206 | foreach ($transformation->getParametersWithKey('twig-extension') as $extension) { |
||
207 | $extensionValue = $extension->getValue(); |
||
208 | if (!class_exists($extensionValue)) { |
||
209 | throw new InvalidArgumentException('Unknown twig extension: ' . $extensionValue); |
||
210 | } |
||
211 | |||
212 | // to support 'normal' Twig extensions we check the interface to determine what instantiation to do. |
||
213 | $implementsInterface = in_array( |
||
214 | 'phpDocumentor\Transformer\Writer\Twig\ExtensionInterface', |
||
215 | class_implements($extensionValue), |
||
216 | true |
||
217 | ); |
||
218 | |||
219 | $twigEnvironment->addExtension( |
||
220 | $implementsInterface ? new $extensionValue($project, $transformation) : new $extensionValue() |
||
221 | ); |
||
222 | } |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Uses the currently selected node and transformation to assemble the destination path for the file. |
||
227 | * |
||
228 | * The Twig writer accepts the use of a Query to be able to generate output for multiple objects using the same |
||
229 | * template. |
||
230 | * |
||
231 | * The given node is the result of such a query, or if no query given the selected element, and the transformation |
||
232 | * contains the destination file. |
||
233 | * |
||
234 | * Since it is important to be able to generate a unique name per element can the user provide a template variable |
||
235 | * in the name of the file. |
||
236 | * Such a template variable always resides between double braces and tries to take the node value of a given |
||
237 | * query string. |
||
238 | * |
||
239 | * Example: |
||
240 | * |
||
241 | * An artifact stating `classes/{{name}}.html` will try to find the |
||
242 | * node 'name' as a child of the given $node and use that value instead. |
||
243 | * |
||
244 | * @param DescriptorAbstract|ProjectDescriptor $node |
||
245 | * @throws InvalidArgumentException if no artifact is provided and no routing rule matches. |
||
246 | * @throws UnexpectedValueException if the provided node does not contain anything. |
||
247 | * @return false|string returns the destination location or false if generation should be aborted. |
||
248 | */ |
||
249 | protected function getDestinationPath($node, Transformation $transformation) |
||
250 | { |
||
251 | $writer = $this; |
||
252 | |||
253 | if (!$node) { |
||
254 | throw new UnexpectedValueException( |
||
255 | 'The transformation node in the twig writer is not expected to be false or null' |
||
256 | ); |
||
257 | } |
||
258 | |||
259 | if (!$transformation->getArtifact()) { |
||
260 | $rule = $this->routers->match($node); |
||
0 ignored issues
–
show
|
|||
261 | if (!$rule) { |
||
262 | throw new InvalidArgumentException( |
||
263 | 'No matching routing rule could be found for the given node, please provide an artifact location, ' |
||
264 | . 'encountered: ' . ($node === null ? 'NULL' : get_class($node)) |
||
265 | ); |
||
266 | } |
||
267 | |||
268 | $rule = new ForFileProxy($rule); |
||
269 | $url = $rule->generate($node); |
||
270 | if ($url === false || $url[0] !== DIRECTORY_SEPARATOR) { |
||
271 | return false; |
||
272 | } |
||
273 | |||
274 | $path = $transformation->getTransformer()->getTarget() . str_replace('/', DIRECTORY_SEPARATOR, $url); |
||
275 | } else { |
||
276 | $path = $transformation->getTransformer()->getTarget() |
||
277 | . DIRECTORY_SEPARATOR . $transformation->getArtifact(); |
||
278 | } |
||
279 | |||
280 | $finder = new Pathfinder(); |
||
281 | $destination = preg_replace_callback( |
||
282 | '/{{([^}]+)}}/', // explicitly do not use the unicode modifier; this breaks windows |
||
283 | function ($query) use ($node, $finder) { |
||
284 | // strip any surrounding \ or / |
||
285 | $filepart = trim((string) current($finder->find($node, $query[1])), '\\/'); |
||
286 | |||
287 | // make it windows proof |
||
288 | if (extension_loaded('iconv')) { |
||
289 | $filepart = iconv('UTF-8', 'ASCII//TRANSLIT', $filepart); |
||
290 | } |
||
291 | |||
292 | return strpos($filepart, '/') !== false |
||
293 | ? implode('/', array_map('urlencode', explode('/', $filepart))) |
||
294 | : implode('\\', array_map('urlencode', explode('\\', $filepart))); |
||
295 | }, |
||
296 | $path |
||
297 | ); |
||
298 | |||
299 | // replace any \ with the directory separator to be compatible with the |
||
300 | // current filesystem and allow the next file_exists to do its work |
||
301 | $destination = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $destination); |
||
302 | |||
303 | // create directory if it does not exist yet |
||
304 | if (!file_exists(dirname($destination))) { |
||
305 | mkdir(dirname($destination), 0777, true); |
||
306 | } |
||
307 | |||
308 | return $destination; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Returns the path belonging to the template. |
||
313 | */ |
||
314 | protected function getTemplatePath(Transformation $transformation): string |
||
315 | { |
||
316 | $parts = preg_split('[\\\\|/]', $transformation->getSource()); |
||
317 | |||
318 | return $parts[0] . DIRECTORY_SEPARATOR . $parts[1]; |
||
319 | } |
||
320 | |||
321 | public function setRouters(Queue $routers): void |
||
322 | { |
||
323 | $this->routers = $routers; |
||
324 | } |
||
325 | } |
||
326 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.