Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.71% covered (danger)
33.71%
30 / 89
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractRenamespacerCommand
33.71% covered (danger)
33.71%
30 / 89
20.00% covered (danger)
20.00%
1 / 5
163.00
0.00% covered (danger)
0.00%
0 / 1
 configure
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
 initialize
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
 getLogger
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
156
 createConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Log level, filesystem
4 */
5
6namespace BrianHenryIE\Strauss\Console\Commands;
7
8use BrianHenryIE\Strauss\Composer\Extra\StraussConfig;
9use BrianHenryIE\Strauss\Composer\ProjectComposerPackage;
10use BrianHenryIE\Strauss\Helpers\FileSystem;
11use BrianHenryIE\Strauss\Helpers\Log\PadColonColumnsLogProcessor;
12use BrianHenryIE\Strauss\Helpers\Log\RelativeFilepathLogProcessor;
13use BrianHenryIE\Strauss\Helpers\ReadOnlyFileSystem;
14use Composer\InstalledVersions;
15use Elazar\Flystream\FilesystemRegistry;
16use Monolog\Handler\PsrHandler;
17use Monolog\Logger;
18use Monolog\Processor\PsrLogMessageProcessor;
19use Psr\Log\LoggerAwareTrait;
20use Symfony\Component\Console\Command\Command;
21use Symfony\Component\Console\Input\InputInterface;
22use Symfony\Component\Console\Input\InputOption;
23use Symfony\Component\Console\Output\OutputInterface;
24use League\Flysystem\Config;
25use League\Flysystem\Local\LocalFilesystemAdapter;
26use Psr\Log\LoggerInterface;
27use Psr\Log\LogLevel;
28use Psr\Log\NullLogger;
29use Symfony\Component\Console\Logger\ConsoleLogger;
30
31abstract class AbstractRenamespacerCommand extends Command
32{
33    use LoggerAwareTrait;
34
35    /** No trailing slash */
36    protected string $workingDir;
37
38    /** @var FileSystem */
39    protected Filesystem $filesystem;
40    protected ProjectComposerPackage $projectComposerPackage;
41
42    protected StraussConfig $config;
43
44    /**
45     * Set name and description, call parent class to add dry-run, verbosity options.
46     *
47     * @used-by \Symfony\Component\Console\Command\Command::__construct
48     * @override {@see \Symfony\Component\Console\Command\Command::configure()} empty method.
49     *
50     * @return void
51     */
52    protected function configure()
53    {
54        $this->addOption(
55            'dry-run',
56            null,
57            InputOption::VALUE_OPTIONAL,
58            'Do not actually make any changes',
59            false
60        );
61
62        $this->addOption(
63            'info',
64            null,
65            InputOption::VALUE_OPTIONAL,
66            'output level',
67            false
68        );
69
70        $this->addOption(
71            'debug',
72            null,
73            InputOption::VALUE_OPTIONAL,
74            'output level',
75            false
76        );
77
78        /** @var string $installedSymfonyVersion */
79        $installedSymfonyVersion = InstalledVersions::getVersion('symfony/console');
80
81        if (version_compare($installedSymfonyVersion, '7.2', '<')) {
82            $this->addOption(
83                'silent',
84                's',
85                InputOption::VALUE_OPTIONAL,
86                'output level',
87                false
88            );
89        }
90    }
91
92    protected function execute(InputInterface $input, OutputInterface $output): int
93    {
94        if (!isset($this->config)) {
95            $this->config = $this->createConfig($input);
96        }
97
98        if ($this->config->isDryRun()) {
99            /**
100             * `league/flysystem` v2.x throws deprecation errors on newer PHP versions.
101             * `league/flysystem` v3.x requires PHP ^8.02 and Strauss's backward compatibility promise keeps us at 7.4 until WordPress itself requires newer PHP.
102             */
103            set_error_handler(function () {
104            }, E_DEPRECATED | E_USER_DEPRECATED);
105
106            $this->filesystem =
107                new FileSystem(
108                    new ReadOnlyFileSystem(
109                        $this->filesystem,
110                        Filesystem::makePathNormalizer($this->workingDir)
111                    ),
112                    $this->workingDir
113                );
114
115            restore_error_handler();
116
117            /** @var FilesystemRegistry $registry */
118            $registry = \Elazar\Flystream\ServiceLocator::get(\Elazar\Flystream\FilesystemRegistry::class);
119
120            // Register a file stream mem:// to handle file operations by third party libraries.
121            // This exception handling probably doesn't matter in real life but does in unit tests.
122            try {
123                $registry->get('mem');
124            } catch (\Exception $e) {
125                $registry->register('mem', $this->filesystem);
126            }
127        }
128
129        $logger = new Logger('logger');
130        $logger->pushProcessor(new PsrLogMessageProcessor());
131        $logger->pushProcessor(new RelativeFilepathLogProcessor($this->filesystem));
132        $logger->pushProcessor(new PadColonColumnsLogProcessor());
133        $logger->pushHandler(new PsrHandler($this->getLogger($input, $output)));
134        $this->setLogger($logger);
135
136        return Command::SUCCESS;
137    }
138
139    /**
140     * Symfony hook that runs before execute(). Sets working directory, filesystem and logger.
141     */
142    protected function initialize(InputInterface $input, OutputInterface $output): void
143    {
144        $this->workingDir = getcwd() . '';
145
146        if (!isset($this->filesystem)) {
147            /**
148             * `league/flysystem` v2.x throws deprecation errors on newer PHP versions.
149             * `league/flysystem` v3.x requires PHP ^8.02 and Strauss's backward compatibility promise keeps us at 7.4 until WordPress itself requires newer PHP.
150             */
151            set_error_handler(function () {
152            }, E_DEPRECATED | E_USER_DEPRECATED);
153
154            try {
155                $localFilesystemAdapter = new LocalFilesystemAdapter(
156                    FileSystem::getFsRoot($this->workingDir),
157                    null,
158                    LOCK_EX,
159                    LocalFilesystemAdapter::SKIP_LINKS
160                );
161
162                $this->filesystem = new FileSystem(
163                    new \League\Flysystem\Filesystem(
164                        $localFilesystemAdapter,
165                        [
166                            Config::OPTION_DIRECTORY_VISIBILITY => 'public',
167                        ],
168                        Filesystem::makePathNormalizer($this->workingDir)
169                    ),
170                    $this->workingDir
171                );
172            } finally {
173                restore_error_handler();
174            }
175        }
176
177        if (method_exists($this, 'setLogger')) {
178            $this->setLogger($this->getLogger($input, $output));
179        }
180    }
181
182    /**
183     * Build a logger honoring optional --info/--debug/--silent flags if present.
184     */
185    protected function getLogger(InputInterface $input, OutputInterface $output): LoggerInterface
186    {
187        // If a subclass has a config and it is a dry-run, increase verbosity
188        $isDryRun = property_exists($this, 'config') && isset($this->config) && method_exists($this->config, 'isDryRun') && $this->config->isDryRun();
189
190        // Who would want to dry-run without output?
191        if (!$isDryRun && $input->hasOption('silent') && $input->getOption('silent') !== false) {
192            return new NullLogger();
193        }
194
195        $logLevel = [LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL];
196
197        if ($input->hasOption('info') && $input->getOption('info') !== false) {
198            $logLevel[LogLevel::INFO] = OutputInterface::VERBOSITY_NORMAL;
199        }
200
201        if ($isDryRun || ($input->hasOption('debug') && $input->getOption('debug') !== false)) {
202            $logLevel[LogLevel::INFO] = OutputInterface::VERBOSITY_NORMAL;
203            $logLevel[LogLevel::DEBUG] = OutputInterface::VERBOSITY_NORMAL;
204        }
205
206        return new ConsoleLogger($output, $logLevel);
207    }
208
209
210    protected function createConfig(InputInterface $input): StraussConfig
211    {
212        return new StraussConfig();
213    }
214}