Skip to content

Instantly share code, notes, and snippets.

@castarco
Last active December 8, 2019 12:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save castarco/e3d0626448df330174fd to your computer and use it in GitHub Desktop.
Save castarco/e3d0626448df330174fd to your computer and use it in GitHub Desktop.
<?php
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 Andrés Correa Casablanca <castarco@litipk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
/**
* This is a helper context.
*
* It helps to maintain state between scenario steps, and to use variables
* inside the scenario's steps declarations.
*
* For example:
*
* Scenario: I try to see the profile of a registered user
* @Given the user "terminator" is registered in the system and we refer to him as "registeredUser"
* @When I make a "GET" request on the "/users/:registeredUser:id/profile"
* @Then the HTTP status code is "200"
*
* In the presented example, the context saves the previously registered user in the `registeredUser`
* context variable using the `setSenarioVariable` method. In the following step, the context changes
* the ":registeredUser:id" substring by the `id` property of the context's `registeredUser` variable.
*/
abstract class StatefulContext implements Context, SnippetAcceptingContext
{
/**
* @var StatefulContext[]
*/
protected $contexts = [];
/**
* @var array[string]mixed
*/
protected $scenarioState;
/**
* @BeforeScenario
* @param BeforeScenarioScope $scope
*/
public function beforeScenario(BeforeScenarioScope $scope)
{
$this->scenarioState = [];
$this->contexts = [];
$environment = $scope->getEnvironment();
if ($environment instanceof InitializedContextEnvironment) {
foreach ($this->contexts = $environment->getContexts() as $context) {
if ($context instanceof StatefulContext) {
$this->contexts[] = $context;
}
}
}
}
/**
* @AfterScenario
* @param AfterScenarioScope $scope
*/
public function afterScenario(AfterScenarioScope $scope)
{
// Removing references before destroying the object to avoid circular dependencies
$this->contexts = null;
}
/**
* This method finds a context variable across contexts, it first searches in the current context,
* and then searches in other contexts in no particular order.
*
* @param string $variableName
* @param boolean $nestedCall
* @return mixed
* @throws \Exception When the variable can't be found.
*/
protected function getScenarioVariable($variableName, $nestedCall = false)
{
if (isset($this->scenarioState[$variableName])) {
return $this->scenarioState[$variableName];
}
if ($nestedCall) throw new \Exception('Not found context variable ('.$variableName.')');
foreach ($this->contexts as $context) {
try {
if ($context instanceof StatefulContext) {
return $context->getScenarioVariable($variableName, true);
}
} catch (\Exception $e) {
// Nothing to do here
}
}
throw new \Exception('Not found context variable ('.$variableName.')');
}
/**
* @param string $variableName
* @param mixed $variableValue
*/
protected function setScenarioVariable($variableName, $variableValue)
{
$this->scenarioState[$variableName] = $variableValue;
}
/**
* @param string $input
* @return string
* @throws \Exception
*/
protected function fillWithContextVariables($input)
{
if (0 === preg_match_all('/:[a-z]+(:[0-9a-z]+)*/i', $input, $matches)) {
return $input;
}
$output = $input;
$matches = $matches[0];
foreach ($matches as $match) {
$matchParts = explode(':', substr($match, 1));
$value = $this->getScenarioVariable($matchParts[0]);
foreach(array_slice($matchParts, 1) as $matchPart) {
$arrayIndex = filter_var($matchPart, FILTER_VALIDATE_INT);
if (false !== $arrayIndex) {
if (!is_array($value) || count($value) <= $arrayIndex) {
throw new \Exception('Bad context variable path ('.$match.')');
}
$value = $value[$arrayIndex];
} else {
if (!is_object($value)) {
throw new \Exception('Bad context variable path ('.$match.')');
}
if (isset($value->{$matchPart})) {
$value = $value->{$matchPart};
} elseif (method_exists($value, 'get'.ucfirst($matchPart))) {
$reflectedValue = new \ReflectionObject($value);
$reflectedMethod = $reflectedValue->getMethod('get'.ucfirst($matchPart));
if(!$reflectedMethod->isPublic()) {
throw new \Exception('Bad context variable path ('.$match.')');
}
$value = $reflectedMethod->invoke($value);
} else {
throw new \Exception('Bad context variable path ('.$match.')');
}
}
}
if (is_bool($value)) {
$value = $value ? 'true' : 'false'; // Bool values need custom conversion
}
$output = preg_replace('/'.$match.'/', (string)$value, $output, 1);
}
return $output;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment