RepositoryStepTokensReplacer::replaceTokens()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 1
eloc 10
nc 1
nop 3
1
<?php
2
3
/**
4
 * @author Alexei Gorobet <[email protected]>
5
 */
6
7
namespace Behat\TokensExtension;
8
9
use Behat\Gherkin\Node\ArgumentInterface;
10
use Behat\Gherkin\Node\FeatureNode;
11
use Behat\Gherkin\Node\PyStringNode;
12
use Behat\Gherkin\Node\StepNode;
13
use Behat\Gherkin\Node\TableNode;
14
use Behat\Testwork\Call\CallCenter;
15
use Behat\Testwork\Call\CallResult;
16
use Behat\Testwork\Environment\Environment;
17
use Behat\TokensExtension\Call\TokensReplacement;
18
use Behat\TokensExtension\Call\TokensReplacementCall;
19
use Behat\TokensExtension\StepTokensReplacer;
20
21
/**
22
 * Tokens replacer based on discoverable annotations repository.
23
 *
24
 * @author Alexei Gorobets <[email protected]>
25
 */
26
final class RepositoryStepTokensReplacer implements StepTokensReplacer
27
{
28
    /**
29
     * @var TokensReplacementsRepository
30
     */
31
    private $repository;
32
    /**
33
     * @var CallCenter
34
     */
35
    private $callCenter;
36
37
    /**
38
     * Initializes tokens replacer.
39
     *
40
     * @param TokensReplacementsRepository $repository
41
     * @param CallCenter               $callCenter
42
     */
43
    public function __construct(
44
        TokensReplacementsRepository $repository,
45
        CallCenter $callCenter
46
    ) {
47
        $this->repository = $repository;
48
        $this->callCenter = $callCenter;
49
    }
50
    
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function replaceTokens(Environment $environment, FeatureNode $feature, StepNode $step)
55
    {
56
        $tokens_replacements = $this->repository->getEnvironmentTokensReplacements($environment);
57
        $text = $this->replaceTokensText($step->getText(), $tokens_replacements, $step, $environment);
58
        $arguments = $this->replaceTokensArguments($step->getArguments(), $tokens_replacements, $step, $environment);
59
60
        return new StepNode(
61
            $step->getKeyword(),
62
            $text,
63
            $arguments,
64
            $step->getLine(),
65
            $step->getKeywordType()
66
        );
67
    }
68
69
    /**
70
     * Replace tokens in text with values.
71
     *
72
     * @param string $value
73
     *   Text value to replace the tokens in.
74
     * @param TokensReplacement[] $tokens_replacements
75
     *   Tokens replacement callees read.
76
     * @param StepNode $step
77
     *   Step node to be modified.
78
     * @param Environment $environment
79
     *   Environment from to read.
80
     * @return string
81
     *   Processed step text with replaced tokens.
82
     * @throws null
83
     */
84
    protected function replaceTokensText($value, $tokens_replacements, StepNode $step, Environment $environment)
85
    {
86
        $newValue = $value;
87
        foreach ($tokens_replacements as $tokens_replacement) {
88
            if ($this->isApplicableTokensReplacement($value, $tokens_replacement, $matches)) {
89
                foreach ($matches['capture_groups'] as $match_index => $match) {
90
                    // @todo: See how we can cache already replaced results and use the same value for same key.
91
                    $replacedValue = $this->execute($match, $environment, $step, $tokens_replacement);
92
                    // Allow step token replacements to return false if they don't know how to deal with the token.
93
                    if (false !== $replacedValue) {
94
                        $newValue = str_replace($matches['matched_string'][$match_index], $replacedValue, $newValue);
95
                    }
96
                }
97
            }
98
        }
99
100
        return $newValue;
101
    }
102
103
    /**
104
     * Replaces tokens in arguments with values.
105
     *
106
     * @param ArgumentInterface[] $arguments
107
     *
108
     * @return ArgumentInterface[]
109
     */
110
    protected function replaceTokensArguments(array $arguments, array $tokens_replacements, StepNode $step, Environment $environment)
111
    {
112
        foreach ($arguments as $num => $argument) {
113
            if ($argument instanceof TableNode) {
114
                $arguments[$num] = $this->replaceTokensArgumentTable($argument, $tokens_replacements, $step, $environment);
115
            }
116
            if ($argument instanceof PyStringNode) {
117
                $arguments[$num] = $this->replaceTokensArgumentPyString($argument, $tokens_replacements, $step, $environment);
118
            }
119
        }
120
121
        return $arguments;
122
    }
123
124
125
    /**
126
     * Replaces tokens in table with values.
127
     *
128
     * @param TableNode $argument
129
     *
130
     * @return TableNode
131
     */
132
    protected function replaceTokensArgumentTable(TableNode $argument, array $tokens_replacements, StepNode $step, Environment $environment)
133
    {
134
        $table = $argument->getTable();
135
        foreach ($table as $line => $row) {
136
            foreach (array_keys($row) as $col) {
137
                $table[$line][$col] = $this->replaceTokensText($table[$line][$col], $tokens_replacements, $step, $environment);
138
            }
139
        }
140
141
        return new TableNode($table);
142
    }
143
144
    /**
145
     * Replaces tokens in PyString with values.
146
     *
147
     * @param PyStringNode $argument
148
     *
149
     * @return PyStringNode
150
     */
151
    protected function replaceTokensArgumentPyString(PyStringNode $argument, array $tokens_replacements, StepNode $step, Environment $environment)
152
    {
153
        $strings = $argument->getStrings();
154
        foreach ($strings as $line => $string) {
155
            $strings[$line] = $this->replaceTokensText($strings[$line], $tokens_replacements, $step, $environment);
156
        }
157
158
        return new PyStringNode($strings, $argument->getLine());
159
    }
160
161
    /**
162
     * Checks if pattern tokens replacement is applicable.
163
     *
164
     * @param string $value
165
     *   String to replace tokens.
166
     * @param TokensReplacement $tokensReplacement
167
     *   Token replacement callee.
168
     * @param $matches
169
     *   Array of matches identified after
170
     * @return bool
171
     *   TRUE if text matched the pattern, FALSE otherwise.
172
     */
173
    private function isApplicableTokensReplacement($value, TokensReplacement $tokensReplacement, &$matches)
174
    {
175
        $regex = $tokensReplacement->getPattern();
176
177
        if (is_string($value) && preg_match_all($regex, $value, $matches)) {
178
            // take arguments from capture groups if there are some
179
            if (count($matches) > 1) {
180
                // Save full matched string and captured groups.
181
                $matches = [
182
                    'matched_string' => $matches[0],
183
                    'capture_groups' => $matches[1],
184
                ];
185
            }
186
187
            return true;
188
        }
189
190
        return false;
191
    }
192
193
    /**
194
     * Executes tokens replacement.
195
     *
196
     * @param string $value
197
     * @param Environment $environment
198
     * @param StepNode $step
199
     * @param TokensReplacement $tokensReplacement
200
     *
201
     * @return mixed
202
     *   Return value from the callee callable.
203
     */
204
    private function execute($value, Environment $environment, StepNode $step, TokensReplacement $tokensReplacement)
205
    {
206
        $call = new TokensReplacementCall(
207
            $environment,
208
            $step,
209
            $tokensReplacement,
210
            array($value, $step)
211
        );
212
213
        $result = $this->callCenter->makeCall($call);
214
215
        if ($result->hasException()) {
216
            throw $result->getException();
217
        }
218
219
        return $result->getReturn();
220
    }
221
}
222