Kernel::__invoke()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 6
nop 2
dl 0
loc 29
rs 9.6666
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace App;
4
5
use App\Domain\Content;
6
use App\Domain\Contents;
7
use App\Domain\Path;
8
use App\Domain\PathPart;
9
use App\Domain\ProjectRootPathAware;
10
use App\Domain\RootPathPartAware;
11
use App\UI\UserInterface;
12
use Symfony\Component\Filesystem\Filesystem;
13
use Symfony\Component\Finder\Finder;
14
use Symfony\Component\Yaml\Yaml;
15
16
final class Kernel
17
{
18
    /** @var string */
19
    private $projectDir;
20
21
    /** @var string */
22
    private $configDir;
23
24
    /** @var \App\UI\UserInterface */
25
    private $ui;
26
27
    /**
28
     * @param string $projectDir
29
     */
30
    public function __construct(string $projectDir)
31
    {
32
        $this->projectDir = rtrim($projectDir, DIRECTORY_SEPARATOR);
33
        $this->configDir = $projectDir.DIRECTORY_SEPARATOR.'config';
34
    }
35
36
    /**
37
     * @param \App\UI\UserInterface $ui
38
     * @param string $singleConfigFilePath
39
     *
40
     * @throws \RuntimeException
41
     * @throws \Symfony\Component\Filesystem\Exception\IOException
42
     * @throws \Symfony\Component\Yaml\Exception\ParseException
43
     */
44
    public function __invoke(UserInterface $ui, $singleConfigFilePath = ''): void
45
    {
46
        $this->ui = $ui;
47
48
        $configFilesPaths = !empty($singleConfigFilePath)
49
            ? [$this->getConfigAbsoluteFilePath($singleConfigFilePath)]
50
            : $this->getAllConfigFilesPaths();
51
52
        foreach ($configFilesPaths as $configFilePath) {
53
            $this->ui->getSymfonyStyle()->section(
54
                sprintf(
55
                    'Processing configuration file <info>%s</info>',
56
                    $this->getConfigRelativeFilePath($configFilePath)
57
                )
58
            );
59
60
            $config = (array) Yaml::parseFile($configFilePath);
61
            $config['enabled'] = $config['enabled'] ?? true;
62
63
            if (!$config['enabled']) {
64
                $this->ui->getSymfonyStyle()->note('Skipped.');
65
                continue;
66
            }
67
68
            $rootPathPart = $this->getRootPathPart($config['path_part'] ?? []);
69
            $contents = new Contents([]);
70
71
            $this->crawlSources($config['sources'], $contents, $rootPathPart);
72
            $this->applyProcessors($config['processors'], $contents, $rootPathPart);
73
        }
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    private function getAllConfigFilesPaths(): array
80
    {
81
        $paths = [];
82
83
        foreach ((new Finder())->in($this->configDir)->files()->sortByName()->name('*.yml')->getIterator() as $file) {
84
            $paths[] = $file->getRealPath();
85
        }
86
87
        return $paths;
88
    }
89
90
    /**
91
     * @param array $config
92
     *
93
     * @return \App\Domain\PathPart
94
     *
95
     * @throws \RuntimeException
96
     * @throws \Symfony\Component\Filesystem\Exception\IOException
97
     */
98
    private function getRootPathPart(array $config): PathPart
99
    {
100
        $config['substitutions'] = ['%project_root%' => $this->projectDir] + ($config['substitutions'] ?? []);
101
        $rootPathPart = new PathPart($config);
102
103
        // Try to create the root directory... 'cause if it fails, nothing will work.
104
        (new Filesystem())->mkdir($rootPathPart->getPath());
105
106
        return $rootPathPart;
107
    }
108
109
    /**
110
     * @param string $configFilePath
111
     *
112
     * @return string
113
     */
114
    private function getConfigRelativeFilePath(string $configFilePath): string
115
    {
116
        return (string) str_replace($this->projectDir.DIRECTORY_SEPARATOR, '', $configFilePath);
117
    }
118
119
    /**
120
     * @param string $configFilePath
121
     *
122
     * @return string
123
     * @throws \RuntimeException
124
     */
125
    private function getConfigAbsoluteFilePath(string $configFilePath): string
126
    {
127
        if (DIRECTORY_SEPARATOR === $configFilePath{0}) {
128
            return $configFilePath;
129
        }
130
131
        return $this->projectDir.DIRECTORY_SEPARATOR.$configFilePath;
132
    }
133
134
    /**
135
     * @param array $config
136
     * @param Contents $contents
137
     * @param PathPart $rootPathPart
138
     */
139
    private function crawlSources(array $config, Contents $contents, PathPart $rootPathPart): void
140
    {
141
        foreach ($config as $sources) {
142
            foreach ($sources as $sourceClassName => $sourceConfig) {
143
144
                /** @var \App\Domain\Source $source */
145
                $source = new $sourceClassName($this->ui, $sourceConfig ?? []);
146
                $this->informObjectOfContext($source, $rootPathPart);
147
148
                // Add the root path part to the contents' path
149
                $sourceContents = $source->getContents()
150
                    ->map(function (Content $content) use ($rootPathPart) {
151
                        $content->getPath()->add($rootPathPart);
152
153
                        return $content;
154
                    });
155
156
                foreach ($sourceContents as $content) {
157
                    $contents->add($content);
158
                }
159
            }
160
        }
161
    }
162
163
    /**
164
     * @param array $config
165
     * @param Contents $contents
166
     * @param PathPart $rootPathPart
167
     */
168
    private function applyProcessors(array $config, Contents $contents, PathPart $rootPathPart): void
169
    {
170
        foreach ($config as $processors) {
171
            foreach ($processors as $processorClassName => $processorConfig) {
172
173
                /** @var \App\Domain\ContentsProcessor $processor */
174
                $processor = new $processorClassName($this->ui, $processorConfig ?? []);
175
                $this->informObjectOfContext($processor, $rootPathPart);
176
                $processor->processContents(clone $contents);
177
            }
178
        }
179
    }
180
181
    /**
182
     * @param object $object
183
     * @param PathPart $rootPathPart
184
     */
185
    private function informObjectOfContext($object, PathPart $rootPathPart): void
186
    {
187
        if ($object instanceof RootPathPartAware) {
188
            $object->setRootPathPart($rootPathPart);
189
        }
190
        if ($object instanceof ProjectRootPathAware) {
191
            $object->setProjectRootPath(new Path([new PathPart(['path' => $this->projectDir])]));
192
        }
193
    }
194
}
195