SmartReader::extractScenarios()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 12
rs 10
cc 3
nc 3
nop 0
1
<?php
2
3
namespace Knp\FriendlyContexts\Reader;
4
5
use Behat\Behat\Definition\Call;
6
use Behat\Behat\Definition\Call\DefinitionCall;
7
use Behat\Behat\Definition\Exception\SearchException;
8
use Behat\Behat\Definition\SearchResult;
9
use Behat\Behat\Tester\Result\ExecutedStepResult;
10
use Behat\Behat\Tester\Result\SkippedStepResult;
11
use Behat\Behat\Tester\Result\UndefinedStepResult;
12
use Behat\Gherkin\Gherkin;
13
use Behat\Gherkin\Node\FeatureNode;
14
use Behat\Gherkin\Node\StepNode;
15
use Behat\Testwork\Environment\Environment;
16
use Behat\Testwork\Environment\Reader\EnvironmentReader;
17
use Behat\Testwork\Specification\Locator\SpecificationLocator;
18
use Behat\Testwork\Suite\SuiteRepository;
19
use Knp\FriendlyContexts\Call\CallCenter;
20
use Knp\FriendlyContexts\Definition\DefinitionFinder;
21
22
class SmartReader implements EnvironmentReader
23
{
24
    public function __construct(Gherkin $gherkin, SuiteRepository $registry, SpecificationLocator $locator, DefinitionFinder $definitionFinder, CallCenter $callCenter, $smartTag)
25
    {
26
        $this->gherkin          = $gherkin;
0 ignored issues
show
Bug Best Practice introduced by
The property gherkin does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
27
        $this->registry         = $registry;
0 ignored issues
show
Bug Best Practice introduced by
The property registry does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
28
        $this->locator          = $locator;
0 ignored issues
show
Bug Best Practice introduced by
The property locator does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
29
        $this->definitionFinder = $definitionFinder;
0 ignored issues
show
Bug Best Practice introduced by
The property definitionFinder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
30
        $this->callCenter       = $callCenter;
0 ignored issues
show
Bug Best Practice introduced by
The property callCenter does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
31
        $this->smartTag         = $smartTag;
0 ignored issues
show
Bug Best Practice introduced by
The property smartTag does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
32
    }
33
34
    public function supportsEnvironment(Environment $environment)
35
    {
36
        return true;
37
    }
38
39
    public function readEnvironmentCallees(Environment $environment)
40
    {
41
        $callees = [];
42
43
        foreach ($this->extractScenarios() as $data) {
44
            list($feature, $scenarios) = $data;
45
            foreach ($scenarios as $scenario) {
46
                $callable = function () use ($environment, $feature, $scenario) {
47
                    $steps = $scenario->getSteps();
48
                    foreach ($steps as $step) {
49
                        $result = $this->testStep($environment, $feature, $step);
50
51
                        if ($result instanceof SkippedStepResult) {
52
                            throw new \RuntimeException('Step has been skipped.');
53
                        } elseif (true === $result->hasException()) {
0 ignored issues
show
Bug introduced by
The method hasException() does not exist on Behat\Behat\Tester\Result\UndefinedStepResult. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
                        } elseif (true === $result->/** @scrutinizer ignore-call */ hasException()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
54
                            throw $result->getException();
0 ignored issues
show
Bug introduced by
The method getException() does not exist on Behat\Behat\Tester\Result\UndefinedStepResult. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
                            throw $result->/** @scrutinizer ignore-call */ getException();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
55
                        }
56
                    }
57
                };
58
59
                $callees = array_merge($callees, $this->buildCallee($feature, $scenario, $callable));
60
            }
61
        }
62
63
        return $callees;
64
    }
65
66
    public function extractScenarios()
67
    {
68
        $scenarios = [];
69
70
        foreach ($this->registry->getSuites() as $suite) {
71
            foreach ($this->locator->locateSpecifications($suite, '') as $feature) {
72
                $collection = array_filter($feature->getScenarios(), function ($e) { return $e->hasTag($this->smartTag); });
73
                $scenarios[] = [ $feature, $collection ];
74
            }
75
        }
76
77
        return $scenarios;
78
    }
79
80
    protected function buildCallee($feature, $scenario, $callable)
81
    {
82
        $description = sprintf('%s:%s', $feature->getFile(), $scenario->getLine());
83
84
        return [
85
            new Call\Given(sprintf('/^%s$/', $scenario->getTitle()), $callable, $description),
86
        ];
87
    }
88
89
    protected function testStep(Environment $environment, FeatureNode $feature, StepNode $step, $skip = false)
90
    {
91
        try {
92
            $search = $this->searchDefinition($environment, $feature, $step);
93
            $result = $this->testDefinition($environment, $feature, $step, $search, $skip);
94
        } catch (SearchException $exception) {
95
            $result = new UndefinedStepResult();
96
        }
97
98
        return $result;
99
    }
100
101
    private function searchDefinition(Environment $environment, FeatureNode $feature, StepNode $step)
102
    {
103
        return $this->definitionFinder->findDefinition($environment, $feature, $step);
104
    }
105
106
    private function testDefinition(Environment $environment, FeatureNode $feature, StepNode $step, SearchResult $search, $skip = false)
107
    {
108
        if ($skip || !$search->hasMatch()) {
109
            return new SkippedStepResult($search, null, null);
0 ignored issues
show
Unused Code introduced by
The call to Behat\Behat\Tester\Resul...epResult::__construct() has too many arguments starting with null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

109
            return /** @scrutinizer ignore-call */ new SkippedStepResult($search, null, null);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
110
        }
111
112
        $call = $this->createDefinitionCall($environment, $feature, $search, $step);
113
        $result = $this->callCenter->makeCall($call);
114
115
        return new ExecutedStepResult($search, $result);
116
    }
117
118
    private function createDefinitionCall(Environment $environment, FeatureNode $feature, SearchResult $search, StepNode $step)
119
    {
120
        $definition = $search->getMatchedDefinition();
121
        $arguments = $search->getMatchedArguments();
122
123
        return new DefinitionCall($environment, $feature, $step, $definition, $arguments);
0 ignored issues
show
Bug introduced by
It seems like $definition can also be of type null; however, parameter $definition of Behat\Behat\Definition\C...tionCall::__construct() does only seem to accept Behat\Behat\Definition\Definition, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
        return new DefinitionCall($environment, $feature, $step, /** @scrutinizer ignore-type */ $definition, $arguments);
Loading history...
Bug introduced by
It seems like $arguments can also be of type null; however, parameter $arguments of Behat\Behat\Definition\C...tionCall::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
        return new DefinitionCall($environment, $feature, $step, $definition, /** @scrutinizer ignore-type */ $arguments);
Loading history...
124
    }
125
}
126