Test Failed
Push — master ( a58c94...1bcec0 )
by Brent
03:26
created

Stitcher::parseVariables()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Brendt\Stitcher;
4
5
use Brendt\Stitcher\Exception\InvalidSiteException;
6
use Brendt\Stitcher\Exception\TemplateNotFoundException;
7
use Brendt\Stitcher\Factory\AdapterFactory;
8
use Brendt\Stitcher\Factory\ParserFactory;
9
use Brendt\Stitcher\Factory\TemplateEngineFactory;
10
use Brendt\Stitcher\Site\Page;
11
use Brendt\Stitcher\Site\Site;
12
use Brendt\Stitcher\Template\TemplateEngine;
13
use Symfony\Component\Filesystem\Filesystem;
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\Finder\SplFileInfo;
16
use Symfony\Component\Yaml\Exception\ParseException;
17
use Symfony\Component\Yaml\Yaml;
18
19
/**
20
 * The Stitcher class is the core compiler of every Stitcher application. This class takes care of all routes, pages,
21
 * templates and data, and "stitches" everything together.
22
 *
23
 * The stitching process is done in several steps, with the final result being a fully rendered website in the
24
 * `directories.public` folder.
25
 */
26
class Stitcher {
27
28
    /**
29
     * A collection of all templates available when rendering a Stitcher application.
30
     *
31
     * @var SplFileInfo[]
32
     */
33
    protected $templates;
34
35
    /**
36
     * The template engine which is configured via `engines.template`.
37
     *
38
     * @var TemplateEngine
39
     */
40
    private $templateEngine;
41
42
    /**
43
     * Stitcher constructor.
44
     */
45
    public function __construct() {
46
        /** @var TemplateEngineFactory $templateEngineFactory */
47
        $templateEngineFactory = Config::getDependency('factory.template.engine');
48
49
        $this->templateEngine = $templateEngineFactory->getByType(Config::get('engines.template'));
50
    }
51
52
    /**
53
     * The core stitcher function. This function will compile the configured site and return an array of parsed
54
     * data.
55
     *
56
     * Compiling a site is done in the following steps.
57
     *
58
     *      - Load the site configuration @see \Brendt\Stitcher\Stitcher::loadSite()
59
     *      - Load all available templates @see \Brendt\Stitcher\Stitcher::loadTemplates()
60
     *      - Loop over all pages and transform every page with the configured adapters (in any are set) @see
61
     *      \Brendt\Stitcher\Stitcher::parseAdapters()
62
     *      - Loop over all transformed pages and parse the variables which weren't parsed by the page's adapters. @see
63
     *      \Brendt\Stitcher\Stitcher::parseVariables()
64
     *      - Add all variables to the template engine and render the HTML for each page.
65
     *
66
     * This function takes two optional parameters which are used to render pages on the fly when using the
67
     * developer controller. The first one, `routes` will take a string or array of routes which should be rendered,
68
     * instead of all available routes. The second one, `filterValue` is used to provide a filter when the
69
     * CollectionAdapter is used, and only one entry page should be rendered.
70
     *
71
     * @param string|array $routes
72
     * @param string       $filterValue
73
     *
74
     * @return array
75
     * @throws TemplateNotFoundException
76
     *
77
     * @see \Brendt\Stitcher\Stitcher::save()
78
     * @see \Brendt\Stitcher\Controller\DevController::run()
79
     * @see \Brendt\Stitcher\Adapter\CollectionAdapter::transform()
80
     */
81
    public function stitch($routes = [], $filterValue = null) {
82
        $blanket = [];
83
84
        $site = $this->loadSite();
85
        $templates = $this->loadTemplates();
86
87
        if (is_string($routes)) {
88
            $routes = [$routes];
89
        }
90
91
        foreach ($site as $page) {
92
            $route = $page->getId();
93
94
            $skipRoute = count($routes) && !in_array($route, $routes);
95
            if ($skipRoute) {
96
                continue;
97
            }
98
99
            $templateIsset = isset($templates[$page->getTemplatePath()]);
100
101
            if (!$templateIsset) {
102
                if (isset($page['template'])) {
103
                    throw new TemplateNotFoundException("Template {$page['template']} not found.");
104
                } else {
105
                    throw new TemplateNotFoundException('No template was set.');
106
                }
107
            }
108
109
            $pages = $this->parseAdapters($page, $filterValue);
110
111
            $pageTemplate = $templates[$page->getTemplatePath()];
112
            foreach ($pages as $entryPage) {
113
                $entryPage = $this->parseVariables($entryPage);
114
115
                // Render each page
116
                $this->templateEngine->addTemplateVariables($entryPage->getVariables());
117
                $blanket[$entryPage->getId()] = $this->templateEngine->renderTemplate($pageTemplate);
118
                $this->templateEngine->clearTemplateVariables();
119
            }
120
        }
121
122
        return $blanket;
123
    }
124
125
    /**
126
     * Load a site from YAML configuration files in the `directories.src`/site directory.
127
     * All YAML files are loaded and parsed into Page objects and added to a Site collection.
128
     *
129
     * @return Site
130
     * @throws InvalidSiteException
131
     *
132
     * @see \Brendt\Stitcher\Site\Page
133
     * @see \Brendt\Stitcher\Site\Site
134
     */
135
    public function loadSite() {
136
        $src = Config::get('directories.src');
137
        $files = Finder::create()->files()->in("{$src}/site")->name('*.yml');
138
        $site = new Site();
139
140
        foreach ($files as $file) {
141
            try {
142
                $fileContents = Yaml::parse($file->getContents());
143
            } catch (ParseException $e) {
144
                throw new InvalidSiteException("{$file->getRelativePathname()}: {$e->getMessage()}");
145
            }
146
147
            if (!is_array($fileContents)) {
148
                continue;
149
            }
150
151
            foreach ($fileContents as $route => $data) {
152
                $page = new Page($route, $data);
153
                $site->addPage($page);
154
            }
155
        }
156
157
        return $site;
158
    }
159
160
    /**
161
     * Load all templates from either the `directories.template` directory. Depending on the configured template
162
     * engine, set with `engines.template`; .html or .tpl files will be loaded.
163
     *
164
     * @return SplFileInfo[]
165
     */
166
    public function loadTemplates() {
167
        $templateFolder = Config::get('directories.template');
168
        $templateExtension = $this->templateEngine->getTemplateExtension();
169
        $files = Finder::create()->files()->in($templateFolder)->name("*.{$templateExtension}");
170
        $templates = [];
171
172
        foreach ($files as $file) {
173
            $id = str_replace(".{$templateExtension}", '', $file->getRelativePathname());
174
            $templates[$id] = $file;
175
        }
176
177
        return $templates;
178
    }
179
180
    /**
181
     * This function takes a page and optional entry id. The page's adapters will be loaded and looped.
182
     * An adapter will transform a page's original configuration and variables to one or more pages.
183
     * An entry id can be provided as a filter. This filter can be used in an adapter to skip rendering unnecessary
184
     * pages. The filter parameter is used to render pages on the fly when using the developer controller.
185
     *
186
     * @param Page   $page
187
     * @param string $entryId
188
     *
189
     * @return Page[]
190
     *
191
     * @see  \Brendt\Stitcher\Adapter\Adapter::transform()
192
     * @see  \Brendt\Stitcher\Controller\DevController::run()
193
     *
194
     * @todo When a page has multiple adapters, this function won't correctly parse more than one. This is considered a
195
     *       bug, but not a major one because there are only two adapters at this moment, and they can not be used
196
     *       together anyway.
197
     *
198
     */
199
    public function parseAdapters(Page $page, $entryId = null) {
200
        /** @var AdapterFactory $adapterFactory */
201
        $adapterFactory = Config::getDependency('factory.adapter');
202
        $pages = [];
203
204
        if (count($page->getAdapters())) {
205
            foreach ($page->getAdapters() as $type => $adapterConfig) {
206
                $adapter = $adapterFactory->getByType($type);
207
208
                if ($entryId !== null) {
209
                    $pages = $adapter->transform($page, $entryId);
210
                } else {
211
                    $pages = $adapter->transform($page);
212
                }
213
            }
214
        } else {
215
            $pages = [$page->getId() => $page];
216
        }
217
218
        return $pages;
219
    }
220
221
    /**
222
     * This function takes a Page object and parse its variables using a Parser. It will only parse variables which
223
     * weren't parsed already by an adapter.
224
     *
225
     * @param Page $page
226
     *
227
     * @return Page
228
     *
229
     * @see \Brendt\Stitcher\Factory\ParserFactory
230
     * @see \Brendt\Stitcher\Parser\Parser
231
     * @see \Brendt\Stitcher\Site\Page::isParsedVariable()
232
     */
233
    public function parseVariables(Page $page) {
234
        foreach ($page->getVariables() as $name => $value) {
235
            if ($page->isParsedVariable($name)) {
236
                continue;
237
            }
238
239
            $page
240
                ->setVariableValue($name, $this->getData($value))
241
                ->setVariableIsParsed($name);
242
        }
243
244
        return $page;
245
    }
246
247
    /**
248
     * This function will save a stitched output to HTML files in the `directories.public` directory.
249
     *
250
     * @param array $blanket
251
     *
252
     * @see \Brendt\Stitcher\Stitcher::stitch()
253
     */
254
    public function save(array $blanket) {
255
        $fs = new Filesystem();
256
        $public = Config::get('directories.public');
257
258
        if (!$fs->exists($public)) {
259
            $fs->mkdir($public);
260
        }
261
262
        foreach ($blanket as $path => $page) {
263
            if ($path === '/') {
264
                $path = 'index';
265
            }
266
267
            $fs->dumpFile($public . "/{$path}.html", $page);
268
        }
269
    }
270
271
    /**
272
     * This function will get the parser based on the value. This value is parsed by the parser, or returned if no
273
     * suitable parser was found.
274
     *
275
     * @param $value
276
     *
277
     * @return mixed
278
     *
279
     * @see \Brendt\Stitcher\Factory\ParserFactory
280
     */
281
    private function getData($value) {
282
        /** @var ParserFactory $parserFactory */
283
        $parserFactory = Config::getDependency('factory.parser');
284
        $parser = $parserFactory->getParser($value);
285
286
        if (!$parser) {
287
            return $value;
288
        }
289
290
        return $parser->parse($value);
291
    }
292
293
}
294
295
296