Passed
Push — master ( bfc354...2981b7 )
by Alexander
01:36 queued 10s
created

Aliases::remove()   A

Complexity

Conditions 6
Paths 16

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6.0359

Importance

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