8000 Add command to list twig functions, filters, globals and tests · symfony/symfony@7d61154 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7d61154

Browse files
Seldaekfabpot
authored andcommitted
Add command to list twig functions, filters, globals and tests
1 parent caabd41 commit 7d61154

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed
Lines changed: 187 additions & 0 deletions
F438
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\TwigBundle\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
15+
use Symfony\Component\Console\Input\InputArgument;
16+
use Symfony\Component\Console\Input\InputOption;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
20+
/**
21+
* Lists twig functions, filters, globals and tests present in the current project
22+
*
23+
* @author Jordi Boggiano <j.boggiano@seld.be>
24+
*/
25+
class DebugCommand extends ContainerAwareCommand
26+
{
27+
protected function configure()
28+
{
29+
$this
30+
->setName('twig:debug')
31+
->setDefinition(array(
32+
new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'),
33+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Output format: text or json', 'text'),
34+
))
35+
->setDescription('Shows a list of twig functions, filters, globals and tests')
36+
->setHelp(<<<EOF
37+
The <info>%command.name%</info> command outputs a list of twig functions,
38+
filters, globals and tests. Output can be filtered with an optional argument.
39+
40+
<info>php %command.full_name%</info>
41+
42+
The command lists all functions, filters, etc.
43+
44+
<info>php %command.full_name% date</info>
45+
46+
The command lists everything that contains the word date.
47+
48+
<info>php %command.full_name% --format=json</info>
49+
50+
The command lists everything in a machine readable json format.
51+
EOF
52+
)
53+
;
54+
}
55+
56+
protected function execute(InputInterface $input, OutputInterface $output)
57+
{
58+
$twig = $this->getContainer()->get('twig');
59+
$types = array('functions', 'filters', 'tests', 'globals');
60+
61+
if ($input->getOption('format') === 'json') {
62+
$data = array();
63+
foreach ($types as $type) {
64+
foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
65+
$data[$type][$name] = $this->getMetadata($type, $entity);
66+
}
67+
}
68+
$data['tests'] = array_keys($data['tests']);
69+
$output->writeln(json_encode($data));
70+
71+
return 0;
72+
}
73+
74+
$filter = $input->getArgument('filter');
75+
76+
foreach ($types as $index => $type) {
77+
$items = array();
78+
foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
79+
if (!$filter || false !== strpos($name, $filter)) {
80+
$items[$name] = $name . $this->getPrettyMetadata($type, $entity);
81+
}
82+
}
83+
84+
if (!$items) {
85+
continue;
86+
}
87+
if ($index > 0) {
88+
$output->writeln('');
89+
}
90+
$output->writeln('<info>' . ucfirst($type) . '</info>');
91+
ksort($items);
92+
foreach ($items as $item) {
93+
$output->writeln(' '.$item);
94+
}
95+
}
96+
97+
return 0;
98+
}
99+
100+
private function getMetadata($type, $entity)
101+
{
102+
if ($type === 'globals') {
103+
return $entity;
104+
}
105+
if ($type === 'tests') {
106+
return;
107+
}
108+
if ($type === 'functions' || $type === 'filters') {
109+
$args = array();
110+
$cb = $entity->getCallable();
111+
if (is_null($cb)) {
112+
return;
113+
}
114+
if (is_array($cb)) {
115+
if (!method_exists($cb[0], $cb[1])) {
116+
return;
117+
}
118+
$refl = new \ReflectionMethod($cb[0], $cb[1]);
119+
} elseif (is_object($cb) && is_callable($cb)) {
120+
$refl = new \ReflectionMethod($cb, '__invoke');
121+
} elseif (function_exists($cb)) {
122+
$refl = new \ReflectionFunction($cb);
123+
} elseif (is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) {
124+
$refl = new \ReflectionMethod($m[1], $m[2]);
125+
} else {
126+
throw new \UnexpectedValueException('Unsupported callback type');
127+
}
128+
129+
// filter out context/environment args
130+
$args = array_filter($refl->getParameters(), function ($param) use ($entity) {
131+
if ($entity->needsContext() && $param->getName() === 'context') {
132+
return false;
133+
}
134+
135+
return !$param->getClass() || $param->getClass()->getName() !== 'Twig_Environment';
136+
});
137+
138+
// format args
139+
$args = array_map(function ($param) {
140+
if ($param->isDefaultValueAvailable()) {
141+
return $param->getName() . ' = ' . json_encode($param->getDefaultValue());
142+
}
143+
144+
return $param->getName();
145+
}, $args);
146+
147+
if ($type === 'filters') {
148+
// remove the value the filter is applied on
149+
array_shift($args);
150+
}
151+
152+
return $args;
153+
}
154+
}
155+
156+
private function getPrettyMetadata($type, $entity)
157+
{
158+
if ($type === 'tests') {
159+
return '';
160+
}
161+
162+
try {
163+
$meta = $this->getMetadata($type, $entity);
164+
if ($meta === null) {
165+
return '(unknown?)';
166+
}
167+
} catch (\UnexpectedValueException $e) {
168+
return ' <error>' . $e->getMessage() . '</error>';
169+
}
170+
171+
if ($type === 'globals') {
172+
if (is_object($meta)) {
173+
return ' = object('.get_class($meta).')';
174+
}
175+
176+
return ' = '.substr(@json_encode($meta), 0, 50);
177+
}
178+
179+
if ($type === 'functions') {
180+
return '(' . implode(', ', $meta) . ')';
181+
}
182+
183+
if ($type === 'filters') {
184+
return $meta ? '(' . implode(', ', $meta) . ')' : '';
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)
0