• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orchestral / sidekick / 19785671912

29 Nov 2025 03:16PM UTC coverage: 92.04% (-0.3%) from 92.35%
19785671912

push

github

crynobone
Merge branch '1.1.x' into 1.2.x

16 of 18 new or added lines in 1 file covered. (88.89%)

185 of 201 relevant lines covered (92.04%)

6.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.66
/src/functions.php
1
<?php
2

3
namespace Orchestra\Sidekick;
4

5
use BackedEnum;
6
use Closure;
7
use Composer\InstalledVersions;
8
use Composer\Semver\VersionParser;
9
use Illuminate\Foundation\Application;
10
use Illuminate\Support\Arr;
11
use OutOfBoundsException;
12
use PHPUnit\Runner\Version;
13
use RuntimeException;
14
use UnitEnum;
15

16
if (! \function_exists('Orchestra\Sidekick\enum_name')) {
17
    /**
18
     * Get the proper name from enum.
19
     *
20
     * @api
21
     *
22
     * @throws \RuntimeException
23
     */
24
    function enum_name(BackedEnum|UnitEnum $enum): string
25
    {
26
        return mb_convert_case(str_replace('_', ' ', $enum->name), MB_CASE_TITLE, 'UTF-8');
5✔
27
    }
28
}
29

30
if (! \function_exists('Orchestra\Sidekick\enum_value')) {
31
    /**
32
     * Get the proper name from enum.
33
     *
34
     * @api
35
     *
36
     * @template TValue
37
     * @template TDefault
38
     *
39
     * @param  TValue  $value
40
     * @param  TDefault|callable(TValue): TDefault  $default
41
     * @return ($value is empty ? TDefault : mixed)
42
     *
43
     * @throws \RuntimeException
44
     */
45
    function enum_value(mixed $value, mixed $default = null): mixed
46
    {
47
        return match (true) {
48
            $value instanceof BackedEnum => $value->value,
20✔
49
            $value instanceof UnitEnum => $value->name,
16✔
50

51
            default => $value ?? value($default),
20✔
52
        };
53
    }
54
}
55

56
if (! \function_exists('Orchestra\Sidekick\join_paths')) {
57
    /**
58
     * Join the given paths together.
59
     *
60
     * @api
61
     */
62
    function join_paths(?string $basePath, string ...$paths): string
63
    {
64
        foreach ($paths as $index => $path) {
18✔
65
            if (empty($path) && $path !== '0') {
18✔
66
                unset($paths[$index]);
18✔
67
            } else {
68
                $paths[$index] = DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR);
18✔
69
            }
70
        }
71

72
        return $basePath.implode('', $paths);
18✔
73
    }
74
}
75

76
if (! \function_exists('Orchestra\Sidekick\once')) {
77
    /**
78
     * Run callback only once.
79
     *
80
     * @api
81
     *
82
     * @param  mixed  $callback
83
     * @return \Closure():mixed
84
     */
85
    function once($callback): Closure
86
    {
87
        $response = new UndefinedValue;
22✔
88

89
        return function () use ($callback, &$response) {
22✔
90
            if ($response instanceof UndefinedValue) {
22✔
91
                $response = value($callback) ?? null;
22✔
92
            }
93

94
            return $response;
22✔
95
        };
22✔
96
    }
97
}
98

99
if (! \function_exists('Orchestra\Sidekick\is_safe_callable')) {
100
    /**
101
     * Determine if the value is a callable and not a string matching an available function name.
102
     *
103
     * @api
104
     */
105
    function is_safe_callable(mixed $value): bool
106
    {
107
        if ($value instanceof Closure) {
6✔
108
            return true;
1✔
109
        }
110

111
        if (! \is_callable($value)) {
5✔
112
            return false;
2✔
113
        }
114

115
        if (\is_array($value)) {
3✔
116
            return \count($value) === 2 && array_is_list($value) && method_exists(...$value);
1✔
117
        }
118

119
        return ! \is_string($value);
2✔
120
    }
121
}
122

123
if (! \function_exists('Orchestra\Sidekick\is_symlink')) {
124
    /**
125
     * Determine if the path is a symlink for both Unix and Windows environments.
126
     *
127
     * @api
128
     */
129
    function is_symlink(string $path): bool
130
    {
131
        if (windows_os() && is_dir($path) && readlink($path) !== $path) {
1✔
132
            return true;
×
133
        } elseif (is_link($path)) {
1✔
134
            return true;
×
135
        }
136

137
        return false;
1✔
138
    }
139
}
140

141
if (! \function_exists('Orchestra\Sidekick\is_testbench_cli')) {
142
    /**
143
     * Determine if command executed via Testbench CLI.
144
     *
145
     * @api
146
     */
147
    function is_testbench_cli(?bool $dusk = null): bool
148
    {
149
        $usingTestbench = \defined('TESTBENCH_CORE');
1✔
150
        $usingTestbenchDusk = \defined('TESTBENCH_DUSK');
1✔
151

152
        return match ($dusk) {
1✔
153
            false => $usingTestbench === true && $usingTestbenchDusk === false,
×
154
            true => $usingTestbench === true && $usingTestbenchDusk === true,
1✔
155
            default => $usingTestbench === true,
1✔
156
        };
1✔
157
    }
158
}
159

160
if (! \function_exists('Orchestra\Sidekick\transform_relative_path')) {
161
    /**
162
     * Transform relative path.
163
     *
164
     * @api
165
     */
166
    function transform_relative_path(string $path, string $workingPath): string
167
    {
168
        return str_starts_with($path, './')
1✔
169
            ? rtrim($workingPath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.mb_substr($path, 2)
1✔
170
            : $path;
1✔
171
    }
172
}
173

174
if (! \function_exists('Orchestra\Sidekick\working_path')) {
175
    /**
176
     * Get the working path.
177
     *
178
     * @api
179
     *
180
     * @no-named-arguments
181
     *
182
     * @param  array<int, string|null>|string  ...$path
183
     */
184
    function working_path(array|string $path = ''): string
185
    {
186
        return is_testbench_cli() && \function_exists('Orchestra\Testbench\package_path')
1✔
187
            ? \Orchestra\Testbench\package_path($path)
×
188
            : base_path(join_paths(...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path)));
1✔
189
    }
190
}
191

192
if (! \function_exists('Orchestra\Sidekick\laravel_normalize_version')) {
193
    /**
194
     * Laravel normalize version.
195
     *
196
     * @api
197
     *
198
     * @throws \OutOfBoundsException
199
     */
200
    function laravel_normalize_version(): string
201
    {
202
        if (! class_exists(Application::class)) {
18✔
NEW
203
            throw new OutOfBoundsException('Unable to verify "laravel/framework" version');
×
204
        }
205

206
        /** @var string $version */
207
        $version = transform(
18✔
208
            Application::VERSION,
18✔
209
            fn (string $version) => match ($version) {
18✔
210
                '13.x-dev' => '13.0.0',
18✔
211
                default => $version,
18✔
212
            }
213
        );
18✔
214

215
        return (new VersionParser)->normalize($version);
18✔
216
    }
217
}
218

219
if (! \function_exists('Orchestra\Sidekick\phpunit_normalize_version')) {
220
    /**
221
     * PHPUnit normalize version.
222
     *
223
     * @api
224
     *
225
     * @throws \OutOfBoundsException
226
     */
227
    function phpunit_normalize_version(): string
228
    {
229
        if (! class_exists(Version::class)) {
1✔
NEW
230
            throw new OutOfBoundsException('Unable to verify "phpunit/phpunit" version');
×
231
        }
232

233
        /** @var string $version */
234
        $version = transform(
1✔
235
            Version::id(),
1✔
236
            fn (string $version) => match (true) {
1✔
237
                str_starts_with($version, '12.4-') => '12.4.0',
1✔
238
                default => $version,
1✔
239
            }
240
        );
1✔
241

242
        return (new VersionParser)->normalize($version);
1✔
243
    }
244
}
245

246
if (! \function_exists('Orchestra\Sidekick\laravel_version_compare')) {
247
    /**
248
     * Laravel version compare.
249
     *
250
     * @api
251
     *
252
     * @template TOperator of string|null
253
     *
254
     * @phpstan-param  TOperator  $operator
255
     *
256
     * @phpstan-return (TOperator is null ? int : bool)
257
     *
258
     * @throws \RuntimeException
259
     *
260
     * @codeCoverageIgnore
261
     */
262
    function laravel_version_compare(string $version, ?string $operator = null): int|bool
263
    {
264
        if (! class_exists(Application::class)) {
265
            return package_version_compare('laravel/framework', $version, $operator);
266
        }
267

268
        $laravel = laravel_normalize_version();
269
        $version = (new VersionParser)->normalize($version);
270

271
        if (\is_null($operator)) {
272
            return version_compare($laravel, $version);
273
        }
274

275
        return version_compare($laravel, $version, $operator);
276
    }
277
}
278

279
if (! \function_exists('Orchestra\Sidekick\package_version_compare')) {
280
    /**
281
     * Package version compare.
282
     *
283
     * @api
284
     *
285
     * @template TOperator of string|null
286
     *
287
     * @phpstan-param  TOperator  $operator
288
     *
289
     * @phpstan-return (TOperator is null ? int : bool)
290
     *
291
     * @throws \OutOfBoundsException
292
     * @throws \RuntimeException
293
     *
294
     * @codeCoverageIgnore
295
     */
296
    function package_version_compare(string $package, string $version, ?string $operator = null): int|bool
297
    {
298
        $prettyVersion = InstalledVersions::getPrettyVersion($package);
299

300
        if (\is_null($prettyVersion)) {
301
            throw new RuntimeException(\sprintf('Unable to compare "%s" version', $package));
302
        }
303

304
        $versionParser = new VersionParser;
305

306
        $package = $versionParser->normalize($prettyVersion);
307
        $version = $versionParser->normalize($version);
308

309
        if (\is_null($operator)) {
310
            return version_compare($package, $version);
311
        }
312

313
        return version_compare($package, $version, $operator);
314
    }
315
}
316

317
if (! \function_exists('Orchestra\Sidekick\phpunit_version_compare')) {
318
    /**
319
     * PHPUnit version compare.
320
     *
321
     * @api
322
     *
323
     * @template TOperator of string|null
324
     *
325
     * @phpstan-param  TOperator  $operator
326
     *
327
     * @phpstan-return (TOperator is null ? int : bool)
328
     *
329
     * @throws \OutOfBoundsException
330
     * @throws \RuntimeException
331
     *
332
     * @codeCoverageIgnore
333
     */
334
    function phpunit_version_compare(string $version, ?string $operator = null): int|bool
335
    {
336
        if (! class_exists(Version::class)) {
337
            return package_version_compare('phpunit/phpunit', $version, $operator);
338
        }
339

340
        $phpunit = phpunit_normalize_version();
341
        $version = (new VersionParser)->normalize($version);
342

343
        if (\is_null($operator)) {
344
            return version_compare($phpunit, $version);
345
        }
346

347
        return version_compare($phpunit, $version, $operator);
348
    }
349
}
350

351
if (! \function_exists('Orchestra\Sidekick\php_binary')) {
352
    /**
353
     * Determine the PHP Binary.
354
     *
355
     * @api
356
     *
357
     * @codeCoverageIgnore
358
     */
359
    function php_binary(): string
360
    {
361
        return (new PhpExecutableFinder)->find(false) ?: 'php';
362
    }
363
}
364

365
if (! \function_exists('Orchestra\Sidekick\windows_os')) {
366
    /**
367
     * Determine whether the current environment is Windows-based.
368
     *
369
     * @api
370
     *
371
     * @codeCoverageIgnore
372
     */
373
    function windows_os(): bool
374
    {
375
        return PHP_OS_FAMILY === 'Windows';
376
    }
377
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc