Behat+symfony2 access container parameters set in custom extension - symfony

(I'm going to explain situation, incase someone knows of a better way to accomplice what I want to do).
Using Symfony2 + Behat + Symfony2Extension + Mink +
We have an application with multiple urls that will be visited during scenarios.
I do understand that you use the parameters sent in from the FeatureContext __construct method, but what I'm trying to do is set up the urls in the behat.yml file so that we can use them in our custom Context to visit the urls.
Looking at how the extensions work I have setup the dependency injection as follows:
class Extension implements ExtensionInterface
{
/**
* Loads a specific configuration.
*
* #param array $config Extension configuration hash (from behat.yml)
* #param ContainerBuilder $container ContainerBuilder instance
*/
public function load(array $config, ContainerBuilder $container)
{
$container->setParameter('url_one', $config['url_one']);
$container->setParameter('url_two', $config['url_two']);
}
/**
* Setups configuration for current extension.
*
* #param ArrayNodeDefinition $builder
*/
public function getConfig(ArrayNodeDefinition $builder)
{
$builder->
children()->
scalarNode('one_url')->
isRequired()->
end()->
scalarNode('two_url')->
isRequired()->
end()->
end();
}
code continues....
And my behat.yml looks like this:
default:
extensions:
Behat\MinkExtension\Extension:
goutte: ~
selenium2: ~
Behat\Symfony2Extension\Extension: ~
Acme\AcmeExtension\Extension:
url_one: 'http://example1.com'
url_two: 'http:/example2.com'
Now in my FeatureContext.php class I would like to do the following:
$url = $kernel->getContainer()->getParameter('url_one');
But this is not working, it is returning parameters from my Symfony2 application, which is expected since I have symfony2extension enabled. But I can not access the parameters or services from the extension class.
(Please note that if I'm in the Extension class in the load method and I call the parameter I just set it returns it, so I know it is set, but it must be set to a different container?)
First off is this possible? And if so what should I do to make it work.
Many thanks for any help.

Obviously, $kernel->getContainer() returns you container of the Symfony2 app kernel. Those kernel and container are not shared with Behat. Behat has its own container instance, which it uses to manage own services. Which means that extension is setting parameters inside Behat container, but you are attempting to access your app kernel container. That's why you have different results.
Now, the question is, how would you pass some value from your extension to context class. Answer is context initialiser. Check out:
https://github.com/Behat/MinkExtension/blob/master/src/Behat/MinkExtension/services/core.xml#L43-L47
https://github.com/Behat/MinkExtension/blob/master/src/Behat/MinkExtension/Context/Initializer/MinkAwareInitializer.php#L26-L77

Related

Steps not defined in FeatureContext file. I have the steps defined. Not sure if the featurecontext file is being invoked

This is my behat.yml file. I tried variants inorder to link the FeatureContext file. Can someone help me by checking if that is the right way to link the feature context file in the behat.yml file. in case path for the featurecontext file is required this is the path %path.base%/features/bootstrap
default:
suites:
default:
path: %paths.base%/features
contexts:
- Behat\MinkExtension\Context\MinkContext
- FeatureContext
extensions:
Behat\MinkExtension:
base_url: http://echidnaacerdev.prod.acquia-sites.com/
sessions:
default:
goutte: ~
This is my FeatureContext.php file
<?php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context
{
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* Click some text
*
* #When /^I click on the text :text$/
*/
public function iClickOnTheText($text)
{
$session = $this->getSession();
$element = $session->getPage()->find('xpath',$session->getSelectorsHandler()->selectorToXpath('xpath', '*//*[text()="'. $text .'"]'));
if (null === $element) {
throw new InvalidArgumentException(sprintf('Cannot find text: "%s"', $text));
}
$element->click();
}
}
Im not sure how to i define the feature context file in my behat.yml. Can anyone help me out with this. the error message i get is that the
behat\minkextension\context\minkcontext has missing steps. define them with the snippets
For the particular step When I click on the text "Members"
You are not using the step correctly, you should use When I click on the text Members , remember the matching is done by regular expression and the sintax should be Gherkin or Turnip.
FeatureContext should extend MinkContext
Remove MinkContext from yml, is not needed if you extend it as I said, also you can remove that path, by default it is looking in features/bootstrap

Inject repository into phpleague/oauth-server:authServer

I'm currently trying to set up the phpleague/oauth-server in a symfony 3.3 project. For that reason i want to specify the AuthorizationServer as service to be able to load it from the container (an not set up the whole thing everywhere its used).
To set the AuthorizationServer as service I need to inject multiple repositories as arguments.
This is the service definition for the AuthorizationServer:
app.oauth2.authorization_server:
class: League\OAuth2\Server\AuthorizationServer
arguments: ["#app.oauth2.client_repository", "#app.oauth2.access_token_repository", "#app.oauth2.scope_repository", "%oauth_private_key%", "%oauth_encryption_key%"]
configurator: "app.oauth2.authorization_server.configurator:configure"
The current definition of the repositories looks like this:
app.oauth2.client_repository:
class: Appbundle\Repository\OAuth2\ClientRepository
factory: 'doctrine.orm.entity_manager:getRepository'
arguments: [AppBundle\Entity\OAuth2\Client]
...
I tried many ways of defining the repositories as services but everytime i get the same error:
Type error: Argument 1 passed to League\\OAuth2\\Server\\AuthorizationServer::__construct() must be an instance of League\\OAuth2\\Server\\Repositories\\ClientRepositoryInterface, instance of Doctrine\\ORM\\EntityRepository given
This is how the ClientRepository looks like:
<?php
namespace AppBundle\Repository\OAuth2;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
class ClientRepository implements ClientRepositoryInterface
{
/**
* Get a client.
*
* #param string $clientIdentifier The client's identifier
* #param string $grantType The grant type used
* #param null|string $clientSecret The client's secret (if sent)
* #param bool $mustValidateSecret If true the client must attempt to validate the secret if the client
* is confidential
*
* #return ClientEntityInterface
*/
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true)
{
// TODO: Implement getClientEntity() method.
}
}
Here are some other ways i tried to implement it:
https://matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/
How to configure dependency injection for repository class in symfony 3
Neither of them worked. Has any one of you an idea why the service definition of my repository is not accepted as valid input for the AuthorizationServer?
Yours,
FMK
I had tried to extend the EntityRepository but forgot to define the repository in the Entities. With the repositories extending the entityRepository and the ORM\Entity definition in the Entities it seems to work!

Service in symfony2 - how service file should look like?

I am trying to create service in symfony2 which will verify if session contains certain information and if not redirect the user to another controller. I want this piece of code to work as a service as I will be using it in many controllers.
I have problem as manual on Symfony2 book does not provide information how service file should look like. Should it be a normal php class?
Please find below dump of my files with information on error that I receive.
In \AppBundle\Services I create file my_isbookchosencheck.php containing:
<?php
namespace AppBundle\my_isbookchosencheck;
class my_isbookchosencheck
{
public function __construct();
{
$session = new Session();
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
My service.yml:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
My conntroller file:
/**
* This code is aimed at checking if the book is choseen and therefore whether any further works may be carried out
*/
$checker = $this->get('my_isbookchosencheck');
Error:
FileLoaderLoadException in FileLoader.php line 125: There is no extension able to load the configuration for "my_isbookchosencheck" (in C:/wamp/www/symfony_learn/app/config\services.yml). Looked for namespace "my_isbookchosencheck", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "fos_user", "knp_paginator", "genemu_form", "debug", "acme_demo", "web_profiler", "sensio_distribution" in C:/wamp/www/symfony_learn/app/config\services.yml (which is being imported from "C:/wamp/www/symfony_learn/app/config\config.yml").
There are few mistakes that you made, which I am going to explain in short, and I will give you an example of the service you want to create.
You created your service in AppBundle\Services, yet your namespace is registered differently - namespace AppBundle\Services\my_isbookchosencheck;. It should be namespace AppBundle\Services;. I would also advise you to use singular names when creating directories - in this case Service would be better, instead of Services.
You're using your __constructor directly to apply some logic and return the result of it. Better way would be to create a custom method, which could be accessed when necessary.
You're creating new instance of Session which means that you wont be able to access anything that was previously added and stored in session. The right way here, would be to inject RequestStack which holds the current Request and get the session from there.
I believe you also registered your service wrong. In your services.yml file, it should be under services: option. This is why you got the error you pasted.
So, let's see how your service should like.
services.yml
services:
book_service:
class: AppBundle\Service\BookService
arguments:
- #request_stack
- #router
BookService.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class BookService {
/* #var $request Request */
private $request;
/* #var $router RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router) {
$this->request = $requestStack->getCurrentRequest();
$this->router = $router;
}
public function isBookChoosen() {
$session = $this->request->getSession();
// Now you can access session the proper way.
// If anything was added in session from your controller
// you can access it here as well.
// Apply your logic here and use $this->router->generate()
}
}
Now in your controller you can simply use it like this:
$this->get('book_service')->isBookChoosen()
Well this is a short example, but I hope you got the idea.
try
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
in your services.yml, and check that you use the correct namespaces.
Your Class is fine and it should work, however may i suggest that you use
symfony2 session service instead of creating the session object yourself, you can pass it as a constructor argument:
<?php
// namespace edited
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\Session\Session;
class my_isbookchosencheck
{
public function __construct(Session $session);
{
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
and then edit your services.yml accordingly, so the service container will inject the session object:
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
arguments: [#session]
Also check out his question on so:
How do you access a users session from a service in Symfony2?
Services are just regular PHP classes, nothing special. But you must register it in order to be recognized by the system. Here are the steps how you do it,
Create a regular PHP class (you can inject other services if it requires)
namespace Acme\DemoBundle\Service;
class MyService
{
private $session;
public function _construct(SessionInterface $session /* here we're injecting the session service which implements the SessionInterface */)
{
$this->session = $session;
}
// other methods go here, which holds the business logic of this class
}
ok, we created a class, we need to register it to be able to use it by service container, here how you do it:
the simplest way is to put it into config.yml file, like this:
services:
my_service:
class: Acme\DemoBundle\Service\MyService
arguments:
- #session
or, another way, is to create a file (e.g. services.yml, may be in config folder), and import it inside the config.yml file (the content of the file is the same as the first way):
imports:
- { resource: services.yml }
or, you can create a services.yml(the content of the file is the same as the first way) file inside you bundle's Resources folder, specify it under the load method of your Extension class (under the DependencyInjection folder), (this way requires some special directory and file structure, read about it in the doc):
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources'));
$loader->load('services.yml');
}
}
In you case, you're not registering your service, the service container just couldn't find it. Register it by one of the above ways.

Testing Symfony2 emails with Behat 3

I followed the Behat 2.5 docs to test mails. After a few tweaks to match Behat 3 I have ended with the following code (I have removed non-relevant parts):
public function getSymfonyProfile()
{
$driver = $this->mink->getSession()->getDriver();
if (!$driver instanceof KernelDriver) {
// Throw exception
}
$profile = $driver->getClient()->getProfile();
if (false === $profile) {
// Throw exception
}
return $profile;
}
/**
* #Then I should get an email with subject :subject on :email
*/
public function iShouldGetAnEmail($subject, $email)
{
$profile = $this->getSymfonyProfile();
$collector = $profile->getCollector('swiftmailer');
foreach ($collector->getMessages() as $message) {
// Assert email
}
// Throw an error if something went wrong
}
When I run this test, it throws the following error:
exception 'LogicException' with message 'Missing default data in Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector' in vendor/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle/DataCollector/MessageDataCollector.php:93
Stack trace:
#0 vendor/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle/DataCollector/MessageDataCollector.php(122): Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector->getMailerData('default')
#1 features/bootstrap/FeatureContext.php(107): Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector->getMessages()
My profiler is configured as follows:
# app/config/config_test.yml
framework:
test: ~
profiler:
enabled: true
collect: true
It seems that the Profile is correctly loaded and the MessageDataCollector from Swiftmailer does exist, but it is not doing its work as expected. Any clue to solve this?
Maybe the issue you have has been fixed as I do not have this anymore (I'm using Behat v3.0.15, BrowserKit driver 1.3.* and Symfony v2.6.6).
I managed to reproduce your error but only when I forgot to enable profiler data collecting:
profiler:
collect: false
Once this problem solved (the configuration you provided solving the problem for me) I managed to check emails in my Behat tests.
Two solutions for this:
Solution #1: Intercepting redirects globally
If it does not break all your other tests you can do so by configuring your web profiler as follows:
web_profiler:
intercept_redirects: true
Solution #2: Preventing client to follow redirections temporarily
For my part, intercepting redirections globally in the configuration broke most of my other functional tests. I therefore use this method instead.
As preventing redirections allows mainly to check data in the data collectors I decided to use a tag #collect on each scenario requiring redirect interception. I then used #BeforeScenario and #AfterScenario to enable this behaviour only for those scenarios:
/**
* Follow client redirection once
*
* #Then /^(?:|I )follow the redirection$/
*/
public function followRedirect()
{
$this->getDriver()->getClient()->followRedirect();
}
/**
* Restore the automatic following of redirections
*
* #param BeforeScenarioScope $scope
*
* #BeforeScenario #collect
*/
public static function disableFollowRedirects(BeforeScenarioScope $scope)
{
$context = $scope->getEnvironment()->getContext(get_class());
$context->getDriver()->getClient()->followRedirects(false);
}
/**
* Restore the automatic following of redirections
*
* #param AfterScenarioScope $scope
*
* #AfterScenario #collect
*/
public static function restoreFollowRedirects(AfterScenarioScope $scope)
{
$context = $scope->getEnvironment()->getContext(get_class());
$context->getDriver()->getClient()->followRedirects(true);
}
It's not the answer your are looking for, but I'm pretty sure it will suits your needs (perhaps more).
If I can suggest, try using Mailcatcher with this bundle: https://packagist.org/packages/alexandresalome/mailcatcher
You'll be able to easily tests if emails are sent, what's their subject, follow a link in the body, and so on...
Many steps are included with this bundle.

Doctrine PHPCR-ODM under Symfony not detecting mapped Document class

I am attempting to integrate PHPCR-ODM with an existing Symfony project, and am having trouble getting it to (presumably) detect my mapped Document class. Specifically, I get an error like this when attempting to persist a Document of my class MyDocument:
[Doctrine\Common\Persistence\Mapping\MappingException]
The class 'Example\Common\ORM\Document\MyDocument' was not found in the chain configured namespaces Doctrine\ODM\PHPCR\Document
My class is in a potentially strange namespace because this project uses Doctrine ORM as well, and thus far I've just added a new space for mapped Documents off of that, but I can't imagine the choice of namespace name affects the functionality.
Per the docs, I have added to my app/autoload.php:
AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');
My app/config/config.yml includes the following (with parameters set in parameters.yml):
doctrine_phpcr:
session:
backend:
type: jackrabbit
url: %jackrabbit_url%
workspace: %jackrabbit_workspace%
username: %jackrabbit_user%
password: %jackrabbit_password%
odm:
auto_mapping: true
My document class lives in src/Example/Common/ORM/Document/MyDocument.php and looks like:
<?php
namespace Example\Common\ORM\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* #PHPCRODM\Document
*/
class MyDocument
{
/**
* #PHPCRODM\Id
*/
private $id;
/**
* #PHPCRODM\ParentDocument
*/
private $parent;
/**
* #PHPCRODM\Nodename
*/
private $name;
// .. etc
Finally, the code I am using to test the integration is inside a simple console command, and looks like:
use Example\Common\ORM\Document\MyDocument;
// ...
$documentManager = $this->container->get('doctrine_phpcr.odm.default_document_manager');
$document = new MyDocument();
$document->setParent($documentManager->find(null, '/'));
$document->setName('ExampleName');
$documentManager->persist($document);
$documentManager->flush();
I have verified that my MyDocument class is being correctly loaded, but it seems that the annotations are not being processed in a way that is making the DocumentManager aware that it is a mapped Document class.
My guess is that I have overlooked some simple configuration step, but from looking repeatedly and thoroughly at the docs for PHPCR, PHPCR-ODM, and even Symfony CMF, I can't seem to find anything. Most of the examples out there involve using PHPCR via Symfony CMF, and I wasn't able to find many (any?) real world examples of PHPCR-ODM being integrated in a regular Symfony project.
edit: The Eventual Solution
I followed the advice that #WouterJ gave below and it fixed my problem, and I further followed his suggestion of adding a compiler pass to my Symfony bundle to make this work with a non-standard namespace (i.e., something other than YourBundle\Document). In my case, this is going into a library that will be re-used elsewhere rather than a bundle, so it was appropriate.
To do this, I added a method to the src/Example/Bundle/ExampleBundle/ExampleBundle.php file like so:
<?php
namespace Example\Bundle\ExampleBundle;
use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ExampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$mappedDirectories = array(
realpath(__DIR__ . '/../../Common/ODM/Document')
);
$mappedNamespaces = array(
'Example\Common\ODM\Document'
);
$phpcrCompilerClass = 'Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass';
if (class_exists($phpcrCompilerClass)) {
$container->addCompilerPass(
DoctrinePhpcrMappingsPass::createAnnotationMappingDriver(
$mappedNamespaces,
$mappedDirectories
));
}
}
}
That code allows any mapped document classes to be placed in the Example\Common\ODM\Document namespace and it will pick them up. This example uses annotations but the same pattern can be used for XML or YAML mappings (see the Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass class for method signatures).
I found that I also needed to define the doctrine_phpcr.odm.metadata.annotation_reader service for this to work, which I did in app/config.yml:
services:
doctrine_phpcr.odm.metadata.annotation_reader:
class: Doctrine\Common\Annotations\AnnotationReader
There may be a better way to do that, but that was enough to make it work for me.
The document should be placed in the Document namespace of the bundle, not the ORM\Document namespace.
If you really want to put it in the ORM\Document namespace (which is very strange, because we are talking about an ODM not an ORM), you can use the doctrine mapping compiler pass: http://symfony.com/doc/current/cookbook/doctrine/mapping_model_classes.html

Resources