Skip to content
This repository has been archived by the owner on Jul 29, 2022. It is now read-only.

wip: Decoupling argument injection from store business #31

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions argumentsExt/Annotation/StepInjectorArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\StepArgumentInjectorBehatExtension\Annotation;

/**
* @author Rodrigue Villetard <[email protected]>
*/
interface StepInjectorArgument
{
/**
* @return string
*/
public function getName();

/**
* @return string
*/
public function getArgument();
}
Copy link
Owner Author

@gorghoa gorghoa Jul 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface may still be too related with the state sharing business 🤔

Why on earth should we force the annotation to deal with the getName? getArgument may be enough, but does it? Maybe we could get rid of it too…

Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
<?php

/*
* This file is part of the ScenarioStateBehatExtension project.
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\ScenarioStateBehatExtension\Argument;
namespace Gorghoa\StepArgumentInjectorBehatExtension\Argument;

use Behat\Testwork\Argument\ArgumentOrganiser;
use Behat\Testwork\Argument\ArgumentOrganiser as BehatArgumentOrganiser;
use Doctrine\Common\Annotations\Reader;
use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument;
use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer;
use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepArgumentInjectorArgument;
use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument;
// use Gorghoa\StepArgumentInjectorBehatExtension\Context\Initializer\StepArgumentInjectorInitializer;
use ReflectionFunctionAbstract;

/**
* @author Rodrigue Villetard <[email protected]>
* @author Vincent Chalamon <[email protected]>
*/
final class ScenarioStateArgumentOrganiser implements ArgumentOrganiser
final class ArgumentOrganiser implements BehatArgumentOrganiser
{
/**
* @var ArgumentOrganiser
* @var BehatArgumentOrganiser
*/
private $baseOrganiser;

/**
* @var ScenarioStateInitializer
* @var StepArgumentInjectorInitializer
*/
private $store;
private $hookers;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the bad wordplay, couldn’t resist, will change that.

Copy link
Collaborator

@Simperfit Simperfit Jul 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are a man of public things !


/**
* @var Reader
*/
private $reader;

public function __construct(ArgumentOrganiser $organiser, ScenarioStateInitializer $store, Reader $reader)
public function __construct(BehatArgumentOrganiser $organiser, array $hookers, Reader $reader)
{
$this->baseOrganiser = $organiser;
$this->store = $store;
$this->hookers = $hookers;
$this->reader = $reader;
}

Expand All @@ -59,16 +60,21 @@ public function organiseArguments(ReflectionFunctionAbstract $function, array $m
return $this->baseOrganiser->organiseArguments($function, $match);
}

/** @var ScenarioStateArgument[] $annotations */
/** @var StepArgumentInjectorArgument[] $annotations */
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this stage, it could be any type of annotation

$annotations = $this->reader->getMethodAnnotations($function);
$store = $this->store->getStore();

foreach ($annotations as $annotation) {
if ($annotation instanceof ScenarioStateArgument &&
in_array($annotation->getArgument(), $paramsKeys) &&
$store->hasStateFragment($annotation->getName())
if ($annotation instanceof StepInjectorArgument &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!$annotation instanceof StepInjectorArgument ||
    !in_array($argument = $annotation->getArgument(), $paramsKeys)
) {
    continue;
}

in_array($annotation->getArgument(), $paramsKeys)
) {
$match[$annotation->getArgument()] = $store->getStateFragment($annotation->getName());
$match[strval(++$i)] = $store->getStateFragment($annotation->getName());
foreach ($this->hookers as $hooker) {
if ($hooker->hasStepArgumentFor($annotation->getName())) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this block should be handled by business service, not at framework level

$match[$annotation->getArgument()]
= $match[strval(++$i)]
= $hooker->getStepArgumentFor($annotation->getName())
;
}
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions argumentsExt/Argument/StepArgumentHolder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\StepArgumentInjectorBehatExtension\Argument;

/**
* @author Rodrigue Villetard <[email protected]>
*/
interface StepArgumentHolder
{
public function hasStepArgumentFor($key);
Copy link
Owner Author

@gorghoa gorghoa Jul 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@should be rename with something more meaningful like hasStepArgumentHandlerFor($key) or doesHandleStepArgument($key)


public function getStepArgumentFor($key);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be rename by getStepArgumentValueFor($key), it would be more self explaining

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<?php

/*
* This file is part of the ScenarioStateBehatExtension project.
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\ScenarioStateBehatExtension\Call\Handler;
namespace Gorghoa\StepArgumentInjectorBehatExtension\Call\Handler;

use Behat\Behat\Transformation\Call\TransformationCall;
use Behat\Testwork\Call\Call;
use Behat\Testwork\Call\Handler\CallHandler;
use Behat\Testwork\Environment\Call\EnvironmentCall;
use Behat\Testwork\Hook\Call\HookCall;
use Gorghoa\ScenarioStateBehatExtension\Resolver\ArgumentsResolver;
use Gorghoa\StepArgumentInjectorBehatExtension\Resolver\ArgumentsResolver;

/**
* @author Vincent Chalamon <[email protected]>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
<?php

/*
* This file is part of the ScenarioStateBehatExtension project.
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\ScenarioStateBehatExtension\Resolver;
namespace Gorghoa\StepArgumentInjectorBehatExtension\Resolver;

use Doctrine\Common\Annotations\Reader;
use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument;
use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer;
use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument;

/**
* @author Vincent Chalamon <[email protected]>
*/
class ArgumentsResolver
{
/**
* @var ScenarioStateInitializer
* @var Reader
*/
private $store;
private $reader;

/**
* @var Reader
* @var array
*/
private $reader;
private $hookers;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think that's the best word in the world

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that’s too bad, cause i find it a perfectly suitable naming for our needs :'(

image

(https://en.wiktionary.org/wiki/hooker)

The 1st definition is quite what I’m looking for…: “One who, or that which, hooks.”

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But be at ease, i see the politically incorrect and discomfort that this naming carry.


/**
* @param ScenarioStateInitializer $store
* @param Reader $reader
* @param Reader $reader
*/
public function __construct(ScenarioStateInitializer $store, Reader $reader)
public function __construct(array $hookers, Reader $reader)
{
$this->store = $store;
$this->hookers = $hookers;
$this->reader = $reader;
}

Expand All @@ -48,25 +46,27 @@ public function __construct(ScenarioStateInitializer $store, Reader $reader)
*/
public function resolve(\ReflectionMethod $function, array $arguments)
{
// No `@ScenarioStateArgument` annotation found
if (null === $this->reader->getMethodAnnotation($function, ScenarioStateArgument::class)) {
// No `@StepArgumentInjectorArgument` annotation found
if (null === $this->reader->getMethodAnnotation($function, StepInjectorArgument::class)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StepInjectorArgument is not an annotation

return $arguments;
}

$paramsKeys = array_map(function (\ReflectionParameter $element) {
return $element->getName();
}, $function->getParameters());
$store = $this->store->getStore();

// Prepare arguments from annotations
/** @var ScenarioStateArgument[] $annotations */
/** @var StepArgumentInjectorArgument[] $annotations */
$annotations = $this->reader->getMethodAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof ScenarioStateArgument &&
in_array($annotation->getArgument(), $paramsKeys) &&
$store->hasStateFragment($annotation->getName())
if ($annotation instanceof StepInjectorArgument &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!$annotation instanceof StepInjectorArgument ||
    !in_array($argument = $annotation->getArgument(), $paramsKeys)
) {
    continue;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated code from ArgumentOrganiser

in_array($annotation->getArgument(), $paramsKeys)
) {
$arguments[$annotation->getArgument()] = $store->getStateFragment($annotation->getName());
foreach ($this->hookers as $hooker) {
if ($hooker->hasStepArgumentFor($annotation->getName())) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this if block is business logic and should be moved elsewhere in businessland.

$arguments[$annotation->getArgument()] = $hooker->getStepArgumentFor($annotation->getName());
}
}
}
}

Expand Down
123 changes: 123 additions & 0 deletions argumentsExt/ServiceContainer/StepArgumentInjectorExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

/*
* This file is part of the StepArgumentInjectorBehatExtension project.
*
* (c) Rodrigue Villetard <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gorghoa\StepArgumentInjectorBehatExtension\ServiceContainer;

use Behat\Testwork\Argument\ServiceContainer\ArgumentExtension;
use Behat\Testwork\Call\ServiceContainer\CallExtension;
use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface;
use Behat\Testwork\ServiceContainer\ExtensionManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Gorghoa\StepArgumentInjectorBehatExtension\Argument\ArgumentOrganiser;
use Gorghoa\StepArgumentInjectorBehatExtension\Call\Handler\RuntimeCallHandler;
use Gorghoa\StepArgumentInjectorBehatExtension\Resolver\ArgumentsResolver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* Behat store for Behat contexts.
*
* @author Rodrigue Villetard <[email protected]>
* @author Vincent Chalamon <[email protected]>
*/
class StepArgumentInjectorExtension implements ExtensionInterface
{
const STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID = 'argument.step_argument_injector.organiser';
const STEP_ARGUMENT_INJECTOR_DISPATCHER_ID = 'hook.step_argument_injector.dispatcher';
const STEP_ARGUMENT_INJECTOR_TESTER_ID = 'tester.step_argument_injector.wrapper';
const STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID = 'call.step_argument_injector.call_handler';
const STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID = 'step_argument_injector.arguments_resolver';
const STEP_ARGUMENT_INJECTOR_STORE_ID = 'behatstore.context_initializer.store_aware';
const STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID = 'doctrine.reader.annotation';
const STEP_ARGUMENT_INJECTOR_HOOK_TAG_ID = 'step_argument_injector.hook_tag_id';

/**
* {@inheritdoc}
*/
public function getConfigKey()
{
return 'stepargumentinjector';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step_argument_injector

}

/**
* {@inheritdoc}
*/
public function initialize(ExtensionManager $extensionManager)
{
}

/**
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{
}

/**
* {@inheritdoc}
*/
public function load(ContainerBuilder $container, array $config)
{
// Declare Doctrine annotation reader as service
$container->register(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID, AnnotationReader::class)
// Ignore Behat annotations in reader
->addMethodCall('addGlobalIgnoredName', ['Given'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to #32 (comment), you should also ignore lowercase given (same for all Behat annotations).
BTW: does Behat works if I declare my annotation as @gIvEn?

->addMethodCall('addGlobalIgnoredName', ['When'])
->addMethodCall('addGlobalIgnoredName', ['Then'])
->addMethodCall('addGlobalIgnoredName', ['Transform'])
->addMethodCall('addGlobalIgnoredName', ['BeforeStep'])
->addMethodCall('addGlobalIgnoredName', ['BeforeScenario'])
->addMethodCall('addGlobalIgnoredName', ['BeforeFeature'])
->addMethodCall('addGlobalIgnoredName', ['BeforeSuite'])
->addMethodCall('addGlobalIgnoredName', ['AfterStep'])
->addMethodCall('addGlobalIgnoredName', ['AfterScenario'])
->addMethodCall('addGlobalIgnoredName', ['AfterFeature'])
->addMethodCall('addGlobalIgnoredName', ['AfterSuite']);

$taggedServices = array_map(function ($serviceId) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this $taggedServices is boilerplate copy/pasting and should be renamed. Maybe something like $stepArgumentInjectorPluggedServices?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a local variable, you can name it as you want (but please not a too long variable name 😉 )

return new Reference($serviceId);
}, array_keys($container->findTaggedServiceIds(self::STEP_ARGUMENT_INJECTOR_HOOK_TAG_ID)));

// Arguments resolver: resolve StepArgumentInjector arguments from annotation
$container->register(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID, ArgumentsResolver::class)
->setArguments([
$taggedServices,
new Reference(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID),
]);

// Argument organiser
$container->register(self::STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID, ArgumentOrganiser::class)
->setDecoratedService(ArgumentExtension::PREG_MATCH_ARGUMENT_ORGANISER_ID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should find a better way to extend Behat without decorating its kernel services.
Or Behat allows us to extend its kernel more easily (PR to open), or we could create another extension which implements this extend for any extension. WDYT @gorghoa?

->setPublic(false)
->setArguments([
new Reference(sprintf('%s.inner', self::STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID)),
$taggedServices,
new Reference(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID),
new Reference(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID),
]);

// Override calls process
$container->register(self::STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID, RuntimeCallHandler::class)
->setDecoratedService(CallExtension::CALL_HANDLER_TAG.'.runtime')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

->setArguments([
new Reference(self::STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID.'.inner'),
new Reference(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID),
]);
}

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

"autoload": {
"psr-4": {
"Gorghoa\\StepArgumentInjectorBehatExtension\\": "argumentsExt/",
"Gorghoa\\ScenarioStateBehatExtension\\": "src/",
"Symfony\\Component\\Process\\": "vendor/symfony/process/"
}
Expand Down
1 change: 1 addition & 0 deletions src/.php_cs.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"php":"7.1.7-1+0~20170711133214.5+stretch~1.gbp5284f4","version":"2.4.0:v2.4.0#63661f3add3609e90e4ab8115113e189ae547bb4","rules":{"binary_operator_spaces":{"align_double_arrow":false,"align_equals":false},"blank_line_after_opening_tag":true,"blank_line_before_statement":{"statements":["return"]},"braces":{"allow_single_line_closure":true},"cast_spaces":true,"class_definition":{"singleLine":true},"concat_space":{"spacing":"none"},"declare_equal_normalize":true,"function_typehint_space":true,"hash_to_slash_comment":true,"include":true,"lowercase_cast":true,"magic_constant_casing":true,"method_argument_space":true,"method_separation":true,"native_function_casing":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_consecutive_blank_lines":{"tokens":["curly_brace_block","extra","parenthesis_brace_block","square_brace_block","throw","use"]},"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":{"use":"echo"},"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_unneeded_control_parentheses":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"php_unit_fqcn_annotation":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_empty_return":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_types":true,"phpdoc_var_without_name":true,"pre_increment":true,"protected_to_private":true,"return_type_declaration":true,"self_accessor":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_class_element_per_statement":true,"single_quote":true,"space_after_semicolon":true,"standardize_not_equals":true,"ternary_operator_spaces":true,"trailing_comma_in_multiline_array":true,"trim_array_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"blank_line_after_namespace":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_constants":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"encoding":true,"full_opening_tag":true},"hashes":{"\/ScenarioStateInterface.php":2824836438,"\/Annotation\/ScenarioStateArgument.php":2979727378,"\/Context\/Initializer\/ScenarioStateInitializer.php":781987277,"\/Context\/ScenarioStateAwareTrait.php":2441401241,"\/Context\/ScenarioStateAwareContext.php":1438795547,"\/ScenarioState.php":3549039778,"\/ServiceContainer\/ScenarioStateExtension.php":1698856486,"\/Exception\/MissingStateException.php":1656618837}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be committed

Copy link
Owner Author

@gorghoa gorghoa Jul 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed

Loading