I got a class which accepts multiple Consumer implementations as constructor arguments.
I want to "fill in" all my Consumers via the Symfony DI-Container.
I tried injection tagged services.
final class SynchronousMessageDispatcher implements MessageDispatcher
{
/**
* #var Consumer[]
*/
private $consumers;
public function __construct(Consumer ...$consumers)
{
$this->consumers = $consumers;
}
}
So I tried to Tag the services in the services.yml like that:
services:
_instanceof:
EventSauce\EventSourcing\Consumer:
tags: ['eventsauce.consumer']
And then inject it like this:
eventsauce.message_dispatcher:
class: EventSauce\EventSourcing\SynchronousMessageDispatcher
arguments: [!tagged eventsauce.consumer]
Now I'm getting the following error:
Argument 1 passed to EventSauce\EventSourcing\SynchronousMessageDispatcher::__construct() must implement interface EventSauce\EventSourcing\Consumer, instance of Symfony\Component\DependencyInjection\Argument\RewindableGenerator given
I fully understand why. Is there a way to unpack services
In other words: Is it possible to modify [!tagged eventsauce.consumer] somehow. Or is the ...$consumers syntax incompatible with the Tagged service Injection in Symfony.
Don't get me wrong. I know that I can easily implement MessageDispatcher myself. Just wanted to know ;-)
My original solution:
As "Tomáš Votruba" mentioned you'd have to rewrite your own !tagged functionality. e.g. !tagged-variadic.
This is not worth the effort for me. I'd rather implement the class using an iteratable ("nifr" explained the benefits, thanks).
For further reading, there is a closed issue on symfony/symfony#23608
My new solution
I used Argument unpacking and the Delegation pattern to use the class the library provided with my tagged services.
Work :-) Hurray.
final class TaggedMessageDispatcher implements MessageDispatcher {
public function __construct(iterable $consumers)
{
$this->dispatcher = new SynchronousMessageDispatcher(... $consumers);
}
public function dispatch(Message ...$messages): void
{
$this->dispatcher->dispatch(... $messages);
}
}
You're using a wrong typehint here.
With the [!tagged <tag>] syntax a single iterable will be injected - not an undefined number of arguments as expected by the splat operator.
You're actually typehinting for multiple Consumer objects as arguments with the splat (...$arguments) operator here.
So the answer to your question is:
The splat operator is not compatible with the [!tagged ..] syntax.
You'd indeed need to write your own injection type that splits up the tagged services when using a new notation like [!tagged-call_user_func ..].
That said it doesn't really make sense to collect a list of objects, extract them to be function arguments just to let PHP put them back into a list again. I get your idea behind it in terms of code cleanliness though.
Another limitation is the fact that you can't pass multiple variadic arguments to a function. So ...
public function __construct(Alpha ...$alphas, Beta ...$betas)
... is not possible.
A possible solution/workaround allowing you to keep the typehinting for the collection would be the following:
final class SynchronousMessageDispatcher implements MessageDispatcher
{
/**
* #var Consumer[]
*/
private $consumers;
public function __construct(iterable $consumers)
{
foreach($consumers as $consumer) {
assert($consumer instanceof Consumer, \InvalidArgumentException('..'));
}
$this->consumers = $consumers;
}
}
Background: symfony3
I have just stuck in the fact that redirectToRoute and addFlash methods in controller are protected in symfony. I have a separate class for action.
namespace AppBundle\Action;
class Base {
public function __construct($controller) {
$this->controller = $controller;
}
}
As you can see base action class requires a controller. Basically it is logical because action class is part of a controller and should have access to all its methods. However I cannot call $this->controller->addFlash as it is protected. If it is protected then there might be some reason for it. I cannot find it. Can you please hint me how I can change my action class so that it could use controller methods.
The variant about extending action from a controller does not fit me as I have additional functionality in the main controller. It is configured in a proper way.
Update: my goal is to devide controller functionality by responsibility. I invented an action class. My end code look like following:
public function editAction() {
$instance = new \AppBundle\Action\MyController\Edit($this);
return $insance->run();
}
In this case I keep controller clean and not verbose.
Here is a link to Symfony Controller Trait, that you can duplicate, if you really want to work that way.
But since you are injecting a whole symfony controller into your own controllers, you will be better off with extending instead. Injection is used here for injecting separate service by their IDs.
I got a method able to create a CSV file thanks to StreamedResponse object of Symfony2 framework. I use the method several times so I put a callback parameter to personalise the behavior (I forget the buzz word for this practice in Object-Oriented Programming).
Where is the best place to put this method in a MVC project?
Repository? (Model/DAO/Manager)
Entity? (POPO)
Controller
Service
Through a interface (This object able to create CSV file)
Other
As your logic returns a response, the most adapted context is a controller.
Also, if your logic is called from multiple contexts or by multiple classes of the same context (e.g. controllers), to avoid duplicated code, you have two possibilities (at least) :
1 - Use an AbstractController and make your controllers extends the abstract.
2- Use a service (i.e. CsvManager).
If you want some example implementations, see Symfony2 reusable functions in controllers and the Controller as a service chapter of the Symfony documentation.
An example of service implementation:
// src/AppBundle/Services/CsvManager.php
class CsvManager
{
public function generate(/** params */)
{
// Return your streamed response
}
}
The service declaration :
// app/config/services.yml
services:
# ...
app.csv_manager:
class: AppBundle\Services\CsvManager
Now, you can use the service from all your controllers and other contexts that implements the services container. example:
// src/AppBundle/Controller/TestController.php;
class TestController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function printCsvAction()
{
$csvManager = $this->get('app.csv_manager');
return $csvManager->generate(/** params */);
}
}
I am learning asp.net mvc3. one example I found online is to show me how to use IOC.
public class HomeController : Controller
{
private IHelloService _service;
public HomeController():this(new HelloService())
{}
public HomeController(IHelloService service)
{
_service = service;
}
}
there are two constructors in this example. I understand the second one. the first one I understand what that for, but to me, it seems like extra code, you will never need it.
can someone please explain to me whats the point to add the first constructor.
public HomeController():this(new HelloService())
{}
When the MVC Framework instantiates a controller, it uses the default (parameter-less) constructor.
By default, you are injecting a concrete IHelloService implementation. This will be used when a user navigates to the action.
Unit Tests would use the overload and pass in the mock IHelloService implementation rather than calling the default constructor.
It can be useful if you don't use a dependency injection framework that injects this for you. In this way you never have to manually inject the object, the object will handle that by itself.
The second constructor is, of course, useful to inject custom objects when unit testing.
Normally one would need to do this:
IFoo foo = new Foo();
IBar bar = new Bar(foo);
When your constructor creates a default object you can just do this:
IBar bar = new Bar();
Bar will then create a Foo and inject it into itself.
Does anyone have a good way to unit test an entity's validation constraints in Symfony2?
Ideally I want to have access to the Dependency Injection Container within the unit test which would then give me access to the validator service. Once I have the validator service I can run it manually:
$errors = $validator->validate($entity);
I could extend WebTestCase and then create a client to get to the container as per the docs however it doesn't feel right. The WebTestCase and client read in the docs as more of a facility to test actions as a whole and therefore it feels broken to use it to unit test an entity.
So, does anyone know how to either a) get the container or b) create the validator inside a unit test?
Ok since this got two votes I guess other people are interested.
I decided to get my shovel out and was pleasantly surprised (so far anyway) that this wasn't at all difficult to pull off.
I remembered that each Symfony2 component can be used in a stand alone mode and therefore that I could create the validator myself.
Looking at the docs at: https://github.com/symfony/Validator/blob/master/ValidatorFactory.php
I realised that since there was a ValidatorFactory it was trivial to create a validator (especially for validation done by annotations which I am, although if you look at the docblock on the page I linked above you'll also find ways to validate xml and yml).
First:
# Symfony >=2.1
use Symfony\Component\Validator\Validation;
# Symfony <2.1
use Symfony\Component\Validator\ValidatorFactory;
and then:
# Symfony >=2.1
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
# Symfony <2.1
$validator = ValidatorFactory::buildDefault()->getValidator();
$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));
I hope this helps anyone else whose conscience wouldn't allow them to just use WebTestCase ;).
We end up rolling your own base test case to access the dependency container from within a test case. Here the class in question:
<?php
namespace Application\AcmeBundle\Tests;
// This assumes that this class file is located at:
// src/Application/AcmeBundle/Tests/ContainerAwareUnitTestCase.php
// with Symfony 2.0 Standard Edition layout. You may need to change it
// to fit your own file system mapping.
require_once __DIR__.'/../../../../app/AppKernel.php';
class ContainerAwareUnitTestCase extends \PHPUnit_Framework_TestCase
{
protected static $kernel;
protected static $container;
public static function setUpBeforeClass()
{
self::$kernel = new \AppKernel('dev', true);
self::$kernel->boot();
self::$container = self::$kernel->getContainer();
}
public function get($serviceId)
{
return self::$kernel->getContainer()->get($serviceId);
}
}
With this base class, you can now do this in your test methods to access the validator service:
$validator = $this->get('validator');
We decided to go with a static function instead of the class constructor but you could easily change the behavior to instantiate the kernel into the constructor directly instead of relying on the static method setUpBeforeClass provided by PHPUnit.
Also, keep in mind that each single test method in you test case won't be isolated fro, each others because the container is shared for the whole test case. Making modification to the container may have impact on you other test method but this should not be the case if you access only the validator service. However, this way, the test cases will run faster because you will not need to instantiate and boot a new kernel for each test methods.
For the sake of reference, we find inspiration for this class from this blog post. It is written in French but I prefer to give credit to whom it belongs :)
Regards,
Matt
I liked Kasheens answer, but it doesn't work for Symfony 2.3 anymore.
There are little changes:
use Symfony\Component\Validator\Validation;
and
$validator = Validation::createValidatorBuilder()->getValidator();
If you want to validate Annotations for instance, use enableAnnotationMapping() like below:
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
the rest stays the same:
$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));
With Symfony 2.8, it seems that you can now use the AbstractConstraintValidatorTest class this way :
<?php
namespace AppBundle\Tests\Constraints;
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use AppBundle\Constraints\MyConstraint;
use AppBundle\Constraints\MyConstraintValidator;
use AppBundle\Entity\MyEntity;
use Symfony\Component\Validator\Validation;
class MyConstraintValidatorTest extends AbstractConstraintValidatorTest
{
protected function getApiVersion()
{
return Validation::API_VERSION_2_5;
}
protected function createValidator()
{
return new MyConstraintValidator();
}
public function testIsValid()
{
$this->validator->validate(null, new MyEntity());
$this->assertNoViolation();
}
public function testNotValid()
{
$this->assertViolationRaised(new MyEntity(), MyConstraint::SOME_ERROR_NAME);
}
}
You have got a good sample with the IpValidatorTest class
The answer in https://stackoverflow.com/a/41884661/4560833 has to be changed a little for Symfony 4:
Use ConstraintValidatorTestCase instead of AbstractConstraintValidatorTest.
Answer (b): Create the Validator inside the Unit Test (Symfony 2.0)
If you built a Constraint and a ConstraintValidator you don't need any DI container at all.
Say for example you want to test the Type constraint from Symfony and it's TypeValidator. You can simply do the following:
use Symfony\Component\Validator\Constraints\TypeValidator;
use Symfony\Component\Validator\Constraints\Type;
class TypeValidatorTest extends \PHPUnit_Framework_TestCase
{
function testIsValid()
{
// The Validator class.
$v = new TypeValidator();
// Call the isValid() method directly and pass a
// configured Type Constraint object (options
// are passed in an associative array).
$this->assertTrue($v->isValid(5, new Type(array('type' => 'integer'))));
$this->assertFalse($v->isValid(5, new Type(array('type' => 'string'))));
}
}
With this you can check every validator you like with any constraint configuration. You neither need the ValidatorFactory nor the Symfony kernel.
Update: As #psylosss pointed out, this doesn't work in Symfony 2.5. Nor does it work in Symfony >= 2.1. The interface from ConstraintValidator got changed: isValid was renamed to validate and doesn't return a boolean anymore. Now you need an ExecutionContextInterface to initialize a ConstraintValidator which itself needs at least a GlobalExecutionContextInterface and a TranslatorInterface... So basically it's not possible anymore without way too much work.
I don't see a problem with the WebTestCase. If you don't want a client, don't create one ;) But using a possibly different service than your actual application will use, that's a potential pit fall. So personally, I've done like this:
class ProductServiceTest extends Symfony\Bundle\FrameworkBundle\Test\WebTestCase
{
/**
* Setup the kernel.
*
* #return null
*/
public function setUp()
{
$kernel = self::getKernelClass();
self::$kernel = new $kernel('dev', true);
self::$kernel->boot();
}
public function testFoo(){
$em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
$v = self::$kernel->getContainer()->get('validator');
// ...
}
}
It's less DRY than Matt answer -- as you'll repeat the code (for each test class) and boot the kernel often (for each test method), but it's self-contained and require no extra dependencies, so it depends on your needs. Plus I got rid of the static require.
Also, you're sure to have the same services that your application is using -- not default or mock, as you boot the kernel in the environnement that you wish to test.
If people still read this one in 2023, prefer to inject the ValidatorInterface for Symfony > 3 / 4.
use Symfony\Component\Validator\Validator\ValidatorInterface;
// ...
$this->validator->validate($myEntity);