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

eliashaeussler / phpstan-config / 22729413729

05 Mar 2026 05:44PM UTC coverage: 41.935% (-54.7%) from 96.654%
22729413729

Pull #132

github

eliashaeussler
[FEATURE] Add custom rule to compare composer.json and ext_emconf.php
Pull Request #132: [FEATURE] Add custom rule to compare composer.json and ext_emconf.php

3 of 354 new or added lines in 9 files covered. (0.85%)

260 of 620 relevant lines covered (41.94%)

3.68 hits per line

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

0.0
/src/Resource/ComposerPackage.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/phpstan-config".
7
 *
8
 * Copyright (C) 2023-2026 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\PHPStanConfig\Resource;
25

26
use Composer\Semver;
27
use EliasHaeussler\PHPStanConfig\Enums;
28
use EliasHaeussler\PHPStanConfig\Exception;
29

30
use function is_string;
31

32
/**
33
 * ComposerPackage.
34
 *
35
 * @author Elias Häußler <elias@haeussler.dev>
36
 * @license GPL-3.0-or-later
37
 */
38
final readonly class ComposerPackage
39
{
40
    public string $constraint;
41

NEW
42
    public function __construct(
×
43
        public string $name,
44
        string $constraint,
45
        public Enums\ComposerPackageRelationKey $relationKey,
46
        public ?string $type = null,
47
        public ?string $extensionKey = null,
48
    ) {
NEW
49
        $this->constraint = self::parseConstraint($constraint, $relationKey);
×
50
    }
51

52
    /**
53
     * @param array{name?: mixed, type?: mixed, extra?: array{'typo3/cms'?: array{'extension-key': mixed}}} $declaration
54
     *
55
     * @throws Exception\ComposerPackageDeclarationIsInvalid
56
     */
NEW
57
    public static function fromDeclaration(
×
58
        array $declaration,
59
        string $constraint,
60
        Enums\ComposerPackageRelationKey $relationKey,
61
    ): self {
NEW
62
        $name = $declaration['name'] ?? null;
×
NEW
63
        $type = $declaration['type'] ?? null;
×
NEW
64
        $extensionKey = $declaration['extra']['typo3/cms']['extension-key'] ?? null;
×
65

NEW
66
        if (!is_string($name)) {
×
NEW
67
            throw new Exception\ComposerPackageDeclarationIsInvalid();
×
68
        }
69

NEW
70
        if (!is_string($type) && null !== $type) {
×
NEW
71
            throw new Exception\ComposerPackageDeclarationIsInvalid();
×
72
        }
73

NEW
74
        if (!is_string($extensionKey) && null !== $extensionKey) {
×
NEW
75
            throw new Exception\ComposerPackageDeclarationIsInvalid();
×
76
        }
77

NEW
78
        return new self($name, $constraint, $relationKey, $type, $extensionKey);
×
79
    }
80

81
    /**
82
     * @param list<array<string, mixed>> $versions
83
     *
84
     * @throws Exception\ComposerPackageDeclarationIsInvalid
85
     */
NEW
86
    public static function fromPackageVersions(
×
87
        array $versions,
88
        string $constraint,
89
        Enums\ComposerPackageRelationKey $relationKey,
90
    ): ?self {
NEW
91
        $parsedConstraint = self::parseConstraint($constraint, $relationKey);
×
92

NEW
93
        foreach ($versions as $metadata) {
×
NEW
94
            $version = $metadata['version'] ?? null;
×
95

NEW
96
            if (!is_string($version)) {
×
NEW
97
                continue;
×
98
            }
99

100
            try {
NEW
101
                if (!Semver\Semver::satisfies($version, $parsedConstraint)) {
×
NEW
102
                    continue;
×
103
                }
NEW
104
            } catch (\Exception) {
×
NEW
105
                continue;
×
106
            }
107

108
            /** @var array{'typo3/cms'?: array{'extension-key': mixed}} $extra */
NEW
109
            $extra = $metadata['extra'] ?? [];
×
NEW
110
            $declaration = [
×
NEW
111
                'name' => $metadata['name'] ?? null,
×
NEW
112
                'type' => $metadata['type'] ?? null,
×
NEW
113
                'extra' => $extra,
×
NEW
114
            ];
×
115

NEW
116
            return self::fromDeclaration($declaration, $parsedConstraint, $relationKey);
×
117
        }
118

NEW
119
        return null;
×
120
    }
121

122
    /**
123
     * @phpstan-assert-if-true !null $this->extensionKey
124
     */
NEW
125
    public function isExtension(): bool
×
126
    {
NEW
127
        return null !== $this->extensionKey;
×
128
    }
129

130
    /**
131
     * @return list<string>
132
     */
NEW
133
    public function getPossibleEmConfRelationNames(): array
×
134
    {
NEW
135
        if ('php' === $this->name) {
×
NEW
136
            return ['php'];
×
137
        }
138

NEW
139
        if (!$this->isExtension()) {
×
NEW
140
            return [];
×
141
        }
142

NEW
143
        $relationNames = [$this->extensionKey];
×
144

NEW
145
        if ($this->isTypo3FrameworkPackage()) {
×
NEW
146
            $relationNames[] = 'typo3';
×
147
        }
148

NEW
149
        return $relationNames;
×
150
    }
151

NEW
152
    public function isTypo3FrameworkPackage(): bool
×
153
    {
NEW
154
        return 'typo3-cms-framework' === $this->type;
×
155
    }
156

NEW
157
    public function normalizeConstraint(): Semver\Constraint\ConstraintInterface
×
158
    {
NEW
159
        return (new Semver\VersionParser())->parseConstraints($this->constraint);
×
160
    }
161

NEW
162
    private static function parseConstraint(string $constraint, Enums\ComposerPackageRelationKey $relationKey): string
×
163
    {
NEW
164
        $regex = match ($relationKey) {
×
NEW
165
            Enums\ComposerPackageRelationKey::Conflict, Enums\ComposerPackageRelationKey::Require => '/^(.+)$/',
×
NEW
166
            Enums\ComposerPackageRelationKey::Suggest => '/^.+ \(([^)]+)\)$/',
×
NEW
167
        };
×
168

NEW
169
        if (1 !== preg_match($regex, $constraint, $matches)) {
×
NEW
170
            return Enums\ComposerPackageRelationKey::Suggest === $relationKey ? '*' : $constraint;
×
171
        }
172

NEW
173
        return $matches[1];
×
174
    }
175
}
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