splash / bridge-builder
Splash Bridge Builder - Package Connectors as Isolated PHAR Workers
Requires
- php: ^8.1
- splash/php-bundle: ^2.0|^3.0
Requires (Dev)
- badpixxel/php-sdk: ^3.0
- symfony/config: ^5.4|^6.4|^7.0
- symfony/console: ^5.4|^6.4|^7.0
- symfony/dependency-injection: ^5.4|^6.4|^7.0
- symfony/finder: ^5.4|^6.4|^7.0
- symfony/flex: ^1.0|^2.0|^7.0
- symfony/form: ^5.4|^6.4|^7.0
- symfony/framework-bundle: ^5.4|^6.4|^7.0
- symfony/http-kernel: ^5.4|^6.4|^7.0
- symfony/monolog-bundle: ^2.0|^3.0
- symfony/twig-bundle: ^5.4|^6.4|^7.0
- symfony/yaml: ^5.4|^6.4|^7.0
This package is auto-updated.
Last update: 2026-04-01 10:40:16 UTC
README
The Bridge Builder packages any Splash connector into an isolated, standalone worker that communicates via JSON-RPC over stdin/stdout. This is the build-time companion to the Bridge Bundle, which consumes these workers at runtime.
Why?
Splash connectors (Shopify, Faker, Mailchimp, etc.) are Symfony bundles with their own dependencies. When migrating to a new stack version, dependency conflicts make it impossible to run legacy connectors in the same process. The Bridge solves this by running each connector in its own isolated PHP process with its own vendor directory.
The Builder takes a connector's source code, wraps it in a minimal Symfony micro-kernel with a JSON-RPC worker loop, installs its dependencies independently, and packages everything into a standalone build directory or .phar archive.
The result: any app can spawn php faker.phar (or php dist/faker/worker.php) and communicate with the connector via stdin/stdout, with zero dependency conflicts and sub-millisecond latency.
How it works
- You declare what connector to package in your
composer.json(extra.splash-bridge) - The builder creates a mini Symfony project with the connector sources, a micro-kernel, and a JSON-RPC worker loop
- Composer installs the connector's dependencies in isolation (separate vendor directory)
- Post-build scripts run to warm up the cache, initialize the database, etc.
- The worker is verified with a ping to make sure everything boots correctly
- Optionally, the whole thing is packaged into a single
.pharfile
The Bridge Bundle then spawns this worker via proc_open, sends JSON-RPC requests on stdin, and reads responses from stdout. From the ConnectorsManager's perspective, it's just another connector.
Install
composer require --dev splash/bridge-builder
Configure
All configuration lives in your project's composer.json under extra.splash-bridge:
{
"extra": {
"splash-bridge": {
// Unique name for this connector build
// Used for: build dir (dist/{name}/), phar file (dist/{name}.phar),
// and the connector's bridge ID ({version}@{name})
"name": "faker",
// The connector class (FQCN) or service ID
// This is the class that implements ConnectorInterface
// The BridgeWorkerBundle auto-detects it via the splash.connector tag
// and wires it into the WorkerService at container warmup
"connector": "Splash\\Connectors\\Faker\\Connectors\\FakeConnector",
// Files and directories to copy into the build
// Keys = target path in the build dir
// Values = source path relative to the project root
"sources": {
// The connector's composer.json — used to read its "require" section
// so the builder knows which dependencies the connector needs
"composer.json": "vendor/splash/faker/composer.json",
// The connector's source code
// IMPORTANT: the target path must match the PSR-4 namespace structure
// so the Kernel can auto-discover the *Bundle.php file
// Example: Splash\Connectors\Faker\ lives in connector/Faker/
"connector/Faker": "vendor/splash/faker/src",
// Optional: Symfony config files the connector needs
// (doctrine, twig, splash config, extra bundles, etc.)
// These override the default stubs config
"config/packages/doctrine.yaml": "vendor/splash/faker/tests/config/packages/doctrine.yaml",
"config/packages/splash.yaml": "vendor/splash/faker/tests/config/packages/splash.yaml",
// Optional: extra bundles the connector requires
// Classes that don't exist in the build are automatically filtered out
"config/bundles.php": "vendor/splash/faker/tests/config/bundles.php"
},
// Extra dependencies needed for the connector to run standalone
// These are NOT in the connector's own require (they come from the host app)
// They go into require-dev of the generated composer.json
// so you can clearly see what the connector needs vs what was added
"require": {
// Symfony components (the connector relies on the host app for these)
"symfony/console": "^6.4",
"symfony/http-kernel": "^6.4",
"symfony/framework-bundle": "^6.4",
"symfony/twig-bundle": "^6.4",
"symfony/translation": "^6.4",
"symfony/routing": "^6.4",
"symfony/form": "^6.4",
"symfony/yaml": "^6.4",
"symfony/runtime": "^6.4",
"symfony/monolog-bundle": "*",
// Doctrine (if the connector uses entities)
"doctrine/orm": "^2.6",
"doctrine/annotations": "^1.0|^2.0",
"doctrine/doctrine-bundle": "^1.9|^2.0",
// Logging
"monolog/monolog": "^2.0|^3.0",
// SQLite for the local database (used by Faker for entity storage)
"ext-pdo_sqlite": "*"
}
}
},
"scripts": {
// Commands to run in the build directory after composer install
// The build dir is a full Symfony project with bin/console available
// Use this to warm up the cache, create the database schema,
// seed data, or anything else the connector needs to boot
"post-build-cmd": [
"php bin/console cache:clear --no-debug",
"php bin/console doctrine:schema:update --force --complete --no-interaction --no-debug"
]
}
}
Configuration reference
name (required)
The connector's identifier. Determines:
- Build directory:
dist/{name}/ - PHAR filename:
dist/{name}.phar - Bridge ID in the ConnectorsManager:
{version}@{name}
connector (required)
The FQCN of the connector class (e.g. Splash\Connectors\Faker\Connectors\FakeConnector). Can also be a Symfony service ID.
At build time, the BridgeWorkerBundle uses a CompilerPass to find the service tagged splash.connector that matches this class and automatically injects it into the WorkerService. No manual wiring needed.
sources (required)
A map of target => source paths to copy into the build directory.
The composer.json source is special: it's read by the builder to extract the connector's own require dependencies. Other entries are copied as-is.
The connector source target path must match the PSR-4 namespace. For example, if the connector's namespace is Splash\Connectors\Faker\, the sources should be copied to connector/Faker/ so the Kernel's auto-discovery finds connector/Faker/FakerBundle.php and resolves it as Splash\Connectors\Faker\FakerBundle.
You can also copy config files (config/packages/*.yaml, config/bundles.php) to provide Symfony configuration the connector needs (Doctrine, Twig, etc.).
require (optional)
Extra Composer dependencies that the connector needs at runtime but doesn't declare in its own require (because they come from the host Symfony app).
These are placed in require-dev of the generated composer.json so you can clearly distinguish between what the connector needs natively and what was added for standalone operation.
Common entries: Symfony components, Doctrine, Monolog, PHP extensions.
scripts.post-build-cmd (optional)
An array of shell commands executed in the build directory after composer install. The build dir is a full Symfony project with bin/console available.
Typical uses:
php bin/console cache:clear --no-debug— warm up the Symfony cachephp bin/console doctrine:schema:update --force— create the database schemaphp bin/console app:seed-data— seed default data if the connector needs it
These scripts run before the worker and manifest are generated, so the Symfony container is fully compiled and ready when the worker boots.
Build
Build the standalone worker directory:
vendor/bin/bridge-builder
Build with .phar packaging (requires phar.readonly=0):
php -d phar.readonly=0 vendor/bin/bridge-builder
Build pipeline
========================================
Splash Bridge Builder
========================================
[1/7] Creating project structure <- Copies stubs + connector sources
[2/7] Composer install <- Generates composer.json, installs deps
[3/7] Post-build scripts <- Runs post-build-cmd (cache, DB, etc.)
[4/7] Generating worker <- Creates worker.php + bridge config
[5/7] Generating manifest <- Queries the worker for its profile
[6/7] Packaging .phar <- Builds the .phar (if phar.readonly=0)
[7/7] Verifying <- Sends a ping to verify everything works
========================================
Build complete!
========================================
Output
dist/
faker/ <- Standalone build directory (debuggable)
worker.php <- Entry point: stdin/stdout JSON-RPC loop
manifest.json <- Connector metadata (name, version, protocol)
composer.json <- Generated composer.json
.env <- APP_ENV=prod
bin/console <- Symfony console (for debugging)
vendor/ <- Isolated dependencies
bridge/ <- BridgeWorker runtime
Kernel.php <- Micro-kernel with bundle auto-discovery
BridgeWorkerBundle.php
Worker/ <- WorkerService, WorkerRunner, WorkerResponse
connector/ <- Connector sources (auto-discovered by Kernel)
Faker/
FakerBundle.php
Connectors/
Objects/
...
config/ <- Symfony config
bundles/core.php <- Core bundles (Framework + BridgeWorker)
bundles.php <- Optional: extra bundles from connector
packages/ <- Symfony packages config
services.yaml <- Bridge connector parameter
var/ <- Cache + database (after post-build scripts)
faker.phar <- Self-contained PHAR (same content, single file)
Testing the worker manually
# Ping
echo '{"id":"1","method":"ping","params":[]}' | php dist/faker/worker.php
# Full session
printf '{"id":"1","method":"ping","params":[]}\n{"id":"2","method":"getAvailableObjects","params":[]}\n{"id":"3","method":"__shutdown__","params":[]}\n' | php dist/faker/worker.php
Architecture
Php-BridgeBuilder/
src/ <- Splash\BridgeBuilder namespace
Collectors/
ConfigCollector.php <- Reads extra.splash-bridge from composer.json
Builders/
ProjectBuilder.php <- Step 1: copies stubs + sources
ComposerBuilder.php <- Step 2: generates composer.json + install
ScriptsBuilder.php <- Step 3: runs post-build-cmd
WorkerBuilder.php <- Step 4: generates worker.php + config
ManifestBuilder.php <- Step 5: generates manifest.json
PharBuilder.php <- Step 6: packages into .phar
VerifyBuilder.php <- Step 7: ping verification
stubs/ <- Template files copied into every build
bridge/ <- Splash\BridgeWorker namespace (runtime code)
Kernel.php <- MicroKernel with 3-step bundle loading
BridgeWorkerBundle.php <- CompilerPass wires WorkerService to connector
Worker/
WorkerService.php <- Public entry point (connector injected via DI)
WorkerRunner.php <- stdin/stdout JSON-RPC loop
WorkerResponse.php <- Encodes responses + Splash logs
Dictionary/
ProtocolMessages.php <- JSON-RPC protocol constants
DependencyInjection/
Compiler/
WorkerConnectorPass.php <- Auto-detects splash.connector service
Resources/config/
services.yaml <- Autowires Worker classes
config/
bundles/core.php <- FrameworkBundle + BridgeWorkerBundle
packages/
framework.yaml <- Minimal framework config
bin/console <- Symfony console
public/index.php <- Web entry point
.env <- Default environment
bin/bridge-builder <- CLI entry point
License
MIT - See LICENSE