splash/bridge-builder

Splash Bridge Builder - Package Connectors as Isolated PHAR Workers

Maintainers

Package info

gitlab.com/SplashTools/bridge-builder

Issues

pkg:composer/splash/bridge-builder

Statistics

Installs: 22

Dependents: 2

Suggesters: 0

Stars: 0

3.0.x-dev 2026-04-01 12:40 UTC

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

  1. You declare what connector to package in your composer.json (extra.splash-bridge)
  2. The builder creates a mini Symfony project with the connector sources, a micro-kernel, and a JSON-RPC worker loop
  3. Composer installs the connector's dependencies in isolation (separate vendor directory)
  4. Post-build scripts run to warm up the cache, initialize the database, etc.
  5. The worker is verified with a ping to make sure everything boots correctly
  6. Optionally, the whole thing is packaged into a single .phar file

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 cache
  • php bin/console doctrine:schema:update --force — create the database schema
  • php 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