Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
53.57% covered (warning)
53.57%
15 / 28
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ManagedWorkspace
53.57% covered (warning)
53.57%
15 / 28
75.00% covered (warning)
75.00%
3 / 4
45.92
0.00% covered (danger)
0.00%
0 / 1
 getOutputDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCacheDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getWorkspaceRoot
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 getProjectRelativeWorkspaceRoot
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
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\Path;
21
22use FastForward\DevTools\Environment\Environment;
23use FastForward\DevTools\Environment\EnvironmentInterface;
24use Symfony\Component\Filesystem\Path;
25
26/**
27 * Provides canonical repository-local paths for generated DevTools artifacts.
28 */
29 class ManagedWorkspace
30{
31    /**
32     * @var string the environment variable used to override the generated artifact workspace
33     */
34    public const string ENV_WORKSPACE_DIR = 'FAST_FORWARD_WORKSPACE_DIR';
35
36    /**
37     * @var string the output segment used for coverage artifacts
38     */
39    public const string COVERAGE = 'coverage';
40
41    /**
42     * @var string the output segment used for metrics artifacts
43     */
44    public const string METRICS = 'metrics';
45
46    /**
47     * @var string the cache segment used for phpDocumentor
48     */
49    public const string PHPDOC = 'phpdoc';
50
51    /**
52     * @var string the cache segment used for PHPUnit
53     */
54    public const string PHPUNIT = 'phpunit';
55
56    /**
57     * @var string the cache segment used for Rector
58     */
59    public const string RECTOR = 'rector';
60
61    /**
62     * @var string the cache segment used for PHP-CS-Fixer
63     */
64    public const string PHP_CS_FIXER = 'php-cs-fixer';
65
66    /**
67     * @var string the repository-local root directory for generated artifacts
68     */
69    public const string WORKSPACE_ROOT = '.dev-tools';
70
71    /**
72     * @var string the repository-local root directory for generated tool caches
73     */
74    private const string CACHE_ROOT = 'cache';
75
76    /**
77     * Returns a repository-local managed output directory.
78     *
79     * The optional $path MUST be a relative segment within the managed
80     * workspace, while $baseDir MAY provide the repository root used to
81     * materialize the same `.dev-tools` structure under a different base path.
82     *
83     * @param string $path the optional relative segment to append under the managed output root
84     * @param string $baseDir the optional repository root used to resolve the managed workspace path
85     * @param EnvironmentInterface|null $environment explicit environment reader used to resolve FAST_FORWARD_WORKSPACE_DIR
86     */
87    public static function getOutputDirectory(
88        string $path = '',
89        string $baseDir = '',
90        ?EnvironmentInterface $environment = null,
91    ): string {
92        $baseDir = self::getWorkspaceRoot(baseDir: $baseDir, environment: $environment);
93
94        return '' === $path
95            ? $baseDir
96            : Path::join($baseDir, $path);
97    }
98
99    /**
100     * Returns a repository-local managed cache directory.
101     *
102     * The optional $path MUST be a relative cache segment, while $baseDir MAY
103     * resolve the managed workspace root before the `cache` directory is
104     * appended.
105     *
106     * @param string $path the optional relative cache segment to append under the managed cache root
107     * @param string $baseDir the optional repository root used to resolve the managed cache path
108     * @param EnvironmentInterface|null $environment explicit environment reader used to resolve FAST_FORWARD_WORKSPACE_DIR
109     */
110    public static function getCacheDirectory(
111        string $path = '',
112        string $baseDir = '',
113        ?EnvironmentInterface $environment = null,
114    ): string {
115        $baseDir = self::getOutputDirectory(path: self::CACHE_ROOT, baseDir: $baseDir, environment: $environment);
116
117        return '' === $path
118            ? $baseDir
119            : Path::join($baseDir, $path);
120    }
121
122    /**
123     * Returns the configured workspace root.
124     *
125     * Relative workspace paths stay relative when no base directory is provided.
126     * When a base directory is provided, relative workspaces are materialized
127     * under that base directory while absolute workspaces are used as-is.
128     *
129     * @param string $baseDir the optional repository root used to resolve a relative workspace
130     * @param EnvironmentInterface|null $environment explicit environment reader used to resolve FAST_FORWARD_WORKSPACE_DIR
131     */
132    public static function getWorkspaceRoot(
133        string $baseDir = '',
134        ?EnvironmentInterface $environment = null,
135    ): string {
136        $environment ??= new Environment();
137
138        $workspaceRoot = $environment->get(self::ENV_WORKSPACE_DIR);
139
140        if (null === $workspaceRoot || '' === $workspaceRoot) {
141            $workspaceRoot = self::WORKSPACE_ROOT;
142        }
143
144        if ('' === $baseDir || Path::isAbsolute($workspaceRoot)) {
145            return $workspaceRoot;
146        }
147
148        return Path::join($baseDir, $workspaceRoot);
149    }
150
151    /**
152     * Returns the workspace root as a project-relative directory when tooling
153     * should skip generated artifacts during source scans.
154     *
155     * @param string $baseDir the optional repository root used to relativize absolute workspace paths
156     * @param EnvironmentInterface|null $environment explicit environment reader used for tests
157     */
158    public static function getProjectRelativeWorkspaceRoot(
159        string $baseDir = '',
160        ?EnvironmentInterface $environment = null,
161    ): ?string {
162        $environment ??= new Environment();
163        $workspaceRoot = $environment->get(self::ENV_WORKSPACE_DIR);
164
165        if (null === $workspaceRoot || '' === $workspaceRoot) {
166            $workspaceRoot = self::WORKSPACE_ROOT;
167        }
168
169        if ('' === $baseDir) {
170            return Path::isAbsolute($workspaceRoot) ? null : $workspaceRoot;
171        }
172
173        if (! Path::isAbsolute($workspaceRoot)) {
174            return $workspaceRoot;
175        }
176
177        $baseDir = Path::canonicalize($baseDir);
178        $workspaceRoot = Path::canonicalize($workspaceRoot);
179
180        if ($baseDir === $workspaceRoot || ! str_starts_with($workspaceRoot, $baseDir . '/')) {
181            return null;
182        }
183
184        return Path::makeRelative($workspaceRoot, $baseDir);
185    }
186}