Where should I setup DI container in Yii2? - dependency-injection

I have an interface. And a class. The classimplements the interface and extends BaseObject. It's something like business logic service. I a'm going to inject it in a controlller via constructor. My target is use dependency from the interface.
I'm looking on the documentation and I don't understand, where should I write this code. Is it a parth of main config? Or it's some kind of new one config? Is it a separete file? If yse, how Yii2 will understand that it's DI configuration?

You can set up DI container in your app config. For example in web.php config:
$config = [
// ...
'container' => [
'definitions' => [
\my\namespace\MyInterface::class => \my\namespace\MyClass::class,
// ... other definitions
],
],
// ... other configs
];
Another good place to set up DI might be in bootstrap method of component. For example in Module class.
class MyModule extends \yii\base\Module implements \yii\base\BootstrapInterface
{
public function bootstrap($app)
{
Yii::$container->set(
\my\namespace\MyInterface::class,
\my\namespace\MyClass::class
);
}
}
In this case you have to add the module to app's bootstrap property.

Related

NestJS: UseGuards Doesn't Use Dependency Injection - Can't Override Guard

Current behavior
The documentation states here that:
...we passed the RolesGuard type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.
So I'm expecting to "override" a guard with another, via the module's providers.
This doesn't work as expected.
Interestingly enough,
injecting the same service via the controller's constructor does yield the correct service, though.
Input Code
Simple: https://github.com/dima-gusyatiner/nestjs-guards-override/tree/only-app-module
With Another module, using exports: https://github.com/dima-gusyatiner/nestjs-guards-override/tree/master
Controller:
#Controller()
#UseGuards(AuthGuard)
Module:
#Module({
controllers: [AppController],
providers: [
{
provide: AuthGuard,
useClass: AuthOverrideGuard,
}
],
})
Expected behavior
I would expect AuthGuard to never even be constructed.
Instead, both classes are constructed, and AuthGuard is used as the guard.
Environment
- Nest version: 8.0.6
- Node version: 16.8.0
- Platform: Linux
Bug Report
I've also opened a ticket for this:
https://github.com/nestjs/nest/issues/8011
Question
Am I doing something wrong here, or is this a bug?
Anybody can suggest a workaround?
I am not sure I understand it that well either but from what I saw:
With UseGuards, the passed in class is not resolved as provider. Only when you declare the guard as a dependency. Just like in AppController, is it looked up in the provider scope.
Sure the guard's dependencies are resolved as providers within the calling module's scope but it itself is not. Just regular instantiation with dependency injection.
I think that is why both guards are still created, just that AuthOverrideGuard, is registered as a provider.
That is why you get the following output:
[Nest] 77074 - 05/09/2021, 05:01:39 LOG [NestFactory] Starting Nest application...
Construct AuthOverrideGuard
Construct AuthGuard
AuthGuard
AppController AuthOverrideGuard {}
This is why this.guard is correctly resolved as AuthOverrideGuard from the provider scope
To summarize, in my understanding, the docs are inaccurate.
The documentation states here that:
...we passed the RolesGuard type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.
This is inaccurate, since RolesGuard won't be instantiated with proper dependency injection. Instead, it will be instantiated using this exact class only.
To achieve proper dependency injection, one should use another class, injected via the guard's constructor. For example:
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Observable } from 'rxjs';
import { AuthService } from '../services';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly auth: AuthService,
) {}
canActivate(context: ExecutionContext) {
return this.auth.canActivate(context);
}
}
You can never provide another AuthGuard, it won't work. But you can provide another AuthService, which this guard uses.
#Module({
controllers: [AppController],
providers: [
{
provide: AuthService,
useClass: AuthServiceOverride,
}
],
})

Possible to manual inject NgZone?

I'm trying to use Injector to inject my component/service but one of them requires NgZone as its dependency. From https://angular.io/api/core/Injector
export MyComponent{
constructor() {
const injector = Injector.create({
providers: [
{ provide: NgZone, deps: [ ] },
{ provide: MyService, deps: [ NgZone ] }
]
});
this.myService = injector.get(MyService);
}
}
Then in child class:
export MyOtherComponent extends MyComponent {
constructor() {
super();
}
public helloWorld() {
this.myService.stuff();
}
}
But I'm getting the following error:
ERROR Error: StaticInjectorError[MyService -> NgZone]:
NullInjectorError: No provider for NgZone!
at NullInjector.get (core.js:8896)
I tried with a dummy service that don't have anything in the constructor, and it worked.
Is there a way to provide NgZone manually through the deps like that?
Is there another way to get the "global" NgZone object (there should only be 1 instance of NgZone running right?)
MyService is also a downgraded service and is being used in both AngularJS and Angular7, not sure if that changes anything.
Edit: Reason I'm trying to do this, is because MyComponent is a component base class that will get extends upon and have many child class extending on that. If I could do it like this by manually injecting it internally, then I don't need to pass all those dependencies from the children. Imagine I have 6-7 dependencies and 30+ childrens, and lets say I need some new dependencies, I'd have to update every single one of them...
You could inject injector — it would be a single dependency. And all your children could then get what they need from this injector. Yes, you would need to provide that injector through your inheritance chain of super() calls, but at least it would be just one thing.
There's also this:
https://github.com/angular/angular/issues/16566#issuecomment-338188342
This comment states it is possible to use DI in abstract classes if you decorate them. As for NgZone instance — yes, I believe there must be only one and I also tried to get a hold of it once but couldn't come up with an elegant solution.
Guess I was brain dead last night. This morning after digging deeper, I think I've found a way to grab the global Zone and it seems to work (it triggered the change detection).
Since #waterplea also have the assumption that there should only be 1 instance of the NgZone, I decided to just look around in console and what do you know.
Then I tried to just pass the global Zone to it like this:
{ provide: NgZone, useValue: Zone },
And it gave me the error that this.ngZone.run is undefined. OK... digging deeper, oh hey, there is a root object in Zone and hey, look, a run function!
So I went and updated the code to this and it worked.
{ provide: NgZone, useValue: Zone.root },

How to use autowire(...), create(...), and get(...) in definitions for PHP-DI?

PHP-DI 6 provides multiple functions, that working in the definitions. Three of them seem to do the same in the definitions context: autowire(...), create(...), and get(...). E.g. I have following types:
FooServiceInterface
BarServiceInterface
FooAService implements FooServiceInterface
dependencies: BarServiceInterface $barService
FooBService implements FooServiceInterface
dependencies: BarServiceInterface $barService
BarService implements BarServiceInterface
dependencies: -
The FooServiceInterface gets injected into a Symfony controller (constructor injection).
Now my file with the definitions:
return [
FooServiceInterface::class => DI\autowire(FooBService::class),
BarServiceInterface::class => DI\autowire(BarService::class),
];
It works.
I also can set it up like this:
return [
FooServiceInterface::class => DI\get(FooBService::class),
BarServiceInterface::class => DI\get(BarService::class),
];
And it's still working.
This
return [
FooServiceInterface::class => DI\create(FooBService::class),
BarServiceInterface::class => DI\create(BarService::class),
];
doesn't work.
And this
return [
FooServiceInterface::class => DI\get(FooBService::class),
BarServiceInterface::class => DI\create(BarService::class),
];
does.
What is the difference between the three functions (in the context of definitions)? Which one is the recommended function to set up a common interface dependency definition (like SomeInterface::class => DI\recommendedFunction(SomeClass::class))?
I would say use get() only if you know why you would need it.
Then to choose between autowire() and create() is up to you: do you need autowiring or not?
Using simply create() is telling PHP-DI to just do new FooService() to create the service. If that's enough, then fine. If your service has dependencies, you can either rely on autowiring (using autowire()) or define the dependencies to inject manually using create(). It's up to you.

Google guice, override configuration

I'm working with Guice and have one design question. My App consists of few module:
myapp-persistence (JPA Entities, DAO, other DB related stuff)
myapp-backend (Some background daemons, they use myapp-persistence )
myapp-rest (REST app that depends on myapp-persistence)
myapp-persistence must have singleton HibernateSessionFactory. It's by Hibernate design.
No problem I can solve it with Guice:
class MyAppPersistenceModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[SomeStuff])
bind(classOf[ClientDao])
bind(classOf[CustomerDao])
bind(classOf[SessionFactory]).toProvider(classOf[HibernateSessionFactoryProvider]).asEagerSingleton()
}
#Provides
def provideDatabaseConnectionConfiguration: DatabaseConnectionConfiguration = {
DatabaseConnectionConfiguration.fromSysEnv
}
}
The problem with passing DatabaseConnectionConfiguration to that singleton. myapp-persistence module doesn't really care how to get that config. Right now it's taken from sys variables.
myapp-rest is play-app and it wants to read conf from application.conf and inject it into other components using Guice.
myapp-backend does more or less the same.
Right now I'm locked myself with
#Provides
def provideDatabaseConnectionConfiguration: DatabaseConnectionConfiguration = {
DatabaseConnectionConfiguration.fromSysEnv
}
And I don't understand how to make it flexible and configurable for myapp-rest and myapp-backend.
UPD
According to answer, I did it this way:
Defined trait
trait DbConfProvider {
def dbConf: DbConf
}
Singleton factory now depends on provider:
class HibernateSessionFactoryProvider #Inject()(dbConfProvider: DbConfProvider) extends Provider[SessionFactory] {
}
myapp-persistence module exposes public guice module with all piblic persistence module DAO.
myapp-persistence has module used only for testing purposes. myapp-persistence Injector load module described below:
class MyAppPersistenceDbConfModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[DbConfProvider]).to(classOf[DbConfSysEnvProvider])
}
}
DbConfSysEnvProvider reads DB connection settings from sys env. Non production use case.
Play app has it's own conf mechanism. I've added my custom module to app conf:
# play-specific config
play.modules.enabled += "common.components.MyAppPersistenceDbConfModule"
# public components from myapp-persistence module.
play.modules.enabled += "com.myapp.persistence.connection.PersistenceModule"
And my configuration service:
#Singleton
class ConfigurationService #Inject()(configuration: Configuration) extends DbConfProvider {
...}
I am not an expert on Play-specific setup, but generally this kind of design problem is solved in one of the following ways:
No default. Remove the binding of DatabaseConnectionConfiguration from the upstream module (myapp-persistence), and define it in each downstream module (myapp-backend, myapp-rest) as appropriate.
Default with override. Keep the default binding of DatabaseConnectionConfiguration like you did, implementing the most common configuration strategy there. Override it in downstream modules using Guice Modules.override(..) API when needed.
Implement a unified configuration mechanism across the modules, that does not depend on particular frameworks used. (E.g. Bootique, which is built on Guice ... Haven't used it with Play though).
I personally prefer the approach #3, but in the absence of something like Bootique, #2 is a good substitute.

Add setter injection to an existing service

I want to add a setter injection to an existing service. The base service is defined as follow:
[container] Information for service doctrine.orm.default_entity_manager
Service Id doctrine.orm.default_entity_manager
Class Doctrine\ORM\EntityManager
Tags -
Scope container
Public yes
I have overridden the class parameter of the service in the config.yml file to use my own class which add a setExample() method to the base class. The service definition now looks like:
[container] Information for service doctrine.orm.default_entity_manager
Service Id doctrine.orm.default_entity_manager
Class MyCustom\EntityManager
Tags -
Scope container
Public yes
How can I modify/override this service configuration to add my own setter injection in my own services.yml file ?
What I would do is use the extension class in your bundle DependencyInjection namespace. It should be something like:
<?php
namespace YourNamespace\YourBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Definition;
/**
* Provide a DIC extension to support configurable cheeta subscription adapters.
*/
class YourNamespaceYourBundleExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load( array $configs, ContainerBuilder $container )
{
// Some stuff with configuration...
$configuration = new Configuration();
$config = $this->processConfiguration( $configuration, $configs );
// Load service configuration
$loader = new Loader\XmlFileLoader( $container, new FileLocator(__DIR__.'/../Resources/config' ) );
$loader->load( 'services.xml' );
$container->findDefinition('doctrine.orm.default_entity_manager')->addMethodCall('setExample', array(/* Eventual parameters */));
}
}
There is probably also a way to do the same using service inheritance, but I never tried. In the Extension class however you can do the same things you do in the service.xml file plus use php logic and semantic configuration to do more advanced stuffs.

Resources