Passed
Pull Request — master (#44)
by Alexander
02:38
created

Aliases   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 98.51%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 63
c 7
b 0
f 0
dl 0
loc 217
ccs 66
cts 67
cp 0.9851
rs 9.84
wmc 32

8 Methods

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