Parameters in behat.yml - symfony

I want to make
behat.yml -
default:
extensions:
Behat\MinkExtension\Extension:
base_url: 'my-url'
a parameter pulled from parameters.yml... Is this possible? I made a mink_base_url parameter in parameters.yml and then added
imports:
- { resource: parameters.yml }
to behat.yml. No matter what I do, I get this
[Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException]
The service "behat.mink.context.initializer" has a dependency on a non-existent parameter "mink_base_url"

Behat configuration is in no way related to Symfony's. It's true that Behat uses Symfony's DI container, but it's a separate instance.
If wanted to implement it, you'd probably need to create your own Behat extension to support the imports section.

This worked for me with Symfony 3. Just omit base_url from behat.yml, and set it from the container parameters. Thanks to #DanielM for providing the hint.
<?php
use Behat\MinkExtension\Context\MinkContext;
use Symfony\Component\DependencyInjection\ContainerInterface;
class FeatureContext extends MinkContext {
/**
* FeatureContext constructor.
* #param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* #BeforeScenario
*/
public function setUpTestEnvironment()
{
$this->setMinkParameter('base_url', $this->container->getParameter('my_url'));
}
}

It is possible to access the symfony parameters within behat yaml as using
- '%%name_of_the_parameter%%'
Double percentage sign (%%) does the trick.

If you just want to access base_url, you can get it once mink has been started.
$this->getMinkParameter('base_url');
Here's an example :
class AbstractBehatContext extends MinkContext {
/**
* The base url as set behat.yml
* #var bool
*/
protected $baseUrl;
/**
* #BeforeScenario
*/
public function getBaseUrl() {
$this->baseUrl = $this->getMinkParameter('base_url');
}
}
Note, this needs to be able to access Mink, so it won't work in __construct or in #BeforeSuite. Additionally #BeforeScenario will be called at the start of every scenario which is going to set it pointlessly a lot.

Related

Symfony upgrade give me error from 4.1 to 4.4

I just made the migration from symfony 4.1 to 4.4
I have this error:
Argument 1 passed to App\EventListener\KernelRequestListener::__construct() must be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage, instance of Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage given, called in C:\xampp\htdocs\chat-project-symfony\var\cache\dev\Container06Mjwya\srcApp_KernelDevDebugContainer.php on line 1130
While if you look at my KernelRequestListener :
<?php
namespace App\EventListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
//..
class KernelRequestListener
{
private $tokenStorage;
/**
* KernelRequestListener constructor.
* #param TokenStorage $tokenStorage
* ...
*/
public function __construct(TokenStorage $tokenStorage/*...*/)
{
$this->tokenStorage = $tokenStorage;
//..
}
}
Here is my config/services.yaml file:
#...
services:
#..
App\EventListener\KernelRequestListener:
arguments: [ '#security.token_storage' ]
tags:
- { name: kernel.event_listener, event: kernel.request }
- { name: kernel.event_listener, event: kernel.response }
I don't know why symfony tell me that I'm using Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage while it's clearing written Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage
I already tried to clear the cache folder and also delete the cache folder and it didn't change.
How can I fix this ?
Thank you
I don't know why symfony tell me that I'm using Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage while it's clearing written Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage
It's not symfony but PHP's type checking feature. You are stating that your Listener wants a TokenStorage but symfony is passing to it different class, thus the error.
So, as #JaredFarrish pointed, you should be using TokenStorageInterface in your constructor, like this:
namespace App\EventListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
//..
class KernelRequestListener
{
private $tokenStorage;
/**
* KernelRequestListener constructor.
* #param TokenStorageInterface $tokenStorage
* ...
*/
public function __construct(TokenStorageInterface $tokenStorage/*...*/)
{
$this->tokenStorage = $tokenStorage;
//..
}
}
It's a common practice to use interfaces where they exists, because this way you will loose coupling with other classes and provide a way to unit test your classes.
Take a look: https://github.com/symfony/security-bundle/blob/master/Resources/config/security.xml#L22 they switched class for #security.token_storage service, because of deprecation. But when you use an interface you don't care of anything underlying, you just know that you will have your methods because of interface contract.
I fixed it changing this line:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
With this one:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface as TokenStorage;

Symfony Customrepository inject translator

I am not sure if this is even best practice or possible at all.
So I have a situation where I use DataTables and I need to change a boolean value to text in order to display true/false instead of numbers. But I also need to do that in different languages.
Since I need this in several places in the app i was thinking that I should make an app specific Repository class that extends EntityRepository and use it as extended class for the repositories I am building. For this i want to inject translator object in in order to translate some keys, but translation is never set:
CustomRepository class
class CustomRepository extends EntityRepository
{
/**
* #var Translator
*/
protected $translator;
/**
* #param Translator $translator
*/
public function setTranslator(Translator $translator)
{
$this->translator = $translator; //*******this one is not set...
}
/**
* Replace bool results into string values
*
* #param $aRes
* #param $sField
*
* #return mixed
*/
protected function _replaceBoolToStringResult(&$aRes, $sField)
{
if (1 == $aRes[$sField]) {
$aRes[$sField] = str_replace('1', $this->translator->trans('site.true'), $aRes[$sField]);
} else {
$aRes[$sField] = str_replace('0', $this->translator->trans('site.false'), $aRes[$sField]);
}
return $aRes;
}
}
services.yml
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
#should i call here all the constructor vars from EntityRepository class as arguments?
calls:
- [setTranslator, ["#translator.default"]]
Repository with custom DQL
class SettingsRepository extends CustomRepository
{
public function findOverviewSettingsAsJson()
{
$aResult = $this->createQueryBuilder('s')
->select('s.identifier, s.type, s.isActive')
->getQuery()
->getScalarResult();
// ******** HERE I WANT TO USE _replaceBoolToStringResult
return json_encode($aResult);
}
}
I found this article by Matthias to be useful on this issue. (I know link only answers are frowned on...)
You must use the factory pattern when you use a repository as a service.
See possible duplicates :
Symfony 2: Creating a service from a Repository
How to inject a repository into a service in Symfony2?
Note : the syntax changed in latest SF version : http://symfony.com/doc/current/components/dependency_injection/factories.html
Edit :
You should use your repository as a service :
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- App\CommonBundle\Entity\CustomEntity
calls:
- [setTranslator, ["#translator.default"]]
Then call this service as any other service in your code. For example from inside a controller :
$this->get('app.custom.repository')->...

Symfony 3 dependency injection into behat testing class - Type error: Argument 1 passed to __construct() must be an instance

I have such configuration file:
src/AppBundle/services.yml
#imports:
# - { resource: '../../app/config/config.yml' }
parameters:
#laikinas, tikras yra config.yml
app_url: http://app.guru
services:
UserManagement:
class: Tests\AppBundle\SharedCode\UserManagement\UserManagement
arguments: [%app_url%]
UserRegistrationContext:
class: Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext
arguments: ['#UserManagement']
tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php
<?php
namespace Tests\AppBundle\features\user_registration\bootstrap;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Tests\AppBundle\SharedCode\UserManagement\UserManagement;
use AppBundle\Controller\UserController;
use Tests\AppBundle\features\BaseContext;
/**
* Defines application features from the specific context.
*
* To run:
* sudo vendor/behat/behat/bin/behat
* tests/AppBundle/features/user_registration/user_registration.feature
* --stop-on-failure
*/
class UserRegistrationContext extends BaseContext implements Context, SnippetAcceptingContext
{
private $userManagement;
/**
* UserRegistrationContext constructor.
*/
public function __construct(UserManagement $userManagement)
{
//$this->userManagement = new UserManagement();
$this->userManagement = $userManagement;
parent::__construct();
}
}
I run behat tests and get an error:
vagrant#php7dev:/shared$ sudo vendor/behat/behat/bin/behat tests/AppBundle/features/user_registration/user_registration.feature
Fatal error: Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 1 passed to Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext::__construct() must be an instance of Tests\AppBundle\SharedCode\UserManagement\UserManagement, none given in /shared/tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php:30
Stack trace:
#0 [internal function]: Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext->__construct()
#1 /shared/vendor/behat/behat/src/Behat/Behat/Context/ContextFactory.php(123): ReflectionClass->newInstance()
#2 /shared/vendor/behat/behat/src/Behat/Behat/Context/ContextFactory.php(80): Behat\Behat\Context\ContextFactory->createInstance(Object(ReflectionClass), Array)
#3 /shared/vendor/behat/behat/src/Behat/Behat/Context/Environment/Handler/ContextEnvironmentHandler.php(104): Behat\Behat\Context\ContextFactory->createContext('Tests\\AppBundle...', Array)
#4 /shared/vendor/behat/behat/src/Behat/Testwork/Environme in /shared/tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php on line 30
We can see that in services.yml I have given the parameter. What is wrong?
For services.yml to be read, as I understand I need exctension class, here it is:
src/AppBundle/DependencyInjection/AppExtension.php
<?php
namespace AppBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
class AppExtension extends Extension
{
/**
* #param array $configs configs
* #param ContainerBuilder $container container
* #return null
*/
public function load(array $configs, ContainerBuilder $container)
{
// ... you'll load the files here later
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../')
);
$loader->load('services.yml');
}
}
Thank you so much Matteo, so awesome, from his comment alone was able to fix the problem.
It turns out that I had to use Symfony2 Extension and configure dependencies in behat.yml instead of my config. Here is how behat.yml looks now:
default:
autoload:
'': %paths.base%/tests/AppBundle/features/user_registration/bootstrap
formatters:
progress: ~
suites:
app_features:
paths: [ %paths.base%//tests/AppBundle/features ]
contexts:
- Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext:
userManagement: '#UserManagement'
- Tests\AppBundle\features\user_login\bootstrap\UserLoginContext:
userManagement: '#UserManagement'
- Tests\AppBundle\features\password_reset\bootstrap\PasswordResetContext:
userManagement: '#UserManagement'
extensions:
Behat\Symfony2Extension: ~
And I even commented out UserManagement from src/AppBundle/services.yml and it finds it somehow, I do not understand how actually.
And here is something written about this, I googled again for symfony3 behat dependency injection after I solved the problem:
http://docs.behat.org/en/v3.0/cookbooks/1.symfony2_integration.html
I remember I saw this page before, but this was not in my head when solving this problem. Maybe because in the example there was Session being injected which is symfony component, while UserManagement class was my created component.
Update:
Done from scratch and will give minimal versions of files how they look:
behat.yml has to be in the root of the project. http://docs.behat.org/en/v3.0/cookbooks/1.symfony2_integration.html
default:
suites:
default:
contexts:
- FeatureContext:
userRepository: "#user_repository"
extensions:
Behat\Symfony2Extension: ~
features/bootstrap/FeatureContext.php
use AppBundle\Repository\UserRepository;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context, SnippetAcceptingContext
{
private $userRepository;
/**
* 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(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
//other methods
}
Pay attention that there has to be matches of keys - if there is such key:
userRepository: "#user_repository"
then in constructor the variable has to be named
$userRepository

Sonata Media Bundle - how to write custom provider?

There is an example in the official documentation about how to write custom provider, but it doesn't work.
My question is: what is the best way to write custom provider, especially how to write and register provider as a new service?
When I try to use this code from documentation, I get errors about type of arguments.
What does mean empty argument?
Thank you.
After some investigation, the following code works:
Register provider as a service:
// src/Application/Sonata/MediaBundle/Resources/config/services.yml
parameters:
application_sonata_media.custom_class: Application\Sonata\MediaBundle\Provider\CustomProvider
services:
sonata.media.provider.custom:
class: %application_sonata_media.custom_class%
tags:
- { name: sonata.media.provider }
arguments:
- sonata.media.provider.custom
- #sonata.media.filesystem.local
- #sonata.media.cdn.server
- #sonata.media.generator.default
- #sonata.media.thumbnail.format
Custom Provider code:
// src/Application/Sonata/MediaBundle/Provider/CustomProvider.php
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\FileProvider;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\HttpFoundation\File\File;
/**
* Class CustomProvider
* #package Application\Sonata\MediaBundle\Provider
*/
class CustomProvider extends FileProvider
{
/**
* #param MediaInterface $media
*/
protected function doTransform(MediaInterface $media)
{
// ...
}
/**
* {#inheritdoc}
*/
public function generatePublicUrl(MediaInterface $media, $format)
{
// new logic
}
/**
* {#inheritdoc}
*/
public function postPersist(MediaInterface $media)
{
}
/**
* {#inheritdoc}
*/
public function postUpdate(MediaInterface $media)
{
}
}
Updated sonata configuration:
// app/config/sonata/sonata_media.yml
sonata_media:
...
product:
providers:
- sonata.media.provider.image
- sonata.media.provider.custom
formats:
small: { width: 40 , quality: 100}
...
And I've also setup DI extension to autoload services.yml
I made a PR to update outdated documentation.
I couldn’t get this to work until i named the service exactly as the one i was overriding (sonata.media.provider.image)
See https://stackoverflow.com/a/20118256/4239642

Lookup route in symfony 2

I've defined a route in my app routing file:
RouteName:
pattern: /some/route
defaults: { _controller: MyAppBundle:Controller:action }
In a controller I can use:
$this->get('router')->generate('RouteName');
How would I simply access that from a fresh class I create, for example a view class that doesn't extend anything:
namespace My\AppBundle\View;
class ViewClass {
public function uri()
{
return getTheRoute('RouteName');
}
}
You need to inject "router" service into your ViewClass. Eg. in place where your define your ViewClass service:
viewclass.service:
class: Namespace\For\ViewClass
arguments:
router: "#router"
and then in your constructor:
public function __construct(\Symfony\Bundle\FrameworkBundle\Routing\Router $router)
{
$this->router = $router;
}
The clue is in how the $this->generateUrl() method works in Controllers. See:
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param Boolean $absolute Whether to generate an absolute URL
*
* #return string The generated URL
*/
public function generateUrl($route, $parameters = array(), $absolute = false)
{
return $this->container->get('router')->generate($route, $parameters, $absolute);
}
So you'll need to define your class as a service and inject the #router service. Either that or have your class implement ContainerAwareInterface, but the first method would definitely be better.
You should register your class as a service and insert the router as a dependency.
See the chapter on the service container in the excellent symfony2 docs.
If you're not familiar with the concepts of the service container and dependency injection, you might feel a bit overwhelmed. However, try your best to understand it because it is a essential part of the symfony2 architecture.
You could pass the entire container from your controller to your view class on instantiation. This is NOT BEST PRACTICE and not recommended.
class View
{
protected $container;
public function __construct(\Symfony\Component\DependencyInjection\Container $container)
{
$this->container = $container;
}
}
Then in your code you could use
$this->container->get('router')->generate($route, $parameters, $absolute);

Resources