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

orchestral / testbench-core / 13780733903

11 Mar 2025 05:02AM UTC coverage: 92.613% (+0.1%) from 92.514%
13780733903

push

github

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

19 of 21 new or added lines in 2 files covered. (90.48%)

1492 of 1611 relevant lines covered (92.61%)

74.77 hits per line

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

94.25
/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 Orchestra\Testbench\Foundation\Config;
19
use Orchestra\Testbench\Foundation\Env;
20
use Symfony\Component\Process\Process;
21

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

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

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

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

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

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

83
    $binary = \defined('TESTBENCH_DUSK') ? 'testbench-dusk' : 'testbench';
12✔
84

85
    $commander = is_file($vendorBinary = package_path('vendor', 'bin', $binary))
12✔
NEW
86
        ? $vendorBinary
×
87
        : $binary;
12✔
88

89
    return $remote->handle($commander, $command);
12✔
90
}
91

92
/**
93
 * Register after resolving callback.
94
 *
95
 * @api
96
 *
97
 * @param  \Illuminate\Contracts\Foundation\Application  $app
98
 * @param  string  $name
99
 * @param  (\Closure(object, \Illuminate\Contracts\Foundation\Application):(mixed))|null  $callback
100
 * @return void
101
 */
102
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
103
{
104
    $app->afterResolving($name, $callback);
192✔
105

106
    if ($app->resolved($name)) {
192✔
107
        value($callback, $app->make($name), $app);
1✔
108
    }
109
}
110

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

130
/**
131
 * Get defined environment variables.
132
 *
133
 * @api
134
 *
135
 * @return array<string, mixed>
136
 */
137
function defined_environment_variables(): array
138
{
139
    return Collection::make(array_merge($_SERVER, $_ENV))
12✔
140
        ->keys()
12✔
141
        ->mapWithKeys(static fn (string $key) => [$key => Env::forward($key)])
12✔
142
        ->unless(
12✔
143
            Env::has('TESTBENCH_WORKING_PATH'), static fn ($env) => $env->put('TESTBENCH_WORKING_PATH', package_path())
12✔
144
        )->all();
12✔
145
}
146

147
/**
148
 * Get default environment variables.
149
 *
150
 * @api
151
 *
152
 * @param  iterable<string, mixed>  $variables
153
 * @return array<int, string>
154
 */
155
function parse_environment_variables($variables): array
156
{
157
    return Collection::make($variables)
4✔
158
        ->transform(static function ($value, $key) {
4✔
159
            if (\is_bool($value) || \in_array($value, ['true', 'false'])) {
4✔
160
                $value = \in_array($value, [true, 'true']) ? '(true)' : '(false)';
4✔
161
            } elseif (\is_null($value) || \in_array($value, ['null'])) {
1✔
162
                $value = '(null)';
1✔
163
            } else {
164
                $value = $key === 'APP_DEBUG' ? \sprintf('(%s)', Str::of($value)->ltrim('(')->rtrim(')')) : "'{$value}'";
1✔
165
            }
166

167
            return "{$key}={$value}";
4✔
168
        })->values()->all();
4✔
169
}
170

171
/**
172
 * Refresh router lookups.
173
 *
174
 * @api
175
 *
176
 * @param  \Illuminate\Routing\Router  $router
177
 * @return void
178
 */
179
function refresh_router_lookups(Router $router): void
180
{
181
    $router->getRoutes()->refreshNameLookups();
192✔
182
}
183

184
/**
185
 * Transform realpath to alias path.
186
 *
187
 * @api
188
 *
189
 * @param  string  $path
190
 * @param  string|null  $workingPath
191
 * @return string
192
 */
193
function transform_realpath_to_relative(string $path, ?string $workingPath = null, string $prefix = ''): string
194
{
195
    $separator = DIRECTORY_SEPARATOR;
13✔
196

197
    if (! \is_null($workingPath)) {
13✔
198
        return str_replace(rtrim($workingPath, $separator).$separator, $prefix.$separator, $path);
1✔
199
    }
200

201
    $laravelPath = base_path();
12✔
202
    $workbenchPath = workbench_path();
12✔
203
    $packagePath = package_path();
12✔
204

205
    return match (true) {
206
        str_starts_with($path, $laravelPath) => str_replace($laravelPath.$separator, '@laravel'.$separator, $path),
12✔
207
        str_starts_with($path, $workbenchPath) => str_replace($workbenchPath.$separator, '@workbench'.$separator, $path),
8✔
208
        str_starts_with($path, $packagePath) => str_replace($packagePath.$separator, '.'.$separator, $path),
8✔
209
        ! empty($prefix) => implode($separator, [$prefix, ltrim($path, $separator)]),
8✔
210
        default => $path,
12✔
211
    };
212
}
213

214
/**
215
 * Get the default skeleton path.
216
 *
217
 * @api
218
 *
219
 * @no-named-arguments
220
 *
221
 * @param  array<int, string|null>|string  ...$path
222
 * @return string
223
 */
224
function default_skeleton_path(array|string $path = ''): string
225
{
226
    return (string) realpath(Sidekick\join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path)));
189✔
227
}
228

229
/**
230
 * Get the migration path by type.
231
 *
232
 * @api
233
 *
234
 * @param  string|null  $type
235
 * @return string
236
 *
237
 * @throws \InvalidArgumentException
238
 */
239
function default_migration_path(?string $type = null): string
240
{
241
    $path = realpath(
57✔
242
        \is_null($type) ? base_path('migrations') : base_path(Sidekick\join_paths('migrations', $type))
57✔
243
    );
57✔
244

245
    if ($path === false) {
57✔
246
        throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel'));
×
247
    }
248

249
    return $path;
57✔
250
}
251

252
/**
253
 * Get the path to the package folder.
254
 *
255
 * @api
256
 *
257
 * @no-named-arguments
258
 *
259
 * @param  array<int, string|null>|string  ...$path
260
 * @return string
261
 */
262
function package_path(array|string $path = ''): string
263
{
264
    $argumentCount = \func_num_args();
92✔
265

266
    $workingPath = \defined('TESTBENCH_WORKING_PATH')
92✔
267
        ? TESTBENCH_WORKING_PATH
52✔
268
        : Env::get('TESTBENCH_WORKING_PATH', getcwd());
40✔
269

270
    if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) {
92✔
271
        return Sidekick\transform_relative_path($path, $workingPath);
7✔
272
    }
273

274
    $path = Sidekick\join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path));
92✔
275

276
    return str_starts_with($path, './')
92✔
277
        ? Sidekick\transform_relative_path($path, $workingPath)
×
278
        : Sidekick\join_paths(rtrim($workingPath, DIRECTORY_SEPARATOR), $path);
92✔
279
}
280

281
/**
282
 * Get the workbench configuration.
283
 *
284
 * @api
285
 *
286
 * @return array<string, mixed>
287
 */
288
function workbench(): array
289
{
290
    /** @var \Orchestra\Testbench\Contracts\Config $config */
291
    $config = app()->bound(Contracts\Config::class)
47✔
292
        ? app()->make(Contracts\Config::class)
45✔
293
        : new Foundation\Config;
2✔
294

295
    return $config->getWorkbenchAttributes();
47✔
296
}
297

298
/**
299
 * Get the path to the workbench folder.
300
 *
301
 * @api
302
 *
303
 * @no-named-arguments
304
 *
305
 * @param  array<int, string|null>|string  ...$path
306
 * @return string
307
 */
308
function workbench_path(array|string $path = ''): string
309
{
310
    return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
73✔
311
}
312

313
/**
314
 * Get the migration path by type.
315
 *
316
 * @api
317
 *
318
 * @param  string|null  $type
319
 * @return string
320
 *
321
 * @throws \InvalidArgumentException
322
 *
323
 * @deprecated
324
 *
325
 * @codeCoverageIgnore
326
 */
327
#[\Deprecated(message: 'Use `Orchestra\Testbench\default_migration_path()` instead', since: '9.5.1')]
328
function laravel_migration_path(?string $type = null): string
329
{
330
    return default_migration_path($type);
331
}
332

333
/**
334
 * Determine if vendor symlink exists on the laravel application.
335
 *
336
 * @api
337
 *
338
 * @param  \Illuminate\Contracts\Foundation\Application  $app
339
 * @param  string|null  $workingPath
340
 * @return bool
341
 */
342
function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool
343
{
344
    $filesystem = new Filesystem;
11✔
345

346
    $appVendorPath = $app->basePath('vendor');
11✔
347
    $workingPath ??= package_path('vendor');
11✔
348

349
    return $filesystem->isFile(Sidekick\join_paths($appVendorPath, 'autoload.php')) &&
11✔
350
        $filesystem->hash(Sidekick\join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(Sidekick\join_paths($workingPath, 'autoload.php'));
11✔
351
}
352

353
/**
354
 * Laravel version compare.
355
 *
356
 * @api
357
 *
358
 * @template TOperator of string|null
359
 *
360
 * @param  string  $version
361
 * @param  string|null  $operator
362
 * @return int|bool
363
 *
364
 * @phpstan-param  TOperator  $operator
365
 *
366
 * @phpstan-return (TOperator is null ? int : bool)
367
 *
368
 * @codeCoverageIgnore
369
 */
370
function laravel_version_compare(string $version, ?string $operator = null): int|bool
371
{
372
    return Sidekick\laravel_version_compare($version, $operator);
373
}
374

375
/**
376
 * PHPUnit version compare.
377
 *
378
 * @api
379
 *
380
 * @template TOperator of string|null
381
 *
382
 * @param  string  $version
383
 * @param  string|null  $operator
384
 * @return int|bool
385
 *
386
 * @throws \RuntimeException
387
 *
388
 * @phpstan-param  TOperator  $operator
389
 *
390
 * @phpstan-return (TOperator is null ? int : bool)
391
 *
392
 * @codeCoverageIgnore
393
 */
394
function phpunit_version_compare(string $version, ?string $operator = null): int|bool
395
{
396
    return Sidekick\phpunit_version_compare($version, $operator);
397
}
398

399
/**
400
 * Determine the PHP Binary.
401
 *
402
 * @api
403
 *
404
 * @param  bool  $escape
405
 * @return string
406
 */
407
function php_binary(bool $escape = false): string
408
{
409
    $phpBinary = Sidekick\php_binary();
12✔
410

411
    return $escape === true ? ProcessUtils::escapeArgument((string) $phpBinary) : $phpBinary;
12✔
412
}
413

414
/**
415
 * Join the given paths together.
416
 *
417
 * @param  string|null  $basePath
418
 * @param  string  ...$paths
419
 * @return string
420
 *
421
 * @deprecated 10.0.0 Use `Orchestra\Sidekick\join_paths()` instead.
422
 *
423
 * @codeCoverageIgnore
424
 */
425
#[\Deprecated('Use `Orchestra\Sidekick\join_paths()` instead', since: '10.0.0')]
426
function join_paths(?string $basePath, string ...$paths): string
427
{
428
    return Sidekick\join_paths($basePath, ...$paths);
429
}
430

431
/**
432
 * Ensure the provided `$app` return an instance of Laravel application or throw an exception.
433
 *
434
 * @internal
435
 *
436
 * @param  \Illuminate\Foundation\Application|null  $app
437
 * @param  string|null  $caller
438
 * @return \Illuminate\Foundation\Application
439
 *
440
 * @throws \Orchestra\Testbench\Exceptions\ApplicationNotAvailableException
441
 */
442
function laravel_or_fail($app, ?string $caller = null): Application
443
{
444
    if ($app instanceof Application) {
186✔
445
        return $app;
186✔
446
    }
447

448
    if (\is_null($caller)) {
1✔
449
        $caller = transform(debug_backtrace()[1] ?? null, function ($debug) {
1✔
450
            /** @phpstan-ignore isset.offset */
451
            if (isset($debug['class']) && isset($debug['function'])) {
1✔
452
                return \sprintf('%s::%s', $debug['class'], $debug['function']);
1✔
453
            }
454

455
            /** @phpstan-ignore offsetAccess.notFound */
456
            return $debug['function'];
×
457
        });
1✔
458
    }
459

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