Skip to content

Commit fc80eb1

Browse files
Bind default WorkflowRepositoryInterface in package provider
On a fresh composer install, only the package's WaterlineServiceProvider is auto-loaded. The host app has not yet published or registered its own WaterlineApplicationServiceProvider subclass, so the WorkflowRepositoryInterface binding — previously defined only in WaterlineApplicationServiceProvider::register() — is absent. Any request to /waterline/api/stats or the flow list endpoints hits a 500 with "Target [Waterline\Repositories\Workflow\Interfaces\WorkflowRepositoryInterface] is not instantiable". Move the default resolver binding into the package provider's register() method using bindIf. A published WaterlineApplicationServiceProvider subclass that binds its own implementation still takes precedence because bindIf defers to any existing binding. Also mirror the Workflow\Models\Model class alias shim so both the package provider and the Application provider remain safe to register in either order. The new testPackageRegisterBindsDefaultRepositoryWithoutApplicationProvider covers the fresh-install path and testPackageRegisterLeavesHostAppBindingInPlace covers the user-override path.
1 parent f138303 commit fc80eb1

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

app/WaterlineServiceProvider.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@
22

33
namespace Waterline;
44

5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Support\Facades\DB;
57
use Illuminate\Support\Facades\Route;
68
use Illuminate\Support\ServiceProvider;
9+
use Waterline\Repositories\Workflow\Infrastructure\UnavailableV2WorkflowRepository;
10+
use Waterline\Repositories\Workflow\Infrastructure\V2WorkflowRepository;
11+
use Waterline\Repositories\Workflow\Infrastructure\WorkflowRepositoryMySQL;
12+
use Waterline\Repositories\Workflow\Infrastructure\WorkflowRepositoryPostgreSQL;
13+
use Waterline\Repositories\Workflow\Infrastructure\WorkflowRepositorySQLite;
14+
use Waterline\Repositories\Workflow\Infrastructure\WorkflowRepositorySQLServer;
15+
use Waterline\Repositories\Workflow\Interfaces\WorkflowRepositoryInterface;
16+
use Waterline\Support\WorkflowEngineSourceResolver;
717
use Waterline\Support\WorkflowPackageApiFloor;
18+
use Workflow\Models\StoredWorkflow;
819

920
class WaterlineServiceProvider extends ServiceProvider
1021
{
@@ -112,5 +123,40 @@ public function register()
112123
if (! defined('WATERLINE_PATH')) {
113124
define('WATERLINE_PATH', realpath(__DIR__.'/../'));
114125
}
126+
127+
if (! class_exists('Workflow\Models\Model')) {
128+
class_alias(config('workflows.base_model', Model::class), 'Workflow\Models\Model');
129+
}
130+
131+
// Bind the default WorkflowRepositoryInterface resolver so Waterline
132+
// routes work on a fresh composer install without requiring the host
133+
// app to publish and register WaterlineApplicationServiceProvider
134+
// first. Published provider subclasses that bind their own
135+
// implementation in register() still take precedence because bindIf()
136+
// defers to any pre-existing binding.
137+
$this->app->bindIf(WorkflowRepositoryInterface::class, static function () {
138+
$engineSource = WorkflowEngineSourceResolver::status();
139+
140+
if (($engineSource['uses_v2'] ?? false) === true) {
141+
return app(V2WorkflowRepository::class);
142+
}
143+
144+
if (($engineSource['resolved'] ?? null) === 'v2') {
145+
return new UnavailableV2WorkflowRepository($engineSource);
146+
}
147+
148+
$drivers = [
149+
'mysql' => WorkflowRepositoryMySQL::class,
150+
'pgsql' => WorkflowRepositoryPostgreSQL::class,
151+
'sqlite' => WorkflowRepositorySQLite::class,
152+
'sqlsrv' => WorkflowRepositorySQLServer::class,
153+
];
154+
155+
$driver = DB::connection(
156+
(new (config('workflows.stored_workflow_model', StoredWorkflow::class)))->getConnectionName()
157+
)->getDriverName();
158+
159+
return app($drivers[$driver] ?? WorkflowRepositoryMySQL::class);
160+
});
115161
}
116162
}

tests/Unit/WaterlineServiceProviderTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,36 @@ public function testExplicitV2EngineSourceBindsUnavailableRepositoryWhenOperator
6868
$this->assertInstanceOf(UnavailableV2WorkflowRepository::class, $repository);
6969
}
7070

71+
public function testPackageRegisterBindsDefaultRepositoryWithoutApplicationProvider(): void
72+
{
73+
// Simulates a fresh composer install: the package service provider
74+
// is loaded via auto-discovery, but the host app has not published
75+
// and registered WaterlineApplicationServiceProvider yet. The
76+
// Waterline /api/stats and /api/flows/* routes must still resolve.
77+
$this->app->offsetUnset(WorkflowRepositoryInterface::class);
78+
79+
(new WaterlineServiceProvider($this->app))->register();
80+
81+
$repository = $this->app->make(WorkflowRepositoryInterface::class);
82+
83+
$this->assertInstanceOf(WorkflowRepositoryInterface::class, $repository);
84+
}
85+
86+
public function testPackageRegisterLeavesHostAppBindingInPlace(): void
87+
{
88+
// When the host app (or a published WaterlineApplicationServiceProvider
89+
// subclass) has already bound WorkflowRepositoryInterface, the
90+
// package default must not overwrite it.
91+
$custom = $this->createMock(WorkflowRepositoryInterface::class);
92+
93+
$this->app->offsetUnset(WorkflowRepositoryInterface::class);
94+
$this->app->bind(WorkflowRepositoryInterface::class, static fn () => $custom);
95+
96+
(new WaterlineServiceProvider($this->app))->register();
97+
98+
$this->assertSame($custom, $this->app->make(WorkflowRepositoryInterface::class));
99+
}
100+
71101
public function testBootPassesFloorGuardWhenPinnedToV1EvenIfV2SurfaceIsMissing(): void
72102
{
73103
config()->set('waterline.engine_source', 'v1');

0 commit comments

Comments
 (0)