Completed
Push — master ( 8dfbb4...5f3e12 )
by Vladimir
9s
created

Compiler   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 372
Duplicated Lines 8.06 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 80.15%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 14
dl 30
loc 372
ccs 109
cts 136
cp 0.8015
rs 9.8
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setRedirectTemplate() 0 4 1
A setTargetFolder() 0 4 1
A setPageViews() 0 5 1
A compileAll() 0 7 2
A compileSome() 0 11 3
B compilePageView() 0 25 4
A compileStaticPageView() 0 8 1
A compileRepeaterPageViews() 17 17 2
A compileContentItem() 13 13 1
A compileStandardRedirects() 0 15 2
A compileExpandedRedirects() 0 22 3
A renderRepeaterPageView() 0 14 1
A renderDynamicPageView() 0 9 1
A renderStaticPageView() 0 10 1
A createTwigTemplate() 0 18 2
B compileDynamicPageViews() 0 23 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx;
9
10
use allejo\stakx\Command\BuildableCommand;
11
use allejo\stakx\Document\ContentItem;
12
use allejo\stakx\Document\DynamicPageView;
13
use allejo\stakx\Document\PageView;
14
use allejo\stakx\Document\RepeaterPageView;
15
use allejo\stakx\FrontMatter\ExpandedValue;
16
use allejo\stakx\Manager\BaseManager;
17
use allejo\stakx\Manager\TwigManager;
18
use allejo\stakx\System\Folder;
19
use Twig_Environment;
20
use Twig_Error_Syntax;
21
use Twig_Source;
22
use Twig_Template;
23
24
/**
25
 * This class takes care of rendering the Twig body of PageViews with the respective information and it also takes care
26
 * of writing the rendered Twig to the filesystem.
27
 *
28
 * @internal
29
 *
30
 * @since 0.1.1
31
 */
32
class Compiler extends BaseManager
33
{
34
    /** @var string|false */
35
    private $redirectTemplate;
36
37
    /** @var PageView[] */
38
    private $pageViewsFlattened;
39
40
    /** @var PageView[][] */
41
    private $pageViews;
42
43
    /** @var Folder */
44
    private $folder;
45
46
    /** @var Twig_Environment */
47
    private $twig;
48
49 14
    public function __construct()
50
    {
51 14
        parent::__construct();
52
53 14
        $this->twig = TwigManager::getInstance();
54 14
    }
55
56
    /**
57
     * @param string|false $template
58
     */
59
    public function setRedirectTemplate($template)
60
    {
61
        $this->redirectTemplate = $template;
62
    }
63
64
    /**
65
     * @param Folder $folder
66
     */
67 14
    public function setTargetFolder(Folder $folder)
68
    {
69 14
        $this->folder = $folder;
70 14
    }
71
72
    /**
73
     * @param PageView[][] $pageViews
74
     * @param PageView[]   $pageViewsFlattened
75
     */
76 14
    public function setPageViews(array &$pageViews, array &$pageViewsFlattened)
77
    {
78 14
        $this->pageViews = &$pageViews;
79 14
        $this->pageViewsFlattened = &$pageViewsFlattened;
80 14
    }
81
82
    ///
83
    // IO Functionality
84
    ///
85
86
    /**
87
     * Compile all of the PageViews registered with the compiler.
88
     *
89
     * @since 0.1.0
90
     */
91 14
    public function compileAll()
92
    {
93 14
        foreach ($this->pageViewsFlattened as &$pageView)
94
        {
95 14
            $this->compilePageView($pageView);
96 14
        }
97 14
    }
98
99
    public function compileSome($filter = array())
100
    {
101
        /** @var PageView $pageView */
102
        foreach ($this->pageViewsFlattened as &$pageView)
103
        {
104
            if ($pageView->hasTwigDependency($filter['namespace'], $filter['dependency']))
105
            {
106
                $this->compilePageView($pageView);
107
            }
108
        }
109
    }
110
111
    /**
112
     * Compile an individual PageView item.
113
     *
114
     * This function will take care of determining *how* to treat the PageView and write the compiled output to a the
115
     * respective target file.
116
     *
117
     * @param DynamicPageView|RepeaterPageView|PageView $pageView The PageView that needs to be compiled
118
     *
119
     * @since 0.1.1
120
     */
121 14
    private function compilePageView(&$pageView)
122
    {
123 14
        $this->output->debug('Compiling {type} PageView: {pageview}', array(
124 14
            'pageview' => $pageView->getRelativeFilePath(),
125 14
            'type' => $pageView->getType()
126 14
        ));
127
128 14
        switch ($pageView->getType())
129
        {
130 14
            case PageView::STATIC_TYPE:
131 10
                $this->compileStaticPageView($pageView);
132 10
                $this->compileStandardRedirects($pageView);
133 10
                break;
134
135 4
            case PageView::DYNAMIC_TYPE:
136 2
                $this->compileDynamicPageViews($pageView);
1 ignored issue
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\PageView> is not a sub-type of object<allejo\stakx\Document\DynamicPageView>. It seems like you assume a child class of the class allejo\stakx\Document\PageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
137 2
                $this->compileStandardRedirects($pageView);
138 2
                break;
139
140 2
            case PageView::REPEATER_TYPE:
141 2
                $this->compileRepeaterPageViews($pageView);
1 ignored issue
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\PageView> is not a sub-type of object<allejo\stakx\Document\RepeaterPageView>. It seems like you assume a child class of the class allejo\stakx\Document\PageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
142 2
                $this->compileExpandedRedirects($pageView);
143 2
                break;
144 14
        }
145 14
    }
146
147
    /**
148
     * Write the compiled output for a static PageView.
149
     *
150
     * @param PageView $pageView
151
     *
152
     * @since 0.1.1
153
     */
154 10
    private function compileStaticPageView(&$pageView)
155
    {
156 10
        $targetFile = $pageView->getTargetFile();
157 10
        $output = $this->renderStaticPageView($pageView);
158
159 10
        $this->output->notice('Writing file: {file}', array('file' => $targetFile));
160 10
        $this->folder->writeFile($targetFile, $output);
161 10
    }
162
163
    /**
164
     * Write the compiled output for a dynamic PageView.
165
     *
166
     * @param DynamicPageView $pageView
167
     *
168
     * @since 0.1.1
169
     */
170 3
    private function compileDynamicPageViews(&$pageView)
171
    {
172 2
        $contentItems = $pageView->getContentItems();
173 2
        $template = $this->createTwigTemplate($pageView);
174
175 2
        foreach ($contentItems as &$contentItem)
176
        {
177 2
            if ($contentItem->isDraft() && !Service::getParameter(BuildableCommand::USE_DRAFTS))
178 2
            {
179 1
                $this->output->debug('{file}: marked as a draft', array(
180 1
                    'file' => $contentItem->getRelativeFilePath()
181 1
                ));
182
183 1
                continue;
184
            }
185
186 2
            $targetFile = $contentItem->getTargetFile();
187 3
            $output = $this->renderDynamicPageView($template, $pageView, $contentItem);
188
189 2
            $this->output->notice('Writing file: {file}', array('file' => $targetFile));
190 2
            $this->folder->writeFile($targetFile, $output);
191 2
        }
192 2
    }
193
194
    /**
195
     * Write the compiled output for a repeater PageView.
196
     *
197
     * @param RepeaterPageView $pageView
198
     *
199
     * @since 0.1.1
200
     */
201 2 View Code Duplication
    private function compileRepeaterPageViews(&$pageView)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203 2
        $pageView->rewindPermalink();
204
205 2
        $template = $this->createTwigTemplate($pageView);
206 2
        $permalinks = $pageView->getRepeaterPermalinks();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class allejo\stakx\Document\PageView as the method getRepeaterPermalinks() does only exist in the following sub-classes of allejo\stakx\Document\PageView: allejo\stakx\Document\RepeaterPageView. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
207
208 2
        foreach ($permalinks as $permalink)
209
        {
210 2
            $pageView->bumpPermalink();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class allejo\stakx\Document\PageView as the method bumpPermalink() does only exist in the following sub-classes of allejo\stakx\Document\PageView: allejo\stakx\Document\RepeaterPageView. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
211 2
            $targetFile = $pageView->getTargetFile();
212 2
            $output = $this->renderRepeaterPageView($template, $pageView, $permalink);
213
214 2
            $this->output->notice('Writing repeater file: {file}', array('file' => $targetFile));
215 2
            $this->folder->writeFile($targetFile, $output);
216 2
        }
217 2
    }
218
219
    /**
220
     * @deprecated
221
     *
222
     * @todo This function needs to be rewritten or removed. Something
223
     *
224
     * @param ContentItem $contentItem
225
     */
226 View Code Duplication
    public function compileContentItem(&$contentItem)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
    {
228
        $pageView = $contentItem->getPageView();
229
        $template = $this->createTwigTemplate($pageView);
230
231
        $contentItem->evaluateFrontMatter($pageView->getFrontMatter(false));
232
233
        $targetFile = $contentItem->getTargetFile();
234
        $output = $this->renderDynamicPageView($template, $pageView, $contentItem);
235
236
        $this->output->notice('Writing file: {file}', array('file' => $targetFile));
237
        $this->folder->writeFile($targetFile, $output);
238
    }
239
240
    ///
241
    // Redirect handling
242
    ///
243
244
    /**
245
     * Write redirects for standard redirects.
246
     *
247
     * @param PageView $pageView
248
     *
249
     * @since 0.1.1
250
     */
251 12
    private function compileStandardRedirects(&$pageView)
252
    {
253 12
        $redirects = $pageView->getRedirects();
254
255 12
        foreach ($redirects as $redirect)
0 ignored issues
show
Bug introduced by
The expression $redirects of type null|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
256
        {
257 4
            $redirectPageView = PageView::createRedirect(
258 4
                $redirect,
259 4
                $pageView->getPermalink(),
260 4
                $this->redirectTemplate
261 4
            );
262
263 4
            $this->compileStaticPageView($redirectPageView);
264 12
        }
265 12
    }
266
267
    /**
268
     * Write redirects for expanded redirects.
269
     *
270
     * @param RepeaterPageView $pageView
271
     *
272
     * @since 0.1.1
273
     */
274 2
    private function compileExpandedRedirects(&$pageView)
275
    {
276 2
        $permalinks = $pageView->getRepeaterPermalinks();
277
278
        /** @var ExpandedValue[] $repeaterRedirect */
279 2
        foreach ($pageView->getRepeaterRedirects() as $repeaterRedirect)
280
        {
281
            /**
282
             * @var int           $index
283
             * @var ExpandedValue $redirect
284
             */
285
            foreach ($repeaterRedirect as $index => $redirect)
286
            {
287
                $redirectPageView = PageView::createRedirect(
288
                    $redirect->getEvaluated(),
289
                    $permalinks[$index]->getEvaluated(),
290
                    $this->redirectTemplate
291
                );
292
                $this->compilePageView($redirectPageView);
293
            }
294 2
        }
295 2
    }
296
297
    ///
298
    // Twig Functionality
299
    ///
300
301
    /**
302
     * Get the compiled HTML for a specific iteration of a repeater PageView.
303
     *
304
     * @param Twig_Template $template
305
     * @param PageView      $pageView
306
     * @param ExpandedValue $expandedValue
307
     *
308
     * @since  0.1.1
309
     *
310
     * @return string
311
     */
312 2
    private function renderRepeaterPageView(&$template, &$pageView, &$expandedValue)
313
    {
314 2
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
315
316 2
        $pageView->setFrontMatter(array(
317 2
            'permalink' => $expandedValue->getEvaluated(),
318 2
            'iterators' => $expandedValue->getIterators(),
319 2
        ));
320
321
        return $template
322 2
            ->render(array(
323 2
                'this' => $pageView->createJail(),
324 2
            ));
325
    }
326
327
    /**
328
     * Get the compiled HTML for a specific ContentItem.
329
     *
330
     * @param Twig_Template $template
331
     * @param PageView      $pageView
332
     * @param ContentItem   $contentItem
333
     *
334
     * @since  0.1.1
335
     *
336
     * @return string
337
     */
338 2
    private function renderDynamicPageView(&$template, &$pageView, &$contentItem)
339
    {
340 2
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
341
342
        return $template
343 2
            ->render(array(
344 2
                'this' => $contentItem->createJail(),
345 2
            ));
346
    }
347
348
    /**
349
     * Get the compiled HTML for a static PageView.
350
     *
351
     * @param PageView $pageView
352
     *
353
     * @since  0.1.1
354
     *
355
     * @throws \Exception
356
     * @throws \Throwable
357
     * @throws Twig_Error_Syntax
358
     *
359
     * @return string
360
     */
361 10
    private function renderStaticPageView(&$pageView)
362
    {
363 10
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
364
365 10
        return $this
366 10
            ->createTwigTemplate($pageView)
367 10
            ->render(array(
368 10
                'this' => $pageView->createJail(),
369 10
            ));
370
    }
371
372
    /**
373
     * Create a Twig template that just needs an array to render.
374
     *
375
     * @param PageView $pageView The PageView whose body will be used for Twig compilation
376
     *
377
     * @since  0.1.1
378
     *
379
     * @throws \Exception
380
     * @throws \Throwable
381
     * @throws Twig_Error_Syntax
382
     *
383
     * @return Twig_Template
384
     */
385 14
    private function createTwigTemplate(&$pageView)
386
    {
387
        try
388
        {
389 14
            return $this->twig->createTemplate($pageView->getContent());
390
        }
391
        catch (Twig_Error_Syntax $e)
392
        {
393
            $e->setTemplateLine($e->getTemplateLine() + $pageView->getLineOffset());
394
            $e->setSourceContext(new Twig_Source(
395
                $pageView->getContent(),
396
                $pageView->getName(),
397
                $pageView->getRelativeFilePath()
398
            ));
399
400
            throw $e;
401
        }
402
    }
403
}
404