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

orchestral / testbench-core / 20918478109

12 Jan 2026 11:56AM UTC coverage: 92.549% (-0.01%) from 92.559%
20918478109

push

github

web-flow
[7.x] Use `Orchestra\Sidekick\package_path()` for better root package path detection (#381)

* Define `TESTBENCH_WORKING_PATH` from `Composer\InstalledVersions`

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

* wip

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

* Remove unused import in autoload.php

Removed unused import of Composer\InstalledVersions.

* wip

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

* wip

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

---------

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

2 of 4 new or added lines in 1 file covered. (50.0%)

1416 of 1530 relevant lines covered (92.55%)

66.12 hits per line

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

93.26
/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
 * @return \Orchestra\Testbench\Foundation\Application
28
 */
29
function container(?string $basePath = null, ?callable $resolvingCallback = null, array $options = []): Foundation\Application
30
{
31
    return Foundation\Application::make($basePath, $resolvingCallback, $options);
3✔
32
}
33

34
/**
35
 * Run artisan command.
36
 *
37
 * @api
38
 *
39
 * @param  \Orchestra\Testbench\Contracts\TestCase|\Illuminate\Contracts\Foundation\Application  $context
40
 * @param  string  $command
41
 * @param  array<string, mixed>  $parameters
42
 * @return int
43
 */
44
function artisan(Contracts\TestCase|ApplicationContract $context, string $command, array $parameters = []): int
45
{
46
    if ($context instanceof ApplicationContract) {
10✔
47
        return $context->make(ConsoleKernel::class)->call($command, $parameters);
1✔
48
    }
49

50
    $command = $context->artisan($command, $parameters);
10✔
51

52
    return $command instanceof PendingCommand ? $command->run() : $command;
10✔
53
}
54

55
/**
56
 * Run remote action using Testbench CLI.
57
 *
58
 * @api
59
 *
60
 * @param  array<int, string>|string  $command
61
 * @param  array<string, mixed>|string  $env
62
 * @param  bool|null  $tty
63
 * @return \Orchestra\Testbench\Foundation\Process\ProcessDecorator
64
 */
65
function remote(array|string $command, array|string $env = [], ?bool $tty = null): Foundation\Process\ProcessDecorator
66
{
67
    $remote = new Foundation\Process\RemoteCommand(
11✔
68
        package_path(), $env, $tty
11✔
69
    );
11✔
70

71
    $binary = Sidekick\is_testbench_cli(dusk: true) ? 'testbench-dusk' : 'testbench';
11✔
72

73
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
11✔
74
        ? $vendorBinary
×
75
        : $binary;
11✔
76

77
    return $remote->handle($commander, $command);
11✔
78
}
79

80
/**
81
 * Run callback only once.
82
 *
83
 * @api
84
 *
85
 * @param  mixed  $callback
86
 * @return \Closure():mixed
87
 *
88
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\once()` instead.
89
 *
90
 * @codeCoverageIgnore
91
 */
92
function once($callback): Closure
93
{
94
    return Sidekick\once($callback);
95
}
96

97
/**
98
 * Register after resolving callback.
99
 *
100
 * @api
101
 *
102
 * @template TLaravel of \Illuminate\Contracts\Foundation\Application
103
 *
104
 * @param  TLaravel  $app
105
 * @param  class-string|string  $name
106
 * @param  (\Closure(object, TLaravel):(mixed))|null  $callback
107
 * @return void
108
 */
109
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
110
{
111
    Sidekick\after_resolving($app, $name, $callback);
173✔
112
}
113

114
/**
115
 * Load migration paths.
116
 *
117
 * @api
118
 *
119
 * @param  \Illuminate\Contracts\Foundation\Application  $app
120
 * @param  array<int, string>|string  $paths
121
 * @return void
122
 */
123
function load_migration_paths(ApplicationContract $app, array|string $paths): void
124
{
125
    after_resolving($app, 'migrator', static function ($migrator) use ($paths) {
38✔
126
        foreach (Arr::wrap($paths) as $path) {
15✔
127
            /** @var \Illuminate\Database\Migrations\Migrator $migrator */
128
            $migrator->path($path);
15✔
129
        }
130
    });
38✔
131
}
132

133
/**
134
 * Get default environment variables.
135
 *
136
 * @return array<int, string>
137
 *
138
 * @deprecated
139
 *
140
 * @codeCoverageIgnore
141
 */
142
function default_environment_variables(): array
143
{
144
    return [];
145
}
146

147
/**
148
 * Get defined environment variables.
149
 *
150
 * @api
151
 *
152
 * @return array<string, mixed>
153
 */
154
function defined_environment_variables(): array
155
{
156
    return (new Collection(array_merge($_SERVER, $_ENV)))
11✔
157
        ->keys()
11✔
158
        ->mapWithKeys(static fn (string $key) => [$key => Sidekick\Env::forward($key)])
11✔
159
        ->unless(
11✔
160
            Sidekick\Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
11✔
161
        )->all();
11✔
162
}
163

164
/**
165
 * Get default environment variables.
166
 *
167
 * @api
168
 *
169
 * @param  iterable<string, mixed>  $variables
170
 * @return array<int, string>
171
 */
172
function parse_environment_variables($variables): array
173
{
174
    return (new Collection($variables))
4✔
175
        ->transform(static function ($value, $key) {
4✔
176
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
177
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
178
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
179
                $value = '(null)';
1✔
180
            } else {
181
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
182
            }
183

184
            return "{$key}={$value}";
4✔
185
        })->values()->all();
4✔
186
}
187

188
/**
189
 * Refresh router lookups.
190
 *
191
 * @api
192
 *
193
 * @param  \Illuminate\Routing\Router  $router
194
 * @return void
195
 */
196
function refresh_router_lookups(Router $router): void
197
{
198
    $router->getRoutes()->refreshNameLookups();
173✔
199
}
200

201
/**
202
 * Transform realpath to alias path.
203
 *
204
 * @api
205
 *
206
 * @param  string  $path
207
 * @param  string|null  $workingPath
208
 * @return string
209
 */
210
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
211
{
212
    $separator = DIRECTORY_SEPARATOR;
13✔
213

214
    if (! \is_null($workingPath)) {
13✔
215
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
216
    }
217

218
    $laravelPath = base_path();
12✔
219
    $workbenchPath = workbench_path();
12✔
220
    $packagePath = package_path();
12✔
221

222
    return match (true) {
12✔
223
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
224
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
12✔
225
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
12✔
226
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
12✔
227
        default => $path,
12✔
228
    };
12✔
229
}
230

231
/**
232
 * Transform relative path.
233
 *
234
 * @api
235
 *
236
 * @param  string  $path
237
 * @param  string  $workingPath
238
 * @return string
239
 *
240
 * @deprecated 7.55.0 Use `Orchestra\Sidekick\transform_relative_path()` instead.
241
 *
242
 * @codeCoverageIgnore
243
 */
244
function transform_relative_path(string $path, string $workingPath): string
245
{
246
    return Sidekick\transform_relative_path($path, $workingPath);
247
}
248

249
/**
250
 * Get the default skeleton path.
251
 *
252
 * @api
253
 *
254
 * @no-named-arguments
255
 *
256
 * @param  array<int, string|null>|string  ...$path
257
 * @return string
258
 */
259
function default_skeleton_path(array|string $path = ''): string
260
{
261
    return (string) realpath(
170✔
262
        Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))
170✔
263
    );
170✔
264
}
265

266
/**
267
 * Get the migration path by type.
268
 *
269
 * @api
270
 *
271
 * @param  string|null  $type
272
 * @return string
273
 *
274
 * @throws \InvalidArgumentException
275
 */
276
function default_migration_path(?string $type = null): string
277
{
278
    $path = realpath(
44✔
279
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
44✔
280
    );
44✔
281

282
    if ($path === false) {
44✔
283
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
284
    }
285

286
    return $path;
44✔
287
}
288

289
/**
290
 * Get the path to the package folder.
291
 *
292
 * @api
293
 *
294
 * @no-named-arguments
295
 *
296
 * @param  array<int, string|null>|string  ...$path
297
 * @return string
298
 */
299
function package_path(array|string $path = ''): string
300
{
301
    $argumentCount = \func_num_args();
69✔
302

303
    $workingPath = Sidekick\package_path();
69✔
304

305
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
69✔
NEW
306
        return Sidekick\transform_relative_path($path, $workingPath);
×
307
    }
308

309
    $path = Sidekick\join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path));
69✔
310

311
    return str_starts_with($path, './')
69✔
NEW
312
        ? Sidekick\transform_relative_path($path, $workingPath)
×
313
        : Sidekick\join_paths($workingPath, $path);
69✔
314
}
315

316
/**
317
 * Get the workbench configuration.
318
 *
319
 * @api
320
 *
321
 * @return array<string, mixed>
322
 */
323
function workbench(): array
324
{
325
    /** @var \Orchestra\Testbench\Contracts\Config $config */
326
    $config = app()->bound(Contracts\Config::class)
35✔
327
        ? app()->make(Contracts\Config::class)
33✔
328
        : new Foundation\Config;
2✔
329

330
    return $config->getWorkbenchAttributes();
35✔
331
}
332

333
/**
334
 * Get the path to the workbench folder.
335
 *
336
 * @api
337
 *
338
 * @no-named-arguments
339
 *
340
 * @param  array<int, string|null>|string  ...$path
341
 * @return string
342
 */
343
function workbench_path(array|string $path = ''): string
344
{
345
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
59✔
346
}
347

348
/**
349
 * Get the migration path by type.
350
 *
351
 * @api
352
 *
353
 * @param  string|null  $type
354
 * @return string
355
 *
356
 * @throws \InvalidArgumentException
357
 *
358
 * @deprecated
359
 */
360
function laravel_migration_path(?string $type = null): string
361
{
362
    return default_migration_path($type);
1✔
363
}
364

365
/**
366
 * Determine if vendor symlink exists on the laravel application.
367
 *
368
 * @api
369
 *
370
 * @param  \Illuminate\Contracts\Foundation\Application  $app
371
 * @param  string|null  $workingPath
372
 * @return bool
373
 */
374
function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool
375
{
376
    $filesystem = new Filesystem;
9✔
377

378
    $appVendorPath = $app->basePath('vendor');
9✔
379
    $workingPath ??= package_path('vendor');
9✔
380

381
    return $filesystem->isFile(join_paths($appVendorPath, 'autoload.php')) &&
9✔
382
        $filesystem->hash(join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(join_paths($workingPath, 'autoload.php'));
9✔
383
}
384

385
/**
386
 * Laravel version compare.
387
 *
388
 * @api
389
 *
390
 * @template TOperator of string|null
391
 *
392
 * @param  string  $version
393
 * @param  string|null  $operator
394
 *
395
 * @phpstan-param  TOperator  $operator
396
 *
397
 * @return int|bool
398
 *
399
 * @phpstan-return (TOperator is null ? int : bool)
400
 */
401
function laravel_version_compare(string $version, ?string $operator = null)
402
{
403
    return Sidekick\laravel_version_compare($version, $operator);
1✔
404
}
405

406
/**
407
 * Package version compare.
408
 *
409
 * @api
410
 *
411
 * @template TOperator of string|null
412
 *
413
 * @param  string  $package
414
 * @param  string  $version
415
 * @param  string|null  $operator
416
 *
417
 * @phpstan-param  TOperator  $operator
418
 *
419
 * @return int|bool
420
 *
421
 * @phpstan-return (TOperator is null ? int : bool)
422
 *
423
 * @throws \OutOfBoundsException
424
 * @throws \RuntimeException
425
 */
426
function package_version_compare(string $package, string $version, ?string $operator = null)
427
{
428
    return Sidekick\package_version_compare($package, $version, $operator);
×
429
}
430

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

455
/**
456
 * Determine the PHP Binary.
457
 *
458
 * @api
459
 *
460
 * @param  bool  $escape
461
 * @return string
462
 */
463
function php_binary(bool $escape = false): string
464
{
465
    $phpBinary = Sidekick\php_binary();
11✔
466

467
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
11✔
468
}
469

470
/**
471
 * Join the given paths together.
472
 *
473
 * @param  string|null  $basePath
474
 * @param  string  ...$paths
475
 * @return string
476
 *
477
 * @codeCoverageIgnore
478
 */
479
function join_paths(?string $basePath, string ...$paths): string
480
{
481
    return Sidekick\join_paths($basePath, ...$paths);
482
}
483

484
/**
485
 * Ensure the provided `$app` return an instance of Laravel application or throw an exception.
486
 *
487
 * @internal
488
 *
489
 * @param  \Illuminate\Foundation\Application|null  $app
490
 * @param  string|null  $caller
491
 * @return \Illuminate\Foundation\Application
492
 *
493
 * @throws \Orchestra\Testbench\Exceptions\ApplicationNotAvailableException
494
 */
495
function laravel_or_fail($app, ?string $caller = null): Application
496
{
497
    if ($app instanceof Application) {
167✔
498
        return $app;
167✔
499
    }
500

501
    if (\is_null($caller)) {
1✔
502
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
503
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
504
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
505
            }
506

507
            return $debug['function'];
×
508
        });
1✔
509
    }
510

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