Completed
Pull Request — master (#41)
by Vladimir
03:10
created

Compiler::compileAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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\FrontMatter\ExpandedValue;
11
use allejo\stakx\Manager\BaseManager;
12
use allejo\stakx\Manager\TwigManager;
13
use allejo\stakx\Object\ContentItem;
14
use allejo\stakx\Object\DynamicPageView;
15
use allejo\stakx\Object\PageView;
16
use allejo\stakx\Object\RepeaterPageView;
17
use allejo\stakx\System\Folder;
18
use Twig_Environment;
19
use Twig_Error_Syntax;
20
use Twig_Source;
21
use Twig_Template;
22
23
/**
24
 * This class takes care of rendering the Twig body of PageViews with the respective information and it also takes care
25
 * of writing the rendered Twig to the filesystem.
26
 *
27
 * @internal
28
 * @since 0.1.1
29
 */
30
class Compiler extends BaseManager
31
{
32
    /** @var string|false */
33
    private $redirectTemplate;
34
35
    /** @var PageView[] */
36
    private $pageViews;
37
38
    /** @var Folder */
39
    private $folder;
40
41
    /** @var Twig_Environment */
42
    private $twig;
43
44
    public function __construct()
45
    {
46
        parent::__construct();
47
48
        $this->twig = TwigManager::getInstance();
49
    }
50
51
    /**
52
     * @param string|false $template
53
     */
54
    public function setRedirectTemplate ($template)
55
    {
56
        $this->redirectTemplate = $template;
57
    }
58
59
    /**
60
     * @param Folder $folder
61
     */
62
    public function setTargetFolder (Folder $folder)
63
    {
64
        $this->folder = $folder;
65
    }
66
67
    /**
68
     * @param PageView[] $pageViews
69
     */
70
    public function setPageViews (array $pageViews)
71
    {
72
        $this->pageViews = $pageViews;
73
    }
74
75
    ///
76
    // IO Functionality
77
    ///
78
79
    /**
80
     * Compile all of the PageViews registered with the compiler
81
     *
82
     * @since 0.1.0
83
     */
84
    public function compileAll ()
85
    {
86
        foreach ($this->pageViews as &$pageView)
87
        {
88
            $this->compilePageView($pageView);
89
        }
90
    }
91
92
    /**
93
     * Compile an individual PageView item
94
     *
95
     * This function will take care of determining *how* to treat the PageView and write the compiled output to a the
96
     * respective target file.
97
     *
98
     * @param DynamicPageView|RepeaterPageView|PageView $pageView The PageView that needs to be compiled
99
     * @since 0.1.1
100
     */
101
    private function compilePageView (&$pageView)
102
    {
103
        switch ($pageView->getType())
104
        {
105
            case PageView::STATIC_TYPE:
106
                $this->compileStaticPageView($pageView);
107
                break;
108
109
            case PageView::DYNAMIC_TYPE:
110
                $this->compileDynamicPageViews($pageView);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Object\PageView> is not a sub-type of object<allejo\stakx\Object\DynamicPageView>. It seems like you assume a child class of the class allejo\stakx\Object\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...
111
                break;
112
113
            case PageView::REPEATER_TYPE:
114
                $this->compileRepeaterPageViews($pageView);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Object\PageView> is not a sub-type of object<allejo\stakx\Object\RepeaterPageView>. It seems like you assume a child class of the class allejo\stakx\Object\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...
115
                break;
116
        }
117
    }
118
119
    /**
120
     * Write the compiled output for a static PageView
121
     *
122
     * @param PageView $pageView
123
     * @since 0.1.1
124
     */
125
    private function compileStaticPageView (&$pageView)
126
    {
127
        $targetFile = $pageView->getTargetFile();
128
        $output = $this->renderStaticPageView($pageView);
129
130
        $this->output->notice("Writing file: {file}", array('file' => $targetFile));
131
        $this->folder->writeFile($targetFile, $output);
132
    }
133
134
    /**
135
     * Write the compiled output for a dynamic PageView
136
     *
137
     * @param DynamicPageView $pageView
138
     * @since 0.1.1
139
     */
140 View Code Duplication
    private function compileDynamicPageViews (&$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...
141
    {
142
        $contentItems = $pageView->getContentItems();
143
        $template = $this->createTwigTemplate($pageView);
144
145
        foreach ($contentItems as $contentItem)
146
        {
147
            $targetFile = $contentItem->getTargetFile();
148
            $output = $this->renderDynamicPageView($template, $pageView, $contentItem);
149
150
            $this->output->notice("Writing file: {file}", array('file' => $targetFile));
151
            $this->folder->writeFile($targetFile, $output);
152
        }
153
    }
154
155
    /**
156
     * Write the compiled output for a repeater PageView
157
     *
158
     * @param RepeaterPageView $pageView
159
     * @since 0.1.1
160
     */
161 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...
162
    {
163
        $pageView->rewindPermalink();
164
165
        $template = $this->createTwigTemplate($pageView);
166
        $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\Object\PageView as the method getRepeaterPermalinks() does only exist in the following sub-classes of allejo\stakx\Object\PageView: allejo\stakx\Object\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...
167
168
        foreach ($permalinks as $permalink)
169
        {
170
            $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\Object\PageView as the method bumpPermalink() does only exist in the following sub-classes of allejo\stakx\Object\PageView: allejo\stakx\Object\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...
171
            $targetFile = $pageView->getTargetFile();
172
            $output = $this->renderRepeaterPageView($template, $pageView, $permalink);
173
174
            $this->output->notice("Writing repeater file: {file}", array('file' => $targetFile));
175
            $this->folder->writeFile($targetFile, $output);
176
        }
177
    }
178
179
    ///
180
    // Twig Functionality
181
    ///
182
183
    /**
184
     * Get the compiled HTML for a specific iteration of a repeater PageView
185
     *
186
     * @param Twig_Template $template
187
     * @param PageView      $pageView
188
     * @param ExpandedValue $expandedValue
189
     *
190
     * @since  0.1.1
191
     * @return string
192
     */
193
    private function renderRepeaterPageView (&$template, &$pageView, &$expandedValue)
194
    {
195
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
196
197
        $pageView->setFrontMatter(array(
198
            'permalink' => $expandedValue->getEvaluated(),
199
            'iterators' => $expandedValue->getIterators()
200
        ));
201
202
        return $template
203
            ->render(array(
204
                'this' => $pageView->createJail()
205
            ));
206
    }
207
208
    /**
209
     * Get the compiled HTML for a specific ContentItem
210
     *
211
     * @param  Twig_Template $template
212
     * @param  PageView      $pageView
213
     * @param  ContentItem   $contentItem
214
     *
215
     * @since  0.1.1
216
     * @return string
217
     */
218
    private function renderDynamicPageView (&$template, &$pageView, &$contentItem)
219
    {
220
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
221
222
        return $template
223
            ->render(array(
224
                'this' => $contentItem->createJail()
225
            ));
226
    }
227
228
    /**
229
     * Get the compiled HTML for a static PageView
230
     *
231
     * @param  PageView $pageView
232
     *
233
     * @since  0.1.1
234
     * @return string
235
     *
236
     * @throws \Exception
237
     * @throws \Throwable
238
     * @throws Twig_Error_Syntax
239
     */
240
    private function renderStaticPageView (&$pageView)
241
    {
242
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
243
244
        return $this
245
            ->createTwigTemplate($pageView)
246
            ->render(array(
247
                'this' => $pageView->createJail()
248
            ));
249
    }
250
251
    /**
252
     * Create a Twig template that just needs an array to render
253
     *
254
     * @param  PageView $pageView The PageView whose body will be used for Twig compilation
255
     * @since  0.1.1
256
     *
257
     * @return Twig_Template
258
     *
259
     * @throws \Exception
260
     * @throws \Throwable
261
     * @throws Twig_Error_Syntax
262
     */
263
    private function createTwigTemplate (&$pageView)
264
    {
265
        try
266
        {
267
            return $this->twig->createTemplate($pageView->getContent());
268
        }
269
        catch (Twig_Error_Syntax $e)
270
        {
271
            $e->setTemplateLine($e->getTemplateLine() + $pageView->getLineOffset());
272
            $e->setSourceContext(new Twig_Source(
273
                $pageView->getContent(),
274
                $pageView->getName(),
275
                $pageView->getRelativeFilePath()
276
            ));
277
278
            throw $e;
279
        }
280
    }
281
}
282