SourceCodeExtension::getCallableReflector()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 4
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Explicit Architecture POC,
7
 * which is created on top of the Symfony Demo application.
8
 *
9
 * (c) Herberto Graça <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Acme\App\Infrastructure\TemplateEngine\Twig\Extension\SourceCode;
16
17
use ReflectionFunction;
18
use ReflectionMethod;
19
use ReflectionObject;
20
use Twig\Environment;
21
use Twig\Extension\AbstractExtension;
22
use Twig\Template;
23
use Twig\TwigFunction;
24
use Twig_Template;
25
use Twig_TemplateWrapper;
26
27
/**
28
 * CAUTION: this is an extremely advanced Twig extension. It's used to get the
29
 * source code of the controller and the template used to render the current
30
 * page. If you are starting with Symfony, don't look at this code and consider
31
 * studying instead the code of the Md2HtmlExtension.php extension.
32
 *
33
 * @author Ryan Weaver <[email protected]>
34
 * @author Javier Eguiluz <[email protected]>
35
 */
36
class SourceCodeExtension extends AbstractExtension
37
{
38
    /**
39
     * @var callable| null
40
     */
41
    private $controller;
42
43
    public function setController(?callable $controller): void
44
    {
45
        $this->controller = $controller;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function getFunctions(): array
52
    {
53
        return [
54
            new TwigFunction('show_source_code', [$this, 'showSourceCode'], ['is_safe' => ['html'], 'needs_environment' => true]),
55
        ];
56
    }
57
58
    /**
59
     * @param string|Twig_Template|Twig_TemplateWrapper|array $template
60
     *
61
     * @throws \Twig_Error_Loader
62
     * @throws \Twig_Error_Runtime
63
     * @throws \Twig_Error_Syntax
64
     */
65
    public function showSourceCode(Environment $twig, $template): string
66
    {
67
        return $twig->render('@TemplateEngine/Twig/Extension/SourceCode/source_code.html.twig', [
68
            'controller' => $this->getController(),
69
            'template' => $this->getTemplateSource($twig->resolveTemplate($template)),
0 ignored issues
show
Bug introduced by
It seems like $twig->resolveTemplate($template) targeting Twig\Environment::resolveTemplate() can also be of type object<Twig\TemplateWrapper>; however, Acme\App\Infrastructure\...on::getTemplateSource() does only seem to accept object<Twig\Template>, maybe add an additional type check?

This check looks at variables that 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.

Loading history...
70
        ]);
71
    }
72
73
    private function getController(): ?array
74
    {
75
        // this happens for example for exceptions (404 errors, etc.)
76
        if ($this->controller === null) {
77
            return null;
78
        }
79
80
        $method = $this->getCallableReflector($this->controller);
81
82
        $classCode = file($method->getFileName());
83
        $methodCode = \array_slice($classCode, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1);
84
        $controllerCode = '    ' . $method->getDocComment() . "\n" . implode('', $methodCode);
85
86
        return [
87
            'file_path' => $method->getFileName(),
88
            'starting_line' => $method->getStartLine(),
89
            'source_code' => $this->unIndentCode($controllerCode),
90
        ];
91
    }
92
93
    /**
94
     * Gets a reflector for a callable.
95
     *
96
     * This logic is copied from Symfony\Component\HttpKernel\Controller\ControllerResolver::getArguments
97
     *
98
     * @throws \ReflectionException
99
     */
100
    private function getCallableReflector(callable $callable): \ReflectionFunctionAbstract
101
    {
102
        if (\is_array($callable)) {
103
            return new ReflectionMethod($callable[0], $callable[1]);
104
        }
105
106
        if (\is_object($callable) && !$callable instanceof \Closure) {
107
            $r = new ReflectionObject($callable);
108
109
            return $r->getMethod('__invoke');
110
        }
111
112
        return new ReflectionFunction($callable);
113
    }
114
115
    private function getTemplateSource(Template $template): array
116
    {
117
        $templateSource = $template->getSourceContext();
118
119
        return [
120
            // Twig templates are not always stored in files (they can be stored
121
            // in a database for example). However, for the needs of the Symfony
122
            // Demo app, we consider that all templates are stored in files and
123
            // that their file paths can be obtained through the source context.
124
            'file_path' => $templateSource->getPath(),
125
            'starting_line' => 1,
126
            'source_code' => $templateSource->getCode(),
127
        ];
128
    }
129
130
    /**
131
     * Utility method that "unIndents" the given $code when all its lines start
132
     * with a tabulation of four white spaces.
133
     */
134
    private function unIndentCode(string $code): string
135
    {
136
        $formattedCode = $code;
137
        $codeLines = explode("\n", $code);
138
139
        $indentedLines = array_filter($codeLines, function ($lineOfCode) {
140
            return $lineOfCode === '' || mb_substr($lineOfCode, 0, 4) === '    ';
141
        });
142
143
        if (\count($indentedLines) === \count($codeLines)) {
144
            $formattedCode = array_map(function ($lineOfCode) {
145
                return mb_substr($lineOfCode, 4);
146
            }, $codeLines);
147
            $formattedCode = implode("\n", $formattedCode);
148
        }
149
150
        return $formattedCode;
151
    }
152
}
153