🤔 What's the problem you're trying to solve?
Behat 3.x introduced first-class support for PHP 8 Attributes as an alternative to docblock annotations for declaring step definitions and hooks (see Behat docs):
use Behat\Step\Given;
use Behat\Step\When;
use Behat\Step\Then;
final class TestRepositoryContext implements Context
{
#[Given('a test repository instance exists')]
public function assertRepositoryInjected(): void { ... }
#[When('I request the entity class name managed by the repository')]
public function requestEntityClassName(): void { ... }
#[Then('I should get the fully qualified class name of TestEntity')]
public function assertEntityClassNameIsTestEntity(): void { ... }
}
The cucumber/language-service currently only recognises docblock annotations (@Given, @When, @Then) as step definition markers when scanning PHP glue files. PHP 8 Attribute-based definitions are completely invisible to the language service, which produces two problems:
- All Gherkin steps are underlined as "undefined" in the editor, even though they are correctly wired at runtime.
- "Go to step definition" and autocomplete do not work — the language service cannot resolve the link between a
.feature step and its implementing method.
This makes Attribute-based Behat projects essentially broken from an IDE-assistance perspective, despite being fully functional at test execution time.
✨ What's your proposed solution?
Extend the PHP step definition parser in cucumber/language-service to recognise PHP 8 Attributes from the following namespaces alongside the existing docblock strategy:
| Attribute class |
Equivalent docblock |
Behat\Step\Given |
@Given |
Behat\Step\When |
@When |
Behat\Step\Then |
@Then |
Behat\Step\Step |
(generic) |
The pattern to match in PHP source files:
// Fully-qualified in use statement, then short form on method:
use Behat\Step\Given;
#[Given('some step text')]
public function someMethod(): void { ... }
// Or fully-qualified inline:
#[\Behat\Step\Given('some step text')]
public function someMethod(): void { ... }
The step expression (the string argument to the attribute constructor) should be extracted exactly the same way as the pattern in a docblock annotation, so all existing expression parsing (Cucumber Expressions, regex) continues to apply unchanged.
⛏ Have you considered any alternatives or workarounds?
- Keeping docblock annotations: Reverting to
/** @Given('...') */ works for the language service but goes against the direction of modern PHP (attributes are the idiomatic, statically-analysable mechanism since PHP 8.0) and produces a split-style codebase when the rest of the project uses attributes.
- Duplicating both forms: Writing both a docblock and an attribute on every method is noisy and error-prone.
- Custom
cucumber.glue regex override: The extension does not currently expose a hook to customise how step definitions are extracted from source files.
None of these are satisfying for a project that has deliberately adopted PHP 8 Attributes as its annotation standard.
📚 Any additional context?
- Behat version that introduced Attribute support: Behat 3.x (PHP 8 attributes). The official Behat docs now show attributes as the primary example.
- Relevant Behat namespaces:
Behat\Step\Given, Behat\Step\When, Behat\Step\Then, Behat\Step\Step; hooks use Behat\Hook\BeforeScenario, Behat\Hook\AfterScenario, etc.
- PHP version floor: PHP 8.0+ (attributes are not available on PHP 7.x, so the parser can safely assume PHP 8 syntax when it encounters
#[).
- Existing language-service PHP parser: The fix is likely localised to the PHP step definition extractor — the attribute argument string is a plain PHP string literal in the same position as the docblock pattern string, so the expression-parsing layer should need no changes.
- Impact: Any Behat project targeting PHP 8+ that follows the current Behat documentation will hit this issue out of the box.
🤔 What's the problem you're trying to solve?
Behat 3.x introduced first-class support for PHP 8 Attributes as an alternative to docblock annotations for declaring step definitions and hooks (see Behat docs):
The
cucumber/language-servicecurrently only recognises docblock annotations (@Given,@When,@Then) as step definition markers when scanning PHP glue files. PHP 8 Attribute-based definitions are completely invisible to the language service, which produces two problems:.featurestep and its implementing method.This makes Attribute-based Behat projects essentially broken from an IDE-assistance perspective, despite being fully functional at test execution time.
✨ What's your proposed solution?
Extend the PHP step definition parser in
cucumber/language-serviceto recognise PHP 8 Attributes from the following namespaces alongside the existing docblock strategy:Behat\Step\Given@GivenBehat\Step\When@WhenBehat\Step\Then@ThenBehat\Step\StepThe pattern to match in PHP source files:
The step expression (the string argument to the attribute constructor) should be extracted exactly the same way as the pattern in a docblock annotation, so all existing expression parsing (Cucumber Expressions, regex) continues to apply unchanged.
⛏ Have you considered any alternatives or workarounds?
/** @Given('...') */works for the language service but goes against the direction of modern PHP (attributes are the idiomatic, statically-analysable mechanism since PHP 8.0) and produces a split-style codebase when the rest of the project uses attributes.cucumber.glueregex override: The extension does not currently expose a hook to customise how step definitions are extracted from source files.None of these are satisfying for a project that has deliberately adopted PHP 8 Attributes as its annotation standard.
📚 Any additional context?
Behat\Step\Given,Behat\Step\When,Behat\Step\Then,Behat\Step\Step; hooks useBehat\Hook\BeforeScenario,Behat\Hook\AfterScenario, etc.#[).