Passed
Push — trunk ( 45f743...c781e9 )
by Christian
11:43 queued 13s
created

GetClassesPerAreaCommand   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 87
c 2
b 0
f 0
dl 0
loc 164
rs 10
wmc 19

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getShopwareClasses() 0 9 2
A configure() 0 29 1
A __construct() 0 6 1
B execute() 0 55 10
A getClassesPerArea() 0 23 5
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\DevOps\StaticAnalyze\Coverage\Command;
4
5
use Composer\Autoload\ClassLoader;
6
use Shopware\Core\Framework\Log\Package;
7
use Symfony\Component\Console\Attribute\AsCommand;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
13
/**
14
 * @internal
15
 */
16
#[AsCommand(
17
    name: 'coverage:classes-per-area',
18
    description: 'Output all classes of the Shopware-namespace aggregated by area.
19
20
  In order for this command to work properly, you need to dump the composer autoloader before running it:
21
  $ composer dump-autoload -o
22
'
23
)]
24
#[Package('core')]
25
class GetClassesPerAreaCommand extends Command
26
{
27
    public const OPTION_JSON = 'json';
28
    public const OPTION_PRETTY = 'pretty-print';
29
30
    public const OPTION_GENERATE_PHPUNIT_TEST = 'generate-phpunit-test';
31
32
    public const OPTION_NAMESPACE_PATTERN = 'ns-pattern';
33
34
    public const NAMESPACE_PATTERN_DEFAULT = '#^Shopware\\\\(Core|Administration|Storefront|Elasticsearch)\\\\#';
35
36
    private ClassLoader $classLoader;
37
38
    private string $nsPattern;
39
40
    /**
41
     * @internal
42
     */
43
    public function __construct(
44
        private readonly string $projectDir,
45
    ) {
46
        parent::__construct();
47
48
        $this->classLoader = require $this->projectDir . '/vendor/autoload.php';
49
    }
50
51
    protected function configure(): void
52
    {
53
        $this->addOption(
54
            self::OPTION_JSON,
55
            'j',
56
            InputOption::VALUE_NONE,
57
            'Output as JSON'
58
        );
59
60
        $this->addOption(
61
            self::OPTION_PRETTY,
62
            'H',
63
            InputOption::VALUE_NONE,
64
            'Format output to be human-readable'
65
        );
66
67
        $this->addOption(
68
            self::OPTION_GENERATE_PHPUNIT_TEST,
69
            'g',
70
            InputOption::VALUE_NONE,
71
            'Generate phpunit.xml'
72
        );
73
74
        $this->addOption(
75
            self::OPTION_NAMESPACE_PATTERN,
76
            null,
77
            InputOption::VALUE_REQUIRED,
78
            'The pattern the namespace has to match',
79
            self::NAMESPACE_PATTERN_DEFAULT,
80
        );
81
    }
82
83
    protected function execute(InputInterface $input, OutputInterface $output): int
84
    {
85
        $this->nsPattern = $input->getOption(self::OPTION_NAMESPACE_PATTERN);
86
87
        $classesPerArea = $this->getClassesPerArea();
88
        if ($input->getOption(self::OPTION_JSON)) {
89
            $output->write(
90
                json_encode(
91
                    $classesPerArea,
92
                    $input->getOption(self::OPTION_PRETTY) ? \JSON_PRETTY_PRINT : 0
93
                ) ?: ''
94
            );
95
        } else {
96
            $output->write(
97
                var_export(
98
                    $classesPerArea,
99
                    true
100
                )
101
            );
102
        }
103
104
        if ($input->getOption(self::OPTION_GENERATE_PHPUNIT_TEST)) {
105
            $unitFiles = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $unitFiles is dead and can be removed.
Loading history...
106
            foreach ($classesPerArea as $area => $classToFile) {
107
                $unitFile = new \DOMDocument();
108
                // Load phpunit template
109
                $unitFile->load('phpunit.xml.dist');
110
                $unitDocument = $unitFile->documentElement;
111
                if ($unitDocument === null) {
112
                    return 1;
113
                }
114
                $coverage = $unitDocument->getElementsByTagName('coverage')->item(0);
115
                if ($coverage === null) {
116
                    return 1;
117
                }
118
                $includeChildElement = $coverage->getElementsByTagName('include')->item(0);
119
                if ($includeChildElement === null) {
120
                    return 1;
121
                }
122
                // Remove include from coverage to create our own includes
123
                $coverage->removeChild($includeChildElement);
124
                $includeElement = $unitFile->createElement('include');
125
126
                foreach ($classToFile as $class => $file) {
127
                    $fileElement = $unitFile->createElement('file', $file);
128
                    $includeElement->appendChild($fileElement);
129
                }
130
                $coverage->appendChild($includeElement);
131
132
                // Create phpunit file per area
133
                file_put_contents("phpunit.$area.xml", $unitFile->saveXML());
134
            }
135
        }
136
137
        return 0;
138
    }
139
140
    /**
141
     * @return array<string, array<string, string>>
142
     */
143
    private function getClassesPerArea(): array
144
    {
145
        $areas = [];
146
147
        foreach ($this->getShopwareClasses() as $class => $path) {
148
            try {
149
                $area = Package::getPackageName($class);
150
            } catch (\Throwable $e) {
151
                $areas['unknown'][$class] = $path;
152
153
                continue;
154
            }
155
156
            if (!\is_string($area)) {
157
                continue;
158
            }
159
160
            $areaTrim = strstr($area, \PHP_EOL, true) ?: $area;
161
162
            $areas[trim($areaTrim)][$class] = $path;
163
        }
164
165
        return $areas;
166
    }
167
168
    /**
169
     * @return array<string, string>
170
     */
171
    private function getShopwareClasses(): array
172
    {
173
        return array_filter($this->classLoader->getClassMap(), function (string $class): bool {
174
            if (str_starts_with($class, 'Shopware\\')) {
175
                return (bool) preg_match($this->nsPattern, $class);
176
            }
177
178
            return false;
179
        }, \ARRAY_FILTER_USE_KEY);
180
    }
181
}
182