Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Behat Extension Driver Locator Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package

    composer require --dev bex/behat-extension-driver-locator
    
  2. 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();
    }
    
  3. 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!";
        }
    }
    
  4. 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']
        );
    }
    
  5. 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'] ?? []
        );
    }
    
  6. Configure behat.yml

    default:
      extensions:
        My\Awesome\BehatExtension:
          active_my_awesome_drivers: first_awesomeness
          my_awesome_drivers:
            first_awesomeness:
              option1: "value1"
              option2: 42
    

Implementation Patterns

Driver Lifecycle Management

  1. Driver Discovery

    • The package auto-discovers drivers in the specified namespace (e.g., My\Awesome\BehatExtension\Driver).
    • Driver keys are derived from class names (e.g., FirstAwesomenessfirst_awesomeness).
  2. Configuration Validation

    • Each driver’s configure() method defines a schema for its config.
    • The DriverLocator validates user-provided config against this schema before loading.
  3. Dependency Injection

    • Pass the ContainerBuilder to drivers via load().
    • Register services or dependencies inside load():
      public function load(ContainerBuilder $container, array $config)
      {
          $container->set('my.awesome.service', $this);
      }
      
  4. Multi-Driver Support

    • Activate multiple drivers by passing an array to findDrivers():
      $drivers = $driverLocator->findDrivers($container, ['first_awesomeness', 'second_awesomeness'], $config);
      
  5. Default Drivers

    • Specify fallback drivers in buildDriverNodes() (e.g., ['first_awesomeness']).
    • If no active_my_awesome_drivers is set, the default is used.

Workflows

Dynamic Driver Switching

  1. 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
    
  2. 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);
    

Driver-Specific Logic

  1. 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();
        }
    }
    
  2. Composite Drivers Combine multiple drivers into a single service:

    $compositeDriver = new CompositeDriver($drivers);
    $container->set('composite.awesome', $compositeDriver);
    

Integration Tips

  1. 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);
    }
    
  2. 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();
        }
    }
    
  3. Testing Drivers Mock drivers in tests:

    $mockDriver = $this->createMock(MyAwesomeDriverInterface::class);
    $mockDriver->method('doSomethingAwesome')->willReturn('Mocked!');
    $container->set('awesome.driver', $mockDriver);
    

Gotchas and Tips

Pitfalls

  1. Namespace Mismatches

    • Ensure the $driverNamespace in DriverLocator and DriverNodeBuilder matches the actual driver location.
    • Fix: Verify the namespace in composer dump-autoload.
  2. Interface Inheritance

    • Drivers must implement both DriverInterface (from the package) and your custom interface.
    • Fix: Extend DriverInterface in your custom interface:
      interface MyAwesomeDriverInterface extends DriverInterface { ... }
      
  3. Config Validation Errors

    • If configure() returns an invalid schema (e.g., missing type), the locator will throw an exception.
    • Fix: Validate schemas manually or use tools like Symfony’s Config Component.
  4. Circular Dependencies

    • Avoid circular references between drivers or services registered in load().
    • Fix: Use lazy loading or interfaces to decouple dependencies.
  5. Driver Key Collisions

    • If two drivers have the same key (e.g., First and FirstDriver), the last one loaded will override.
    • Fix: Use unique, descriptive class names (e.g., FirstDatabaseDriver).

Debugging

  1. 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');
    }
    
  2. Log Driver Discovery Add logging to DriverLocator to inspect discovered drivers:

    $driverLocator = DriverLocator::getInstance(...);
    \Log::debug('Discovered drivers:', $driverLocator->getDiscoveredDrivers());
    
  3. Validate Config Manually Test config validation separately:

    $validator = new \Symfony\Component\Validator\Validator\ValidatorBuilder();
    $validator = $validator->getValidator();
    $errors = $validator->validate($config, new DriverConfigConstraints());
    

Tips

  1. Leverage DriverInterface Methods
    • Use getConfigTree() (deprecated in favor of configure()) for backward compatibility.
    • Override supports() to filter drivers dynamically:
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky