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

orchestral / testbench-core / 19805653538

30 Nov 2025 10:16PM UTC coverage: 91.938% (+0.004%) from 91.934%
19805653538

push

github

crynobone
Merge branch '8.x' into 9.x

1551 of 1687 relevant lines covered (91.94%)

77.04 hits per line

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

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

3
namespace Orchestra\Testbench;
4

5
use Closure;
6
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
7
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
8
use Illuminate\Filesystem\Filesystem;
9
use Illuminate\Foundation\Application;
10
use Illuminate\Routing\Router;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\Collection;
13
use Illuminate\Support\ProcessUtils;
14
use Illuminate\Support\Str;
15
use Illuminate\Testing\PendingCommand;
16
use InvalidArgumentException;
17
use Orchestra\Sidekick;
18
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
19
use PHPUnit\Runner\ShutdownHandler;
20

21
/**
22
 * Create Laravel application instance.
23
 *
24
 * @api
25
 *
26
 * @param  string|null  $basePath
27
 * @param  (callable(\Illuminate\Foundation\Application):(void))|null  $resolvingCallback
28
 * @param  array{extra?: array{providers?: array, dont-discover?: array, env?: array}, load_environment_variables?: bool, enabled_package_discoveries?: bool}  $options
29
 * @param  \Orchestra\Testbench\Foundation\Config|null  $config
30
 * @return \Orchestra\Testbench\Foundation\Application
31
 */
32
function container(
33
    ?string $basePath = null,
34
    ?callable $resolvingCallback = null,
35
    array $options = [],
36
    ?Foundation\Config $config = null
37
): Foundation\Application {
38
    if ($config instanceof Foundation\Config) {
3✔
39
        return Foundation\Application::makeFromConfig($config, $resolvingCallback, $options);
×
40
    }
41

42
    return Foundation\Application::make($basePath, $resolvingCallback, $options);
3✔
43
}
44

45
/**
46
 * Run artisan command.
47
 *
48
 * @api
49
 *
50
 * @param  \Orchestra\Testbench\Contracts\TestCase|\Illuminate\Contracts\Foundation\Application  $context
51
 * @param  string  $command
52
 * @param  array<string, mixed>  $parameters
53
 * @return int
54
 */
55
function artisan(Contracts\TestCase|ApplicationContract $context, string $command, array $parameters = []): int
56
{
57
    if ($context instanceof ApplicationContract) {
15✔
58
        return $context->make(ConsoleKernel::class)->call($command, $parameters);
1✔
59
    }
60

61
    $command = $context->artisan($command, $parameters);
15✔
62

63
    return $command instanceof PendingCommand ? $command->run() : $command;
15✔
64
}
65

66
/**
67
 * Emit an exit event within a test.
68
 *
69
 * @param  \PHPUnit\Framework\TestCase|object|null  $testCase
70
 * @param  string|int  $status
71
 * @return never
72
 */
73
function bail(?object $testCase, string|int $status = 0): never
74
{
75
    if ($testCase instanceof PHPUnitTestCase && Sidekick\phpunit_version_compare('12.3.5', '>=')) {
×
76
        ShutdownHandler::resetMessage();
×
77
    }
78

79
    exit($status);
×
80
}
81

82
/**
83
 * Emit an exit event within a test.
84
 *
85
 * @param  \PHPUnit\Framework\TestCase|object|null  $testCase
86
 * @param  string|int  $status
87
 * @return never
88
 */
89
function terminate(?object $testCase, string|int $status = 0): never
90
{
91
    bail($testCase, $status);
×
92
}
93

94
/**
95
 * Run remote action using Testbench CLI.
96
 *
97
 * @api
98
 *
99
 * @param  (\Closure():(mixed))|array<int, string>|string  $command
100
 * @param  array<string, mixed>|string  $env
101
 * @param  bool|null  $tty
102
 * @return \Orchestra\Testbench\Foundation\Process\ProcessDecorator
103
 */
104
function remote(Closure|array|string $command, array|string $env = [], ?bool $tty = null): Foundation\Process\ProcessDecorator
105
{
106
    $remote = new Foundation\Process\RemoteCommand(
12✔
107
        package_path(), $env, $tty
12✔
108
    );
12✔
109

110
    $binary = Sidekick\is_testbench_cli(dusk: true) ? 'testbench-dusk' : 'testbench';
12✔
111

112
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
12✔
113
        ? $vendorBinary
×
114
        : $binary;
12✔
115

116
    return $remote->handle($commander, $command);
12✔
117
}
118

119
/**
120
 * Run callback only once.
121
 *
122
 * @api
123
 *
124
 * @param  mixed  $callback
125
 * @return \Closure():mixed
126
 *
127
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\once()` instead.
128
 *
129
 * @codeCoverageIgnore
130
 */
131
#[\Deprecated(message: 'Use `Orchestra\Sidekick\once()` instead', since: '7.55.0')]
132
function once($callback): Closure
133
{
134
    return Sidekick\once($callback);
135
}
136

137
/**
138
 * Register after resolving callback.
139
 *
140
 * @api
141
 *
142
 * @template TLaravel of \Illuminate\Contracts\Foundation\Application
143
 *
144
 * @param  TLaravel  $app
145
 * @param  class-string|string  $name
146
 * @param  (\Closure(object, TLaravel):(mixed))|null  $callback
147
 * @return void
148
 */
149
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
150
{
151
    $app->afterResolving($name, $callback);
194✔
152

153
    if ($app->resolved($name)) {
194✔
154
        value($callback, $app->make($name), $app);
1✔
155
    }
156
}
157

158
/**
159
 * Load migration paths.
160
 *
161
 * @api
162
 *
163
 * @param  \Illuminate\Contracts\Foundation\Application  $app
164
 * @param  array<int, string>|string  $paths
165
 * @return void
166
 */
167
function load_migration_paths(ApplicationContract $app, array|string $paths): void
168
{
169
    after_resolving($app, 'migrator', static function ($migrator) use ($paths) {
51✔
170
        foreach (Arr::wrap($paths) as $path) {
15✔
171
            /** @var \Illuminate\Database\Migrations\Migrator $migrator */
172
            $migrator->path($path);
15✔
173
        }
174
    });
51✔
175
}
176

177
/**
178
 * Get defined environment variables.
179
 *
180
 * @api
181
 *
182
 * @return array<string, mixed>
183
 */
184
function defined_environment_variables(): array
185
{
186
    return (new Collection(array_merge($_SERVER, $_ENV)))
12✔
187
        ->keys()
12✔
188
        ->mapWithKeys(static fn (string $key) => [$key => Sidekick\Env::forward($key)])
12✔
189
        ->unless(
12✔
190
            Sidekick\Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
12✔
191
        )->all();
12✔
192
}
193

194
/**
195
 * Get default environment variables.
196
 *
197
 * @api
198
 *
199
 * @param  iterable<string, mixed>  $variables
200
 * @return array<int, string>
201
 */
202
function parse_environment_variables($variables): array
203
{
204
    return (new Collection($variables))
4✔
205
        ->transform(static function ($value, $key) {
4✔
206
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
207
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
208
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
209
                $value = '(null)';
1✔
210
            } else {
211
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
212
            }
213

214
            return "{$key}={$value}";
4✔
215
        })->values()->all();
4✔
216
}
217

218
/**
219
 * Refresh router lookups.
220
 *
221
 * @api
222
 *
223
 * @param  \Illuminate\Routing\Router  $router
224
 * @return void
225
 */
226
function refresh_router_lookups(Router $router): void
227
{
228
    $router->getRoutes()->refreshNameLookups();
194✔
229
}
230

231
/**
232
 * Transform realpath to alias path.
233
 *
234
 * @api
235
 *
236
 * @param  string  $path
237
 * @param  string|null  $workingPath
238
 * @return string
239
 */
240
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
241
{
242
    $separator = DIRECTORY_SEPARATOR;
13✔
243

244
    if (! \is_null($workingPath)) {
13✔
245
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
246
    }
247

248
    $laravelPath = base_path();
12✔
249
    $workbenchPath = workbench_path();
12✔
250
    $packagePath = package_path();
12✔
251

252
    return match (true) {
253
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
254
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
8✔
255
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
8✔
256
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
8✔
257
        default => $path,
12✔
258
    };
259
}
260

261
/**
262
 * Transform relative path.
263
 *
264
 * @api
265
 *
266
 * @param  string  $path
267
 * @param  string  $workingPath
268
 * @return string
269
 *
270
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\transform_relative_path()` instead.
271
 *
272
 * @codeCoverageIgnore
273
 */
274
#[\Deprecated(message: 'Use `Orchestra\Sidekick\transform_relative_path()` instead', since: '7.55.0')]
275
function transform_relative_path(string $path, string $workingPath): string
276
{
277
    return Sidekick\transform_relative_path($path, $workingPath);
278
}
279

280
/**
281
 * Get the default skeleton path.
282
 *
283
 * @api
284
 *
285
 * @no-named-arguments
286
 *
287
 * @param  array<int, string|null>|string  ...$path
288
 * @return ($path is '' ? string : string|false)
289
 */
290
function default_skeleton_path(array|string $path = ''): string|false
291
{
292
    return realpath(
191✔
293
        Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))
191✔
294
    );
191✔
295
}
296

297
/**
298
 * Determine if application is bootstrapped using Testbench's default skeleton.
299
 *
300
 * @param  string|null  $basePath
301
 * @return bool
302
 */
303
function uses_default_skeleton(?string $basePath = null): bool
304
{
305
    $basePath ??= base_path();
194✔
306

307
    return realpath(Sidekick\join_paths($basePath, 'bootstrap', '.testbench-default-skeleton')) !== false;
194✔
308
}
309

310
/**
311
 * Get the migration path by type.
312
 *
313
 * @api
314
 *
315
 * @param  string|null  $type
316
 * @return string
317
 *
318
 * @throws \InvalidArgumentException
319
 */
320
function default_migration_path(?string $type = null): string
321
{
322
    $path = realpath(
58✔
323
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
58✔
324
    );
58✔
325

326
    if ($path === false) {
58✔
327
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
328
    }
329

330
    return $path;
58✔
331
}
332

333
/**
334
 * Get the path to the package folder.
335
 *
336
 * @api
337
 *
338
 * @no-named-arguments
339
 *
340
 * @param  array<int, string|null>|string  ...$path
341
 * @return string
342
 */
343
function package_path(array|string $path = ''): string
344
{
345
    $argumentCount = \func_num_args();
93✔
346

347
    $workingPath = \defined('TESTBENCH_WORKING_PATH')
93✔
348
        ? TESTBENCH_WORKING_PATH
54✔
349
        : Sidekick\Env::get('TESTBENCH_WORKING_PATH', getcwd());
39✔
350

351
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
93✔
352
        return Sidekick\transform_relative_path($path, $workingPath);
7✔
353
    }
354

355
    $path = Sidekick\join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path));
93✔
356

357
    return str_starts_with($path, './')
93✔
358
        ? Sidekick\transform_relative_path($path, $workingPath)
×
359
        : Sidekick\join_paths(rtrim($workingPath, DIRECTORY_SEPARATOR), $path);
93✔
360
}
361

362
/**
363
 * Get the workbench configuration.
364
 *
365
 * @api
366
 *
367
 * @return array<string, mixed>
368
 */
369
function workbench(): array
370
{
371
    /** @var \Orchestra\Testbench\Contracts\Config $config */
372
    $config = app()->bound(Contracts\Config::class)
48✔
373
        ? app()->make(Contracts\Config::class)
46✔
374
        : new Foundation\Config;
2✔
375

376
    return $config->getWorkbenchAttributes();
48✔
377
}
378

379
/**
380
 * Get the path to the workbench folder.
381
 *
382
 * @api
383
 *
384
 * @no-named-arguments
385
 *
386
 * @param  array<int, string|null>|string  ...$path
387
 * @return string
388
 */
389
function workbench_path(array|string $path = ''): string
390
{
391
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
74✔
392
}
393

394
/**
395
 * Get the migration path by type.
396
 *
397
 * @api
398
 *
399
 * @param  string|null  $type
400
 * @return string
401
 *
402
 * @throws \InvalidArgumentException
403
 *
404
 * @deprecated
405
 *
406
 * @codeCoverageIgnore
407
 */
408
#[\Deprecated(message: 'Use `Orchestra\Testbench\default_migration_path()` instead', since: '9.5.1')]
409
function laravel_migration_path(?string $type = null): string
410
{
411
    return default_migration_path($type);
412
}
413

414
/**
415
 * Determine if vendor symlink exists on the laravel application.
416
 *
417
 * @api
418
 *
419
 * @param  \Illuminate\Contracts\Foundation\Application  $app
420
 * @param  string|null  $workingPath
421
 * @return bool
422
 */
423
function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool
424
{
425
    $filesystem = new Filesystem;
11✔
426

427
    $appVendorPath = $app->basePath('vendor');
11✔
428
    $workingPath ??= package_path('vendor');
11✔
429

430
    return $filesystem->isFile(join_paths($appVendorPath, 'autoload.php')) &&
11✔
431
        $filesystem->hash(join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(join_paths($workingPath, 'autoload.php'));
11✔
432
}
433

434
/**
435
 * Laravel version compare.
436
 *
437
 * @api
438
 *
439
 * @template TOperator of string|null
440
 *
441
 * @param  string  $version
442
 * @param  string|null  $operator
443
 *
444
 * @phpstan-param  TOperator  $operator
445
 *
446
 * @return int|bool
447
 *
448
 * @phpstan-return (TOperator is null ? int : bool)
449
 */
450
function laravel_version_compare(string $version, ?string $operator = null): int|bool
451
{
452
    return Sidekick\laravel_version_compare($version, $operator);
1✔
453
}
454

455
/**
456
 * Package version compare.
457
 *
458
 * @api
459
 *
460
 * @template TOperator of string|null
461
 *
462
 * @param  string  $package
463
 * @param  string  $version
464
 * @param  string|null  $operator
465
 *
466
 * @phpstan-param  TOperator  $operator
467
 *
468
 * @return int|bool
469
 *
470
 * @phpstan-return (TOperator is null ? int : bool)
471
 *
472
 * @throws \OutOfBoundsException
473
 * @throws \RuntimeException
474
 */
475
function package_version_compare(string $package, string $version, ?string $operator = null)
476
{
477
    return Sidekick\package_version_compare($package, $version, $operator);
1✔
478
}
479

480
/**
481
 * PHPUnit version compare.
482
 *
483
 * @api
484
 *
485
 * @template TOperator of string|null
486
 *
487
 * @param  string  $version
488
 * @param  string|null  $operator
489
 *
490
 * @phpstan-param  TOperator  $operator
491
 *
492
 * @return int|bool
493
 *
494
 * @phpstan-return (TOperator is null ? int : bool)
495
 *
496
 * @throws \OutOfBoundsException
497
 * @throws \RuntimeException
498
 */
499
function phpunit_version_compare(string $version, ?string $operator = null): int|bool
500
{
501
    return Sidekick\phpunit_version_compare($version, $operator);
2✔
502
}
503

504
/**
505
 * Determine the PHP Binary.
506
 *
507
 * @api
508
 *
509
 * @param  bool  $escape
510
 * @return string
511
 */
512
function php_binary(bool $escape = false): string
513
{
514
    $phpBinary = Sidekick\php_binary();
12✔
515

516
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
12✔
517
}
518

519
/**
520
 * Join the given paths together.
521
 *
522
 * @param  string|null  $basePath
523
 * @param  string  ...$paths
524
 * @return string
525
 *
526
 * @codeCoverageIgnore
527
 */
528
function join_paths(?string $basePath, string ...$paths): string
529
{
530
    return Sidekick\join_paths($basePath, ...$paths);
531
}
532

533
/**
534
 * Ensure the provided `$app` return an instance of Laravel application or throw an exception.
535
 *
536
 * @internal
537
 *
538
 * @param  \Illuminate\Foundation\Application|null  $app
539
 * @param  string|null  $caller
540
 * @return \Illuminate\Foundation\Application
541
 *
542
 * @throws \Orchestra\Testbench\Exceptions\ApplicationNotAvailableException
543
 */
544
function laravel_or_fail($app, ?string $caller = null): Application
545
{
546
    if ($app instanceof Application) {
188✔
547
        return $app;
188✔
548
    }
549

550
    if (\is_null($caller)) {
1✔
551
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
552
            /** @phpstan-ignore isset.offset */
553
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
554
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
555
            }
556

557
            /** @phpstan-ignore offsetAccess.notFound */
558
            return $debug['function'];
×
559
        });
1✔
560
    }
561

562
    throw Exceptions\ApplicationNotAvailableException::make($caller);
1✔
563
}
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