File size: 5,261 Bytes
d2897cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php

declare(strict_types=1);

namespace Mautic\CoreBundle\Helper;

use Composer\Console\Application;
use Mautic\MarketplaceBundle\DTO\ConsoleOutput;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpKernel\KernelInterface;

/**
 * Provides several helper functions to interact with Composer (composer require, remove, etc.).
 */
class ComposerHelper
{
    public function __construct(
        private KernelInterface $kernel,
        private LoggerInterface $logger
    ) {
    }

    /**
     * Installs a package using its Packagist name.
     *
     * @param string $packageName The package name, e.g. mautic/example-plugin
     * @param bool   $dryRun      Whether to dry-run the installation. Comes in handy during automated tests
     *                            and to test whether an installation would succeed or not.
     */
    public function install(string $packageName, bool $dryRun = false): ConsoleOutput
    {
        $input = [
            'command'  => 'require',
            'packages' => [$packageName],
        ];

        if (true === $dryRun) {
            $input['--dry-run'] = null;
        }

        return $this->runCommand($input);
    }

    /**
     * Removes a package using its Packagist name.
     *
     * @param string $packageName The package name, e.g. mautic/example-plugin
     * @param bool   $dryRun      Whether to dry-run the removal. Comes in handy during automated tests
     *                            and to test whether an removal would succeed or not.
     */
    public function remove(string $packageName, bool $dryRun = false): ConsoleOutput
    {
        /**
         * "composer remove package-name" also triggers an update of all other Mautic dependencies.
         * By using the --no-update option first, we can work around that issue and only delete
         * this specific package from the composer.json file.
         */
        $input = [
            'command'     => 'remove',
            'packages'    => [$packageName],
            '--no-update' => null,
        ];

        if (true === $dryRun) {
            $input['--dry-run'] = null;
        }

        $firstOutput = $this->runCommand($input);

        if (0 === $firstOutput->exitCode) {
            /**
             * Triggering an update of the package we just removed from composer.json
             * will remove it from composer.lock and actually delete the plugin folder
             * as well.
             */
            $input = [
                'command'     => 'update',
                'packages'    => [$packageName],
            ];

            if (true === $dryRun) {
                $input['--dry-run'] = null;
            }

            $secondOutput = $this->runCommand($input);

            // Let's merge the output so that we return all the output we have.
            return new ConsoleOutput(
                $secondOutput->exitCode,
                $firstOutput->output."\n".$secondOutput->output
            );
        }

        return $firstOutput;
    }

    /**
     * Checks if the given Composer package is installed.
     *
     * @param string $packageName The package name, e.g. mautic/exmple-plugin
     */
    public function isInstalled(string $packageName): bool
    {
        return \Composer\InstalledVersions::isInstalled($packageName);
    }

    /**
     * Returns a list of installed Composer packages that are of type mautic-plugin.
     *
     * @return string[]
     */
    public function getMauticPluginPackages(): array
    {
        return \Composer\InstalledVersions::getInstalledPackagesByType('mautic-plugin');
    }

    /**
     * Updates one or multiple Composer packages.
     */
    public function update(?string $packageName = null, bool $dryRun = false): ConsoleOutput
    {
        $input = [
            'command'  => 'update',
        ];

        if (!empty($packageName)) {
            $input['packages'] = [$packageName];
        }

        if (true === $dryRun) {
            $input['--dry-run'] = null;
        }

        return $this->runCommand($input);
    }

    /**
     * @param array<string,mixed> $input
     */
    private function runCommand(array $input): ConsoleOutput
    {
        $arrayInput = new ArrayInput(array_merge(
            $input, [
                '--no-interaction',
                '--working-dir' => $this->kernel->getProjectDir(),
            ]));

        $application = new Application();
        // We don't want our script to stop after running a Composer command
        $application->setAutoExit(false);

        $this->logger->info('Running Composer command: '.$arrayInput->__toString());

        $output   = new BufferedOutput();
        $exitCode = 1;

        try {
            $exitCode = $application->run($arrayInput, $output);
        } catch (\Exception $e) {
            $output->writeln('Exception while running Composer command: '.$e->getMessage());
            $this->logger->error('Exception while running Composer command: '.$e->getMessage());
        }

        $this->logger->info('Composer command output: '.$output->fetch());

        return new ConsoleOutput($exitCode, $output->fetch());
    }
}