bex/behat-extension-driver-locator
Dynamic driver/service loader for Behat extensions. Locates drivers by key in a namespace, enforces interfaces, builds and validates per-driver config trees, and loads services via DI container. Includes a DriverNodeBuilder to generate driver config nodes.
Install the Package
composer require --dev bex/behat-extension-driver-locator
Define Driver Interface
Create a base interface for your drivers (e.g., MyAwesomeDriverInterface):
namespace My\Awesome\BehatExtension\Driver;
use Bex\Behat\ExtensionDriverLocator\DriverInterface;
interface MyAwesomeDriverInterface extends DriverInterface
{
public function doSomethingAwesome();
}
Create a Driver
Implement the interface and DriverInterface:
namespace My\Awesome\BehatExtension\Driver;
use Bex\Behat\ExtensionDriverLocator\DriverInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class FirstAwesomeness implements MyAwesomeDriverInterface, DriverInterface
{
public function configure(): array
{
return [
'option1' => ['type' => 'string'],
'option2' => ['type' => 'integer', 'required' => false],
];
}
public function load(ContainerBuilder $container, array $config): void
{
// Register services or logic here
}
public function doSomethingAwesome()
{
return "I'm awesome!";
}
}
Integrate in configure()
Use DriverNodeBuilder in your Behat extension’s configure() method:
use Bex\Behat\ExtensionDriverLocator\DriverNodeBuilder;
public function configure(ArrayNodeDefinition $node)
{
$driverNodeBuilder = DriverNodeBuilder::getInstance(
'My\Awesome\BehatExtension\Driver',
'My\Awesome\BehatExtension\Driver\MyAwesomeDriverInterface'
);
$driverNodeBuilder->buildDriverNodes(
$node,
'active_my_awesome_drivers',
'my_awesome_drivers',
['first_awesomeness']
);
}
Load Drivers in load()
Use DriverLocator to resolve and load drivers:
use Bex\Behat\ExtensionDriverLocator\DriverLocator;
public function load(ContainerBuilder $container, array $config)
{
$driverLocator = DriverLocator::getInstance(
'My\Awesome\BehatExtension\Driver',
'My\Awesome\BehatExtension\Driver\MyAwesomeDriverInterface'
);
$drivers = $driverLocator->findDrivers(
$container,
$config['active_my_awesome_drivers'],
$config['my_awesome_drivers'] ?? []
);
}
Configure behat.yml
default:
extensions:
My\Awesome\BehatExtension:
active_my_awesome_drivers: first_awesomeness
my_awesome_drivers:
first_awesomeness:
option1: "value1"
option2: 42
Driver Discovery
My\Awesome\BehatExtension\Driver).FirstAwesomeness → first_awesomeness).Configuration Validation
configure() method defines a schema for its config.DriverLocator validates user-provided config against this schema before loading.Dependency Injection
ContainerBuilder to drivers via load().load():
public function load(ContainerBuilder $container, array $config)
{
$container->set('my.awesome.service', $this);
}
Multi-Driver Support
findDrivers():
$drivers = $driverLocator->findDrivers($container, ['first_awesomeness', 'second_awesomeness'], $config);
Default Drivers
buildDriverNodes() (e.g., ['first_awesomeness']).active_my_awesome_drivers is set, the default is used.User Configurable
Allow end-users to switch drivers via behat.yml without code changes:
# behat.yml
default:
extensions:
My\Awesome\BehatExtension:
active_my_awesome_drivers: second_awesomeness
Environment-Specific Drivers
Use environment variables or .env to override driver selection:
$activeDrivers = explode(',', env('BEHAT_DRIVER_KEYS', 'first_awesomeness'));
$drivers = $driverLocator->findDrivers($container, $activeDrivers, $config);
Context-Aware Drivers Use drivers to encapsulate context-specific behavior (e.g., database adapters, API clients):
class DatabaseDriver implements MyAwesomeDriverInterface
{
public function doSomethingAwesome()
{
return DB::table('awesome')->get();
}
}
Composite Drivers Combine multiple drivers into a single service:
$compositeDriver = new CompositeDriver($drivers);
$container->set('composite.awesome', $compositeDriver);
Laravel Service Providers
Register drivers as Laravel services in load():
public function load(ContainerBuilder $container, array $config)
{
$driver = $drivers['first_awesomeness'];
$container->set('awesome.driver', $driver);
$container->getDefinition('awesome.driver')->setPublic(true);
}
Behat Context Integration Inject drivers into Behat contexts:
use Behat\Behat\Context\Context;
use My\Awesome\BehatExtension\Driver\MyAwesomeDriverInterface;
class FeatureContext implements Context
{
private $driver;
public function __construct(MyAwesomeDriverInterface $driver)
{
$this->driver = $driver;
}
public function someFeature()
{
return $this->driver->doSomethingAwesome();
}
}
Testing Drivers Mock drivers in tests:
$mockDriver = $this->createMock(MyAwesomeDriverInterface::class);
$mockDriver->method('doSomethingAwesome')->willReturn('Mocked!');
$container->set('awesome.driver', $mockDriver);
Namespace Mismatches
$driverNamespace in DriverLocator and DriverNodeBuilder matches the actual driver location.composer dump-autoload.Interface Inheritance
DriverInterface (from the package) and your custom interface.DriverInterface in your custom interface:
interface MyAwesomeDriverInterface extends DriverInterface { ... }
Config Validation Errors
configure() returns an invalid schema (e.g., missing type), the locator will throw an exception.Circular Dependencies
load().Driver Key Collisions
First and FirstDriver), the last one loaded will override.FirstDatabaseDriver).Enable Debug Mode
Set BEHAT_DEBUG=1 to see detailed validation errors:
if (getenv('BEHAT_DEBUG')) {
error_reporting(E_ALL);
ini_set('display_errors', '1');
}
Log Driver Discovery
Add logging to DriverLocator to inspect discovered drivers:
$driverLocator = DriverLocator::getInstance(...);
\Log::debug('Discovered drivers:', $driverLocator->getDiscoveredDrivers());
Validate Config Manually Test config validation separately:
$validator = new \Symfony\Component\Validator\Validator\ValidatorBuilder();
$validator = $validator->getValidator();
$errors = $validator->validate($config, new DriverConfigConstraints());
DriverInterface Methods
getConfigTree() (deprecated in favor of configure()) for backward compatibility.supports() to filter drivers dynamically:
How can I help you explore Laravel packages today?