posted by Trần Phước Tú on 2014-05-16 01:26

Có thể hiểu Dependency Injection là 1 pattern theo đó các thành phần phụ thuộc được đưa vào các đối tượng thông qua các function.

1.Inversion of control(IOC):

Có thể tham khảo rất nhiều tài liệu nói về IOC, ví dụ: http://en.wikipedia.org/wiki/Inversion_of_control

Định nghĩa về nó cũng khá rườm rà nhưng có thể hiểu cơ bản nó định nghĩa 1 sự đảo ngược về điều khiển trong dòng chảy thực thi của 1 phần mềm, tức là thay vì A gọi B để lấy kết quả, thì hãy để C gọi B và cung cấp kết quả đó cho A, C ở đây thường là 1 thư viện hay 1 framework.

Hình dung ví dụ như thế này:

Trong 1 ứng dụng web có nhiều chức năng, mỗi chức năng được cụ thể hóa bằng 1 đối tương Action, khi user click vào 1 link, thì trong main script, tôi sẽ khởi tạo đối tượng Action tương ứng, gán cho nó các tham số hay các biến môi trường cần thiết và thực hiện gọi action.run(). Đó là cách mà ta hay bắt gặp. Nhưng với IOC thì trong trường hợp như trên nhiệm vụ của tôi đơn giản chỉ định nghĩa hàm run() của các action, và sẽ có cơ chế khi user click vào đường link, action của tôi sẽ được khởi tạo, các tham số cần thiết sẽ được tự động gán và available trong action, và hàm run() của action sẽ tự động được gọi, tôi không cần phải tạo 1 main script để thực hiện tất cả các bước trên.

Tức là thay vì tôi gọi các action thì sẽ có cơ chế gọi các action này cho tôi. Thường thì các cơ chế này sẽ được đưa thành thư viện hay framework để được sử dụng nhiều lần.

Trong thực tế ta rất hay sử dụng IOC, hầu hết framework đều sử dụng kiến trúc IOC.

Với Yii Framework, ta chỉ cần tạo các Controller và action cho các tác vụ business, các tham số cần thiết đều được Yii cung cấp cho Controller và nhiệm vụ gọi thực thi Controller cũng là của Yii.

 

IOC là 1 kiến trúc, và có nhiều kiểu implement hay pattern cụ thể:

+Factory Pattern

+Service Locator Pattern

+Dependency Injection

Ở đây ta chỉ tìm hiểu sâu hơn về Dependency Injection(DI) vì tính phổ biến của nó so với các pattern còn lại.

 

2. Dependency Injection(DI):

Có thể hiểu Dependency Injection là 1 pattern theo đó các thành phần phụ thuộc được đưa vào các đối tượng thông qua các function. Nghe thì có vẻ rất bình thường và không có gì đặc biệt, nhưng hãy xét ví dụ sau:

 

Ta có class MyService cung cấp các function tiện ích, và class Obj sử dụng class Service, ta xem 2 cách định nghia Obj

 

Cách 1:

class Obj{
    private $service;
    
    public function doSomething(){
        $this->service=new MyService();
        return $this->service->getValue();
    }
}

 

Cách 2:

class Obj{
    private $service;
    
    public function setService($service){
        $this->service = $service;
    }
    
    public function doSomething(){
        return $this->service->getValue();
    }
}

và để sử dụng Obj ta có 1 function để set service cho Obj

function init(){
    $obj=new Obj();
    $service = new MyService();
    $obj->setService($service);
    $obj->doSomething();
}

Với cách 1, ví dụ về sau ta muốn thay đổi $service thành 1 đối tượng của NewMyService rõ ràng ta phải vào thay đổi source của Obj, và trong trường hợp rất nhiều class như Obj sử dụng MyService như cách 1 thì ta phải sửa source của tất cả các class đó. Nhưng với cách 2 ta chỉ cần thay đổi 1 dòng trong function init(): service = new NewMyService();

Trong cách 2 ta đang sử dụng pattern DI, và ta cũng thấy phần nào lơi ích của DI

Trong DI ta có 4 thành phần:

+Service interface

+Service implement

+Client

+DI container

 

Trong ví dụ trên, ở cách 2, nếu ta có 1 interface là ServiceInf:

interface ServiceInf{
    public function getValue();
}

và các class MyService và NewMyService đều implement ServiceInf thì ta sẽ có:

+ServiceInf tương ứng với Service interface

+MyService và NewMyService tương ứng với Service implement

+Obj tương ứng với Client

+function init() tương ứng với DI container

 

Có thể tham khảo http://en.wikipedia.org/wiki/Dependency_injection để hiểu rõ hơn về DI và xem các lợi ích hay các vấn đề gặp khi dùng DI

 

Có 3 loại injection:

+constructor injection: các phụ thuộc được đưa vào thông qua constructor

+setter injection: phụ thuộc được đưa vào thông qua các hàm setter, như ví dụ ở trên

+interface injection: loại này ít dùng hơn so với 2 loại trên

 

DI sẽ thể hiện sức mạnh của nó nếu được dùng với các file config, khi đó container sẽ đọc file config, khởi tạo các đối tượng và tự động inject các phụ thuộc nếu cần. Ta có thể tìm thấy ví dụ ở Yii Framework vì thực chất Yii cũng là 1 DI container. Trong file config của Yii, ta có thể bắt gặp như sau:

'db' => array(
            'class'=>'CDbConnection',
            'connectionString' => 'mysql:host=localhost;dbname=mydb',
            'emulatePrepare' => true,
            'username' => 'root',
            'password' => 'root',
            'charset' => 'utf8',
 ),
        
'session' => array(
            'class' => 'system.web.CDbHttpSession',
            'connectionID' => 'db',
            'autoCreateSessionTable'=>true
 )

khi đọc file config, Yii sẽ khởi tạo 1 đối tượng CDbConnection được gán id là db, CDbConnection phụ thuộc các String object như username, password. Tiếp đó Yii khởi tạo 1 đối tương CdbHttpSession, đối tượng này phụ thuộc vào 1 CDbConnection là connectionID, và được config là refenrence tới db ở trên.

Điểm mạnh của việc inject thông qua file config là dễ dàng thay đổi các service implement, đồng thời việc thay đổi cũng không làm sửa đổi code.

 

3. Yii Dependency Injection:

Như đã thấy, Yii có sử dụng DI trong thiết kế của mình, nhưng DI của Yii theo hướng gọn nhẹ, đơn giản, nên không có các chức năng như inject dùng Reflection hay annotation, và không mạnh mẽ như DI của Symphony hay Zend Framework.

PHP có 1 DI container library nổi tiếng là PHP-DI: http://php-di.org/ , hiện tại version 4, chạy với PHP5.4 trở lên, thực chất các framework như Symphony hay Zend đêì sử dụng PHP-DI

Mình có wrap PHP-DI version 3.5 https://github.com/mnapoli/PHP-DI/tree/3.x/doc (chạy với PHP5.3 trở lên) vào 1 extension cho Yii, có thể download tại đây:

Cách sử dung:

+Extract vào folder extensions

+Config:

'di'=>array(
          'class'=>'ext.di.DIComponent',
          'configFile'=>'container.php',
          'cache'=>'ArrayCache',
          'useReflection'=>true,
          'useAnnotations'=>true,
          'definitionsValidation'=>true,
          'cacheNamespace'=>''
),

Trong đó configFile là file config của PHP-DI, nằm trong folder protected/config

 

+Ví dụ:

Có 2 model là User và Profile, mỗi User có 1 Profile tương ứng

Tạo 2 service tương ứng cho User và Profile

interface UserService {

       public function findUser($id);

}
interface ProfileService {

       public function findUserProfile($userId);

}

 

implement cho 2 service:

 

class UserServiceImpl implements UserService {
        
        /**
         * @Inject
         * @var ProfileService
         */
        private $profileService;
    
        public function findUser($id) {
                $user=new User();
                $user->id=$id;
                $user->name='test user';
                
                $user->profile=  $this->profileService->findUserProfile($id);
                
                return $user;
        }
}
class ProfileServiceImpl implements ProfileService {
        public function findUserProfile($userId) {
                $profile=new Profile;
                $profile->email='test@email.com';
                $profile->birthday='01/01/1990';
                
                return $profile;
        }
}

 

Và trong file protected/config/contaner.php, config như sau:

 

return array(
    'UserService'=>array(
        'class'=>'UserServiceImpl'
    ),
    'ProfileService'=>array(
        'class'=>'ProfileServiceImpl'
    )
);

 

Và sử dụng trong controller như sau:

 

class TestInjectionController extends Controller {
    
        /**
         * @Inject
         * @var UserService 
         */
        private $userService;
        
    
        public function actionViewUser($id){
                $user=  $this->userService->findUser($id);
                echo 'Id: '.$user->id . '<br>';
                echo 'Name: '.$user->name.'<br>';
                echo 'Email: '.$user->profile->email.'<br>';
                echo 'Birthday: '.$user->profile->birthday.'<br>';
        }
}

 

All done, welcome mọi góp ý và sửa chữa :)


Leave a Comment

Fields with * are required.