Passed
Push — master ( a23bbc...df7d7e )
by Alexander
03:50 queued 02:13
created

Aliases::findAlias()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 9
c 2
b 0
f 0
nc 10
nop 1
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 7
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Aliases;
6
7
final class Aliases
8
{
9
    private array $aliases = [];
10
11
    /**
12
     * @param array $config
13
     * @throws \InvalidArgumentException if $path is an invalid alias.
14
     * @see set()
15
     * @see get()
16
     */
17 14
    public function __construct(array $config = [])
18
    {
19 14
        foreach ($config as $alias => $path) {
20 8
            $this->set($alias, $path);
21
        }
22 14
    }
23
24
    /**
25
     * Magic setter to enable simple aliases configuration.
26
     * @param string $name
27
     * @param string $value
28
     */
29 1
    public function __set(string $name, string $value): void
30
    {
31 1
        $this->set($name, $value);
32 1
    }
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 12
    public function set(string $alias, string $path): void
62
    {
63 12
        if (!$this->isAlias($alias)) {
64 1
            $alias = '@' . $alias;
65
        }
66 12
        $pos = strpos($alias, '/');
67 12
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
68
69 12
        $path = rtrim($path, '\\/');
70 12
        if (!array_key_exists($root, $this->aliases)) {
71 12
            if ($pos === false) {
72 11
                $this->aliases[$root] = $path;
73
            } else {
74 12
                $this->aliases[$root] = [$alias => $path];
75
            }
76 3
        } elseif (\is_string($this->aliases[$root])) {
77 3
            if ($pos === false) {
78 1
                $this->aliases[$root] = $path;
79
            } else {
80 2
                $this->aliases[$root] = [
81 2
                    $alias => $path,
82 3
                    $root => $this->aliases[$root],
83
                ];
84
            }
85
        } else {
86 1
            $this->aliases[$root][$alias] = $path;
87 1
            krsort($this->aliases[$root]);
88
        }
89 12
    }
90
91
    /**
92
     * Remove alias.
93
     * @param string $alias Alias to be removed.
94
     */
95 2
    public function remove(string $alias): void
96
    {
97 2
        if (!$this->isAlias($alias)) {
98 1
            $alias = '@' . $alias;
99
        }
100 2
        $pos = strpos($alias, '/');
101 2
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
102
103 2
        if (array_key_exists($root, $this->aliases)) {
104 2
            if (\is_array($this->aliases[$root])) {
105 1
                unset($this->aliases[$root][$alias]);
106 1
            } elseif ($pos === false) {
107 1
                unset($this->aliases[$root]);
108
            }
109
        }
110 2
    }
111
112
    /**
113
     * Translates a path alias into an actual path.
114
     *
115
     * The translation is done according to the following procedure:
116
     *
117
     * 1. If the given alias does not start with '@', it is returned back without change;
118
     * 2. Otherwise, look for the longest registered alias that matches the beginning part
119
     *    of the given alias. If it exists, replace the matching part of the given alias with
120
     *    the corresponding registered path.
121
     * 3. Throw an exception if path alias cannot be resolved.
122
     *
123
     * For example, if '@vendor' is registered as the alias to the vendor directory,
124
     * say '/path/to/vendor'. The alias '@vendor/yiisoft' would then be translated into '/path/to/vendor/yiisoft'.
125
     *
126
     * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config'
127
     * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path.
128
     * This is because the longest alias takes precedence.
129
     *
130
     * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced
131
     * instead of '@foo/bar', because '/' serves as the boundary character.
132
     *
133
     * Note, this method does not check if the returned path exists or not.
134
     *
135
     * @param string $alias the alias to be translated.
136
     * @return string the path corresponding to the alias.
137
     * @throws \InvalidArgumentException if the root alias is not previously registered.
138
     * @see setAlias()
139
     */
140 14
    public function get(string $alias): string
141
    {
142 14
        if (!$this->isAlias($alias)) {
143 4
            return $alias;
144
        }
145
146 13
        $foundAlias = $this->findAlias($alias);
147
148 13
        if ($foundAlias === null) {
149 2
            throw new \InvalidArgumentException("Invalid path alias: $alias");
150
        }
151
152 11
        $foundSubAlias = $this->findAlias($foundAlias);
153 11
        if ($foundSubAlias === null) {
154 10
            return $foundAlias;
155
        }
156
157 3
        return $this->get($foundSubAlias);
158
    }
159
160 13
    private function findAlias(string $alias): ?string
161
    {
162 13
        $pos = strpos($alias, '/');
163 13
        $root = $pos === false ? $alias : substr($alias, 0, $pos);
164
165 13
        if (array_key_exists($root, $this->aliases)) {
166 11
            if (\is_string($this->aliases[$root])) {
167 8
                return $pos === false ? $this->aliases[$root] : $this->aliases[$root] . substr($alias, $pos);
168
            }
169
170 3
            foreach ($this->aliases[$root] as $name => $path) {
171 3
                if (strpos($alias . '/', $name . '/') === 0) {
172 3
                    return $path . substr($alias, strlen($name));
173
                }
174
            }
175
        }
176
177 12
        return null;
178
    }
179
180
    /**
181
     * Returns all path aliases translated into an actual paths.
182
     * @return array Actual paths indexed by alias name.
183
     */
184 1
    public function getAll(): array
185
    {
186 1
        $result = [];
187 1
        foreach ($this->aliases as $name => $path) {
188 1
            $result[$name] = $this->get($path);
189
        }
190 1
        return $result;
191
    }
192
193 14
    private function isAlias(string $alias): bool
194
    {
195 14
        return !strncmp($alias, '@', 1);
196
    }
197
}
198