1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* This file is part of phpDocumentor. |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
* |
11
|
|
|
* @link https://phpdoc.org |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace phpDocumentor\Transformer\Writer\Twig; |
15
|
|
|
|
16
|
|
|
use ArrayIterator; |
17
|
|
|
use Parsedown; |
18
|
|
|
use phpDocumentor\Descriptor\Collection; |
19
|
|
|
use phpDocumentor\Descriptor\Descriptor; |
20
|
|
|
use phpDocumentor\Descriptor\DescriptorAbstract; |
21
|
|
|
use phpDocumentor\Descriptor\Interfaces\VisibilityInterface; |
22
|
|
|
use phpDocumentor\Descriptor\NamespaceDescriptor; |
23
|
|
|
use phpDocumentor\Descriptor\PackageDescriptor; |
24
|
|
|
use phpDocumentor\Descriptor\ProjectDescriptor; |
25
|
|
|
use Twig\Extension\AbstractExtension; |
26
|
|
|
use Twig\Extension\GlobalsInterface; |
27
|
|
|
use Twig\TwigFilter; |
28
|
|
|
use Twig\TwigFunction; |
29
|
|
|
use function array_unshift; |
30
|
|
|
use function count; |
31
|
|
|
use function method_exists; |
32
|
|
|
use function str_replace; |
33
|
|
|
use function strtolower; |
34
|
|
|
use function var_export; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Basic extension adding phpDocumentor specific functionality for Twig |
38
|
|
|
* templates. |
39
|
|
|
* |
40
|
|
|
* Global variables: |
41
|
|
|
* |
42
|
|
|
* - *ast_node*, the current $data element |
43
|
|
|
* |
44
|
|
|
* Functions: |
45
|
|
|
* |
46
|
|
|
* - *path(string) *, converts the given relative path to be based of the projects |
47
|
|
|
* root instead of the current directory |
48
|
|
|
* |
49
|
|
|
* Filters: |
50
|
|
|
* |
51
|
|
|
* - *markdown*, converts the associated text from Markdown formatting to HTML. |
52
|
|
|
* - *trans*, translates the given string |
53
|
|
|
* - *route*, attempts to generate a URL for a given Descriptor |
54
|
|
|
* - *sort_desc*, sorts the given objects by their Name property/getter in a descending fashion |
55
|
|
|
* - *sort_asc*, sorts the given objects by their Name property/getter in a ascending fashion |
56
|
|
|
*/ |
57
|
|
|
final class Extension extends AbstractExtension implements ExtensionInterface, GlobalsInterface |
58
|
|
|
{ |
59
|
|
|
/** @var ProjectDescriptor */ |
60
|
|
|
private $data; |
61
|
|
|
|
62
|
|
|
/** @var LinkRenderer */ |
63
|
|
|
private $routeRenderer; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Registers the structure and transformation with this extension. |
67
|
|
|
* |
68
|
|
|
* @param ProjectDescriptor $project Represents the complete Abstract Syntax Tree. |
69
|
|
|
*/ |
70
|
|
|
public function __construct( |
71
|
|
|
ProjectDescriptor $project, |
72
|
|
|
?LinkRenderer $routeRenderer = null |
73
|
|
|
) { |
74
|
|
|
$this->data = $project; |
75
|
|
|
$this->routeRenderer = $routeRenderer->withProject($project); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Sets the destination directory relative to the Project's Root. |
80
|
|
|
* |
81
|
|
|
* The destination is the target directory containing the resulting |
82
|
|
|
* file. This destination is relative to the Project's root and can |
83
|
|
|
* be used for the calculation of nesting depths, etc. |
84
|
|
|
* |
85
|
|
|
* @see EnvironmentFactory for the invocation of this method. |
86
|
|
|
*/ |
87
|
|
|
public function setDestination(string $destination) : void |
88
|
|
|
{ |
89
|
|
|
$this->routeRenderer->setDestination($destination); |
|
|
|
|
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Returns an array of global variables to inject into a Twig template. |
94
|
|
|
* |
95
|
|
|
* @return array<string, ProjectDescriptor|bool> |
|
|
|
|
96
|
|
|
*/ |
97
|
|
|
public function getGlobals() : array |
98
|
|
|
{ |
99
|
|
|
return [ |
100
|
|
|
'project' => $this->data, |
101
|
|
|
'usesNamespaces' => count($this->data->getNamespace()->getChildren()) > 0, |
102
|
|
|
'usesPackages' => count($this->data->getPackage()->getChildren()) > 1, |
103
|
|
|
]; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns a listing of all functions that this extension adds. |
108
|
|
|
* |
109
|
|
|
* This method is automatically used by Twig upon registering this |
110
|
|
|
* extension (which is done automatically by phpDocumentor) to determine |
111
|
|
|
* an additional list of functions. |
112
|
|
|
* |
113
|
|
|
* See the Class' DocBlock for a listing of functionality added by this |
114
|
|
|
* Extension. |
115
|
|
|
* |
116
|
|
|
* @return TwigFunction[] |
117
|
|
|
*/ |
118
|
|
|
public function getFunctions() : array |
119
|
|
|
{ |
120
|
|
|
return [ |
121
|
|
|
new TwigFunction('path', [$this->routeRenderer, 'convertToRootPath']), |
122
|
|
|
new TwigFunction('link', [$this->routeRenderer, 'link']), |
123
|
|
|
new TwigFunction( |
124
|
|
|
'breadcrumbs', |
125
|
|
|
static function (DescriptorAbstract $baseNode) { |
126
|
|
|
$results = []; |
127
|
|
|
$namespace = $baseNode instanceof NamespaceDescriptor |
128
|
|
|
? $baseNode->getParent() |
129
|
|
|
: $baseNode->getNamespace(); |
130
|
|
|
while ($namespace instanceof NamespaceDescriptor && $namespace->getName() !== '\\') { |
131
|
|
|
array_unshift($results, $namespace); |
132
|
|
|
$namespace = $namespace->getParent(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
return $results; |
136
|
|
|
} |
137
|
|
|
), |
138
|
|
|
new TwigFunction( |
139
|
|
|
'packages', |
140
|
|
|
static function (DescriptorAbstract $baseNode) { |
141
|
|
|
$results = []; |
142
|
|
|
$package = $baseNode instanceof PackageDescriptor |
143
|
|
|
? $baseNode->getParent() |
144
|
|
|
: $baseNode->getPackage(); |
145
|
|
|
while ($package instanceof PackageDescriptor && $package->getName() !== '\\') { |
146
|
|
|
array_unshift($results, $package); |
147
|
|
|
$package = $package->getParent(); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return $results; |
151
|
|
|
} |
152
|
|
|
), |
153
|
|
|
new TwigFunction('methods', static function (DescriptorAbstract $descriptor) : Collection { |
154
|
|
|
$methods = new Collection(); |
155
|
|
|
if (method_exists($descriptor, 'getInheritedMethods')) { |
156
|
|
|
$methods = $methods->merge($descriptor->getInheritedMethods()); |
|
|
|
|
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if (method_exists($descriptor, 'getMagicMethods')) { |
160
|
|
|
$methods = $methods->merge($descriptor->getMagicMethods()); |
|
|
|
|
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
if (method_exists($descriptor, 'getMethods')) { |
164
|
|
|
$methods = $methods->merge($descriptor->getMethods()); |
|
|
|
|
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
return $methods; |
168
|
|
|
}), |
169
|
|
|
new TwigFunction('properties', static function (DescriptorAbstract $descriptor) : Collection { |
170
|
|
|
$properties = new Collection(); |
171
|
|
|
if (method_exists($descriptor, 'getInheritedProperties')) { |
172
|
|
|
$properties = $properties->merge($descriptor->getInheritedProperties()); |
|
|
|
|
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
if (method_exists($descriptor, 'getMagicProperties')) { |
176
|
|
|
$properties = $properties->merge($descriptor->getMagicProperties()); |
|
|
|
|
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
if (method_exists($descriptor, 'getProperties')) { |
180
|
|
|
$properties = $properties->merge($descriptor->getProperties()); |
|
|
|
|
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
return $properties; |
184
|
|
|
}), |
185
|
|
|
new TwigFunction('constants', static function (DescriptorAbstract $descriptor) : Collection { |
186
|
|
|
$constants = new Collection(); |
187
|
|
|
if (method_exists($descriptor, 'getInheritedConstants')) { |
188
|
|
|
$constants = $constants->merge($descriptor->getInheritedConstants()); |
|
|
|
|
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
if (method_exists($descriptor, 'getMagicConstants')) { |
192
|
|
|
$constants = $constants->merge($descriptor->getMagicConstants()); |
|
|
|
|
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if (method_exists($descriptor, 'getConstants')) { |
196
|
|
|
$constants = $constants->merge($descriptor->getConstants()); |
|
|
|
|
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
return $constants; |
200
|
|
|
}), |
201
|
|
|
]; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Returns a list of all filters that are exposed by this extension. |
206
|
|
|
* |
207
|
|
|
* @return TwigFilter[] |
208
|
|
|
*/ |
209
|
|
|
public function getFilters() : array |
210
|
|
|
{ |
211
|
|
|
$parser = Parsedown::instance(); |
212
|
|
|
$parser->setSafeMode(true); |
213
|
|
|
$routeRenderer = $this->routeRenderer; |
214
|
|
|
|
215
|
|
|
return [ |
216
|
|
|
'markdown' => new TwigFilter( |
217
|
|
|
'markdown', |
218
|
|
|
static function (string $value) use ($parser) : string { |
219
|
|
|
return str_replace( |
220
|
|
|
['<pre>', '<code>'], |
221
|
|
|
['<pre class="prettyprint">', '<code class="prettyprint">'], |
222
|
|
|
$parser->text($value) |
223
|
|
|
); |
224
|
|
|
}, |
225
|
|
|
['is_safe' => ['all']] |
226
|
|
|
), |
227
|
|
|
'trans' => new TwigFilter( |
228
|
|
|
'trans', |
229
|
|
|
static function ($value) { |
230
|
|
|
return $value; |
231
|
|
|
} |
232
|
|
|
), |
233
|
|
|
'route' => new TwigFilter( |
234
|
|
|
'route', |
235
|
|
|
static function ( |
236
|
|
|
$value, |
237
|
|
|
string $presentation = LinkRenderer::PRESENTATION_NORMAL |
238
|
|
|
) use ($routeRenderer) { |
239
|
|
|
return $routeRenderer->render($value, $presentation); |
240
|
|
|
}, |
241
|
|
|
['is_safe' => ['all']] |
242
|
|
|
), |
243
|
|
|
'sort' => new TwigFilter( |
244
|
|
|
'sort_*', |
245
|
|
|
/** @var Collection<Descriptor> $collection */ |
|
|
|
|
246
|
|
|
static function (string $direction, Collection $collection) : ArrayIterator { |
247
|
|
|
$iterator = $collection->getIterator(); |
248
|
|
|
$iterator->uasort( |
249
|
|
|
static function (Descriptor $a, Descriptor $b) use ($direction) { |
250
|
|
|
$aElem = strtolower($a->getName()); |
251
|
|
|
$bElem = strtolower($b->getName()); |
252
|
|
|
if ($aElem === $bElem) { |
253
|
|
|
return 0; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
if (($direction === 'asc' && $aElem > $bElem) || |
257
|
|
|
($direction === 'desc' && $aElem < $bElem) |
258
|
|
|
) { |
259
|
|
|
return 1; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
return -1; |
263
|
|
|
} |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
return $iterator; |
267
|
|
|
} |
268
|
|
|
), |
269
|
|
|
'sortByVisibility' => new TwigFilter( |
270
|
|
|
'sortByVisibility', |
271
|
|
|
/** @var Collection<Descriptor> $collection */ |
|
|
|
|
272
|
|
|
static function (Collection $collection) : ArrayIterator { |
273
|
|
|
$visibilityOrder = [ |
274
|
|
|
'public' => 0, |
275
|
|
|
'protected' => 1, |
276
|
|
|
'private' => 2, |
277
|
|
|
]; |
278
|
|
|
$iterator = $collection->getIterator(); |
279
|
|
|
$iterator->uasort( |
280
|
|
|
static function (Descriptor $a, Descriptor $b) use ($visibilityOrder) { |
281
|
|
|
$prio = 0; |
282
|
|
|
if ($a instanceof VisibilityInterface && $b instanceof VisibilityInterface) { |
283
|
|
|
$prio = ($visibilityOrder[$a->getVisibility()] ?? 0) <=> ($visibilityOrder[$b->getVisibility()] ?? 0); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
if ($prio !== 0) { |
287
|
|
|
return $prio; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
$aElem = strtolower($a->getName()); |
291
|
|
|
$bElem = strtolower($b->getName()); |
292
|
|
|
|
293
|
|
|
return $aElem <=> $bElem; |
294
|
|
|
} |
295
|
|
|
); |
296
|
|
|
|
297
|
|
|
return $iterator; |
298
|
|
|
} |
299
|
|
|
), |
300
|
|
|
'export' => new TwigFilter( |
301
|
|
|
'export', |
302
|
|
|
static function ($var) { |
303
|
|
|
return var_export($var, true); |
304
|
|
|
} |
305
|
|
|
), |
306
|
|
|
]; |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.