Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.88% covered (success)
96.88%
62 / 64
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Path
96.88% covered (success)
96.88%
62 / 64
75.00% covered (warning)
75.00%
6 / 8
30
0.00% covered (danger)
0.00%
0 / 1
 normalize
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
 combine
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getDirectoryPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileExtension
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
 getFileNameWithoutExtension
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 toParts
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 setEnvironmentName
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
9.02
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\IO;
6
7use PeServer\Core\Collection\Arr;
8use PeServer\Core\IO\PathParts;
9use PeServer\Core\Text;
10use PeServer\Core\Throws\ArgumentException;
11
12/**
13 * パス処理系。
14 */
15abstract class Path
16{
17    #region function
18
19    /**
20     * パスの正規化。
21     *
22     * @param string $path パス。
23     * @return string 絶対パス。
24     */
25    public static function normalize(string $path): string
26    {
27        $targetPath = Text::replace($path, ['/', '\\'], DIRECTORY_SEPARATOR);
28        $parts = array_filter(Text::split($targetPath, DIRECTORY_SEPARATOR), fn($s) => (bool)mb_strlen($s));
29        $absolutes = [];
30        foreach ($parts as $part) {
31            if ($part === '.') {
32                continue;
33            }
34            if ($part === '..') {
35                array_pop($absolutes);
36            } else {
37                $absolutes[] = $part;
38            }
39        }
40
41        $result = Text::join(DIRECTORY_SEPARATOR, $absolutes);
42        if (Text::getByteCount($targetPath) && $targetPath[0] === DIRECTORY_SEPARATOR) {
43            $result = DIRECTORY_SEPARATOR . $result;
44        }
45
46        return $result;
47    }
48
49    /**
50     * パスの結合。
51     *
52     * @param string $basePath ベースとなるパス。
53     * @param string ...$addPaths 連結していくパス。
54     * @return string 結合後のパス。正規化される。
55     */
56    public static function combine(string $basePath, string ...$addPaths): string
57    {
58        $paths = array_merge([$basePath], array_map(function ($s) {
59            return Text::trim($s, '/\\');
60        }, $addPaths));
61        $paths = array_filter($paths, function ($v, $k) {
62            return !Text::isNullOrEmpty($v) && ($k === 0 ? true :  $v !== '/' && $v !== '\\');
63        }, ARRAY_FILTER_USE_BOTH);
64
65
66        $joinedPath = Text::join(DIRECTORY_SEPARATOR, $paths);
67        return self::normalize($joinedPath);
68    }
69
70    /**
71     * ディレクトリパスを取得。
72     *
73     * `dirname` ラッパー。
74     *
75     * @param string $path
76     * @return string
77     * @see https://php.net/manual/function.dirname.php
78     */
79    public static function getDirectoryPath(string $path): string
80    {
81        return dirname($path);
82    }
83
84    /**
85     * ファイル名を取得。
86     *
87     * `basename` ラッパー。
88     *
89     * @param string $path
90     * @return string
91     * @see https://php.net/manual/function.basename.php
92     */
93    public static function getFileName(string $path): string
94    {
95        return basename($path);
96    }
97
98    /**
99     * 拡張子取得。
100     *
101     * @param string $path
102     * @param boolean $withDot `.` を付与するか。
103     * @return string
104     */
105    public static function getFileExtension(string $path, bool $withDot = false): string
106    {
107        if (Text::isNullOrWhiteSpace($path)) {
108            return Text::EMPTY;
109        }
110
111        $dotIndex = Text::getLastPosition($path, '.');
112        if ($dotIndex === -1) {
113            return Text::EMPTY;
114        }
115
116        $result = Text::substring($path, $dotIndex);
117        if ($withDot) {
118            return $result;
119        }
120
121        if (!Text::getByteCount($result)) {
122            return Text::EMPTY;
123        }
124
125        return Text::substring($result, 1);
126    }
127
128    /**
129     * 拡張子を省いたファイル名を取得。
130     *
131     * @param string $path
132     * @return string
133     */
134    public static function getFileNameWithoutExtension(string $path): string
135    {
136        $fileName = self::getFileName($path);
137        $dotIndex = Text::getLastPosition($fileName, '.');
138        if ($dotIndex === -1) {
139            return $fileName;
140        }
141
142        return Text::substring($fileName, 0, $dotIndex);
143    }
144
145    /**
146     * パスの分割。
147     *
148     * `pathinfo` ラッパー。
149     *
150     * @param string $path
151     * @return PathParts
152     * @throws ArgumentException
153     * @see https://php.net/manual/function.pathinfo.php
154     */
155    public static function toParts(string $path): PathParts
156    {
157        if (Text::isNullOrWhiteSpace($path)) {
158            throw new ArgumentException('$path');
159        }
160
161        $parts = pathinfo($path);
162
163        $result = new PathParts(
164            $parts['dirname'],
165            $parts['basename'],
166            $parts['filename'],
167            $parts['extension'] ?? Text::EMPTY
168        );
169
170        return $result;
171    }
172
173    /**
174     * ファイルパスに対して環境名を付与する。
175     *
176     * * `file.ext` + `debug` = `file.debug.ext`
177     *
178     * @param string $path パス。
179     * @param string $environment 環境名。
180     * @return string 環境名が付与されたファイルパス。入力値によっては環境に合わせたディレクトリセパレータに変わる可能性あり(`./a.b` => `.\a.env.b`)
181     * @throws ArgumentException
182     */
183    public static function setEnvironmentName(string $path, string $environment): string
184    {
185        if (Text::isNullOrWhiteSpace($path)) {
186            throw new ArgumentException('$path');
187        }
188        if (Text::isNullOrWhiteSpace($environment)) {
189            throw new ArgumentException('$environment');
190        }
191
192        $parts = self::toParts($path);
193
194        $name = Text::isNullOrEmpty($parts->extension)
195            ? $parts->fileNameWithoutExtension . '.' . $environment
196            : $parts->fileNameWithoutExtension . '.' . $environment . '.' . $parts->extension;
197
198        if ($parts->directory === '.' && !(Text::startsWith($path, './', false) || Text::startsWith($path, '.\\', false))) {
199            return $name;
200        }
201
202        if ($parts->directory === DIRECTORY_SEPARATOR) {
203            return DIRECTORY_SEPARATOR . $name;
204        }
205
206        if (Text::endsWith($parts->directory, DIRECTORY_SEPARATOR, false)) {
207            return Text::trimEnd($parts->directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
208        }
209
210        return $parts->directory . DIRECTORY_SEPARATOR . $name;
211    }
212
213    #endregion
214}