Passed
Push — master ( 74f7ea...6c0456 )
by Alexander
03:08 queued 01:14
created

Aliases::__set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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