Completed
Push — master ( 00d60f...aabe5c )
by Dion
14s
created

InstallDirectoryCommand::classifyFile()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.4444
c 0
b 0
f 0
cc 8
nc 8
nop 2
1
<?php
2
3
/*
4
 * This file is part of the SexyField package.
5
 *
6
 * (c) Dion Snoeijen <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Tardigrades\Command;
15
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Yaml\Yaml;
21
use Tardigrades\SectionField\Service\ApplicationManagerInterface;
22
use Tardigrades\SectionField\Service\FieldManagerInterface;
23
use Tardigrades\SectionField\Service\FieldTypeManagerInterface;
24
use Tardigrades\SectionField\Service\FieldTypeNotFoundException;
25
use Tardigrades\SectionField\Service\LanguageManagerInterface;
26
use Tardigrades\SectionField\Service\NotFoundException;
27
use Tardigrades\SectionField\Service\SectionManagerInterface;
28
use Tardigrades\SectionField\ValueObject\ApplicationConfig;
29
use Tardigrades\SectionField\ValueObject\ConfigWithHandleInterface;
30
use Tardigrades\SectionField\ValueObject\FieldConfig;
31
use Tardigrades\SectionField\ValueObject\FullyQualifiedClassName;
32
use Tardigrades\SectionField\ValueObject\LanguageConfig;
33
use Tardigrades\SectionField\ValueObject\SectionConfig;
34
use Tardigrades\SectionField\ValueObject\Type;
35
36
class InstallDirectoryCommand extends Command
37
{
38
    /** @var ApplicationConfig[] */
39
    private $applications = [];
40
41
    /** @var ?LanguageConfig */
42
    private $languages = null;
43
44
    /** @var SectionConfig[] */
45
    private $sections = [];
46
47
    /** @var FieldConfig[] */
48
    private $fields = [];
49
50
    /** @var ApplicationManagerInterface */
51
    private $applicationManager;
52
53
    /** @var LanguageManagerInterface */
54
    private $languageManager;
55
56
    /** @var SectionManagerInterface */
57
    private $sectionManager;
58
59
    /** @var FieldManagerInterface */
60
    private $fieldManager;
61
62
    /** @var FieldTypeManagerInterface */
63
    private $fieldTypeManager;
64
65
    public function __construct(
66
        ApplicationManagerInterface $applicationManager,
67
        LanguageManagerInterface $languageManager,
68
        SectionManagerInterface $sectionManager,
69
        FieldManagerInterface $fieldManager,
70
        FieldTypeManagerInterface $fieldTypeManager
71
    ) {
72
        parent::__construct('sf:install-directory');
73
        $this->applicationManager = $applicationManager;
74
        $this->languageManager = $languageManager;
75
        $this->sectionManager = $sectionManager;
76
        $this->fieldManager = $fieldManager;
77
        $this->fieldTypeManager = $fieldTypeManager;
78
    }
79
80
    protected function configure(): void
81
    {
82
        $this
83
            ->setDescription('Install all configuration in a directory')
84
            ->setHelp('This command installs all .yml files in a directory, recursively. Pass it the path to' .
85
                'the directory, for example "app/config/my-sexy-field-config".')
86
            ->addArgument('directory', InputArgument::REQUIRED, 'The config directory');
87
    }
88
89
    /**
90
     * @param InputInterface $input
91
     * @param OutputInterface $output
92
     * @throws \Exception
93
     */
94
    protected function execute(InputInterface $input, OutputInterface $output): void
95
    {
96
        foreach (static::getAllYamls($input->getArgument('directory')) as $fileName => $config) {
97
            $this->classifyFile($fileName, $config);
98
        }
99
100
        $this->verifyConfig();
101
102
        $this->createLanguages($this->languages);
0 ignored issues
show
Bug introduced by
It seems like $this->languages can also be of type null; however, parameter $languages of Tardigrades\Command\Inst...mand::createLanguages() does only seem to accept Tardigrades\SectionField...ueObject\LanguageConfig, 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

102
        $this->createLanguages(/** @scrutinizer ignore-type */ $this->languages);
Loading history...
103
        $output->writeln("<info>Languages created!</info>");
104
105
        $applicationCount = $this->createApplications($this->applications);
106
        $output->writeln("<info>$applicationCount applications created!</info>");
107
108
        $fieldTypeCount = $this->installFieldTypes($this->fields);
109
        $output->writeln("<info>$fieldTypeCount field types installed!</info>");
110
111
        $fieldCount = $this->createFields($this->fields);
112
        $output->writeln("<info>$fieldCount fields created!</info>");
113
114
        $sectionCount = $this->createSections($this->sections);
115
        $output->writeln("<info>$sectionCount sections created!</info>");
116
    }
117
118
    /**
119
     * @throws \Exception
120
     */
121
    private function verifyConfig(): void
122
    {
123
        if (count($this->applications) === 0) {
124
            throw new \Exception("Could not find any application config files");
125
        }
126
        if (is_null($this->languages)) {
127
            throw new \Exception("Could not find a language config file");
128
        }
129
    }
130
131
    /**
132
     * @param FieldConfig[] $fields
133
     * @return int the number of field types detected and installed
134
     */
135
    private function installFieldTypes(array $fields): int
136
    {
137
        $fieldTypes = [];
138
        foreach ($fields as $field) {
139
            $fieldType = $field->toArray()['field']['type'];
140
            if (!in_array($fieldType, $fieldTypes)) {
141
                $fieldTypes[] = $fieldType;
142
            }
143
        }
144
145
        foreach ($fieldTypes as $fieldType) {
146
            try {
147
                $this->fieldTypeManager->readByType(Type::fromString($fieldType));
148
                // Already installed, so continue
149
                // There's no clean way of updating field types
150
            } catch (FieldTypeNotFoundException $exception) {
151
                // Not installed, so install
152
                if ($fieldType === 'DateTimeField') {
153
                    // DateTime has "Field" at the end of its name to avoid confusion with \DateTime.
154
                    // All other field types follow Tardigrades\FieldType\{fieldType}\{fieldType}.
155
                    // This solution is hacky, but there's no good way to detect classes before they've been loaded.
156
                    $className = "Tardigrades\\FieldType\\DateTime\\$fieldType";
157
                } else {
158
                    $className = "Tardigrades\\FieldType\\$fieldType\\$fieldType";
159
                }
160
                $this->fieldTypeManager->createWithFullyQualifiedClassName(
161
                    FullyQualifiedClassName::fromString($className)
162
                );
163
            }
164
        }
165
166
        return count($fieldTypes);
167
    }
168
169
    /**
170
     * @param SectionConfig[] $sections
171
     * @return int the number of sections created
172
     */
173
    private function createSections(array $sections): int
174
    {
175
        return $this->createOrUpdateConfigs($sections, $this->sectionManager);
176
    }
177
178
    /**
179
     * @param FieldConfig[] $fields
180
     * @return int the number of fields created
181
     */
182
    private function createFields(array $fields): int
183
    {
184
        return $this->createOrUpdateConfigs($fields, $this->fieldManager);
185
    }
186
187
    /**
188
     * @param ApplicationConfig[] $applications
189
     * @return int the number of applications created
190
     */
191
    private function createApplications(array $applications): int
192
    {
193
        return $this->createOrUpdateConfigs($applications, $this->applicationManager);
194
    }
195
196
    /**
197
     * @param ConfigWithHandleInterface[] $configs
198
     * @param ApplicationManagerInterface|FieldManagerInterface|SectionManagerInterface $manager
199
     * @return int
200
     */
201
    private function createOrUpdateConfigs(array $configs, $manager): int
202
    {
203
        foreach ($configs as $config) {
204
            try {
205
                $handle = $config->getHandle();
206
                $object = $manager->readByHandle($handle);
207
                $manager->updateByConfig($config, $object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type Tardigrades\Entity\SectionInterface and Tardigrades\Entity\ApplicationInterface; however, parameter $field of Tardigrades\SectionField...rface::updateByConfig() does only seem to accept Tardigrades\Entity\FieldInterface, 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

207
                $manager->updateByConfig($config, /** @scrutinizer ignore-type */ $object);
Loading history...
Bug introduced by
It seems like $object can also be of type Tardigrades\Entity\FieldInterface and Tardigrades\Entity\ApplicationInterface; however, parameter $section of Tardigrades\SectionField...rface::updateByConfig() does only seem to accept Tardigrades\Entity\SectionInterface, 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

207
                $manager->updateByConfig($config, /** @scrutinizer ignore-type */ $object);
Loading history...
Bug introduced by
It seems like $object can also be of type Tardigrades\Entity\SectionInterface and Tardigrades\Entity\FieldInterface; however, parameter $application of Tardigrades\SectionField...rface::updateByConfig() does only seem to accept Tardigrades\Entity\ApplicationInterface, 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

207
                $manager->updateByConfig($config, /** @scrutinizer ignore-type */ $object);
Loading history...
208
            } catch (NotFoundException $exception) {
209
                $manager->createByConfig($config);
210
            }
211
        }
212
        return count($configs);
213
    }
214
215
    /**
216
     * @param LanguageConfig $languages
217
     */
218
    private function createLanguages(LanguageConfig $languages): void
219
    {
220
        $this->languageManager->createByConfig($languages);
221
    }
222
223
    /**
224
     * @param string $fileName
225
     * @param mixed $config
226
     * @throws \Exception
227
     */
228
    private function classifyFile(string $fileName, $config): void
229
    {
230
        if (!is_array($config)) {
231
            throw new \Exception("Malformed file $fileName");
232
        }
233
        $keys = array_keys($config);
234
        if (count($keys) !== 1) {
235
            throw new \Exception("Malformed file $fileName");
236
        }
237
        [$key] = $keys;
238
        switch ($key) {
239
            case 'field':
240
                $this->fields[] = FieldConfig::fromArray($config);
241
                break;
242
            case 'section':
243
                $this->sections[] = SectionConfig::fromArray($config);
244
                break;
245
            case 'application':
246
                $this->applications[] = ApplicationConfig::fromArray($config);
247
                break;
248
            case 'language':
249
                if (!is_nulL($this->languages)) {
250
                    throw new \Exception("Found multiple language config files");
251
                }
252
                $this->languages = LanguageConfig::fromArray($config);
253
                break;
254
            default:
255
                throw new \Exception("Could not identify file $fileName with key $key");
256
        }
257
    }
258
259
    /**
260
     * Get the contents of all yaml files in a directory, recursively.
261
     * @param string $directory
262
     * @return \Generator
263
     */
264
    private static function getAllYamls(string $directory): \Generator
265
    {
266
        /** @var \SplFileInfo $file */
267
        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)) as $file) {
268
            if ($file->isFile() && $file->getExtension() === 'yml') {
269
                $fileName = $file->getPathname();
270
                yield $fileName => Yaml::parse(file_get_contents($fileName));
271
            }
272
        }
273
    }
274
}
275