Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
135b944c
Commit
135b944c
authored
May 04, 2014
by
Qiang Xue
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Finished guide about DI container and service locator.
parent
4e5079ab
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
154 additions
and
50 deletions
+154
-50
concept-di-container.md
docs/guide/concept-di-container.md
+129
-31
concept-service-locator.md
docs/guide/concept-service-locator.md
+16
-19
ContainerTest.php
tests/unit/framework/di/ContainerTest.php
+9
-0
No files found.
docs/guide/concept-di-container.md
View file @
135b944c
Dependency Injection Container
Dependency Injection Container
==============================
==============================
Both service locator and dependency injection are popular design patterns that allow building software
in a loosely-coupled fashion. Yii uses service locator and dependency injection extensively,
even though you may not be aware of them. In this tutorial, we will explore their implementation
and support to help you write code more consciously. We also highly recommend you to read
[
Martin's article
](
http://martinfowler.com/articles/injection.html
)
to get a deeper understanding of
service locator and dependency injection.
A dependency injection (DI) container is an object that knows how to instantiate and configure objects and
A dependency injection (DI) container is an object that knows how to instantiate and configure objects and
all their dependent objects.
[
Martin's article
](
http://martinfowler.com/articles/injection.html
)
has well
all their dependent objects.
[
Martin's article
](
http://martinfowler.com/articles/injection.html
)
has well
explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii.
explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii.
<a
name=
"dependency-injection"
></a>
Dependency Injection
--------------------
Yii provides the DI container feature through the class
[
[yii\di\Container
]
]. It supports the following kinds of
Yii provides the DI container feature through the class
[
[yii\di\Container
]
]. It supports the following kinds of
dependency injection:
dependency injection:
*
Constructor injection;
*
Constructor injection;
*
Setter injection;
*
Setter
and property
injection;
*
PHP callable injection.
*
PHP callable injection.
### Registering Dependencies
<a
name=
"constructor-injection"
></a>
### Constructor Injection
The DI container supports constructor injection with the help of type hints for constructor parameters.
The type hints tell the container which classes or interfaces are dependent when it is used to create a new object.
The container will try to get the instances of the dependent classes or interfaces and then inject them
into the new object through the constructor. For example,
```
php
class
Foo
{
public
function
__construct
(
Bar
$bar
)
{
}
}
$foo
=
$container
->
get
(
'Foo'
);
// which is equivalent to the following:
$bar
=
new
Bar
;
$foo
=
new
Foo
(
$bar
);
```
<a
name=
"setter-and-property-injection"
></a>
### Setter and Property Injection
Setter and property injection is supported through
[
configurations
](
concept-configurations.md
)
.
When registering a dependency or when creating a new object, you can provide a configuration which
will be used by the container to inject the dependencies through the corresponding setters or properties.
For example,
```
php
use
yii\base\Object
;
class
Foo
extends
Object
{
public
$bar
;
private
$_qux
;
public
function
getQux
()
{
return
$this
->
_qux
;
}
public
function
setQux
(
Qux
$qux
)
{
$this
->
_qux
=
$qux
;
}
}
$container
->
get
(
'Foo'
,
[],
[
'bar'
=>
$container
->
get
(
'Bar'
),
'qux'
=>
$container
->
get
(
'Qux'
),
]);
```
<a
name=
"php-callable-injection"
></a>
### PHP Callable Injection
In this case, the container will use a registered PHP callable to build new instances of a class.
The callable is responsible to resolve the dependencies and inject them appropriately to the newly
created objects. For example,
```
php
$container
->
set
(
'Foo'
,
function
()
{
return
new
Foo
(
new
Bar
);
});
$foo
=
$container
->
get
(
'Foo'
);
```
<a
name=
"registering-dependencies"
></a>
Registering Dependencies
------------------------
You can use
[
[yii\di\Container::set()
]
] to register dependencies. The registration requires a dependency name
You can use
[
[yii\di\Container::set()
]
] to register dependencies. The registration requires a dependency name
as well as a dependency definition.
The
name can be a class name, an interface name, or an alias name;
as well as a dependency definition.
A dependency
name can be a class name, an interface name, or an alias name;
and
the
definition can be a class name, a configuration array, or a PHP callable.
and
a dependency
definition can be a class name, a configuration array, or a PHP callable.
```
php
```
php
$container
=
new
\yii\di\Container
;
$container
=
new
\yii\di\Container
;
...
@@ -61,10 +135,14 @@ $container->set('db', [
...
@@ -61,10 +135,14 @@ $container->set('db', [
]);
]);
// register a PHP callable
// register a PHP callable
// The callable will be executed when $container->get('db') is called
// The callable will be executed
each time
when $container->get('db') is called
$container
->
set
(
'db'
,
function
(
$container
,
$params
,
$config
)
{
$container
->
set
(
'db'
,
function
(
$container
,
$params
,
$config
)
{
return
new
\yii\db\Connection
(
$config
);
return
new
\yii\db\Connection
(
$config
);
});
});
// register a component instance
// $container->get('pageCache') will return the same instance each time it is called
$container
->
set
(
'pageCache'
,
new
FileCache
);
```
```
> Tip: If a dependency name is the same as the corresponding dependency definition, you do not
> Tip: If a dependency name is the same as the corresponding dependency definition, you do not
...
@@ -84,28 +162,31 @@ $container->setSingleton('yii\db\Connection', [
...
@@ -84,28 +162,31 @@ $container->setSingleton('yii\db\Connection', [
```
```
### Resolving Dependencies
<a
name=
"resolving-dependencies"
></a>
Resolving Dependencies
----------------------
Once you have registered dependencies, you can use the DI container to create new objects,
Once you have registered dependencies, you can use the DI container to create new objects,
and the container will automatically resolve dependencies by instantiating them and injecting
and the container will automatically resolve dependencies by instantiating them and injecting
them into the newly created objects. The dependency resolution is recursive, meaning that
them into the newly created objects. The dependency resolution is recursive, meaning that
if a dependency has other dependencies, those dependencies will also be resolved automatically.
if a dependency has other dependencies, those dependencies will also be resolved automatically.
You use
[
[yii\di\Container::get()
]
] to create new objects. The method takes a class name or
You can use
[
[yii\di\Container::get()
]
] to create new objects. The method takes a dependency name,
a dependency name (class name, interface name or alias name) that you previously registered
which can be a class name, an interface name or an alias name. The dependency name may or may
via
`set()`
or
`setSingleton()`
. You may optionally provide a list of class constructor parameters
not be registered via
`set()`
or
`setSingleton()`
. You may optionally provide a list of class
and a list of name-value pairs to configure the newly created object. For example,
constructor parameters and a
[
configuration
](
concept-configurations.md
)
to configure the newly created object.
For example,
```
php
```
php
// equivalent to: $map = new \app\components\GoogleMap($apiKey);
$map
=
$container
->
get
(
'app\components\GoogleMap'
,
[
$apiKey
]);
// "db" is a previously registered alias name
// "db" is a previously registered alias name
$db
=
$container
->
get
(
'db'
);
$db
=
$container
->
get
(
'db'
);
// equivalent to: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
$engine
=
$container
->
get
(
'app\components\SearchEngine'
,
[
$apiKey
],
[
'type'
=>
1
]);
```
```
Behind the scene, the DI container does much more work than just creating a new object.
Behind the scene, the DI container does much more work than just creating a new object.
The container will inspect the class constructor to find out dependent class or interface names
The container will
first
inspect the class constructor to find out dependent class or interface names
and then automatically resolve those dependencies recursively.
and then automatically resolve those dependencies recursively.
The following code shows a more sophisticated example. The
`UserLister`
class depends on an object implementing
The following code shows a more sophisticated example. The
`UserLister`
class depends on an object implementing
...
@@ -114,7 +195,6 @@ a `Connection` object. All these dependencies are declared through type hinting
...
@@ -114,7 +195,6 @@ a `Connection` object. All these dependencies are declared through type hinting
With property dependency registration, the DI container is able to resolve these dependencies automatically
With property dependency registration, the DI container is able to resolve these dependencies automatically
and creates a new
`UserLister`
instance with a simple call of
`get('userLister')`
.
and creates a new
`UserLister`
instance with a simple call of
`get('userLister')`
.
```
php
```
php
namespace
app\models
;
namespace
app\models
;
...
@@ -172,11 +252,13 @@ $lister = new UserLister($finder);
...
@@ -172,11 +252,13 @@ $lister = new UserLister($finder);
```
```
### Practical Usage
<a
name=
"practical-usages"
></a>
Practical Usages
----------------
Yii creates a DI container when you include the
`
yii.php`
file in your application's entry script.
Yii creates a DI container when you include the
`
Yii.php`
file in the
[
entry script
](
structure-entry-scripts.md
)
The DI container is accessible via
[
[Yii::$container
]
]. When you call
[
[Yii::createObject()
]
], the method
of your application. The DI container is accessible via
[
[Yii::$container
]
]. When you call
[
[Yii::createObject()
]
],
will actually call the container's
[
[yii\di\Container::get()|get()
]
] method to create a new object.
the method
will actually call the container's
[
[yii\di\Container::get()|get()
]
] method to create a new object.
As aforementioned, the DI container will automatically resolve the dependencies (if any) and inject them
As aforementioned, the DI container will automatically resolve the dependencies (if any) and inject them
into the newly created object. Because Yii uses
[
[Yii::createObject()
]
] in most of its core code to create
into the newly created object. Because Yii uses
[
[Yii::createObject()
]
] in most of its core code to create
new objects, this means you can customize the objects globally by dealing with
[
[Yii::$container
]
].
new objects, this means you can customize the objects globally by dealing with
[
[Yii::$container
]
].
...
@@ -188,13 +270,13 @@ For example, you can customize globally the default number of pagination buttons
...
@@ -188,13 +270,13 @@ For example, you can customize globally the default number of pagination buttons
```
```
Now if you use the widget in a view with the following code, the
`maxButtonCount`
property will be initialized
Now if you use the widget in a view with the following code, the
`maxButtonCount`
property will be initialized
as 5 instead of 10 as defined in the class.
as 5 instead of
the default value
10 as defined in the class.
```
php
```
php
echo
\yii\widgets\LinkPager
::
widget
();
echo
\yii\widgets\LinkPager
::
widget
();
```
```
You can still override the value set via DI container:
You can still override the value set via DI container
, though
:
```
php
```
php
echo
\yii\widgets\LinkPager
::
widget
([
'maxButtonCount'
=>
20
]);
echo
\yii\widgets\LinkPager
::
widget
([
'maxButtonCount'
=>
20
]);
...
@@ -233,13 +315,29 @@ Now if you access the controller again, an instance of `app\components\BookingSe
...
@@ -233,13 +315,29 @@ Now if you access the controller again, an instance of `app\components\BookingSe
created and injected as the 3rd parameter to the controller's constructor.
created and injected as the 3rd parameter to the controller's constructor.
### When to Register Dependencies
<a
name=
"when-to-register-dependencies"
></a>
When to Register Dependencies
-----------------------------
Because dependencies are needed when new objects are being created, their registration should be done
Because dependencies are needed when new objects are being created, their registration should be done
as early as possible. The followings are the recommended practices:
as early as possible. The followings are the recommended practices:
*
If you are the developer of an application, you can register dependencies in your
*
If you are the developer of an application, you can register dependencies in your
application's
entry script
or in a script that is included by the entry script.
application's
[
entry script
](
structure-entry-scripts.md
)
or in a script that is included by the entry script.
*
If you are the developer of a redistributable
extension
, you can register dependencies
*
If you are the developer of a redistributable
[
extension
](
structure-extensions.md
)
, you can register dependencies
in the bootstrap class of the extension.
in the bootstrap class of the extension.
<a
name=
"summary"
></a>
Summary
-------
Both dependency injection and
[
service locator
](
concept-service-locator.md
)
are popular design patterns
that allow building software in a loosely-coupled and more testable fashion. We highly recommend you to read
[
Martin's article
](
http://martinfowler.com/articles/injection.html
)
to get a deeper understanding of
dependency injection and service locator.
Yii implements its
[
service locator
](
concept-service-locator.md
)
on top of the dependency injection (DI) container.
When a service locator is trying to create a new object instance, it will forward the call to the DI container.
The latter will resolve the dependencies automatically as described above.
docs/guide/concept-service-locator.md
View file @
135b944c
Service Locator
Service Locator
===============
===============
> Note: This chapter needs cleanup.
Both service locator and dependency injection are popular design patterns that allow building software
in a loosely-coupled fashion. Yii uses service locator and dependency injection extensively,
even though you may not be aware of them. In this tutorial, we will explore their implementation
and support to help you write code more consciously. We also highly recommend you to read
[
Martin's article
](
http://martinfowler.com/articles/injection.html
)
to get a deeper understanding of
service locator and dependency injection.
A service locator is an object that knows how to provide all sorts of services (or components) that an application
A service locator is an object that knows how to provide all sorts of services (or components) that an application
might need. Within a service locator, each component has only a single instance which is uniquely identified by an ID.
might need. Within a service locator, each component has only a single instance which is uniquely identified by an ID.
You use the ID to retrieve a component from the service locator. In Yii, a service locator is simply an instance
You use the ID to retrieve a component from the service locator.
of
[
[yii\di\ServiceLocator
]
] or its child class.
In Yii, a service locator is simply an instance of
[
[yii\di\ServiceLocator
]
] or its child class.
The most commonly used service locator in Yii is the
*application*
object which can be accessed through
The most commonly used service locator in Yii is the
*application*
object which can be accessed through
`\Yii::$app`
. The services it provides are called
*application components*
, such as
`request`
,
`response`
,
`\Yii::$app`
. The services it provides are called
*application components*
, such as
the
`request`
,
`response`
,
`urlManager`
. You may configure these components or
replace them with your own implementations easily
`urlManager`
components. You may configure these components or even
replace them with your own implementations easily
through functionality provided the service locator.
through functionality provided the service locator.
Besides the application object, each module object is also a service locator.
Besides the application object, each module object is also a service locator.
...
@@ -26,7 +18,10 @@ To use a service locator, the first step is to register components. A component
...
@@ -26,7 +18,10 @@ To use a service locator, the first step is to register components. A component
via
[
[yii\di\ServiceLocator::set()
]
]. The following code shows different ways of registering components:
via
[
[yii\di\ServiceLocator::set()
]
]. The following code shows different ways of registering components:
```
php
```
php
$locator
=
new
\yii\di\ServiceLocator
;
use
yii\di\ServiceLocator
;
use
yii\caching\FileCache
;
$locator
=
new
ServiceLocator
;
// register "cache" using a class name that can be used to create a component
// register "cache" using a class name that can be used to create a component
$locator
->
set
(
'cache'
,
'yii\caching\ApcCache'
);
$locator
->
set
(
'cache'
,
'yii\caching\ApcCache'
);
...
@@ -43,6 +38,9 @@ $locator->set('db', [
...
@@ -43,6 +38,9 @@ $locator->set('db', [
$locator
->
set
(
'search'
,
function
()
{
$locator
->
set
(
'search'
,
function
()
{
return
new
app\components\SolrService
;
return
new
app\components\SolrService
;
});
});
// register "pageCache" using a component
$locator
->
set
(
'pageCache'
,
new
FileCache
);
```
```
Once a component is registered, you can access it using its ID in one of the following two ways:
Once a component is registered, you can access it using its ID in one of the following two ways:
...
@@ -62,11 +60,10 @@ You may use [[yii\di\ServiceLocator::has()]] to check if a component ID has alre
...
@@ -62,11 +60,10 @@ You may use [[yii\di\ServiceLocator::has()]] to check if a component ID has alre
If you call
[
[yii\di\ServiceLocator::get()
]
] with an invalid ID, an exception will be thrown.
If you call
[
[yii\di\ServiceLocator::get()
]
] with an invalid ID, an exception will be thrown.
Because service locators are often being configured using configuration arrays, a method named
Because service locators are often being created with
[
configurations
](
concept-configurations.md
)
,
[
[yii\di\ServiceLocator::setComponents()
]
] is provided to allow registering components in configuration arrays.
a writable property named
[
[yii\di\ServiceLocator::setComponents()|components
]
] is provided so that
The method is a setter which defines a writable property
`components`
that can be configured.
you can configure it and register multiple components at once. The following code shows a configuration array
The following code shows a configuration array that can be used to configure an application and register
that can be used to configure an application and register the "db", "cache" and "search" components:
the "db", "cache" and "search" components:
```
php
```
php
return
[
return
[
...
...
tests/unit/framework/di/ContainerTest.php
View file @
135b944c
...
@@ -36,6 +36,8 @@ class ContainerTest extends TestCase
...
@@ -36,6 +36,8 @@ class ContainerTest extends TestCase
$this
->
assertTrue
(
$foo
instanceof
$Foo
);
$this
->
assertTrue
(
$foo
instanceof
$Foo
);
$this
->
assertTrue
(
$foo
->
bar
instanceof
$Bar
);
$this
->
assertTrue
(
$foo
->
bar
instanceof
$Bar
);
$this
->
assertTrue
(
$foo
->
bar
->
qux
instanceof
$Qux
);
$this
->
assertTrue
(
$foo
->
bar
->
qux
instanceof
$Qux
);
$foo2
=
$container
->
get
(
$Foo
);
$this
->
assertFalse
(
$foo
===
$foo2
);
// full wiring
// full wiring
$container
=
new
Container
;
$container
=
new
Container
;
...
@@ -80,5 +82,12 @@ class ContainerTest extends TestCase
...
@@ -80,5 +82,12 @@ class ContainerTest extends TestCase
$this
->
assertTrue
(
$foo
instanceof
$Foo
);
$this
->
assertTrue
(
$foo
instanceof
$Foo
);
$this
->
assertTrue
(
$foo
->
bar
instanceof
$Bar
);
$this
->
assertTrue
(
$foo
->
bar
instanceof
$Bar
);
$this
->
assertTrue
(
$foo
->
bar
->
qux
instanceof
$Qux
);
$this
->
assertTrue
(
$foo
->
bar
->
qux
instanceof
$Qux
);
// wiring by closure
$container
=
new
Container
;
$container
->
set
(
'qux'
,
new
Qux
);
$qux1
=
$container
->
get
(
'qux'
);
$qux2
=
$container
->
get
(
'qux'
);
$this
->
assertTrue
(
$qux1
===
$qux2
);
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment