This article mainly introduces the attribute injection and method injection of component behavior in the PHP Yii Framework, including the description of dependency injection. For more information, see
Behavior attributes and method injection principles
We learned above that the intention of behavior is to inject its own attributes and methods into the attached class. In Yii, how does one inject the attributes and methods of a Behavior yii \ base \ Behavior into a yii \ base \ Component? The attribute is implemented through the _ get () and _ set () magic methods. For methods, the _ call () method is used.
Attribute injection
Taking reading as an example, if you access $ Component-> property1, what did Yii do behind the scenes? Let's take a look at yii \ base \ Component ::__ get ()
Public function _ get ($ name) {$ getter = 'get '. $ name; if (method_exists ($ this, $ getter) {return $ this-> $ getter ();} else {// pay attention to the content of this else branch, it is exactly different from yii \ base \ Object :__ get () // $ this-> ensureBehaviors (); foreach ($ this-> _ behaviors as $ behavior) {if ($ behavior-> canGetProperty ($ name) {// The attribute must be public in the row. Otherwise, you cannot access it in the following format. Return $ behavior-> $ name ;}} if (method_exists ($ this, 'set '. $ name) {throw new InvalidCallException ('Getting write-only property :'. get_class ($ this ). '::'. $ name);} else {throw new UnknownPropertyException ('Getting unknown property :'. get_class ($ this ). '::'. $ name );}}
The difference between yii \ base \ Compoent ::__ get () and yii \ base \ Object ::__ get. Yii \ base \ Object directly throws an exception for processing undefined getter functions, telling you that the attribute you want to access does not exist. However, yii \ base \ Component does not have a getter. check whether it is an attribute of the injection behavior:
First, call $ this-> ensureBehaviors (). This method has been mentioned earlier, mainly to ensure that the behavior has been bound.
After the behavior is bound, traverse $ this-> _ behaviors. Yii stores all bound behaviors of the class in the yii \ base \ Compoent ::$ _ behaviors [] array.
Finally, the behavior canGetProperty () is used to determine whether the property is a readable attribute of the bound behavior. if so, the behavior's property $ behavior-> name is returned. Read the attribute. As for canGetProperty (), we have already briefly discussed it in: ref: property, and will introduce it in detail later.
For setter, the code is similar, so it will not take up space here.
Method injection
Similar to the property injection method, the _ get () _ set () magic method is used. Yii uses the _ call () magic method to inject behavior methods:
public function __call($name, $params){ $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { return call_user_func_array([$object, $name], $params); } } throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");}
From the code above, we can see that Yii still calls $ this-> ensureBehaviors () to ensure that the behavior has been bound.
Then, the yii \ base \ Component ::$ _ behaviros [] array is traversed. Use the hasMethod () method to determine whether a method exists. If the method to be called in the bound behavior exists, call it using call_user_func_array () of PHP. As for the hasMethod () method, let's talk about it later.
Access control for injection attributes and methods
In the previous section, we gave an example of whether the public, private, and protected members can access the bound classes. Here we will analyze the cause at the code level.
In the above content, we know that a property is inaccessible, mainly depending on the behavior canGetProperty () and canSetProperty (). A method cannot be called. it mainly depends on hasMethod () of the behavior (). Because yii \ base \ Behavior inherits from our old friend yii \ base \ Object, the three judgment methods mentioned above are actually all in the Object. Let's look at it one by one:
public function canGetProperty($name, $checkVars = true){ return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);}public function canSetProperty($name, $checkVars = true){ return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);}public function hasMethod($name){ return method_exists($this, $name);}
These three methods are not complex. In this regard, we can draw the following conclusions:
When you read (write) an attribute bound to the Component, you can access it if the behavior defines a getter (setter) for this attribute. Or, if the behavior does have the member variable, you can use the above judgment. in this case, the member variable can be public, private, or protected. However, only the public member variables can be accessed correctly. The reason has been explained in the above section about the injection principle.
When you call a method for the behavior bound to Component, if the behavior has defined this method, you can use the above judgment. In this case, this method can be public, private, or protected. However, only public methods can be called correctly. If you understand the reason for the previous paragraph, you will understand it here.
Dependency Injection Container
The Dependency Injection (DI) container is an object that knows how to initialize and configure the object and all its dependent objects. Martin's article explains why DI containers are useful. Here we will mainly explain how to use DI containers provided by Yii.
Dependency Injection
Yii provides di Container features through the yii \ DI \ Container class. It supports the following types of dependency injection:
- Constructor injection;
- Setter and attribute injection;
- PHP callback injection.
- Constructor injection
With the help of parameter type prompts, DI container implements constructor injection. When a container is used to create a new object, the type prompt tells it which class or interface it depends on. The container will try to obtain the instance of the class or interface on which it depends, and then inject it into the new object through the constructor. For example:
Class Foo {public function _ construct (Bar $ bar) {}}$ foo = $ container-> get ('Foo'); // The above code is equivalent: $ bar = new Bar; $ foo = new Foo ($ bar );
Setter and property injection
Setter and property injection are supported through configuration. When registering a dependency or creating a new object, you can provide a configuration that will be provided to the container for injecting dependencies through the corresponding Setter or attribute. For example:
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'),]);
PHP callback injection
In this case, the container will use a registered PHP callback to create a new instance of a class. Callback resolves dependencies and appropriately injects them into newly created objects. For example:
$container->set('Foo', function () { return new Foo(new Bar);});$foo = $container->get('Foo');
Register dependency
You can use yii \ di \ Container: set () to register dependencies. Registration uses a dependency name and a dependency definition. The dependency name can be a class name, an interface name, or an alias. The dependency definition can be a class name, a configuration array, or a PHP callback.
$ Container = new \ yii \ di \ Container; // register a dependency with the same name. this can be omitted. $ Container-> set ('yii \ db \ connection'); // register an interface // when a class depends on this interface, the corresponding classes are initialized as dependent objects. $ Container-> set ('yii \ mail \ mailinterface', 'yii \ swiftmailer \ Mailer '); // register an alias. // You can use $ container-> get ('Foo') to create a Connection instance $ container-> set ('foo', 'yii \ db \ connection '); // register a class through configuration // when initializing through get (), the configuration will be used. $ Container-> set ('yii \ db \ connection', ['dsn '=> 'MySQL: host = 127.0.0.1; dbname = demo ', 'username' => 'root', 'password' => '', 'charset' => 'utf8',]); // register an alias through class configuration // in this case, you need to specify this class $ container-> set ('DB ', ['class' => 'yii \ db \ connection', 'dsn '=> 'MySQL: host = 127.0.0.1; dbname = demo', 'username' => 'root ', 'password' => '', 'charset' => 'utf8',]); // register a PHP callback // $ container-> get ('DB ') The callback function is executed. $ Container-> set ('DB', function ($ container, $ params, $ config) {return new \ yii \ db \ Connection ($ config );}); // register a component instance // $ container-> get ('pagecache') returns the same instance each time it is called. $ Container-> set ('pagecache', new FileCache );
Tip: If the dependency name and dependency definition are the same, you do not need to register the dependency through DI container.
The dependency registered through set () generates a new instance each time it is used. You can use yii \ di \ Container: setSingleton () to register the dependencies of a singleton:
$container->setSingleton('yii\db\Connection', [ 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', 'username' => 'root', 'password' => '', 'charset' => 'utf8',]);
Resolve dependencies
After registering the dependency, you can use the DI container to create a new object. The container automatically resolves the dependency and instantiates the dependency and injects it into the newly created object. The solution of dependency relationships is recursive. if there are other dependencies in a dependency relationship, these dependencies will be automatically resolved.
You can use yii \ di \ Container: get () to create a new object. This method receives a dependency name, which can be a class name, an interface name, or an alias. The dependency name may be registered through set () or setSingleton. You can provide the constructor parameter list of a class and a configuration to configure the newly created object. For example:
// "Db" is an alias defined previously $ db = $ container-> get ('DB'); // It is equivalent: $ engine = new \ app \ components \ SearchEngine ($ apiKey, ['type' => 1]); $ engine = $ container-> get ('app \ components \ SearchEngine ', [$ apiKey], ['type' => 1]);
Behind the code, DI containers do more work than creating objects. The container first checks the class construction method, finds the dependent class or interface name, and then automatically recursively solves these dependencies.
The following code shows a more complex example. The UserLister class depends on an object that implements the UserFinderInterface interface. the UserFinder class implements this interface and relies on a Connection object. All these dependencies are defined through the type prompt of the class constructor parameter. By registering property dependencies, the DI container can automatically resolve these dependencies and create a new userLister instance by calling a simple get ('userlister') call.
Namespace app \ models; use yii \ base \ Object; use yii \ db \ Connection; use yii \ di \ Container; interface UserFinderInterface {function findUser ();} class UserFinder extends Object implements UserFinderInterface {public $ db; public function _ construct (Connection $ db, $ config = []) {$ this-> db = $ db; parent :: __construct ($ config);} public function findUser () {}} class UserLister extends Object {public $ finder; public function _ construct (UserFinderInterface $ finder, $ config = []) {$ this-> finder = $ finder; parent ::__ construct ($ config) ;}}$ container = new Container; $ container-> set ('yii \ db \ connection', ['dsn '=> '... ',]); $ container-> set ('app \ models \ userfinderinterface', ['class' => 'app \ models \ UserFinder',]); $ container-> set ('userlister', 'app \ models \ userlister'); $ lister = $ container-> get ('userlister'); // equivalent: $ db = new \ yii \ db \ Connection (['dsn '=> '... ']); $ finder = new UserFinder ($ db); $ lister = new UserLister ($ finder );
Application in practice
When the Yii. php file is introduced into the application's Portal script, Yii creates a DI container. This DI container can be accessed through Yii: $ container. When Yii: createObject () is called, this method actually calls the yii \ di \ Container: get () method of this Container to create a new object. As described above, DI containers automatically resolve dependencies (if any) and inject them into newly created objects. Because Yii uses Yii: createObject () to create new objects in most of its core code, you can use Yii: $ container to globally customize these objects.
For example, you can globally customize the default number of paging buttons in yii \ widgets \ LinkPager:
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
In this way, if you use this pendant in a view using the following code, its maxButtonCount attribute will be initialized to 5 instead of the default value 10 defined in the class.
echo \yii\widgets\LinkPager::widget();
However, you can still overwrite the value set through the DI container:
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
Another example is the benefit of using the DI container to automatically construct method injection. Assume that your controller class depends on some other objects, such as a hotel booking service. You can declare the dependency through a constructor parameter, and then let the DI container help you solve the dependency automatically.
namespace app\controllers;use yii\web\Controller;use app\components\BookingInterface;class HotelController extends Controller{ protected $bookingService; public function __construct($id, $module, BookingInterface $bookingService, $config = []) { $this->bookingService = $bookingService; parent::__construct($id, $module, $config); }}
If you access this controller from a browser, you will see an error message reminding you that the BookingInterface cannot be instantiated. This is because you need to tell the DI container how to handle this dependency.
\ Yii: $ container-> set ('app \ components \ bookinginterface', 'app \ components \ bookingservice ');
Now, if you access this Controller again, an app \ components \ BookingService instance will be created and injected into the controller constructor as the third parameter.
When to register dependency
Since dependencies need to be resolved when creating new objects, their registration should be completed as soon as possible. The following are recommended practices:
If you are an application developer, you can register dependencies in the application's entry script or the script introduced by the entry script.
If you are a scalable developer, you can register dependencies into the extended bootstrap class.
Summary
Dependency Injection and service locator are popular design patterns that allow you to build software in fully decoupled and more conducive to testing. We strongly recommend that you read Martin's article to have a deeper understanding of dependency injection and service locator.
Yii implements its service positioner on the dependency of the inbound (DI) container. When a service locator tries to create a new object instance, it forwards the call to the DI container. The latter will automatically resolve the dependency as described above.