Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.27% covered (warning)
70.27%
26 / 37
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
HasCacheOption
70.27% covered (warning)
70.27%
26 / 37
14.29% covered (danger)
14.29%
1 / 7
30.51
0.00% covered (danger)
0.00%
0 / 1
 addCacheOption
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 addCacheDirOption
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 isCacheEnabled
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 resolveCacheArgument
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 resolveCacheDirArgument
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
8.30
 hasExplicitCacheDirArgument
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
3.19
 isNoCacheRequested
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
3.19
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Console\Input;
21
22use Throwable;
23use InvalidArgumentException;
24use Symfony\Component\Console\Input\InputInterface;
25use Symfony\Component\Console\Input\InputOption;
26use Symfony\Component\Filesystem\Path;
27
28/**
29 * Provides the standard cache option used by cache-writing commands.
30 */
31trait HasCacheOption
32{
33    /**
34     * Adds the standard cache control option to the current command.
35     *
36     * @param string $description the cache option description
37     *
38     * @return static
39     */
40    protected function addCacheOption(string $description): static
41    {
42        if ($this->getDefinition()->hasOption('cache')) {
43            return $this;
44        }
45
46        return $this->addOption(name: 'cache', mode: InputOption::VALUE_NONE, description: $description);
47    }
48
49    /**
50     * Adds the standard cache directory option to the current command.
51     *
52     * @param string $description the cache directory option description
53     * @param string $default the command-specific default cache directory
54     *
55     * @return static
56     */
57    protected function addCacheDirOption(string $description, string $default): static
58    {
59        if ($this->getDefinition()->hasOption('cache-dir')) {
60            return $this;
61        }
62
63        return $this->addOption(
64            name: 'cache-dir',
65            mode: InputOption::VALUE_OPTIONAL,
66            description: $description,
67            default: $default,
68        );
69    }
70
71    /**
72     * Resolves whether cache writes SHOULD be enabled for the current invocation.
73     *
74     * @param InputInterface $input the current command input
75     * @param bool $default the command-specific default cache behavior when the option is omitted
76     */
77    protected function isCacheEnabled(InputInterface $input, bool $default = true): bool
78    {
79        if ((bool) $input->getOption('cache')) {
80            return true;
81        }
82
83        if ($this->isNoCacheRequested($input)) {
84            return false;
85        }
86
87        return $default;
88    }
89
90    /**
91     * Returns the explicit cache flag that SHOULD be forwarded to nested commands.
92     *
93     * @param InputInterface $input the current command input
94     */
95    protected function resolveCacheArgument(InputInterface $input): ?string
96    {
97        if ((bool) $input->getOption('cache')) {
98            return '--cache';
99        }
100
101        if ($this->isNoCacheRequested($input)) {
102            return '--no-cache';
103        }
104
105        return null;
106    }
107
108    /**
109     * Resolves a nested cache directory for a child command.
110     *
111     * @param InputInterface $input the current command input
112     * @param string $path the child cache path relative to the current command cache root
113     */
114    protected function resolveCacheDirArgument(InputInterface $input, string $path = ''): ?string
115    {
116        if (! $this->hasExplicitCacheDirArgument($input)) {
117            return null;
118        }
119
120        try {
121            $cacheDir = $input->getOption('cache-dir');
122        } catch (InvalidArgumentException) {
123            return null;
124        }
125
126        if (! \is_string($cacheDir) || '' === $cacheDir) {
127            return null;
128        }
129
130        return '' === $path
131            ? $cacheDir
132            : Path::join($cacheDir, $path);
133    }
134
135    /**
136     * Determines whether the current invocation explicitly passed `--cache-dir`.
137     *
138     * @param InputInterface $input the current command input
139     */
140    private function hasExplicitCacheDirArgument(InputInterface $input): bool
141    {
142        try {
143            return $input->hasParameterOption('--cache-dir', true);
144        } catch (Throwable) {
145            return false;
146        }
147    }
148
149    /**
150     * Determines whether cache writes were explicitly disabled for the current invocation.
151     *
152     * The Composer application already provides a global `--no-cache` flag, so commands
153     * SHALL reuse that switch instead of redefining a local negated variant.
154     *
155     * @param InputInterface $input the current command input
156     */
157    private function isNoCacheRequested(InputInterface $input): bool
158    {
159        try {
160            return true === $input->getOption('no-cache');
161        } catch (InvalidArgumentException) {
162            return false;
163        }
164    }
165}