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

orchestral / testbench-core / 19805630691

30 Nov 2025 10:14PM UTC coverage: 92.586% (-0.06%) from 92.643%
19805630691

push

github

crynobone
wip

Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com>

1486 of 1605 relevant lines covered (92.59%)

71.14 hits per line

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

93.55
/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

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

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

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

59
    $command = $context->artisan($command, $parameters);
11✔
60

61
    return $command instanceof PendingCommand ? $command->run() : $command;
11✔
62
}
63

64
/**
65
 * Run remote action using Testbench CLI.
66
 *
67
 * @api
68
 *
69
 * @param  array<int, string>|string  $command
70
 * @param  array<string, mixed>|string  $env
71
 * @param  bool|null  $tty
72
 * @return \Orchestra\Testbench\Foundation\Process\ProcessDecorator
73
 */
74
function remote(array|string $command, array|string $env = [], ?bool $tty = null): Foundation\Process\ProcessDecorator
75
{
76
    $remote = new Foundation\Process\RemoteCommand(
12✔
77
        package_path(), $env, $tty
12✔
78
    );
12✔
79

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

82
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
12✔
83
        ? $vendorBinary
×
84
        : $binary;
12✔
85

86
    return $remote->handle($commander, $command);
12✔
87
}
88

89
/**
90
 * Run callback only once.
91
 *
92
 * @api
93
 *
94
 * @param  mixed  $callback
95
 * @return \Closure():mixed
96
 *
97
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\once()` instead.
98
 *
99
 * @codeCoverageIgnore
100
 */
101
function once($callback): Closure
102
{
103
    return Sidekick\once($callback);
104
}
105

106
/**
107
 * Register after resolving callback.
108
 *
109
 * @api
110
 *
111
 * @template TLaravel of \Illuminate\Contracts\Foundation\Application
112
 *
113
 * @param  TLaravel  $app
114
 * @param  class-string|string  $name
115
 * @param  (\Closure(object, TLaravel):(mixed))|null  $callback
116
 * @return void
117
 */
118
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
119
{
120
    $app->afterResolving($name, $callback);
176✔
121

122
    if ($app->resolved($name)) {
176✔
123
        value($callback, $app->make($name), $app);
5✔
124
    }
125
}
126

127
/**
128
 * Load migration paths.
129
 *
130
 * @api
131
 *
132
 * @param  \Illuminate\Contracts\Foundation\Application  $app
133
 * @param  array<int, string>|string  $paths
134
 * @return void
135
 */
136
function load_migration_paths(ApplicationContract $app, array|string $paths): void
137
{
138
    after_resolving($app, 'migrator', static function ($migrator) use ($paths) {
39✔
139
        foreach (Arr::wrap($paths) as $path) {
16✔
140
            /** @var \Illuminate\Database\Migrations\Migrator $migrator */
141
            $migrator->path($path);
16✔
142
        }
143
    });
39✔
144
}
145

146
/**
147
 * Get default environment variables.
148
 *
149
 * @return array<int, string>
150
 *
151
 * @deprecated
152
 *
153
 * @codeCoverageIgnore
154
 */
155
function default_environment_variables(): array
156
{
157
    return [];
158
}
159

160
/**
161
 * Get defined environment variables.
162
 *
163
 * @api
164
 *
165
 * @return array<string, mixed>
166
 */
167
function defined_environment_variables(): array
168
{
169
    return (new Collection(array_merge($_SERVER, $_ENV)))
12✔
170
        ->keys()
12✔
171
        ->mapWithKeys(static fn (string $key) => [$key => Sidekick\Env::forward($key)])
12✔
172
        ->unless(
12✔
173
            Sidekick\Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
12✔
174
        )->all();
12✔
175
}
176

177
/**
178
 * Get default environment variables.
179
 *
180
 * @api
181
 *
182
 * @param  iterable<string, mixed>  $variables
183
 * @return array<int, string>
184
 */
185
function parse_environment_variables($variables): array
186
{
187
    return (new Collection($variables))
4✔
188
        ->transform(static function ($value, $key) {
4✔
189
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
190
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
191
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
192
                $value = '(null)';
1✔
193
            } else {
194
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
195
            }
196

197
            return "{$key}={$value}";
4✔
198
        })->values()->all();
4✔
199
}
200

201
/**
202
 * Refresh router lookups.
203
 *
204
 * @api
205
 *
206
 * @param  \Illuminate\Routing\Router  $router
207
 * @return void
208
 */
209
function refresh_router_lookups(Router $router): void
210
{
211
    $router->getRoutes()->refreshNameLookups();
176✔
212
}
213

214
/**
215
 * Transform realpath to alias path.
216
 *
217
 * @api
218
 *
219
 * @param  string  $path
220
 * @param  string|null  $workingPath
221
 * @return string
222
 */
223
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
224
{
225
    $separator = DIRECTORY_SEPARATOR;
13✔
226

227
    if (! \is_null($workingPath)) {
13✔
228
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
229
    }
230

231
    $laravelPath = base_path();
12✔
232
    $workbenchPath = workbench_path();
12✔
233
    $packagePath = package_path();
12✔
234

235
    return match (true) {
236
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
237
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
8✔
238
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
8✔
239
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
8✔
240
        default => $path,
12✔
241
    };
242
}
243

244
/**
245
 * Transform relative path.
246
 *
247
 * @api
248
 *
249
 * @param  string  $path
250
 * @param  string  $workingPath
251
 * @return string
252
 *
253
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\transform_relative_path()` instead.
254
 *
255
 * @codeCoverageIgnore
256
 */
257
function transform_relative_path(string $path, string $workingPath): string
258
{
259
    return Sidekick\transform_relative_path($path, $workingPath);
260
}
261

262
/**
263
 * Get the default skeleton path.
264
 *
265
 * @api
266
 *
267
 * @no-named-arguments
268
 *
269
 * @param  array<int, string|null>|string  ...$path
270
 * @return string
271
 */
272
function default_skeleton_path(array|string $path = ''): string
273
{
274
    return (string) realpath(
173✔
275
        Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))
173✔
276
    );
173✔
277
}
278

279
/**
280
 * Get the migration path by type.
281
 *
282
 * @api
283
 *
284
 * @param  string|null  $type
285
 * @return string
286
 *
287
 * @throws \InvalidArgumentException
288
 */
289
function default_migration_path(?string $type = null): string
290
{
291
    $path = realpath(
46✔
292
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
46✔
293
    );
46✔
294

295
    if ($path === false) {
46✔
296
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
297
    }
298

299
    return $path;
46✔
300
}
301

302
/**
303
 * Get the path to the package folder.
304
 *
305
 * @api
306
 *
307
 * @no-named-arguments
308
 *
309
 * @param  array<int, string|null>|string  ...$path
310
 * @return string
311
 */
312
function package_path(array|string $path = ''): string
313
{
314
    $argumentCount = \func_num_args();
80✔
315

316
    $workingPath = \defined('TESTBENCH_WORKING_PATH')
80✔
317
        ? TESTBENCH_WORKING_PATH
43✔
318
        : Sidekick\Env::get('TESTBENCH_WORKING_PATH', getcwd());
37✔
319

320
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
80✔
321
        return transform_relative_path($path, $workingPath);
7✔
322
    }
323

324
    $path = Sidekick\join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path));
80✔
325

326
    return str_starts_with($path, './')
80✔
327
        ? transform_relative_path($path, $workingPath)
×
328
        : Sidekick\join_paths(rtrim($workingPath, DIRECTORY_SEPARATOR), $path);
80✔
329
}
330

331
/**
332
 * Get the workbench configuration.
333
 *
334
 * @api
335
 *
336
 * @return array<string, mixed>
337
 */
338
function workbench(): array
339
{
340
    /** @var \Orchestra\Testbench\Contracts\Config $config */
341
    $config = app()->bound(Contracts\Config::class)
36✔
342
        ? app()->make(Contracts\Config::class)
34✔
343
        : new Foundation\Config;
2✔
344

345
    return $config->getWorkbenchAttributes();
36✔
346
}
347

348
/**
349
 * Get the path to the workbench folder.
350
 *
351
 * @api
352
 *
353
 * @no-named-arguments
354
 *
355
 * @param  array<int, string|null>|string  ...$path
356
 * @return string
357
 */
358
function workbench_path(array|string $path = ''): string
359
{
360
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
63✔
361
}
362

363
/**
364
 * Get the migration path by type.
365
 *
366
 * @api
367
 *
368
 * @param  string|null  $type
369
 * @return string
370
 *
371
 * @throws \InvalidArgumentException
372
 *
373
 * @deprecated
374
 */
375
function laravel_migration_path(?string $type = null): string
376
{
377
    return default_migration_path($type);
1✔
378
}
379

380
/**
381
 * Determine if vendor symlink exists on the laravel application.
382
 *
383
 * @api
384
 *
385
 * @param  \Illuminate\Contracts\Foundation\Application  $app
386
 * @param  string|null  $workingPath
387
 * @return bool
388
 */
389
function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool
390
{
391
    $filesystem = new Filesystem;
12✔
392

393
    $appVendorPath = $app->basePath('vendor');
12✔
394
    $workingPath ??= package_path('vendor');
12✔
395

396
    return $filesystem->isFile(join_paths($appVendorPath, 'autoload.php')) &&
12✔
397
        $filesystem->hash(join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(join_paths($workingPath, 'autoload.php'));
12✔
398
}
399

400
/**
401
 * Laravel version compare.
402
 *
403
 * @api
404
 *
405
 * @template TOperator of string|null
406
 *
407
 * @param  string  $version
408
 * @param  string|null  $operator
409
 *
410
 * @phpstan-param  TOperator  $operator
411
 *
412
 * @return int|bool
413
 *
414
 * @phpstan-return (TOperator is null ? int : bool)
415
 */
416
function laravel_version_compare(string $version, ?string $operator = null)
417
{
418
    return Sidekick\laravel_version_compare($version, $operator);
1✔
419
}
420

421
/**
422
 * Package version compare.
423
 *
424
 * @api
425
 *
426
 * @template TOperator of string|null
427
 *
428
 * @param  string  $package
429
 * @param  string  $version
430
 * @param  string|null  $operator
431
 *
432
 * @phpstan-param  TOperator  $operator
433
 *
434
 * @return int|bool
435
 *
436
 * @phpstan-return (TOperator is null ? int : bool)
437
 *
438
 * @throws \OutOfBoundsException
439
 * @throws \RuntimeException
440
 */
441
function package_version_compare(string $package, string $version, ?string $operator = null)
442
{
443
    return Sidekick\package_version_compare($package, $version, $operator);
×
444
}
445

446
/**
447
 * PHPUnit version compare.
448
 *
449
 * @api
450
 *
451
 * @template TOperator of string|null
452
 *
453
 * @param  string  $version
454
 * @param  string|null  $operator
455
 *
456
 * @phpstan-param  TOperator  $operator
457
 *
458
 * @return int|bool
459
 *
460
 * @phpstan-return (TOperator is null ? int : bool)
461
 *
462
 * @throws \OutOfBoundsException
463
 * @throws \RuntimeException
464
 */
465
function phpunit_version_compare(string $version, ?string $operator = null)
466
{
467
    return Sidekick\phpunit_version_compare($version, $operator);
1✔
468
}
469

470
/**
471
 * Determine the PHP Binary.
472
 *
473
 * @api
474
 *
475
 * @param  bool  $escape
476
 * @return string
477
 */
478
function php_binary(bool $escape = false): string
479
{
480
    $phpBinary = Sidekick\php_binary();
12✔
481

482
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
12✔
483
}
484

485
/**
486
 * Join the given paths together.
487
 *
488
 * @param  string|null  $basePath
489
 * @param  string  ...$paths
490
 * @return string
491
 *
492
 * @codeCoverageIgnore
493
 */
494
function join_paths(?string $basePath, string ...$paths): string
495
{
496
    return Sidekick\join_paths($basePath, ...$paths);
497
}
498

499
/**
500
 * Ensure the provided `$app` return an instance of Laravel application or throw an exception.
501
 *
502
 * @internal
503
 *
504
 * @param  \Illuminate\Foundation\Application|null  $app
505
 * @param  string|null  $caller
506
 * @return \Illuminate\Foundation\Application
507
 *
508
 * @throws \Orchestra\Testbench\Exceptions\ApplicationNotAvailableException
509
 */
510
function laravel_or_fail($app, ?string $caller = null): Application
511
{
512
    if ($app instanceof Application) {
170✔
513
        return $app;
170✔
514
    }
515

516
    if (\is_null($caller)) {
1✔
517
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
518
            /** @phpstan-ignore isset.offset */
519
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
520
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
521
            }
522

523
            /** @phpstan-ignore offsetAccess.notFound */
524
            return $debug['function'];
×
525
        });
1✔
526
    }
527

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