Passing dynamic variables to a service constructor - symfony

I have a service in Symfony2 that looks like:
services:
MyCustomService:
class: MyClass
arguments: //Arguments aren't static, but dynamic based on application logic.
Is it possible to pass dynamic variables to a service's constructor?
There doesn't seem to be any extra parameters within a controller's $this->get('MyCustomService');
Is there something I'm missing?

To me, it sounds like, you do not understand what a word "service" really means. What you are trying to achieve, would not be a service anymore.
You can still define a setter method inside your "MyClass" for any custom arguments, while defining some default ones, which you basically override when you use setter method.
You would use something like this:
$this->get('MyCustomService')->setSomething($something);

If, for whatever reason, you are unable to configure the service after instantiation (i.e., with a configurator). What about delegating that responsibility to a factory? It will let you instantiate services with "dynamic arguments".
services:
MyCustomServiceFactory:
class: MyClassFactory
arguments: [ #dynamicService, %time_prefix% ]
MyCustomService:
class: MyClass
factory_service: MyCustomServiceFactory
factory_method: get
Your factory would like something like this:
class MyClassFactory
{
private $dynamicService;
private $timePrefix;
public function __construct(MyDynamicService $dynamicService, $timePrefix)
{
$this->dynamicService = $dynamicService;
$this->timePrefix = $timePrefix;
}
public function get()
{
// Dynamic arguments based on application logic.
$dynamicArg1 = $this->dynamicService->getArg()
$dynamicArg2 = $this->timePrefix . time();
return new MyClass($dynamicArg1, $dynamicArg2);
}
}

Related

Unpack Symfony "tagged Services"

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;
}
}

Best place to implement a method that return response in Symfony2/MVC

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 */);
}
}

Jackson custom deserializer module to abstract class

I have a big set of classes (like more that 100) and they are all extend from some abstract class, let's call it ParentClass. Let's call child classes ChildA,ChildB, etc. How can I register custom deserializer for all children and get class type inside my Deserializer?
I tried:
module.addDeserializer(ParentClass.class, new MyObjectDeserializer());
but it does not work.
I want to skip doing (what is working):
module.addDeserializer(ChildA.class, new MyObjectDeserializer(ChildA.class));
module.addDeserializer(ChildB.class, new MyObjectDeserializer(ChildB.class));
module.addDeserializer(ChildC.class, new MyObjectDeserializer(ChildC.class));
//etc......
Class type should be known, as I am use Jackson for spring #RequestBody method, what have defined class name there.
Any ideas how this can be done?
As far as I know, I don't think there is a mechanism in jackson that will address your exact needs.
However, there are a couple alternatives you can try.
Deserializing polymorphic types with Jackson describes one such alternative, however, you would still need to explicitly define all of the supported subtypes.
Another alternative that would not require you to explicitly define deserialization relationships would be to change your class hierarchy from one of inheritance to that of a container.
For example, converting your abstract parent class to a container like so:
public class DataContainer<T> {
String commonString;
Integer commonInteger;
T subData;
}
Would allow you to simply define in your controller input function as
public String controllerFunction(DataContainer<ClassA> classA);
without a need to define all these subclass deserializations.
Late to the party but I had a similar problem which I solved by registering a custom Deserializers to my SimpleModule. The code is in Kotlin but it should be easy to port it to Java.
The class itself:
class UseBaseClassSimpleDeserializers(
private val baseClass: Class<*>,
private val baseClassDeserializer: JsonDeserializer<*>
) : SimpleDeserializers() {
#Throws(JsonMappingException::class)
override fun findBeanDeserializer(
type: JavaType?,
config: DeserializationConfig?,
beanDesc: BeanDescription?
): JsonDeserializer<*>? {
val beanDeserializer = super.findBeanDeserializer(type, config, beanDesc)
return if (beanDeserializer == null && baseClass.isAssignableFrom(type!!.rawClass)) {
baseClassDeserializer
} else {
beanDeserializer
}
}
}
How to register the custom Deserializers class to a SimpleModule:
val simpleModule = SimpleModule()
simpleModule.setDeserializers(UseBaseClassSimpleDeserializers(ParentClass::class.java, ParentClassDeserializer()))

Symfony2 + inject static class

i'm new here and i hope my question is not too trivial.
I have a package with a static class in it (a Grid Builder) and want to use it in symfony2
So i know about the Class loading and the Service Container but i don't get the Container to work.
The Grid Class is depending on 2 other static classes (one for configuration and one for the SQL Query´s)
The Code to use the class is as following:
$Grid = Grid::get_instance();
$Grid->table('products');
echo $Grid->renderGrid();
And internally the class uses calls like GridConfig::database() - so i Thought maybe i cann simply add all three classes to the Service.yml but that doesn't do anything.
So my question is: How can I inject the Static class in a way that I can use it in the Controller?
Is it Possible and if yes what would be the best Practice case to do it?
Thank you for any help.
Since it is static then there really is no need to inject it. Something like:
$grid = \Grid::get_instance;
Should work. If Grid uses namespaces then you need to add that as well. And you will need to ensure the autoloader can find it.
Of course using globals is kind of frowned up. What you can do is to write your own service to act as a wrapper.
class MyGridService
{
protected $grid;
public function getInstance()
{
if (!$this->grid) $this->grid = \Grid::get_instance();
return $this->grid;
}
}
Add MyGridService to your services.yml file then from the controller you can do:
$grid = $this->get('my_grid_service')->getInstance();
You should define a service that uses a factory method to instantiate the object:
service_name:
class: The\Class\Name\Of\The\Created\Object
factory: [ "Grid", "get_instance" ]
Now you can inject the object into your depending class by injecting the service.
See http://symfony.com/doc/current/components/dependency_injection/factories.html

asp.net mvc3, why do I need to constructors for my controller class

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.

Resources