• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keradus / PHP-CS-Fixer / 17899394524

21 Sep 2025 09:49PM UTC coverage: 94.396% (-0.2%) from 94.55%
17899394524

push

github

web-flow
refactor: introduce concept of AutomaticRuleSet (#9067)

99 of 150 new or added lines in 6 files covered. (66.0%)

4 existing lines in 2 files now uncovered.

28486 of 30177 relevant lines covered (94.4%)

45.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.72
/src/ComposerJsonReader.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer;
16

17
use Composer\Semver\Semver;
18
use Symfony\Component\Filesystem\Exception\IOException;
19

20
/**
21
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
22
 *
23
 * @internal
24
 *
25
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
26
 */
27
final class ComposerJsonReader
28
{
29
    private const COMPOSER_FILENAME = 'composer.json';
30

31
    private bool $isProcessed = false;
32

33
    private ?string $php = null;
34

35
    private ?string $phpUnit = null;
36

37
    public static function createSingleton(): self
38
    {
39
        static $instance = null;
1✔
40

41
        if (!$instance) {
1✔
42
            $instance = new self();
1✔
43
        }
44

45
        return $instance;
1✔
46
    }
47

48
    public function getPhp(): ?string
49
    {
50
        $this->processFile();
4✔
51

52
        return $this->php;
4✔
53
    }
54

55
    public function getPhpUnit(): ?string
56
    {
57
        $this->processFile();
19✔
58

59
        return $this->phpUnit;
19✔
60
    }
61

62
    private function processFile(): void
63
    {
64
        if (true === $this->isProcessed) {
23✔
65
            return;
23✔
66
        }
67

NEW
68
        if (!file_exists(self::COMPOSER_FILENAME)) {
×
NEW
69
            throw new IOException(\sprintf('Failed to read file "%s".', self::COMPOSER_FILENAME));
×
70
        }
71

NEW
72
        $readResult = file_get_contents(self::COMPOSER_FILENAME);
×
NEW
73
        if (false === $readResult) {
×
NEW
74
            throw new IOException(\sprintf('Failed to read file "%s".', self::COMPOSER_FILENAME));
×
75
        }
76

NEW
77
        $this->processJson($readResult);
×
78
    }
79

80
    private function processJson(string $json): void
81
    {
82
        if (true === $this->isProcessed) {
23✔
NEW
83
            return;
×
84
        }
85

86
        $composerJson = json_decode($json, true, 512, \JSON_THROW_ON_ERROR);
23✔
87

88
        $this->php = self::getMinSemVer(self::detectPhp($composerJson));
23✔
89
        $this->phpUnit = self::getMinSemVer(self::detectPackage($composerJson, 'phpunit/phpunit'));
23✔
90

91
        $this->isProcessed = true;
23✔
92
    }
93

94
    private static function getMinSemVer(?string $version): ?string
95
    {
96
        if ('' === $version || null === $version) {
23✔
97
            return null;
23✔
98
        }
99

100
        /** @var non-empty-list<string> $arr */
101
        $arr = Preg::split('/\s*\|\|?\s*/', $version);
21✔
102

103
        $arr = array_map(static function ($v) {
21✔
104
            $v = ltrim($v, '^~>=');
21✔
105

106
            $space = strpos($v, ' ');
21✔
107
            if (false !== $space) {
21✔
108
                $v = substr($v, 0, $space);
1✔
109
            }
110

111
            return $v;
21✔
112
        }, $arr);
21✔
113

114
        $textVersion = array_find($arr, static fn ($v) => true === Preg::match('/^\D/', $v));
21✔
115

116
        if (null !== $textVersion) {
21✔
117
            return null;
5✔
118
        }
119

120
        /** @var non-empty-list<string> $sortedArr */
121
        $sortedArr = Semver::sort($arr);
16✔
122

123
        $min = $sortedArr[0];
16✔
124
        $parts = explode('.', $min);
16✔
125

126
        return \sprintf('%s.%s', (int) $parts[0], (int) ($parts[1] ?? 0));
16✔
127
    }
128

129
    /**
130
     * @param array<string, mixed> $composerJson
131
     */
132
    private static function detectPhp(array $composerJson): ?string
133
    {
134
        $version = [];
23✔
135

136
        if (isset($composerJson['config']['platform']['php'])) {
23✔
137
            $version[] = $composerJson['config']['platform']['php'];
3✔
138
        }
139

140
        if (isset($composerJson['require-dev']['php'])) {
23✔
141
            $version[] = $composerJson['require-dev']['php'];
3✔
142
        }
143

144
        if (isset($composerJson['require']['php'])) {
23✔
145
            $version[] = $composerJson['require']['php'];
3✔
146
        }
147

148
        if (\count($version) > 0) {
23✔
149
            return implode(' || ', $version);
3✔
150
        }
151

152
        return null;
20✔
153
    }
154

155
    /**
156
     * @param array<string, mixed> $composerJson
157
     * @param non-empty-string     $package
158
     */
159
    private static function detectPackage(array $composerJson, string $package): ?string
160
    {
161
        $version = [];
23✔
162

163
        if (isset($composerJson['require-dev'][$package])) {
23✔
164
            $version[] = $composerJson['require-dev'][$package];
17✔
165
        }
166

167
        if (isset($composerJson['require'][$package])) {
23✔
168
            $version[] = $composerJson['require'][$package];
3✔
169
        }
170

171
        if (\count($version) > 0) {
23✔
172
            return implode(' || ', $version);
18✔
173
        }
174

175
        return null;
5✔
176
    }
177
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc