Looking to hire Laravel developers? Try LaraJobs

drove-laravel maintained by oxhq

Description
Narrow Laravel runtime integration for Drove.
Author
Last update
2026/07/30 21:04 (dev-develop)
License
Downloads
6

Comments
comments powered by Disqus

Drove Laravel

This package is a narrow Linux/macOS Laravel 13 bridge for Drove's prepared-process runtime. It adds:

  • a php artisan drove -- <arguments> subprocess command;
  • one root Laravel or Orchestra Testbench application and TestCase binding;
  • four lifecycle callbacks matching Drove dispatch boundaries;
  • one MySQL transaction adapter;
  • one inherited SQLite :memory: adapter; and
  • one physical file-copy adapter for a single SQLite database.

Install

The native Laravel runtime does not install or trust Drove's optional Pest compatibility dependencies:

composer require --dev oxhq/drove-laravel:^0.4@alpha
vendor/bin/drove-install-native
vendor/bin/drove --version

Adapter selection is mandatory. Set DROVE_LARAVEL_STATE=transaction or DROVE_LARAVEL_STATE=sqlite-memory or DROVE_LARAVEL_STATE=sqlite-copy; there is no implicit database mode. The selected connection must be Laravel's default connection. The Artisan command launches the child process with APP_ENV=testing. Direct runtime callers must do the same: boot is rejected unless both the process environment and Laravel's booted application environment are testing, including when configuration is cached.

Runtime selection defaults to DROVE_LARAVEL_RUNTIME=auto: a root with bootstrap/app.php boots as an application, while a package root defers to the selected Orchestra Testbench case. Use application or testbench to force the mode. A Testbench package that ships bootstrap/app.php must opt into testbench.

The runtime entrypoint is:

use Drove\Laravel\LaravelRuntime;

$runtime = LaravelRuntime::boot($projectRoot);
$context = $runtime->scopeContext();

Pest/PHPUnit/Testbench compatibility remains an explicit bridge. Install the dependency set documented by oxhq/drove, then invoke vendor/bin/drove --pest.

Native Drove suites declare the application without a Laravel TestCase:

use App\Providers\AppServiceProvider;
use Drove\Laravel\DroveLaravelServiceProvider;
use Illuminate\Foundation\Application;

use function Drove\Laravel\laravel;

laravel(
    $projectRoot,
    ['driver' => 'sqlite-memory', 'prepared_schema' => true],
    [
        AppServiceProvider::class,
        DroveLaravelServiceProvider::class,
    ],
    static function (Application $app): void {
        // Migrate or seed the prepared parent state exactly once.
    },
);

This path resolves ApplicationRuntime directly and does not enter the PHPUnit/Testbench bridge. State and the application-provider allowlist are mandatory data; bootstrap/providers.php must be the standard literal Provider::class list and is validated without execution before bootstrap/app.php. Native environments require a branchable, scope-isolated provider, so the transaction adapter remains bridge-only.

Native projects must disable Composer package discovery explicitly:

{
    "extra": {
        "laravel": {
            "dont-discover": ["*"]
        }
    }
}

Cached configuration is rejected, including a custom APP_CONFIG_CACHE path. For Laravel's normal declarative bootstrap, package and service manifests from fixed paths, process environment, or .env are not consumed. Drove gives Laravel fresh, per-boot temporary APP_PACKAGES_CACHE and APP_SERVICES_CACHE paths, validates those paths again immediately before provider registration, and removes both manifests after boot.

bootstrap/app.php is trusted construction-only code. Drove cannot undo arbitrary I/O performed while that file is required. The returned application must still be unbootstrapped; Drove then verifies its bootstrap/providers.php path and any withProviders() additions against the allowlist before the console kernel or providers boot. Once Laravel has loaded configuration, Drove also compares config('app.providers') against Laravel's default providers plus the same allowlist immediately before RegisterProviders. bootstrap/app.php and PHP configuration files remain trusted executable project code. The allowlist closes Laravel's declarative bootstrap, configuration, package, and service-manifest channels; it cannot undo arbitrary I/O or direct app()->register() calls performed by trusted code. Trusted code can also mutate or resolve cache paths directly and cause side effects before Drove rejects the changed path; that behavior is outside the isolation guarantee. The optional prepare closure runs after Laravel boot and before the database provider captures its prepared state.

Compatibility-bridge integration calls:

$runtime->assertPlanSupported($scopeIr);
$runtime->bindTestCase($testCase, $context); // no-op for non-Laravel cases
$runtime->beforeDispatch($context, $tasks);
$runtime->enterDescendant($context, $task);
$runtime->leaveDescendant($context, $task);
$runtime->afterDispatch($context, $tasks);

leaveDescendant() and afterDispatch() belong in finally paths. assertPlanSupported() belongs after Scope IR compilation and before lifecycle execution; unsupported topology is input rejection rather than a failed scope.

Each provider contributes a core ResourcePlan to an EnvironmentPlan. Resource plans identify database, filesystem, cache, queue, or object-storage resources and declare explicit Branchable, LeafIsolated, ScopeIsolated, RollbackIsolated, Resettable, and SharedReadOnly capabilities. The environment also declares whether a multi-resource lifecycle is atomic or best-effort.

The current Laravel environment has one managed database resource and declares best-effort coordination because filesystem, cache, queue, and object-storage are explicitly unmanaged. The SQLite providers are Branchable + ScopeIsolated; the transaction provider is LeafIsolated. Planning and runtime dispatch both reject sibling or mixed scopes when any managed resource lacks scope isolation. The four unmanaged resource kinds have no provider in this alpha.

Deliberate limits

The transaction adapter accepts one resolved MySQL connection in a disposable test database. Every existing table must use InnoDB; other engines are rejected before dispatch. It wraps test descendants in a transaction and rolls them back. Scope hooks are not transaction-isolated, a dispatch containing a scope must contain only that scope, and DDL, explicit or implicit commits, connection purges/reconnects, and raw PDO transactions are unsupported. Those operations can persist writes before Drove detects transaction-depth drift. Test cases using RefreshDatabase, LazilyRefreshDatabase, DatabaseMigrations, DatabaseTransactions, or DatabaseTruncation are rejected recursively before execution.

The SQLite adapter accepts one absolute, file-backed database in DELETE journal mode. It rejects in-memory or URI databases, attached databases, WAL/SHM/journal sidecars, open transactions, and already-resolved secondary connections. Descendant state uses verified PHP copy() output. It makes no reflink claim. RefreshDatabase migrates each private copy unless DROVE_LARAVEL_SQLITE_PREPARED_SCHEMA=true declares that the parent file is already migrated.

The sqlite-memory adapter accepts one literal :memory: database and keeps the prepared PDO open across POSIX forks. RefreshDatabase is accepted only with DROVE_LARAVEL_SQLITE_PREPARED_SCHEMA=true; without that explicit contract Drove rejects the suite instead of skipping migrations. Testbench support is limited to one application profile per selected suite. Testbench class/method attributes and suites mixing Testbench with Laravel application cases are rejected before execution.

Orchestra Testbench is optional for Laravel application consumers. Package suites are tested against orchestra/testbench:^11.1; Drove checks the reflected Testbench application/binding contract before it creates an application.

All configured databases must be disposable. If test code explicitly resolves and writes a secondary connection, Drove detects it only during descendant cleanup, after that external write may already have happened. This late detection applies to both SQLite modes and the transaction adapter.

For the built-in adapters, state passed to bootForSuite() or configured with the documented environment variables is trait-preflighted before Testbench calls createApplication(). Custom adapters and state configured only inside the Testbench application are necessarily validated after application boot; provider bootstrap must therefore be safe to run once in the parent.

Neither adapter manages queue workers, cache, Redis, HTTP clients, sockets, or other external resources. Multi-database applications are outside this slice.