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; |
||
0 ignored issues
–
show
|
|||
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); |
||
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 for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.