Completed
Push — master ( b785dd...3df4eb )
by Vladimir
03:46
created

Website::build()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 103
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 103
rs 8.1935
c 0
b 0
f 0
cc 4
eloc 61
nc 8
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Core\StakxLogger;
12
use allejo\stakx\Exception\FileAwareException;
13
use allejo\stakx\Manager\AssetManager;
14
use allejo\stakx\Manager\CollectionManager;
15
use allejo\stakx\Manager\DataManager;
16
use allejo\stakx\Manager\MenuManager;
17
use allejo\stakx\Manager\PageManager;
18
use allejo\stakx\Manager\ThemeManager;
19
use allejo\stakx\Manager\TwigManager;
20
use allejo\stakx\System\FileExplorer;
21
use allejo\stakx\System\Filesystem;
22
use allejo\stakx\System\Folder;
23
use allejo\stakx\Twig\StakxTwigTextProfiler;
24
use Kwf\FileWatcher\Event\AbstractEvent;
25
use Kwf\FileWatcher\Event\Create;
26
use Kwf\FileWatcher\Event\Modify;
27
use Kwf\FileWatcher\Watcher;
28
use Symfony\Component\Console\Output\OutputInterface;
29
30
class Website
31
{
32
    /**
33
     * The location of where the compiled website will be written to.
34
     *
35
     * @var Folder
36
     */
37
    private $outputDirectory;
38
39
    /**
40
     * The main configuration to be used to build the specified website.
41
     *
42
     * @var Configuration
43
     */
44
    private $configuration;
45
46
    /**
47
     * When set to true, the Stakx website will be built without a configuration file.
48
     *
49
     * @var bool
50
     */
51
    private $confLess;
52
53
    /**
54
     * @var StakxLogger
55
     */
56
    private $output;
57
58
    /**
59
     * @var AssetManager
60
     */
61
    private $am;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $am. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
62
63
    /**
64
     * @var CollectionManager
65
     */
66
    private $cm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
67
68
    /**
69
     * @var DataManager
70
     */
71
    private $dm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
72
73
    /**
74
     * @var Filesystem
75
     */
76
    private $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
77
78
    /** @var MenuManager */
79
    private $mm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $mm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
80
81
    /**
82
     * @var PageManager
83
     */
84
    private $pm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $pm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
85
86
    /**
87
     * @var ThemeManager
88
     */
89
    private $tm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $tm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
90
91
    /** @var Compiler */
92
    private $compiler;
93
94
    /**
95
     * Website constructor.
96
     *
97
     * @param OutputInterface $output
98
     */
99
    public function __construct(OutputInterface $output)
100
    {
101
        $this->output = new StakxLogger($output);
102
        $this->cm = new CollectionManager();
103
        $this->dm = new DataManager();
104
        $this->mm = new MenuManager();
105
        $this->pm = new PageManager();
106
        $this->fs = new Filesystem();
107
    }
108
109
    /**
110
     * Compile the website.
111
     *
112
     * @param bool $tracking Whether or not to keep track of files as they're compiled to save time in 'watch'
113
     */
114
    public function build($tracking = false)
115
    {
116
        Service::setParameter(BuildableCommand::WATCHING, $tracking);
117
118
        // Configure the environment
119
        $this->createFolderStructure();
120
121
        // Our output directory
122
        $this->outputDirectory = new Folder($this->getConfiguration()->getTargetFolder());
123
        $this->outputDirectory->setTargetDirectory($this->getConfiguration()->getBaseUrl());
124
125
        // Parse DataItems
126
        $this->dm->setLogger($this->output);
127
        $this->dm->enableTracking($tracking);
128
        $this->dm->parseDataItems($this->getConfiguration()->getDataFolders());
129
        $this->dm->parseDataSets($this->getConfiguration()->getDataSets());
130
131
        // Prepare Collections
132
        $this->cm->setLogger($this->output);
133
        $this->cm->enableTracking($tracking);
134
        $this->cm->parseCollections($this->getConfiguration()->getCollectionsFolders());
135
136
        // Handle PageViews
137
        $this->pm->setLogger($this->output);
138
        $this->pm->enableTracking($tracking);
139
        $this->pm->setCollections($this->cm->getCollections());
140
        $this->pm->parsePageViews($this->getConfiguration()->getPageViewFolders());
141
142
        // Handle the site's menu
143
        $this->mm->setLogger($this->output);
144
        $this->mm->buildFromPageViews($this->pm->getStaticPageViews());
145
146
        // Configure our Twig environment
147
        $theme = $this->configuration->getTheme();
148
        $twigEnv = new TwigManager();
149
        $twigEnv->configureTwig($this->getConfiguration(), array(
150
            'safe'    => Service::getParameter(BuildableCommand::SAFE_MODE),
151
            'globals' => array(
152
                array('name' => 'site', 'value' => $this->getConfiguration()->getConfiguration()),
153
                array('name' => 'collections', 'value' => $this->cm->getJailedCollections()),
154
                array('name' => 'menu', 'value' => $this->mm->getSiteMenu()),
155
                array('name' => 'pages', 'value' => $this->pm->getJailedStaticPageViews()),
156
                array('name' => 'data', 'value' => $this->dm->getDataItems()),
157
            ),
158
        ));
159
160
        $profiler = null;
161
162
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
163
        {
164
            $profiler = new \Twig_Profiler_Profile();
165
            TwigManager::getInstance()->addExtension(new \Twig_Extension_Profiler($profiler));
166
        }
167
168
        // Compile everything
169
        $this->compiler = new Compiler();
170
        $this->compiler->setLogger($this->output);
171
        $this->compiler->setRedirectTemplate($this->getConfiguration()->getRedirectTemplate());
172
        $this->compiler->setPageViews($this->pm->getPageViews(), $this->pm->getPageViewsFlattened());
173
        $this->compiler->setTargetFolder($this->outputDirectory);
174
        $this->compiler->setThemeName($theme);
175
        $this->compiler->compileAll();
176
177
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
178
        {
179
            $dumper = new StakxTwigTextProfiler();
180
            $dumper->setTemplateMappings($this->compiler->getTemplateMappings());
181
            $text = $dumper->dump($profiler);
0 ignored issues
show
Bug introduced by
It seems like $profiler defined by null on line 160 can be null; however, Twig_Profiler_Dumper_Text::dump() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
182
            $this->output->writeln($text);
183
        }
184
185
        // At this point, we are looking at static files to copy over meaning we need to ignore all of the files that
186
        // make up the source of a stakx website
187
        $assetsToIgnore = array_merge(
188
            Configuration::$stakxSourceFiles,
189
            $this->getConfiguration()->getExcludes()
190
        );
191
192
        //
193
        // Theme Management
194
        //
195
        if (!is_null($theme))
196
        {
197
            $this->output->notice("Looking for '${theme}' theme...");
198
199
            $this->tm = new ThemeManager($theme);
200
            $this->tm->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
201
            $this->tm->setLogger($this->output);
202
            $this->tm->enableTracking($tracking);
203
            $this->tm->setFolder($this->outputDirectory);
204
            $this->tm->copyFiles();
205
        }
206
207
        //
208
        // Static file management
209
        //
210
        $this->am = new AssetManager();
211
        $this->am->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
212
        $this->am->setLogger($this->output);
213
        $this->am->setFolder($this->outputDirectory);
214
        $this->am->enableTracking($tracking);
215
        $this->am->copyFiles();
216
    }
217
218
    public function watch()
219
    {
220
        $this->output->writeln('Building website...');
221
        $this->build(true);
222
        $this->output->writeln(sprintf('Watching %s', getcwd()));
223
224
        $exclusions = array_merge($this->getConfiguration()->getExcludes(), array(
225
            $this->getConfiguration()->getTargetFolder()
226
        ));
227
        $fileExplorer = FileExplorer::create(
228
            getcwd(), $exclusions, $this->getConfiguration()->getIncludes()
229
        );
230
231
        $newWatcher = Watcher::create(getcwd());
232
        $newWatcher
233
            ->setLogger($this->output)
234
            ->setExcludePatterns(array_merge(
235
                $exclusions, FileExplorer::$vcsPatterns, array(Configuration::CACHE_FOLDER)
236
            ))
237
            ->setIterator($fileExplorer->getExplorer())
238
            ->addListener(Create::NAME, function ($e) { $this->watchListenerFunction($e); })
239
            ->addListener(Modify::NAME, function ($e) { $this->watchListenerFunction($e); })
240
        ;
241
242
        $this->output->writeln('Watch started successfully');
243
244
        $newWatcher->start();
245
    }
246
247
    private function watchListenerFunction(AbstractEvent $event)
248
    {
249
        $filePath = $this->fs->getRelativePath($event->filename);
250
251
        try
252
        {
253
            switch ($event::getEventName())
254
            {
255
                case Create::NAME:
256
                    $this->creationWatcher($filePath);
257
                    break;
258
259
                case Modify::NAME:
260
                    $this->modificationWatcher($filePath);
261
                    break;
262
            }
263
        }
264
        catch (FileAwareException $e)
265
        {
266
            $this->output->writeln(sprintf("Your website failed to build with the following error in file '%s': %s",
267
                $e->getPath(),
268
                $e->getMessage()
269
            ));
270
        }
271
        catch (\Exception $e)
272
        {
273
            $this->output->writeln(sprintf('Your website failed to build with the following error: %s',
274
                $e->getMessage()
275
            ));
276
        }
277
    }
278
279
    /**
280
     * @return Configuration
281
     */
282
    public function getConfiguration()
283
    {
284
        return $this->configuration;
285
    }
286
287
    /**
288
     * @param string $configFile
289
     *
290
     * @throws \LogicException
291
     */
292
    public function setConfiguration($configFile)
293
    {
294
        if (!$this->fs->exists($configFile) && !$this->isConfLess())
295
        {
296
            $this->output->error('You are trying to build a website in a directory without a configuration file. Is this what you meant to do?');
297
            $this->output->error("To build a website without a configuration, use the '--no-conf' option");
298
299
            throw new \LogicException('Cannot build a website without a configuration when not in Configuration-less mode');
300
        }
301
302
        if ($this->isConfLess())
303
        {
304
            $configFile = '';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $configFile. This often makes code more readable.
Loading history...
305
        }
306
307
        $this->configuration = new Configuration();
308
        $this->configuration->setLogger($this->output);
309
        $this->configuration->parse($configFile);
310
    }
311
312
    /**
313
     * Get whether or not the website is being built in Configuration-less mode.
314
     *
315
     * @return bool True when being built with no configuration file
316
     */
317
    public function isConfLess()
318
    {
319
        return $this->confLess;
320
    }
321
322
    /**
323
     * Set whether or not the website should be built with a configuration.
324
     *
325
     * @param bool $status True when a website should be built without a configuration
326
     */
327
    public function setConfLess($status)
328
    {
329
        $this->confLess = $status;
330
    }
331
332
    private function creationWatcher($filePath)
333
    {
334
        $this->output->writeln(sprintf('File creation detected: %s', $filePath));
335
336
        if ($this->pm->isHandled($filePath))
337
        {
338
            $this->pm->createNewItem($filePath);
339
            $this->pm->refreshItem($filePath);
340
        }
341
        elseif ($this->cm->isHandled($filePath))
342
        {
343
            $contentItem = $this->cm->createNewItem($filePath);
344
            TwigManager::getInstance()->addGlobal('collections', $this->cm->getCollections());
345
346
            $this->pm->trackNewContentItem($contentItem);
347
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
348
            $this->compiler->compileSome(array(
349
                'namespace'  => 'collections',
350
                'dependency' => $contentItem->getCollection(),
351
            ));
352
        }
353 View Code Duplication
        elseif ($this->dm->isHandled($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
354
        {
355
            $change = $this->dm->createNewItem($filePath);
356
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
357
358
            $this->compiler->compileSome(array(
359
                'namespace'  => 'data',
360
                'dependency' => $change,
361
            ));
362
        }
363
        elseif (!is_null($this->tm) && $this->tm->isHandled($filePath))
364
        {
365
            $this->tm->createNewItem($filePath);
366
        }
367
        elseif ($this->am->isHandled($filePath))
368
        {
369
            $this->am->createNewItem($filePath);
370
        }
371
    }
372
373
    private function modificationWatcher($filePath)
374
    {
375
        $this->output->writeln(sprintf('File change detected: %s', $filePath));
376
377
        if ($this->compiler->isParentTemplate($filePath))
378
        {
379
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
380
            $this->compiler->refreshParent($filePath);
381
        }
382
        elseif ($this->pm->isTracked($filePath))
383
        {
384
            $change = $this->pm->refreshItem($filePath);
385
386
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
387
            $this->compiler->compilePageView($change);
388
        }
389
        elseif ($this->cm->isTracked($filePath))
390
        {
391
            $contentItem = &$this->cm->getContentItem($filePath);
392
            $contentItem->refreshFileContent();
393
394
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
395
            $this->compiler->compileSome(array(
396
                'namespace'  => 'collections',
397
                'dependency' => $contentItem->getCollection(),
398
            ));
399
        }
400 View Code Duplication
        elseif ($this->dm->isTracked($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
401
        {
402
            $change = $this->dm->refreshItem($filePath);
403
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
404
405
            $this->compiler->compileSome(array(
406
                'namespace'  => 'data',
407
                'dependency' => $change,
408
            ));
409
        }
410
        elseif (!is_null($this->tm) && $this->tm->isTracked($filePath))
411
        {
412
            $this->tm->refreshItem($filePath);
413
        }
414
        elseif ($this->am->isTracked($filePath))
415
        {
416
            $this->am->refreshItem($filePath);
417
        }
418
    }
419
420
    /**
421
     * Prepare the Stakx environment by creating necessary cache folders.
422
     */
423
    private function createFolderStructure()
424
    {
425
        $targetDir = $this->fs->absolutePath($this->configuration->getTargetFolder());
426
427
        if (!Service::getParameter(BuildableCommand::NO_CLEAN))
428
        {
429
            $this->fs->remove($targetDir);
430
        }
431
432
        if (!Service::getParameter(BuildableCommand::USE_CACHE))
433
        {
434
            $this->fs->remove($this->fs->absolutePath(Configuration::CACHE_FOLDER));
435
            $this->fs->mkdir($this->fs->absolutePath($this->fs->appendPath(Configuration::CACHE_FOLDER, 'twig')));
436
        }
437
438
        $this->fs->mkdir($targetDir);
439
    }
440
}
441