Passed
Push — master ( 0365db...4f1f9e )
by Alexander
01:56
created

Aliases   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 32
eloc 62
c 5
b 0
f 0
dl 0
loc 217
ccs 67
cts 67
cp 1
rs 9.84

8 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 18 4
A remove() 0 13 6
A getArray() 0 5 1
A __construct() 0 4 2
A isAlias() 0 3 1
B set() 0 28 7
B findAlias() 0 18 7
A getAll() 0 14 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Aliases;
6
7
use InvalidArgumentException;
8
9
use function array_key_exists;
10
use function is_array;
11
use function is_string;
12
13
final class Aliases
14
{
15
    /**
16
     * @var array
17
     * @psalm-var array<string, string|array<string, string>>
18
     */
19
    private array $aliases = [];
20
21
    /**
22
     * @param array $config
23
     *
24
     * @psalm-param array<string, string> $config
25
     *
26
     * @throws InvalidArgumentException if $path is an invalid alias.
27
     *
28
     * @see set()
29
     * @see get()
30
     */
31 16
    public function __construct(array $config = [])
32
    {
33 16
        foreach ($config as $alias => $path) {
34 10
            $this->set($alias, $path);
35
        }
36 16
    }
37
38
    /**
39
     * Registers a path alias.
40
     *
41
     * A path alias is a short name representing a long path (a file path, a URL, etc.)
42
     *
43
     * For example, `@vendor` may store path to `vendor` directory.
44
     *
45
     * A path alias must start with the character '@' so that it can be easily differentiated
46
     * from non-alias paths.
47
     *
48
     * Note that this method does not check if the given path exists or not. All it does is
49
     * to associate the alias with the path.
50
     *
51
     * Any trailing '/' and '\' characters in the given path will be trimmed.
52
     *
53
     * @param string $alias the alias name (e.g. "@vendor"). It must start with a '@' character.
54
     * It may contain the forward slash '/' which serves as boundary character when performing
55
     * alias translation by {@see get()}.
56
     * @param string $path the path corresponding to the alias.
57
     * Trailing '/' and '\' characters will be trimmed. This can be
58
     *
59
     * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
60
     * - a URL (e.g. `http://www.yiiframework.com`)
61
     * - a path alias (e.g. `@vendor/yiisoft`). It will be resolved on {@see get()} call.
62
     *
63
     * @see get()
64
     */
65 14
    public function set(string $alias, string $path): void
66
    {
67 14
        if (!$this->isAlias($alias)) {
68 1
            $alias = '@' . $alias;
69
        }
70 14
        $pos = strpos($alias, '/');
71
        /** @psalm-var string $root */
72 14
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
73
74 14
        $path = rtrim($path, '\\/');
75 14
        if (!array_key_exists($root, $this->aliases)) {
76 14
            if ($pos === false) {
77 13
                $this->aliases[$root] = $path;
78
            } else {
79 14
                $this->aliases[$root] = [$alias => $path];
80
            }
81 4
        } elseif (is_string($this->aliases[$root])) {
82 4
            if ($pos === false) {
83 1
                $this->aliases[$root] = $path;
84
            } else {
85 3
                $this->aliases[$root] = [
86 3
                    $alias => $path,
87 4
                    $root => $this->aliases[$root],
88
                ];
89
            }
90
        } else {
91 2
            $this->aliases[$root][$alias] = $path;
92 2
            krsort($this->aliases[$root]);
93
        }
94 14
    }
95
96
    /**
97
     * Remove alias.
98
     *
99
     * @param string $alias Alias to be removed.
100
     */
101 2
    public function remove(string $alias): void
102
    {
103 2
        if (!$this->isAlias($alias)) {
104 1
            $alias = '@' . $alias;
105
        }
106 2
        $pos = strpos($alias, '/');
107 2
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
108
109 2
        if (array_key_exists($root, $this->aliases)) {
110 2
            if (is_array($this->aliases[$root])) {
111 1
                unset($this->aliases[$root][$alias]);
112 1
            } elseif ($pos === false) {
113 1
                unset($this->aliases[$root]);
114
            }
115
        }
116 2
    }
117
118
    /**
119
     * Translates a path alias into an actual path.
120
     *
121
     * The translation is done according to the following procedure:
122
     *
123
     * 1. If the given alias does not start with '@', it is returned back without change;
124
     * 2. Otherwise, look for the longest registered alias that matches the beginning part
125
     *    of the given alias. If it exists, replace the matching part of the given alias with
126
     *    the corresponding registered path.
127
     * 3. Throw an exception if path alias cannot be resolved.
128
     *
129
     * For example, if '@vendor' is registered as the alias to the vendor directory,
130
     * say '/path/to/vendor'. The alias '@vendor/yiisoft' would then be translated into '/path/to/vendor/yiisoft'.
131
     *
132
     * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config'
133
     * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path.
134
     * This is because the longest alias takes precedence.
135
     *
136
     * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced
137
     * instead of '@foo/bar', because '/' serves as the boundary character.
138
     *
139
     * Note, this method does not check if the returned path exists or not.
140
     *
141
     * @param string $alias The alias to be translated.
142
     *
143
     * @throws InvalidArgumentException If the root alias is not previously registered.
144
     *
145
     * @return string The path corresponding to the alias.
146
     *
147
     * @see setAlias()
148
     */
149 15
    public function get(string $alias): string
150
    {
151 15
        if (!$this->isAlias($alias)) {
152 5
            return $alias;
153
        }
154
155 14
        $foundAlias = $this->findAlias($alias);
156
157 14
        if ($foundAlias === null) {
158 2
            throw new InvalidArgumentException("Invalid path alias: $alias");
159
        }
160
161 12
        $foundSubAlias = $this->findAlias($foundAlias);
162 12
        if ($foundSubAlias === null) {
163 11
            return $foundAlias;
164
        }
165
166 3
        return $this->get($foundSubAlias);
167
    }
168
169
    /**
170
     * Bulk translates path aliases into actual paths.
171
     *
172
     * @param string[] $aliases Aliases to be translated.
173
     *
174
     * @throws InvalidArgumentException If the root alias was not previously registered.
175
     *
176
     * @return string[] The paths corresponding to the aliases.
177
     */
178 2
    public function getArray(array $aliases): array
179
    {
180 2
        return array_map(
181 2
            fn (string $alias) => $this->get($alias),
182
            $aliases,
183
        );
184
    }
185
186
    /**
187
     * Returns all path aliases translated into an actual paths.
188
     *
189
     * @return array Actual paths indexed by alias name.
190
     */
191 1
    public function getAll(): array
192
    {
193 1
        $result = [];
194 1
        foreach ($this->aliases as $name => $path) {
195 1
            if (is_array($path)) {
196 1
                foreach ($path as $innerName => $innerPath) {
197 1
                    $result[$innerName] = $innerPath;
198
                }
199
            } else {
200 1
                $result[$name] = $this->get($path);
201
            }
202
        }
203
204 1
        return $result;
205
    }
206
207 14
    private function findAlias(string $alias): ?string
208
    {
209 14
        $pos = strpos($alias, '/');
210 14
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
211
212 14
        if (array_key_exists($root, $this->aliases)) {
213 12
            if (is_string($this->aliases[$root])) {
214 9
                return $pos === false ? $this->aliases[$root] : $this->aliases[$root] . substr($alias, $pos);
215
            }
216
217 3
            foreach ($this->aliases[$root] as $name => $path) {
218 3
                if (strpos($alias . '/', $name . '/') === 0) {
219 3
                    return $path . substr($alias, strlen($name));
220
                }
221
            }
222
        }
223
224 13
        return null;
225
    }
226
227 16
    private function isAlias(string $alias): bool
228
    {
229 16
        return !strncmp($alias, '@', 1);
230
    }
231
}
232