Completed
Push — develop ( fbdd82...317691 )
by Mike
09:29
created

src/Application/Renderer/TwigRenderer.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace phpDocumentor\Application\Renderer;
4
5
use phpDocumentor\DomainModel\Path;
6
use phpDocumentor\Application\Renderer\Template\Action\Twig;
7
use phpDocumentor\Application\Renderer\TwigRenderer\Extension;
8
use phpDocumentor\Application\Renderer\Template\Action;
9
use phpDocumentor\Application\Renderer\TwigRenderer\Pathfinder;
10
use phpDocumentor\DomainModel\Renderer\RenderContext;
11
use phpDocumentor\Infrastructure\Renderer\Template\LocalPathsRepository;
12
use phpDocumentor\DomainModel\Renderer\Router\ForFileProxy;
13
use phpDocumentor\DomainModel\Renderer\Router\Queue;
14
use phpDocumentor\DomainModel\ReadModel\ReadModel;
15
use phpDocumentor\DomainModel\ReadModel\Factory;
16
use phpDocumentor\DomainModel\ReadModel\ReadModels;
17
18
class TwigRenderer
19
{
20
    public function render(ReadModel $view, Path $destination, $template = null)
0 ignored issues
show
The parameter $view is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $destination is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $template is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
21
    {
22
        // TODO: Implement render() method.
23
    }
24
25
    /** @var Pathfinder */
26
    private $pathfinder;
27
28
    /** @var Queue */
29
    private $routers;
30
31
    /** @var LocalPathsRepository */
32
    private $fileRepository;
33
34
    /** @var string */
35
    private $cacheFolder = '';
36
37
    /** @var Factory */
38
    private $viewFactory;
39
40
    public function __construct(
41
        Pathfinder $pathfinder,
42
        Queue $routers,
43
        LocalPathsRepository $fileRepository,
44
        Factory $viewFactory,
45
        $cacheFolder = null
46
    ) {
47
        if ($cacheFolder === null) {
48
            $cacheFolder = sys_get_temp_dir() . '/phpdoc-twig-cache';
49
        }
50
51
        $this->pathfinder       = $pathfinder;
52
        $this->routers          = $routers;
53
        $this->fileRepository   = $fileRepository;
54
        $this->cacheFolder      = $cacheFolder;
55
        $this->viewFactory      = $viewFactory;
56
    }
57
58
    /**
59
     * Executes the activities that this Action represents.
60
     *
61
     * @param Action|Twig $action
62
     *
63
     * @return void
64
     */
65
    public function __invoke(Action $action)
66
    {
67
        $dataView = $this->viewFactory->create($action->getDataView(), $action->getRenderPass()->getDocumentation());
68
        $views    = new ReadModels([$dataView->getName() => $dataView()]);
69
70
        // TODO: Move path finding to View
71
        $nodes = $this->pathfinder->find($dataView(), $action->getQuery());
72
73
        foreach ($nodes as $node) {
74
            if (!$node) {
75
                continue;
76
            }
77
78
            if (! ($action->getDestination())) {
79
                $rule = $this->routers->match($node);
80
                if (!$rule) {
81
                    throw new \InvalidArgumentException(
82
                        'No matching routing rule could be found for the given node, please provide an artifact '
83
                        . 'location, encountered: ' . ($node === null ? 'NULL' : get_class($node))
84
                    );
85
                }
86
87
                $rule = new ForFileProxy($rule);
88
                $url  = $rule->generate($node);
89
                if ($url === false || $url[0] !== DIRECTORY_SEPARATOR) {
90
                    $destination = false;
91
                } else {
92
                    $destination = $action->getDestination() . $url;
93
                }
94
            } else {
95
                $destination = $this->getDestinationPath($node, $action->getDestination());
96
            }
97
98
            if ($destination === false) {
99
                continue;
100
            }
101
102
            $destination = $action->getRenderPass()->getDestination() . '/' . ltrim($destination, '\\/');
103
104
            // create directory if it does not exist yet
105
            if (!file_exists(dirname($destination))) {
106
                mkdir(dirname($destination), 0777, true);
107
            }
108
109
            // move to local variable because we want to add to it without affecting other runs
110
            $templatesFolders = $this->fileRepository->listLocations($action->getTemplate());
111
112
            $environment = new \Twig_Environment(
113
                new \Twig_Loader_Filesystem($templatesFolders),
114
                array('cache' => $this->cacheFolder, 'auto_reload' => true)
115
            );
116
117
            $this->addPhpDocumentorExtension($views, $destination, $environment, $action->getRenderPass());
118
            // $this->addExtensionsFromTemplateConfiguration($transformation, $project, $environment);
119
            $environment->addGlobal('node', $node);
120
121
            $html = $environment->render((string)$action->getView());
122
            file_put_contents($destination, $html);
123
        }
124
    }
125
126
    /**
127
     * Adds the phpDocumentor base extension to the Twig Environment.
128
     *
129
     * @param ReadModels             $views
130
     * @param string            $destination
131
     * @param \Twig_Environment $twigEnvironment
132
     *
133
     * @return void
134
     */
135
    private function addPhpDocumentorExtension(
136
        ReadModels $views,
137
        $destination,
138
        \Twig_Environment $twigEnvironment,
139
        RenderContext $renderPass
140
    ) {
141
        $baseExtension = new Extension($views);
142
        $baseExtension->setDestination(substr($destination, strlen($renderPass->getDestination()) + 1));
0 ignored issues
show
The method getDestination() does not seem to exist on object<phpDocumentor\Dom...Renderer\RenderContext>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
143
        $baseExtension->setRouters($this->routers);
144
        $twigEnvironment->addExtension($baseExtension);
145
    }
146
147
    /**
148
     * Tries to add any custom extensions that have been defined in the template or the transformation's configuration.
149
     *
150
     * This method will read the `twig-extension` parameter of the transformation (which inherits the template's
151
     * parameter set) and try to add those extensions to the environment.
152
     *
153
     * @param Transformation    $transformation
154
     * @param ProjectInterface  $project
155
     * @param \Twig_Environment $twigEnvironment
156
     *
157
     * @throws \InvalidArgumentException if a twig-extension should be loaded but it could not be found.
158
     *
159
     * @return void
160
     */
161
//    protected function addExtensionsFromTemplateConfiguration(
162
//        Transformation $transformation,
163
//        ProjectInterface $project,
164
//        \Twig_Environment $twigEnvironment
165
//    ) {
166
//        $isDebug = $transformation->getParameter('twig-debug')
167
//            ? $transformation->getParameter('twig-debug')->getValue()
168
//            : false;
169
//        if ($isDebug == 'true') {
170
//            $twigEnvironment->enableDebug();
171
//            $twigEnvironment->enableAutoReload();
172
//            $twigEnvironment->addExtension(new \Twig_Extension_Debug());
173
//        }
174
//
175
//        /** @var Template\Parameter $extension */
176
//        foreach ($transformation->getParametersWithKey('twig-extension') as $extension) {
177
//            $extensionValue = $extension->getValue();
178
//            if (!class_exists($extensionValue)) {
179
//                throw new \InvalidArgumentException('Unknown twig extension: ' . $extensionValue);
180
//            }
181
//
182
//            // to support 'normal' Twig extensions we check the interface to determine what instantiation to do.
183
//            $implementsInterface = in_array(
184
//                'phpDocumentor\Plugin\Twig\ExtensionInterface',
185
//                class_implements($extensionValue)
186
//            );
187
//
188
//            $twigEnvironment->addExtension(
189
//                $implementsInterface ? new $extensionValue($project, $transformation) : new $extensionValue()
190
//            );
191
//        }
192
//    }
193
194
    /**
195
     * Uses the currently selected node and transformation to assemble the destination path for the file.
196
     *
197
     * The Twig writer accepts the use of a Query to be able to generate output for multiple objects using the same
198
     * template.
199
     *
200
     * The given node is the result of such a query, or if no query given the selected element, and the transformation
201
     * contains the destination file.
202
     *
203
     * Since it is important to be able to generate a unique name per element can the user provide a template variable
204
     * in the name of the file.
205
     * Such a template variable always resides between double braces and tries to take the node value of a given
206
     * query string.
207
     *
208
     * Example:
209
     *
210
     *   An artifact stating `classes/{{name}}.html` will try to find the
211
     *   node 'name' as a child of the given $node and use that value instead.
212
     *
213
     * @param DescriptorAbstract $node
214
     *
215
     * @throws \InvalidArgumentException if no artifact is provided and no routing rule matches.
216
     * @throws \UnexpectedValueException if the provided node does not contain anything.
217
     *
218
     * @return string|false returns the destination location or false if generation should be aborted.
219
     */
220
    private function getDestinationPath($node, $destination)
221
    {
222
        $destination = preg_replace_callback(
223
            '/{{([^}]+)}}/', // explicitly do not use the unicode modifier; this breaks windows
224
            function ($query) use ($node) {
225
                // strip any surrounding \ or /
226
                $filepart = trim((string)current($this->pathfinder->find($node, $query[1])), '\\/');
227
                $filepart = implode('/', array_map('urlencode', explode('/', $filepart)));
228
229
                return $filepart;
230
            },
231
            $destination
232
        );
233
234
        return $destination;
235
    }
236
}
237