I'm trying to use Guice to instantiate my Objectify DAOs. My hierarchy is the following:
public class EmpresaDao extends ObjectifyDao<Empresa> { ... }
public class ObjectifyDao<T> extends DAOBase { ... }
When I use "new EmpresaDao()", getClass().getGenericSuperclass() gives me:
[INFO] superclass -> br.com.xxxxx.server.service.ObjectifyDao<br.com.xxxxx.domain.Empresa>
When I use "injector.getInstance(EmpresaDao.class)", getClass().getGenericSuperclass() gives me:
[INFO] superclass -> class br.com.xxxx.server.service.EmpresaDao
Obviously, I want to let Guice instantiate my objects with DI.
Can someone explain why this is happen?
Is there any way (instantiating with Guice) to get the same superclass as with "new ()"?.
Thanks.
Thanks to Stuart McCulloch, who helped me here
It's possible to disable AOP (using Guice whitout AOP) which gives me what I wanted (br.com.xxxxx.server.service.ObjectifyDao) (not tested)
But I want to have AOP on my toolkit, so I've solved by getting the TypeArguments from the proxy classes generated by Guice:
clazz = (Class<T>) ((ParameterizedType) TypeLiteral.get(getClass()).getSupertype(ObjectifyDao.class).getType()).getActualTypeArguments()[0];
This is happening because Guice proxies EmpresaDao, by dynamically creating bytecode and inheriting from EmpresaDao.
Related
I am new to Symfony (5.3) and would like to extend the RequestBodyParamConverter (FOSRestBundle 3.0.5) to create a REST api. Using #ParamConverter annotation with the RequestBodyParamConverter works fine. However, I would like to create a custom converter, which does the exact same job as RequestBodyParamConverter plus a little extra work.
My first guess was to simply extend RequestBodyParamConverter and provide my custom subclass in the #ParamConverter annotation. However, RequestBodyParamConverter is defined as final and thus cannot be extended...
Injecting RequestBodyParamConverter / fos_rest.request_body_converter into a custom converter class (see example below) also fails because the service cannot be found. I assume this is because it is defined a private?
So, my last idea was to create a RequestBodyParamConverter inside my custom converter class. While this works, I am not sure if this is the right way to solve this problem. This way RequestBodyParamConverter is created twice. This is nothing special of course, but is this the Symfony way to solve this or are there other solutions?
Example:
Inject RequestBodyParamConverter in custom converter class
class MyParamConverter implements ParamConverterInterface {
protected $parentConverter;
public function __construct(ParamConverterInterface $parentConverter) {
$this->parentConverter = $parentConverter;
}
public function apply(Request $request, ParamConverter $configuration): bool {
doExtraWork();
return $this->parentConverter->apply(...);
}
}
// config/services.yaml
My\Project\MyParamConverter:
tags:
- { name: request.param_converter, converter: my_converter.request_body }
arguments:
# both fails since service is not found
$parentConverter: '#FOS\RestBundle\Request\RequestBodyParamConverter'
# OR
$parentConverter: '#fos_rest.request_body_converter'
Create RequestBodyParamConverter in custom converter class
class MyParamConverter implements ParamConverterInterface {
protected $parentConverter;
public function __construct(...parameters necessary to create converter...) {
$this->parentConverter = new RequestBodyParamConverter(...);
}
...
}
Symfony provide a way to decorate a registered service
To use it you need the FOS service id registered in the container.
To get it you can use this command
symfony console debug:container --tag=request.param_converter
Retrieve the Service ID of the service you want to override.
Then you can configure your service to decorate FOS one
My\Project\MyParamConverter:
decorates: 'TheIdOf_FOS_ParamConverterService'
arguments: [ '#My\Project\MyParamConverter.inner' ] # <-- this is the instance of fos service
Maybe you'll need to add the tags to this declaration, I'm not sure.
Let me know if you're facing an error.
I'm writing a Symfony 4 bundle and inside, in a compiler pass, I create multiple service definitions based on an abstract one (also enabling autowiring based on the argument name):
$managerDefinition = new ChildDefinition(Manager::class);
$managerDefinition->replaceArgument(0, $managerName);
...
$container->registerAliasForArgument($managerId, Manager::class, $managerName . 'Manager');
And this is the abstract service definition:
services:
MyBundle\Manager:
abstract: true
arguments:
- # manager name
So, in my App controller I can have this and it works correctly:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is MyBundle\Manager
}
Now, let's say at some point I decide to extend the Manager class in my App with additional methods:
class MyManager extends \MyBundle\Manager
{
public function newMethod() {
...
}
}
I override the bundle's abstract service like this:
services:
MyBundle\Manager:
class: App\Manager
abstract: true
arguments:
- # manager name
Everything still works as expected:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is App\Manager
$barManager->newMethod(); // Works
}
However, the IDE complains that newMethod() does not exist, as it doesn't exist in the typehinted MyBundle\Manager.
So, it seems more correct to change my constructor definition to let it know the actual class it's going to receive:
public function __construct(App\Manager $barManager)
However, I can't write this, as auto-wiring no longer works.
I suppose I could write a compiler pass in my App that registers autowiring for my custom App\Manager, but that seems like an overkill.
I can't shake the feeling that I'm doing something fundamentally wrong.
I guess my question is, what would be the best way to allow easy overriding of the abstract Manager definition in the bundle?
I would like to use the autowiring in a service that use 2 different entity manager. How to achieve something like that ?
use Doctrine\ORM\EntityManager;
class TestService
{
public function __construct(EntityManager $emA, EntityManager $emB)
{
}
}
My service.yml file use to be configured like that :
app.testservice:
class: App\Services\TestService
arguments:
- "#doctrine.orm.default_entity_manager"
- "#doctrine.orm.secondary_entity_manager"
There are already two good answers posted but I'd like to add a third as well as some context to help chose which approach to use in a given situation.
emix's answer is very simple but a bit fragile in that it relies on the name of the argument for injecting the correct service. Which is fine but you won't get any help from your IDE and sometimes might be a bit awkward. The answer should probably use EntityManagerInterface but that is a minor point.
DynlanKas's answer requires a bit of code in each service to locate the desired manager. It's okay but can be a bit repetitive. On the other hand, the answer is perfect when you don't know in advance exactly which manager is needed. It allows you to select a manager based on some dynamic information.
This third answer is largely based on Ron's Answer but refined just a bit.
Make a new class for each entity manager:
namespace App\EntityManager;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
class AEntityManager extends EntityManagerDecorator {}
class BEntityManager extends EntityManagerDecorator {}
Don't be alarmed that you are extending a decorator class. The class has the same interface and the same functionality as a 'real' entity manager. You just need to inject the desired manager:
# config/services.yaml
App\EntityManager\AEntityManager:
decorates: doctrine.orm.a_entity_manager
App\EntityManager\BEntityManager:
decorates: doctrine.orm.b_entity_manager
This approach requires making a new class for each entity manager as well as a couple of lines of configuration, but allows you to simply typehint against the desired class:
public function __construct(AEntityManager $emA, BEntityManager $emB)
{
}
It is, arguably, the most robust and standard way to approach the original question.
Dylan's answer violates the Demeter's Law principle. It's very easy and elegant since Symfony 3.4, meet Local service binding:
services:
_defaults:
bind:
$emA: "#doctrine.orm.default_entity_manager"
$emB: "#doctrine.orm.secondary_entity_manager"
Then in your service the autoloading will do the hard work for you:
class TestService
{
public function __construct(EntityManager $emA, EntityManager $emB)
{
…
}
}
The easy way would be to autowire ManagerRegistry in your constructor and use it to get the managers you want by using the names of the entity manger you have set in your configuration file (doctrine.yaml) :
use Doctrine\Common\Persistence\ManagerRegistry;
class TestService
{
private $emA;
private $emB;
public function __construct(ManagerRegistry $doctrine)
{
$this->emA = $doctrine->getManager('emA');
$this->emB = $doctrine->getManager('emB');
}
}
And you should be able to use them as you want.
Another way would be to follow this answer by Ron Mikluscak
Simply use EntityManagerInterface $secondaryEntityManager
If you're using Symfony's framework bundle (which I'm pretty sure you are), then Symfony >= 4.4 automatically generates camelcased autowiring aliases for every Entitymanger you define.
You can simply get a list of them using the debug:autowiring console command. For your configuration above, this should look something like this:
bin/console debug:autowiring EntityManagerInterface
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
(only showing classes/interfaces matching EntityManagerInterface)
EntityManager interface
Doctrine\ORM\EntityManagerInterface (doctrine.orm.default_entity_manager)
Doctrine\ORM\EntityManagerInterface $defaultEntityManager (doctrine.orm.default_entity_manager)
Doctrine\ORM\EntityManagerInterface $secondaryEntityManager (doctrine.orm.secondary_entity_manager)
As described in https://symfony.com/doc/4.4/doctrine/multiple_entity_managers.html:
Entity managers also benefit from autowiring aliases when the framework bundle is used. For example, to inject the customer entity manager, type-hint your method with EntityManagerInterface $customerEntityManager.
So you should only need:
use Doctrine\ORM\EntityManagerInterface;
class TestService
{
public function __construct(
EntityManagerInterface $defaultEntityManager,
EntityManagerInterface $secondaryEntityManager
) {
// ...
}
}
The name $defaultEntityManager isn't mandatory, though it helps to distinguish between the two. Every argument that's typehinted with an EntityManagerInterface and isn't in the list returned by debug:autowiring EntityManagerInterface will result in the default Entitymanager being autowired.
Note: As written in the documentation and shown in the output of debug:autowiring, you need to use EntityManagerInterface for this aliased autowiring, not the actual EntityManager class. In fact, you should always autowire the Entitymanager using EntityManagerInterface.
I tried to create an interface to create tagged services that can be injected into another service based on the documentation here https://symfony.com/doc/3.4/service_container/tags.html
I created an interface like
namespace AppBundle\Model;
interface PurgeInterface {
//put your code here
public function purge ();
}
put the definition into the service.yml:
_instanceof:
AppBundle\Model\PurgeInterface:
tags: ['app.purge']
and create services on this interface.
console debug:container shows my services as properly tagged.
I created another service which should work with the tagged services but this do not work.
services.yml:
purge_manager:
class: AppBundle\Service\PurgeManager
arguments: [!tagged app.purge]
The service looks like:
namespace AppBundle\Service;
use AppBundle\Model\PurgeInterface;
class PurgeManager {
public function __construct(iterable $purgers) {
dump($purgers);
}
}
If I test this I get:
Type error: Too few arguments to function AppBundle\Service\PurgeManager::__construct(), 0 passed in /.....Controller.php on line 21 and exactly 1 expected
I haven´t tried to create a compiler pass because I just want to understand why this is not working as it should based on the documentation
Thanks in advance
Sebastian
You can use tags, manual service definition and _instanceof in config. It's one of Symfony ways, but it requires a lot of YAML coding. What are other options?
Use Autowired Array
I've answered it here, but you use case much shorter and I'd like to answer with your specific code.
The simplest approach is to autowire arguments by autowired array.
no tag
support PSR-4 autodiscovery
no coding outside the service
1 compiler pass
Example
namespace AppBundle\Service;
use AppBundle\Model\PurgeInterface;
class PurgeManager
{
/**
* #param PurgeInterface[] $purgers
*/
public function __construct(iterable $purgers) {
dump($purgers);
}
}
This is also called collector pattern.
How to Integrate
Read a post with an example about this here
Or use the Compiler pass
If there are some incompatible classes, exclude them in the constructor of compiler pass:
$this->addCompilerPass(new AutowireArrayParameterCompilerPass([
'Sonata\CoreBundle\Model\Adapter\AdapterInterface'
]);
Is there a possibility to use generateUrl() method outside of controllers?
I tried to use it in a custom repository class with $this->get('router'), but it didn't work.
update
I've found a temporary solution here:
http://www.phamviet.net/2012/12/09/symfony-2-inject-service-as-dependency-in-to-repository/
I injected the whole service container into my repository, although it's "not recommended".
But it works for now.
update2
Injecting router instead of the whole container is probably a better idea :)
If you take a look in the source code of Controller::generateUrl(), you see how it's done:
$this->container->get('router')->generate($route, $parameters, $referenceType);
Basically you just enter the name of the route ($route here); if exists, some parameters ($parameters) and the type of reference (one of the constants of the UrlGeneratorInterface)
Don't inject the container into your repository... Really, don't !
If I were you, I would create a service and injects the router in it. In this service, I would create a method, that uses the repository and adds the needed code using the router.
That's way less dirty and easy to use/understand for another developer.
Inject the router itself into your EntityRepsitory (like described on Development Life blog's post Symfony 2: Injecting service as dependency into doctrine repository), then you can use $this->router->generate('acme_route');
in symfony 4 and Sylius when the FormType extends an (ex.) AbstractResourceType
class PostType extends AbstractResourceType
{
private $router;
public function __construct(RouterInterface $router, $dataClass, $validationGroups = [])
{
$this->router = $router;
parent::__construct($dataClass, $validationGroups);
}
}
Services.yaml :
app.post.form.type:
class: App\Form\Admin\Post\PostType
tags:
- { name: form.type }
arguments: ['#router.default', '%app.model.post.class%' ]