Passed
Push — master ( 2059dd...db97d8 )
by Brent
02:49
created

SiteParser   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 0
loc 235
rs 10
c 0
b 0
f 0
wmc 24
lcom 1
cbo 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B parse() 0 33 5
B loadSite() 0 24 6
A loadTemplates() 0 15 2
A parseAdapters() 0 21 4
A parseVariables() 0 13 3
A setFilter() 0 5 1
A getData() 0 9 2
1
<?php
2
3
namespace Brendt\Stitcher;
4
5
use Brendt\Stitcher\Adapter\Adapter;
6
use Brendt\Stitcher\Factory\ParserFactory;
7
use Brendt\Stitcher\Factory\TemplateEngineFactory;
8
use Brendt\Stitcher\Site\Page;
9
use Brendt\Stitcher\Site\Site;
10
use Symfony\Component\Finder\Finder;
11
use Symfony\Component\Finder\SplFileInfo;
12
use Symfony\Component\Yaml\Yaml;
13
14
class SiteParser
15
{
16
    /**
17
     * @var string
18
     */
19
    private $filter;
20
21
    /**
22
     * @var string
23
     */
24
    private $srcDir;
25
26
    /**
27
     * @var string
28
     */
29
    private $templateDir;
30
31
    /**
32
     * @var ParserFactory
33
     */
34
    private $parserFactory;
35
36
    /**
37
     * @var TemplateEngineFactory
38
     */
39
    private $templateEngineFactory;
40
41
    /**
42
     * SiteParser constructor.
43
     *
44
     * @param string                $srcDir
45
     * @param string                $templateDir
46
     * @param ParserFactory         $parserFactory
47
     * @param TemplateEngineFactory $templateEngineFactory
48
     */
49
    public function __construct(string $srcDir, string $templateDir, ParserFactory $parserFactory, TemplateEngineFactory $templateEngineFactory) {
50
        $this->srcDir = $srcDir;
51
        $this->templateDir = $templateDir;
52
        $this->parserFactory = $parserFactory;
53
        $this->templateEngineFactory = $templateEngineFactory;
54
    }
55
56
    /**
57
     * Parse a path into usable data.
58
     *
59
     * @param array  $routes
60
     * @param string $filterValue
61
     *
62
     * @return mixed
63
     */
64
    public function parse($routes = [], string $filterValue = null) : array {
65
        $templateEngine = $this->templateEngineFactory->getDefault();
66
        $blanket = [];
67
68
        $site = $this->loadSite((array) $routes);
69
        $templates = $this->loadTemplates();
70
71
        foreach ($site as $page) {
72
            $templateIsset = isset($templates[$page->getTemplatePath()]);
73
74
            if (!$templateIsset) {
75
                if ($template = $page->getTemplatePath()) {
76
                    throw new TemplateNotFoundException("Template {$template} not found.");
77
                } else {
78
                    throw new TemplateNotFoundException('No template was set.');
79
                }
80
            }
81
82
            $pages = $this->parseAdapters($page, $filterValue);
83
84
            $pageTemplate = $templates[$page->getTemplatePath()];
85
            foreach ($pages as $entryPage) {
86
                $entryPage = $this->parseVariables($entryPage);
87
88
                // Render each page
89
                $templateEngine->addTemplateVariables($entryPage->getVariables());
90
                $blanket[$entryPage->getId()] = $templateEngine->renderTemplate($pageTemplate);
91
                $templateEngine->clearTemplateVariables();
92
            }
93
        }
94
95
        return $blanket;
96
    }
97
98
    /**
99
     * Load a site from YAML configuration files in the `directories.src`/site directory.
100
     * All YAML files are loaded and parsed into Page objects and added to a Site collection.
101
     *
102
     * @param array $routes
103
     *
104
     * @return Site
105
     * @throws InvalidSiteException
106
     * @see \Brendt\Stitcher\Site\Page
107
     * @see \Brendt\Stitcher\Site\Site
108
     */
109
    public function loadSite(array $routes = []) : Site {
110
        /** @var SplFileInfo[] $files */
111
        $files = Finder::create()->files()->in("{$this->srcDir}/site")->name('*.yml');
112
        $site = new Site();
113
114
        foreach ($files as $file) {
115
            try {
116
                $fileContents = (array) Yaml::parse($file->getContents());
117
            } catch (ParseException $e) {
0 ignored issues
show
Bug introduced by
The class Brendt\Stitcher\ParseException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
118
                throw new InvalidSiteException("{$file->getRelativePathname()}: {$e->getMessage()}");
119
            }
120
121
            foreach ($fileContents as $route => $data) {
122
                if (count($routes) && !in_array($route, $routes)) {
123
                    continue;
124
                }
125
126
                $page = new Page($route, $data);
127
                $site->addPage($page);
128
            }
129
        }
130
131
        return $site;
132
    }
133
134
    /**
135
     * Load all templates from either the `directories.template` directory. Depending on the configured template
136
     * engine, set with `engines.template`; .html or .tpl files will be loaded.
137
     *
138
     * @return SplFileInfo[]
139
     */
140
    public function loadTemplates() {
141
        $templateEngine = $this->templateEngineFactory->getDefault();
142
        $templateExtension = $templateEngine->getTemplateExtension();
143
144
        /** @var SplFileInfo[] $files */
145
        $files = Finder::create()->files()->in($this->templateDir)->name("*.{$templateExtension}");
146
        $templates = [];
147
148
        foreach ($files as $file) {
149
            $id = str_replace(".{$templateExtension}", '', $file->getRelativePathname());
150
            $templates[$id] = $file;
151
        }
152
153
        return $templates;
154
    }
155
156
    /**
157
     * This function takes a page and optional entry id. The page's adapters will be loaded and looped.
158
     * An adapter will transform a page's original configuration and variables to one or more pages.
159
     * An entry id can be provided as a filter. This filter can be used in an adapter to skip rendering unnecessary
160
     * pages. The filter parameter is used to render pages on the fly when using the developer controller.
161
     *
162
     * @param Page   $page
163
     * @param string $entryId
164
     *
165
     * @return Page[]
166
     *
167
     * @see  \Brendt\Stitcher\Adapter\Adapter::transform()
168
     * @see  \Brendt\Stitcher\Controller\DevController::run()
169
     */
170
    public function parseAdapters(Page $page, $entryId = null) {
171
        if (!$page->getAdapters()) {
172
            return [$page->getId() => $page];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($page->getId() => $page); (array<string,Brendt\Stitcher\Site\Page>) is incompatible with the return type documented by Brendt\Stitcher\SiteParser::parseAdapters of type Brendt\Stitcher\Site\Page[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
173
        }
174
175
        $pages = [$page];
176
177
        foreach ($page->getAdapters() as $type => $adapterConfig) {
178
            /** @var Adapter $adapter */
179
            // TODO: Work with adapter factory
180
            $adapter = Stitcher::get("adapter.{$type}");
181
182
            if ($entryId !== null) {
183
                $pages = $adapter->transform($pages, $entryId);
184
            } else {
185
                $pages = $adapter->transform($pages);
186
            }
187
        }
188
189
        return $pages;
190
    }
191
192
    /**
193
     * This function takes a Page object and parse its variables using a Parser. It will only parse variables which
194
     * weren't parsed already by an adapter.
195
     *
196
     * @param Page $page
197
     *
198
     * @return Page
199
     *
200
     * @see \Brendt\Stitcher\Factory\ParserFactory
201
     * @see \Brendt\Stitcher\Parser\Parser
202
     * @see \Brendt\Stitcher\Site\Page::isParsedVariable()
203
     */
204
    public function parseVariables(Page $page) {
205
        foreach ($page->getVariables() as $name => $value) {
206
            if ($page->isParsedVariable($name)) {
207
                continue;
208
            }
209
210
            $page
211
                ->setVariableValue($name, $this->getData($value))
212
                ->setVariableIsParsed($name);
213
        }
214
215
        return $page;
216
    }
217
218
    /**
219
     * @param string $filter
220
     *
221
     * @return SiteParser
222
     */
223
    public function setFilter(string $filter) : SiteParser {
224
        $this->filter = $filter;
225
226
        return $this;
227
    }
228
229
    /**
230
     * This function will get the parser based on the value. This value is parsed by the parser, or returned if no
231
     * suitable parser was found.
232
     *
233
     * @param $value
234
     *
235
     * @return mixed
236
     *
237
     * @see \Brendt\Stitcher\Factory\ParserFactory
238
     */
239
    private function getData($value) {
240
        $parser = $this->parserFactory->getByFileName($value);
241
242
        if (!$parser) {
243
            return $value;
244
        }
245
246
        return $parser->parse($value);
247
    }
248
}
249