Passed
Pull Request — master (#2172)
by Arnaud
04:54
created

Builder   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 463
Duplicated Lines 0 %

Test Coverage

Coverage 91.91%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 142
c 3
b 1
f 0
dl 0
loc 463
rs 7.92
ccs 125
cts 136
cp 0.9191
wmc 51

33 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 5 1
A __construct() 0 16 5
A getConfig() 0 7 2
A setMenus() 0 3 1
A getRenderer() 0 3 1
A getData() 0 12 3
A setPages() 0 3 1
A setTaxonomies() 0 3 1
A setDestinationDir() 0 5 1
A addAsset() 0 4 2
A setRenderer() 0 3 1
A getPages() 0 3 1
A getLogger() 0 3 1
A getAssets() 0 3 1
A getVersion() 0 16 4
A getMetrics() 0 3 1
A getMenus() 0 3 1
A setAssets() 0 3 1
A setPagesFiles() 0 3 1
A checkErrors() 0 5 2
A build() 0 58 4
A getStatic() 0 3 1
A getBuildOptions() 0 3 1
A setSourceDir() 0 7 1
A setStatic() 0 3 1
A setLogger() 0 3 1
A isDebug() 0 3 1
A setConfig() 0 15 3
A setData() 0 3 1
A getPagesFiles() 0 3 1
A importThemesConfig() 0 4 2
A getBuilId() 0 3 1
A getTaxonomies() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Builder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Builder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Cecil;
15
16
use Cecil\Collection\Page\Collection as PagesCollection;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Generator\GeneratorManager;
19
use Cecil\Logger\PrintLogger;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerInterface;
22
use Symfony\Component\Finder\Finder;
23
24
/**
25
 * Class Builder.
26
 */
27
class Builder implements LoggerAwareInterface
28
{
29
    public const VERSION = '8.x-dev';
30
    public const VERBOSITY_QUIET = -1;
31
    public const VERBOSITY_NORMAL = 0;
32
    public const VERBOSITY_VERBOSE = 1;
33
    public const VERBOSITY_DEBUG = 2;
34
35
    /**
36
     * @var array Steps processed by build().
37
     */
38
    protected $steps = [
39
        'Cecil\Step\Pages\Load',
40
        'Cecil\Step\Data\Load',
41
        'Cecil\Step\StaticFiles\Load',
42
        'Cecil\Step\Pages\Create',
43
        'Cecil\Step\Pages\Convert',
44
        'Cecil\Step\Taxonomies\Create',
45
        'Cecil\Step\Pages\Generate',
46
        'Cecil\Step\Menus\Create',
47
        'Cecil\Step\StaticFiles\Copy',
48
        'Cecil\Step\Pages\Render',
49
        'Cecil\Step\Pages\Save',
50
        'Cecil\Step\Assets\Save',
51
        'Cecil\Step\Optimize\Html',
52
        'Cecil\Step\Optimize\Css',
53
        'Cecil\Step\Optimize\Js',
54
        'Cecil\Step\Optimize\Images',
55
    ];
56
57
    /** @var Config Configuration. */
58
    protected $config;
59
60
    /** @var LoggerInterface Logger. */
61
    protected $logger;
62
63
    /** @var bool Debug mode. */
64
    protected $debug = false;
65
66
    /** @var array Build options. */
67
    protected $options = [];
68
69
    /** @var Finder Content iterator. */
70
    protected $content;
71
72
    /** @var array Data collection. */
73
    protected $data = [];
74
75
    /** @var array Static files collection. */
76
    protected $static = [];
77
78
    /** @var PagesCollection Pages collection. */
79
    protected $pages;
80
81
    /** @var array Assets path collection */
82
    protected $assets = [];
83
84
    /** @var array Menus collection. */
85
    protected $menus;
86
87
    /** @var array Taxonomies collection. */
88
    protected $taxonomies;
89
90
    /** @var Renderer\RendererInterface Renderer. */
91
    protected $renderer;
92
93
    /** @var GeneratorManager Generators manager. */
94
    protected $generatorManager;
95
96
    /** @var string Application version. */
97
    protected static $version;
98
99
    /** @var array Build metrics. */
100
    protected $metrics = [];
101
102
    /** @var string curent build ID */
103
    protected $buildId;
104
105
    /**
106
     * @param Config|array|null    $config
107
     * @param LoggerInterface|null $logger
108
     */
109 1
    public function __construct($config = null, ?LoggerInterface $logger = null)
110
    {
111
        // init and set config
112 1
        $this->config = new Config();
113 1
        if ($config !== null) {
114 1
            $this->setConfig($config);
115
        }
116
        // debug mode?
117 1
        if (getenv('CECIL_DEBUG') == 'true' || $this->getConfig()->isEnabled('debug')) {
118 1
            $this->debug = true;
119
        }
120
        // set logger
121 1
        if ($logger === null) {
122
            $logger = new PrintLogger(self::VERBOSITY_VERBOSE);
123
        }
124 1
        $this->setLogger($logger);
125
    }
126
127
    /**
128
     * Creates a new Builder instance.
129
     */
130 1
    public static function create(): self
131
    {
132 1
        $class = new \ReflectionClass(\get_called_class());
133
134 1
        return $class->newInstanceArgs(\func_get_args());
135
    }
136
137
    /**
138
     * Builds a new website.
139
     */
140 1
    public function build(array $options): self
141
    {
142
        // set start script time and memory usage
143 1
        $startTime = microtime(true);
144 1
        $startMemory = memory_get_usage();
145
146
        // checks soft errors
147 1
        $this->checkErrors();
148
149
        // prepare options
150 1
        $this->options = array_merge([
151 1
            'drafts'           => false, // build drafts or not
152 1
            'dry-run'          => false, // if dry-run is true, generated files are not saved
153 1
            'page'             => '',    // specific page to build
154 1
            'render-only-path' => ''     //
155 1
        ], $options);
156
157
        // set build ID
158 1
        $this->buildId = date('YmdHis');
159
160
        // process each step
161 1
        $steps = [];
162
        // init...
163 1
        foreach ($this->steps as $step) {
164
            /** @var Step\StepInterface $stepObject */
165 1
            $stepObject = new $step($this);
166 1
            $stepObject->init($this->options);
167 1
            if ($stepObject->canProcess()) {
168 1
                $steps[] = $stepObject;
169
            }
170
        }
171
        // ...and process!
172 1
        $stepNumber = 0;
173 1
        $stepsTotal = \count($steps);
174 1
        foreach ($steps as $step) {
175 1
            $stepNumber++;
176
            /** @var Step\StepInterface $step */
177 1
            $this->getLogger()->notice($step->getName(), ['step' => [$stepNumber, $stepsTotal]]);
178 1
            $stepStartTime = microtime(true);
179 1
            $stepStartMemory = memory_get_usage();
180 1
            $step->process();
181
            // step duration and memory usage
182 1
            $this->metrics['steps'][$stepNumber]['name'] = $step->getName();
183 1
            $this->metrics['steps'][$stepNumber]['duration'] = Util::convertMicrotime((float) $stepStartTime);
184 1
            $this->metrics['steps'][$stepNumber]['memory']   = Util::convertMemory(memory_get_usage() - $stepStartMemory);
185 1
            $this->getLogger()->info(\sprintf(
186 1
                '%s done in %s (%s)',
187 1
                $this->metrics['steps'][$stepNumber]['name'],
188 1
                $this->metrics['steps'][$stepNumber]['duration'],
189 1
                $this->metrics['steps'][$stepNumber]['memory']
190 1
            ));
191
        }
192
        // build duration and memory usage
193 1
        $this->metrics['total']['duration'] = Util::convertMicrotime($startTime);
0 ignored issues
show
Bug introduced by
It seems like $startTime can also be of type string; however, parameter $start of Cecil\Util::convertMicrotime() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
        $this->metrics['total']['duration'] = Util::convertMicrotime(/** @scrutinizer ignore-type */ $startTime);
Loading history...
194 1
        $this->metrics['total']['memory']   = Util::convertMemory(memory_get_usage() - $startMemory);
195 1
        $this->getLogger()->notice(\sprintf('Built in %s (%s)', $this->metrics['total']['duration'], $this->metrics['total']['memory']));
196
197 1
        return $this;
198
    }
199
200
    /**
201
     * Returns current build ID.
202
     */
203 1
    public function getBuilId(): string
204
    {
205 1
        return $this->buildId;
206
    }
207
208
    /**
209
     * Set configuration.
210
     */
211 1
    public function setConfig(array|Config $config): self
212
    {
213 1
        if (\is_array($config)) {
214 1
            $config = new Config($config);
215
        }
216 1
        if ($this->config !== $config) {
217 1
            $this->config = $config;
218
        }
219
220
        // import themes configuration
221 1
        $this->importThemesConfig();
222
        // autoloads local extensions
223 1
        Util::autoload($this, 'extensions');
224
225 1
        return $this;
226
    }
227
228
    /**
229
     * Returns configuration.
230
     */
231 1
    public function getConfig(): Config
232
    {
233 1
        if ($this->config === null) {
234
            $this->config = new Config();
235
        }
236
237 1
        return $this->config;
238
    }
239
240
    /**
241
     * Config::setSourceDir() alias.
242
     */
243 1
    public function setSourceDir(string $sourceDir): self
244
    {
245 1
        $this->getConfig()->setSourceDir($sourceDir);
246
        // import themes configuration
247 1
        $this->importThemesConfig();
248
249 1
        return $this;
250
    }
251
252
    /**
253
     * Config::setDestinationDir() alias.
254
     */
255 1
    public function setDestinationDir(string $destinationDir): self
256
    {
257 1
        $this->getConfig()->setDestinationDir($destinationDir);
258
259 1
        return $this;
260
    }
261
262
    /**
263
     * Import themes configuration.
264
     */
265 1
    public function importThemesConfig(): void
266
    {
267 1
        foreach ((array) $this->config->get('theme') as $theme) {
268 1
            $this->config->import(Config::loadFile(Util::joinFile($this->config->getThemesPath(), $theme, 'config.yml'), true), Config::PRESERVE);
269
        }
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275 1
    public function setLogger(LoggerInterface $logger): void
276
    {
277 1
        $this->logger = $logger;
278
    }
279
280
    /**
281
     * Returns the logger instance.
282
     */
283 1
    public function getLogger(): LoggerInterface
284
    {
285 1
        return $this->logger;
286
    }
287
288
    /**
289
     * Returns debug mode state.
290
     */
291 1
    public function isDebug(): bool
292
    {
293 1
        return (bool) $this->debug;
294
    }
295
296
    /**
297
     * Returns build options.
298
     */
299 1
    public function getBuildOptions(): array
300
    {
301 1
        return $this->options;
302
    }
303
304
    /**
305
     * Set collected pages files.
306
     */
307 1
    public function setPagesFiles(Finder $content): void
308
    {
309 1
        $this->content = $content;
310
    }
311
312
    /**
313
     * Returns pages files.
314
     */
315 1
    public function getPagesFiles(): ?Finder
316
    {
317 1
        return $this->content;
318
    }
319
320
    /**
321
     * Set collected data.
322
     */
323 1
    public function setData(array $data): void
324
    {
325 1
        $this->data = $data;
326
    }
327
328
    /**
329
     * Returns data collection.
330
     */
331 1
    public function getData(?string $language = null): array
332
    {
333 1
        if ($language) {
334 1
            if (empty($this->data[$language])) {
335
                // fallback to default language
336 1
                return $this->data[$this->config->getLanguageDefault()];
337
            }
338
339 1
            return $this->data[$language];
340
        }
341
342 1
        return $this->data;
343
    }
344
345
    /**
346
     * Set collected static files.
347
     */
348 1
    public function setStatic(array $static): void
349
    {
350 1
        $this->static = $static;
351
    }
352
353
    /**
354
     * Returns static files collection.
355
     */
356 1
    public function getStatic(): array
357
    {
358 1
        return $this->static;
359
    }
360
361
    /**
362
     * Set/update Pages collection.
363
     */
364 1
    public function setPages(PagesCollection $pages): void
365
    {
366 1
        $this->pages = $pages;
367
    }
368
369
    /**
370
     * Returns pages collection.
371
     */
372 1
    public function getPages(): ?PagesCollection
373
    {
374 1
        return $this->pages;
375
    }
376
377
    /**
378
     * Set assets path list.
379
     */
380
    public function setAssets(array $assets): void
381
    {
382
        $this->assets = $assets;
383
    }
384
385
    /**
386
     * Add an asset path to assets list.
387
     */
388 1
    public function addAsset(string $path): void
389
    {
390 1
        if (!\in_array($path, $this->assets, true)) {
391 1
            $this->assets[] = $path;
392
        }
393
    }
394
395
    /**
396
     * Returns list of assets path.
397
     */
398 1
    public function getAssets(): array
399
    {
400 1
        return $this->assets;
401
    }
402
403
    /**
404
     * Set menus collection.
405
     */
406 1
    public function setMenus(array $menus): void
407
    {
408 1
        $this->menus = $menus;
409
    }
410
411
    /**
412
     * Returns all menus, for a language.
413
     */
414 1
    public function getMenus(string $language): Collection\Menu\Collection
415
    {
416 1
        return $this->menus[$language];
417
    }
418
419
    /**
420
     * Set taxonomies collection.
421
     */
422 1
    public function setTaxonomies(array $taxonomies): void
423
    {
424 1
        $this->taxonomies = $taxonomies;
425
    }
426
427
    /**
428
     * Returns taxonomies collection, for a language.
429
     */
430 1
    public function getTaxonomies(string $language): ?Collection\Taxonomy\Collection
431
    {
432 1
        return $this->taxonomies[$language];
433
    }
434
435
    /**
436
     * Set renderer object.
437
     */
438 1
    public function setRenderer(Renderer\RendererInterface $renderer): void
439
    {
440 1
        $this->renderer = $renderer;
441
    }
442
443
    /**
444
     * Returns Renderer object.
445
     */
446 1
    public function getRenderer(): Renderer\RendererInterface
447
    {
448 1
        return $this->renderer;
449
    }
450
451
    /**
452
     * Returns metrics array.
453
     */
454
    public function getMetrics(): array
455
    {
456
        return $this->metrics;
457
    }
458
459
    /**
460
     * Returns application version.
461
     *
462
     * @throws RuntimeException
463
     */
464 1
    public static function getVersion(): string
465
    {
466 1
        if (!isset(self::$version)) {
467
            try {
468 1
                $filePath = Util\File::getRealPath('VERSION');
469
                $version = Util\File::fileGetContents($filePath);
470
                if ($version === false) {
471
                    throw new RuntimeException(\sprintf('Can\'t read content of "%s".', $filePath));
472
                }
473
                self::$version = trim($version);
474 1
            } catch (\Exception) {
475 1
                self::$version = self::VERSION;
476
            }
477
        }
478
479 1
        return self::$version;
480
    }
481
482
    /**
483
     * Log soft errors.
484
     */
485 1
    protected function checkErrors(): void
486
    {
487
        // baseurl is required in production
488 1
        if (empty(trim((string) $this->config->get('baseurl'), '/'))) {
489
            $this->getLogger()->error('`baseurl` configuration key is required in production.');
490
        }
491
    }
492
}
493