behat/gherkin
behat/gherkin is a PHP library for parsing the Gherkin language used in BDD. Read and tokenize feature files, build an AST, and integrate with Behat or other test runners to execute human-readable scenarios in your test suite.
Installation:
composer require behat/gherkin
Add to composer.json if using Laravel's autoloader:
"autoload": {
"psr-4": {
"App\\": "app/",
"Behat\\Gherkin\\": "vendor/behat/gherkin/src/"
}
}
First Use Case:
Parse a .feature file in a Laravel command or controller:
use Behat\Gherkin\Parser;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Keywords\ArrayKeywords;
$keywords = new ArrayKeywords(['en' => [...]]); // Default English keywords
$lexer = new Lexer($keywords);
$parser = new Parser($lexer);
$featureContent = file_get_contents(storage_path('features/auth.feature'));
$featureNode = $parser->parse($featureContent);
// Access parsed data
$featureTitle = $featureNode->getTitle();
$scenarios = $featureNode->getChildren();
Where to Look First:
Given, When, Then).FeatureNode object.FeatureNode, ScenarioNode, StepNode, TableNode, etc. (see src/Node/).ArrayKeywords or DialectProviderInterface (v4.15+).Workflow:
Lexer with language keywords.Parser to get a FeatureNode.Example: Extract Scenarios
$scenarios = $featureNode->getChildren();
foreach ($scenarios as $scenario) {
$steps = $scenario->getSteps();
foreach ($steps as $step) {
$keyword = $step->getKeyword();
$text = $step->getText();
// Log or process step: e.g., dispatch to Laravel events
}
}
ArrayKeywords for simple customizations:
$customKeywords = new ArrayKeywords([
'en' => [
'feature' => 'Use Case',
'given' => 'Setup',
'when' => 'Action',
'then' => 'Outcome',
]
]);
$lexer = new Lexer($customKeywords);
DialectProviderInterface (v4.15+) for advanced dialects:
use Behat\Gherkin\Dialect\DialectProviderInterface;
class CustomDialect implements DialectProviderInterface {
public function getDialect(string $language): array {
return [
'Given' => 'Setup',
'When' => 'Action',
// ...
];
}
}
AppServiceProvider:
$this->app->singleton(Parser::class, function ($app) {
$lexer = new Lexer(new ArrayKeywords(['en' => []]));
return new Parser($lexer);
});
use Illuminate\Console\Command;
use Behat\Gherkin\Parser;
class ParseFeaturesCommand extends Command {
protected $signature = 'features:parse {file}';
public function handle(Parser $parser) {
$feature = $parser->parse(file_get_contents($this->argument('file')));
$this->info($feature->getTitle());
}
}
event(new StepExecuted($step->getKeyword(), $step->getText()));
$tables = $scenario->getTableNodes();
foreach ($tables as $table) {
$rows = $table->getRows();
$headers = $table->getHeader();
// Convert to Laravel collections or arrays
$data = collect($rows)->map(fn($row) => array_combine($headers, $row));
}
$users = User::whereIn('name', $data->pluck('name')->all())->get();
FeatureNode:
$cacheKey = 'features.auth';
$feature = Cache::remember($cacheKey, now()->addHours(1), function () use ($parser) {
return $parser->parse(file_get_contents(storage_path('features/auth.feature')));
});
try {
$feature = $parser->parse($content);
} catch (\Behat\Gherkin\Exception\ParserException $e) {
report($e); // Laravel's error reporting
throw new \RuntimeException('Invalid Gherkin syntax', 0, $e);
}
gherkin-lint in CI or validate tags/steps programmatically:
$invalidTags = $featureNode->getTags()->filter(fn($tag) => !str_starts_with($tag, '@'));
Deprecated Syntax:
@ (e.g., wip&&~slow). Use @wip&&~@slow (v4.16.1+).Lexer/Parser directly is deprecated. Use ParserInterface (v4.15+).Language Handling:
#language:no-such) now throw exceptions in gherkin-32 mode (default: falls back to en).#language: en) is allowed in gherkin-32 mode.Step Keywords:
Given, When) are trimmed by default. Use gherkin-32 mode to preserve whitespace.Table Parsing:
| in tables is ignored (not an error in v4.14+).Performance:
gherkin-32 mode for deeper compatibility with Cucumber.Backward Compatibility:
Scenario and Scenario Outline are treated as synonyms (v4.14+). Use Examples to distinguish.Inspect Tokens:
Use the Lexer to debug tokenization:
$lexer = new Lexer(new ArrayKeywords(['en' => []]));
$tokens = $lexer->tokenize(file_get_contents('feature.feature'));
dd($tokens); // Inspect raw tokens
Enable gherkin-32 Mode:
For stricter Cucumber parity:
$parser = new Parser($lexer, new \Behat\Gherkin\GherkinCompatibilityMode('gherkin-32'));
Check Node Structure:
Dump the FeatureNode to understand its hierarchy:
dd($featureNode->toArray()); // Recursive array representation
Handle Comments:
Comments are parsed but not exposed in the node tree. Use getComments() on nodes if needed (v4.7.3+).
Custom Node Types:
Extend Node\Node or implement NodeInterface for domain-specific nodes.
Pre/Post-Processing: Wrap the parser to add metadata or transform nodes:
class FeatureProcessor {
public function process(FeatureNode $feature) {
$feature->setMetadata(['parsed_at' => now()]);
return $feature;
}
}
Loader Integration:
Use Loader\LoaderInterface to load from non-file sources (e.g., database):
$loader = new \Behat\Gherkin\Loader\FileLoader();
$feature = $loader->load(storage_path('features/auth.feature'));
Tag Filtering: Implement custom tag logic:
$tags
How can I help you explore Laravel packages today?