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

eliashaeussler / composer-update-check / 23526463403

25 Mar 2026 01:39AM UTC coverage: 21.406%. First build
23526463403

Pull #130

github

web-flow
[TASK] Update all dependencies
Pull Request #130: [!!!][FEATURE] Modernize plugin

385 of 1870 new or added lines in 57 files covered. (20.59%)

405 of 1892 relevant lines covered (21.41%)

1.18 hits per line

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

92.16
/src/Composer/Installer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/composer-update-check".
7
 *
8
 * Copyright (C) 2020-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\ComposerUpdateCheck\Composer;
25

26
use Composer\Advisory\AuditConfig;
27
use Composer\Composer;
28
use Composer\DependencyResolver;
29
use Composer\Factory;
30
use Composer\Installer as ComposerInstaller;
31
use Composer\IO;
32
use Composer\Package;
33
use Composer\Repository;
34
use EliasHaeussler\ComposerUpdateCheck\Entity;
35

36
use function array_map;
37
use function array_values;
38
use function method_exists;
39

40
/**
41
 * Installer.
42
 *
43
 * @author Elias Häußler <elias@haeussler.dev>
44
 * @license GPL-3.0-or-later
45
 *
46
 * @internal
47
 */
48
final readonly class Installer
49
{
50
    public function __construct(
5✔
51
        private Composer $composer,
52
    ) {}
5✔
53

54
    public function runInstall(?IO\IOInterface $io = null): int
5✔
55
    {
56
        $io ??= new IO\NullIO();
5✔
57

58
        $composer = $this->buildComposer($io);
5✔
59
        $preferredInstall = $composer->getConfig()->get('preferred-install');
5✔
60
        $installer = ComposerInstaller::create($io, $composer)
5✔
61
            ->setPreferSource('source' === $preferredInstall)
5✔
62
            ->setPreferDist('dist' === $preferredInstall)
5✔
63
            ->setDevMode()
5✔
64
        ;
5✔
65

66
        if (method_exists($installer, 'setAuditConfig')) {
5✔
67
            // Composer >= 2.9
68
            $installer->setAuditConfig(
5✔
69
                AuditConfig::fromConfig($composer->getConfig(), false),
5✔
70
            );
5✔
NEW
71
        } elseif (method_exists($installer, 'setAudit')) {
×
72
            // Composer < 2.9
NEW
73
            $installer->setAudit(false);
×
74
        }
75

76
        return $installer->run();
5✔
77
    }
78

79
    /**
80
     * @param list<Entity\Package\Package> $packages
81
     */
82
    public function runUpdate(array $packages, ?IO\IOInterface $io = null): Entity\Result\ComposerUpdateResult
4✔
83
    {
84
        $io ??= new IO\NullIO();
4✔
85

86
        $outdatedPackages = [];
4✔
87
        $allowedPackageNames = array_map(
4✔
88
            static fn (Entity\Package\Package $package) => $package->getName(),
4✔
89
            $packages,
4✔
90
        );
4✔
91

92
        // Inject installer logger
93
        $composer = $this->buildComposer($io);
4✔
94
        $composer->getInstallationManager()->addInstaller(
4✔
95
            $this->mockInstaller($allowedPackageNames, $outdatedPackages),
4✔
96
        );
4✔
97

98
        // Disable writing of package updates in local repository
99
        $composer->getRepositoryManager()->setLocalRepository(
4✔
100
            $this->mockLocalRepository($composer->getRepositoryManager()->getLocalRepository()),
4✔
101
        );
4✔
102

103
        // Disable lock file management
104
        $composer->getConfig()->merge([
4✔
105
            'config' => [
4✔
106
                'lock' => false,
4✔
107
            ],
4✔
108
        ]);
4✔
109

110
        $preferredInstall = $composer->getConfig()->get('preferred-install');
4✔
111
        $installer = ComposerInstaller::create($io, $composer)
4✔
112
            ->setPreferSource('source' === $preferredInstall)
4✔
113
            ->setPreferDist('dist' === $preferredInstall)
4✔
114
            ->setDevMode()
4✔
115
            ->setUpdate(true)
4✔
116
            ->setDumpAutoloader(false)
4✔
117
            ->setUpdateAllowList($allowedPackageNames)
4✔
118
            ->setUpdateAllowTransitiveDependencies(DependencyResolver\Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS)
4✔
119
        ;
4✔
120

121
        if (method_exists($installer, 'setAuditConfig')) {
4✔
122
            // Composer >= 2.9
123
            $installer->setAuditConfig(
4✔
124
                AuditConfig::fromConfig($composer->getConfig(), false),
4✔
125
            );
4✔
NEW
126
        } elseif (method_exists($installer, 'setAudit')) {
×
127
            // Composer < 2.9
NEW
128
            $installer->setAudit(false);
×
129
        }
130

131
        return new Entity\Result\ComposerUpdateResult(
4✔
132
            $installer->run(),
4✔
133
            array_values($outdatedPackages),
4✔
134
        );
4✔
135
    }
136

137
    /**
138
     * @param list<non-empty-string>                                  $allowedPackageNames
139
     * @param array<non-empty-string, Entity\Package\OutdatedPackage> $outdatedPackages
140
     */
141
    private function mockInstaller(
4✔
142
        array $allowedPackageNames,
143
        array &$outdatedPackages,
144
    ): ComposerInstaller\InstallerInterface {
145
        return new class($allowedPackageNames, $outdatedPackages) extends ComposerInstaller\NoopInstaller {
4✔
146
            /**
147
             * @param list<non-empty-string>                                  $allowedPackageNames
148
             * @param array<non-empty-string, Entity\Package\OutdatedPackage> $outdatedPackages
149
             */
150
            public function __construct(
151
                private readonly array $allowedPackageNames,
152
                private array &$outdatedPackages,
153
            ) {}
4✔
154

155
            public function update(
156
                Repository\InstalledRepositoryInterface $repo,
157
                Package\PackageInterface $initial,
158
                Package\PackageInterface $target,
159
            ) {
160
                $promise = parent::update($repo, $initial, $target);
4✔
161
                /** @var non-empty-string $name */
162
                $name = $initial->getName();
4✔
163

164
                if ($this->isSuitableUpdate($initial, $target)) {
4✔
165
                    $includeSha1 = $initial->getPrettyVersion() === $target->getPrettyVersion();
3✔
166

167
                    $this->outdatedPackages[$name] = new Entity\Package\OutdatedPackage(
3✔
168
                        $name,
3✔
169
                        new Entity\Version(
3✔
170
                            $initial->getPrettyVersion(),
3✔
171
                            $includeSha1 ? ($initial->getDistReference() ?? $initial->getSourceReference()) : null,
3✔
172
                        ),
3✔
173
                        new Entity\Version(
3✔
174
                            $target->getPrettyVersion(),
3✔
175
                            $includeSha1 ? ($target->getDistReference() ?? $target->getSourceReference()) : null,
3✔
176
                        ),
3✔
177
                    );
3✔
178
                }
179

180
                return $promise;
4✔
181
            }
182

183
            private function isSuitableUpdate(
184
                Package\PackageInterface $initial,
185
                Package\PackageInterface $target,
186
            ): bool {
187
                $name = $initial->getName();
4✔
188

189
                if (!in_array($name, $this->allowedPackageNames, true)) {
4✔
190
                    return false;
3✔
191
                }
192

193
                if (array_key_exists($name, $this->outdatedPackages)) {
3✔
NEW
194
                    return false;
×
195
                }
196

197
                if ($initial->getPrettyVersion() !== $target->getPrettyVersion()) {
3✔
198
                    return true;
2✔
199
                }
200

201
                if ($initial->getSourceReference() !== $target->getSourceReference()) {
1✔
202
                    return true;
1✔
203
                }
204

NEW
205
                if ($initial->getDistReference() !== $target->getDistReference()) {
×
NEW
206
                    return true;
×
207
                }
208

NEW
209
                return false;
×
210
            }
211
        };
4✔
212
    }
213

214
    private function mockLocalRepository(
4✔
215
        Repository\InstalledRepositoryInterface $repository,
216
    ): Repository\InstalledRepositoryInterface {
217
        $newRepository = new Repository\InstalledArrayRepository();
4✔
218
        $newRepository->setDevPackageNames($repository->getDevPackageNames());
4✔
219

220
        foreach ($repository->getPackages() as $package) {
4✔
221
            $newPackage = clone $package;
4✔
222
            $newPackage->setId($package->getId());
4✔
223
            $newRepository->addPackage($newPackage);
4✔
224
        }
225

226
        return $newRepository;
4✔
227
    }
228

229
    private function buildComposer(IO\IOInterface $io): Composer
5✔
230
    {
231
        $composer = Factory::create($io, $this->composer->getConfig()->getConfigSource()->getName(), true);
5✔
232
        $composer->getEventDispatcher()->setRunScripts(false);
5✔
233
        $composer->getAutoloadGenerator()->setRunScripts(false);
5✔
234

235
        return $composer;
5✔
236
    }
237
}
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