VHCSoft JSC

Just another VHCSoft weblog

PHP 5.3: Lambda Function & Closure

without comments

Fabien Potencier
http://fabien.potencier.org/article/17/on-php-5-3-lambda-functions-and-closures

PHP 5.3 sẽ có rất nhiều tính năng mới. Một trong những tính năng quan trọng là việc hỗ trợ lambda function và closure. Tôi sẽ không nói nhiều về lambda function hay closure là gì, bạn có thể tìm thấy nhiều bài viết mô tả chúng rất chi tiết. Một cách tóm tắt, lambda function là một function PHP vô danh có thể chứa trong 1 biến và cung cấp như một tham số cho function hay method khác. Closure is a lambda function that is aware of its surrounding context.

Ví dụ đầu tiên về lambda function và closure là kết hợp với các function có sẵn của PHP array_map(), array_reduce(), và array_filter() :

1
2
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

Ví dụ trên lọc mảng input, lấy các giá trị lớn hơn 2:

1
$output == array(2 => 3, 3 => 4, 4 => 5)

function ($v) { return $v > 2; } là một lambda function và có thể dùng lại trong 1 biến:

1
2
3
$max_comparator = function ($v) { return $v > 2; };
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comparator);

Nhưng nếu tôi muốn thay đổi giá trị dùng để lọc mảng thì làm thế nào? Tôi có thể tạo ra một lambda function khác hoặc sử dụng closure:

1
2
3
4
5
6
7
$max_comparator = function ($max)
{
  return function ($v) use ($max) { return $v > $max; };
};
 
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comparator(2));

Bây giờ, function $max_comparator nhận số dùng để lọc và trả về một function phụ thuộc vào số đó. Với ví dụ này, hi vọng bạn thấy được phần lợi ích mà lambda function và closure đem lại.

Closure có thể dùng để thực thi một Y-combinator ‘khó hiểu’, như được mô tả trong bài viết của Stanislav Malyshev:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Y($F)
{
 $func = function ($f) { return $f($f); };
 
 return $func(function ($f) use($F)
 {
  return $F(function ($x) use($f)
  {
   $ff = $f($f);
   return $ff($x);
  });
 });
}

Hôm nay, tôi sẽ đề cập đến một cách sử dụng khác với lambda function và closure. Nó sẽ làm cho code của bạn trở nên đơn giản rất nhiều.

Nếu bạn đọc blog của tôi gần đấy, bạn sẽ biết rằng tôi đang quan tâm đến dependency injection. Bài này sẽ hướng dẫn bạn cách thực hiện một dependency injection container đơn giản nhưng đầy đủ tính năng, sử dụng những tính năng mới của PHP 5.3.

Một dependency injection container phải có khả năng quản lý 2 loại dữ liệu khác nhau: object và parameter. Một objects phải được tạo ở lần đầu tiên chúng được gọi từ container.

Dùng một lớp đơn giản thực thi phương thức magic __get() và __set(), chúng ta có thể dễ dàng quản lý cả object và parameter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DIContainer
{
 protected $values = array();
 
 function __set($id, $value)
 {
   $this->values[$id] = $value;
 }

 function __get($id)
 {
   return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id];
 }
}

Sử dụng container thực sự đơn giản:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$container = new DIContainer();
 
// define the parameters
$container->cookie_name = 'SESSION_ID';
$container->storage_class = 'SessionStorage';
 
// defined the objects
$container->storage = function ($c)
{
 return new $c->storage_class($c->cookie_name);
};
$container->user = function ($c)
{
 return new User($c->storage);
};
 
// get the user object
$user = $container->user;
 
// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $user = new User($storage);

Ví dụ tôi sử dụng trong bài này tương tự như ví dụ đã dùng trong loạt bài về dependency injection.

Việc xác định một object được thực hiện bằng cách xác định một lambda function trả về một instance của object.

Phương thức __get() kiểm tra xem giá trị tương ứng với key có là 1 PHP callable ko (và lambda functions là callable) để tạo sự khác nhau giữa một object và một parameter.

Và cũng chú ý cách chúng ta gọi một lambda:

1
$this->values[$id]($this)

Thủ thuật ở đây là cung cấp container như một tham số của lambda function vì thế nó có thể sử dụng container để truy cập các tham số và object quản lý bởi container.

Chúng ta có thể cải tiến container bằng cách thêm vào khả năng thông báo lỗi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DIContainer
{
 protected $values = array();
 
 function __set($id, $value)
 {
 $this->values[$id] = $value;
 }
 
 function __get($id)
 {
 if (!isset($this->values[$id]))
 {
 throw new InvalidArgumentException(sprintf('Value "%s" is not defined.', $id));
 }
 
 return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id];
 }
}

Chúng ta vẫn còn phải thực thi một tính năng quan trọng của bất kì dependency injection container nào: shared instance. Nó được thực hiện như sau:

1
2
3
4
5
6
7
8
9
10
11
$container->user = function ($c)
{
 static $object;
 
 if (is_null($object))
 {
 $object = new User($c->storage);
 }
 
 return $object;
};

Bằng cách khai báo một biến static trong lambda function, ở lần gọi đầu, object được tạo và trả về. Trong các lần gọi sau, instance đã có sẽ được trả về.

Nó hoạt động tốt, nhưng sẽ bị lặp lại và tiềm ẩn lỗi. Với các instance khác nhau, chúng ta cần thêm đoạn code mẫu này. Và thật phức tạp khi muốn hỗ trợ shared instances như là một tính năng của chính container. Nhờ có closures, việc này được thực hiện dễ dàng:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DIContainer
{
 // ...
 
 function asShared($callable)
 {
 return function ($c) use ($callable)
 {
 static $object;
 
 if (is_null($object))
 {
 $object = $callable($c);
 }
 
 return $object;
 };
 }
}

Phương thức asShared() đóng gói lambda function để thêm vào các logic cần thiết chúng ta sẽ thấy sau. Khai báo một object là duy nhất do container cung cấp trở nên đơn giản:

1
2
3
4
$c->user = $c->asShared(function ($c)
{
 return new User($c->storage);
});

Với ít hơn 30 dòng code PHP, chúng ta đã thực hiên đầy đủ tính năng của một dependency injection container. Thật hoàn hảo!

Nếu chúng ta bỏ shared instance, và xác định parameter và object dùng lambda, chúng ta có thể nén code trong một tweet:

1
2
3
4
5
class Container {
 protected $s=array();
 function __set($k, $c) { $this->s[$k]=$c; }
 function __get($k) { return $this->s[$k]($this); }
}

Tôi gọi tweet container này là twittee, và có website giới thiệu và kho chứa github riêng.

PHP 5.3 thực sự là một bước nhảy vọt của PHP.

Written by quannh

April 17th, 2009 at 1:29 pm

Posted in php

Tagged with , ,

Phần 3: Symfony Service Container

without comments

Fabien Potencier
http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container

Trong các bài viết trước, tôi giới thiệu các khái niệm cơ bản của Dependency Injection, giúp bạn hiểu rõ hơn về việc implement chúng ta sẽ đề cập trong bài này và các bài sau. Bây giờ chúng ta sẽ nói về việc implement service container trong Symfony 2.

Dependency Injection Container trong Symfony được quản lý bởi lớp sfServiceContainer. Lớp này thực thi các tính năng cơ bản chúng ta đã đề cập đến ở bài trước.

Symfony Service Container là một component độc lập, có sẵn trong kho chứa symfony: http://svn.symfony-project.com/components/dependency_injection/trunk/. Chú ý rằng component này vẫn đang được phát triển, hoàn thiện.

Trong Symfony, service là bất kì object nào được quản lý bởi container. Trong ví dụ về Zend_Mail ở bài trước, chúng ta có 2 service: mailer service và mail_transport service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Container
{
  static protected $shared = array();
 
  protected $parameters = array();
 
  public function __construct(array $parameters = array())
  {
    $this->parameters = $parameters;
  }
 
  public function getMailTransport()
  {
    return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
      'auth'     => 'login',
      'username' => $this->parameters['mailer.username'],
      'password' => $this->parameters['mailer.password'],
      'ssl'      => 'ssl',
      'port'     => 465,
    ));
  }
 
  public function getMailer()
  {
    if (isset(self::$shared['mailer']))
    {
      return self::$shared['mailer'];
    }
 
    $class = $this->parameters['mailer.class'];
 
    $mailer = new $class();
    $mailer->setDefaultTransport($this->getMailTransport());
 
    return self::$shared['mailer'] = $mailer;
  }
}

Nếu lớp Container thừa kế từ lớp sfServiceContainer, code trở nên đơn giản hơn chút:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Container extends sfServiceContainer
{
  static protected $shared = array();
 
  protected function getMailTransportService()
  {
    return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
      'auth'     => 'login',
      'username' => $this['mailer.username'],
      'password' => $this['mailer.password'],
      'ssl'      => 'ssl',
      'port'     => 465,
    ));
  }
 
  protected function getMailerService()
  {
    if (isset(self::$shared['mailer']))
    {
      return self::$shared['mailer'];
    }
 
    $class = $this['mailer.class'];
 
    $mailer = new $class();
    $mailer->setDefaultTransport($this->getMailTransportService());
 
    return self::$shared['mailer'] = $mailer;
  }
}

Mã nguồn trở nên rõ ràng hơn với những thay đổi sau:

*

Tên method gắn với hậu tố Service. Thông thường, một service sẽ được xác định bởi 1 method có tên với tiền tố get và hậu tố Service. Mỗi service được xác định duy nhất, với tên phương thức không kèm theo tiền tố, hâu tố. Ví dụ phương thức getMailTransportService() xác định service có tên mail_transport.
*

Method bây giờ là protected. Chúng ta sẽ đề cập đến cách lấy các service từ container ở bên dưới.
*

Container có thể dùng như một mảng để lấy giá trị của các parameter ($this['mailer.class']).

Một service identifier phải là duy nhất và tạo bởi chữ cái, chữ số, dấu gạch dưới, và dấu chấm. Dấu chấm được dùng để xác định “namespaces” trong container (ví dụ mail.mailer và mail.transport).

Sử dụng lớp container mới như sau:

1
2
3
4
5
6
7
8
9
10
require_once 'PATH/TO/sf/lib/sfServiceContainerAutoloader.php';
sfServiceContainerAutoloader::register();
 
$sc = new Container(array(
  'mailer.username' => 'foo',
  'mailer.password' => 'bar',
  'mailer.class'    => 'Zend_Mail',
));
 
$mailer = $sc->mailer;

Do lớp Container thừa kế từ lớp sfServiceContainer, chúng ta có một interface rõ ràng như sau:

*

Lấy Service:

1
2
3
4
5
6
      if ($sc->hasService('mailer'))
      {
        $mailer = $sc->getService('mailer');
      }
       
      $sc->setService('mailer', $mailer);

*

Ta cũng có thể lấy service thông qua property của lớp:

1
2
3
4
5
6
      if (isset($sc->mailer))
      {
        $mailer = $sc->mailer;
      }
       
      $sc->mailer = $mailer;

*

Lấy Parameters:

1
2
3
4
5
6
7
8
9
10
11
12
      if (!$sc->hasParameter('mailer_class'))
      {
        $sc->setParameter('mailer_class', 'Zend_Mail');
      }
       
      echo $sc->getParameter('mailer_class');
       
      // Override all parameters of the container
      $sc->setParameters($parameters);
       
      // Adds parameters
      $sc->addParameters($parameters);

*

Ta cũng có thể lấy parameters dưới dạng mảng:

1
2
3
4
5
6
      if (!isset($sc['mailer.class']))
      {
        $sc['mailer.class'] = 'Zend_Mail';
      }
       
      $mailerClass = $sc['mailer.class'];

*

Bạn cũng có thể duyệt qua tất cả các service của một container:

1
2
3
4
      foreach ($sc as $id => $service)
      {
        echo sprintf("Service %s is an instance of %s.n", $id, get_class($service));
      }

Lớp sfServiceContainer hữu ích khi số service cần quản lý ko nhiều; mặc dù bạn vẫn phải tự làm nhiều thứ, và lặp lại nhiều đoạn code. Nếu số service cần quản lý tăng lên, chúng ta cần 1 cách tốt hơn để mô tả các service.

Đó là lý do tại sao lớp sfServiceContainer thường ko được sử dụng trực tiếp. Nhưng dù sao nó cũng giúp chúng ta hiểu về cách implementdependency injection container trong Symfony.

Trong bài tiếp theo, chúng ta sẽ đề cập đến lớp sfServiceContainerBuilder, giúp xác định tiến trình dịch vụ.

Written by quannh

March 31st, 2009 at 4:27 am

Phần 2: Dependency Injection Container

without comments

Fabien Potencier
http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container

bài đầu tiên trong loạt bài về Dependency Injection, tôi đã cố gắng đưa ra các ví dụ cụ thể về Dependency Injection trong ứng dụng web. Hôm nay, tôi sẽ nói về Dependency Injection Containers.

Đầu tiên, hãy bắt đầu với câu phát biểu sau:

Thông thường, bạn không cần một Dependency Injection Container để thấy được lợi ích của Dependency Injection.

Nhưng khi bạn cần quản lý rất nhiều object phụ thuộc lẫn nhau, Dependency Injection Container trở nên hữu ích (ví dụ trong một framework).

Nếu bạn nhớ lại ví dụ ở bài trước, để tạo object User trước tiên bạn cần tạo object SessionStorage. Ko quá phức tạp, nhưng để tạo một đối tượng bạn cần phải biết tất cả các đối tượng phụ thuộc:

1
2
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);

Trong bài viết tiếp theo, tôi sẽ nói về implement một Dependency Injection Container cho Symfony 2. Do tôi muốn implement nó theo cách độc lập, ko gắn với Symfony, tôi sẽ lấy ví dụ ở Zend Framework để minh họa cho bài viết của mình.

Sẽ ko có cuộc tranh luận nào về các framework PHP ở đây. Tôi thực sự đánh giá cao các component của Zend Framework, và trên thực tế, rất nhiều thư viện của Zend được sử dụng trong một project Symfony.

Thư viện Zend Framework Mail mặc định sử dụng function mail() của PHP để gửi email nên ko được linh hoạt cho lắm. Nhưng ta có thể dễ dàng thay đổi behavior này bằng cách cung cấp một object transport. Đoạn code dưới đây chỉ ra cách tạo một object Zend_Mail để gửi emails sử dụng tài khoản Gmail:

1
2
3
4
5
6
7
8
9
10
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
 'auth' => 'login',
 'username' => 'foo',
 'password' => 'bar',
 'ssl' => 'ssl',
 'port' => 465,
));
 
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);

Để bài viết ngắn gọn, tôi sử dụng các ví dụ đơn giản. Tất nhiên, trong các ví dụ đơn giản này, ta ko thấy cần thiết phải có một container. Hãy nghĩ rằng những ví dụ này là một phần nhỏ của tập các object cần được quản lý bởi container.

Một Dependency Injection Container là một object biết cách tạo và cấu hình cho các object. Và để làm được việc này, nó cần biết các tham số khởi tạo và mối quan hệ giữa các object.

Dưới đây là một container đơn giản cho ví dụ Zend_Mail ở trên:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Container
{
 public function getMailTransport()
 {
 return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
 'auth' => 'login',
 'username' => 'foo',
 'password' => 'bar',
 'ssl' => 'ssl',
 'port' => 465,
 ));
 }
 
 public function getMailer()
 {
 $mailer = new Zend_Mail();
 $mailer->setDefaultTransport($this->getMailTransport());
 
 return $mailer;
 }
}

Việc sử dụng lớp container khá đơn giản:

1
2
$container = new Container();
$mailer = $container->getMailer();

Khi sử dụng container, chúng ta lấy một object mailer mà ko cần biết về cách tạo nó; tất cả chi tiết về cách tạo một instance của mailer được đặt trong container. Mail transport phụ thuộc sẽ được container tự động thêm vào thông qua lời gọi getMailTransport(). Tất cả sức mạnh của container nằm trong lời gọi đơn giản này!

Nhưng độc giả tinh ý sẽ thấy 1 vấn đề ở đây. Mọi thứ được cố định trong container! Vì thế chúng ta cần 1 bước nữa để thêm các tham số:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Container
{
 protected $parameters = array();
 
 public function __construct(array $parameters = array())
 {
 $this->parameters = $parameters;
 }
 
 public function getMailTransport()
 {
 return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
 'auth' => 'login',
 'username' => $this->parameters['mailer.username'],
 'password' => $this->parameters['mailer.password'],
 'ssl' => 'ssl',
 'port' => 465,
 ));
 }
 
 public function getMailer()
 {
 $mailer = new Zend_Mail();
 $mailer->setDefaultTransport($this->getMailTransport());
 
 return $mailer;
 }
}

Bây giờ ta có thể thay đổi tên và mật khẩu bằng cách cung cấp tham số cho container constructor:

1
2
3
4
5
$container = new Container(array(
 'mailer.username' => 'foo',
 'mailer.password' => 'bar',
));
$mailer = $container->getMailer();

Nếu bạn cần thay đổi lớp mailer để test, bạn có thể cung cấp tên của lớp như là 1 tham số:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Container
{
 // ...
 
 public function getMailer()
 {
 $class = $this->parameters['mailer.class'];
 
 $mailer = new $class();
 $mailer->setDefaultTransport($this->getMailTransport());
 
 return $mailer;
 }
}
 
$container = new Container(array(
 'mailer.username' => 'foo',
 'mailer.password' => 'bar',
 'mailer.class' => 'Zend_Mail',
));
$mailer = $container->getMailer();

Cuối cùng, mỗi khi bạn muốn lấy một mailer, bạn ko cần tạo một instance mới. Vì thế, ta có thể sửa lại container để trả về cùng một object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Container
{
 static protected $shared = array();
 
 // ...
 
 public function getMailer()
 {
 if (isset(self::$shared['mailer']))
 {
 return self::$shared['mailer'];
 }
 
 $class = $this->parameters['mailer.class'];
 
 $mailer = new $class();
 $mailer->setDefaultTransport($this->getMailTransport());
 
 return self::$shared['mailer'] = $mailer;
 }
}

Với property static $shared, mỗi khi bạn gọi phương thức getMailer(), nó sẽ trả về đối tượng đã tạo trong lần gọi đầu tiên.

Đây là những tính năng cơ bản khi implement Dependency Injection Container. Một Dependency Injection Container quản lý các object: từ khi khởi tạo cho đến bước cấu hình chúng. Các object ko biết rằng chúng được quản lý bởi một container và ko biết gì về container đó. Đó là lý do tại sao một container có thể quản lý bất kì đối tượng PHP nào. Container tỏ ra hữu ích hơn khi các đối tượng sử dụng dependency injection cho sự phụ thuộc của chúng, nhưng điều đó là ko bắt buộc.

Trong bài tiếp theo chúng ta sẽ nói về việc thực thi dependency injection container trong Symfony 2.

Written by quannh

March 29th, 2009 at 2:49 am

Phần 1: Dependency Injection

without comments

Fabien Potencier
http://fabien.potencier.org/article/11/what-is-dependency-injection

Đây là bài đầu tiên trong loạt bài về Dependency Injection và cách thực thi một Dependency Injection Container trong PHP.

Trong bài này, tôi sẽ giới thiệu về Dependency Injection với một ví dụ cụ thể để làm rõ lợi ích mà nó đem lại. Nếu bạn đã biết về Dependency Injection, bạn có thể bỏ qua bài này và đợi bài viết tiếp theo.

Dependency Injection là 1 trong những design pattern đơn giản nhất mà tôi biết. Có thể bạn đã từng sử dụng Dependency Injection. Nhưng thật khó để diễn giải nó. Tôi nghĩ một phần là do các ví dụ dùng để mô tả về Dependency Injection ko được trực quan. Tôi sẽ cố gắng đưa ra các ví dụ phù hợp với ngôn ngữ PHP. Do PHP có mục đích chính dành cho phát triển web, nên ta hãy lấy 1 ví dụ về Web.

Để vượt qua hạn chế của giao thức HTTP, các ứng dụng web cần có cách để lưu trữ thông tin về người dùng giữa các lần yêu cầu khác nhau. Thông tin này được lưu trữ trong cookie, hoặc tốt hơn là sử dụng cơ chế session của PHP:

1
$_SESSION['language'] = 'fr';

Đoạn code trên chứa thông tin về ngôn ngữ của người dùng trong biến session language. Nhờ đó, trong các lần yêu cầu tiếp theo của người dùng này, thông tin về ngôn ngữ đã được chứa trong mảng toàn cục $_SESSION:

1
$user_language = $_SESSION['language'];

Do Dependency Injection được dùng trong lập trình hướng đối tượng, nên chúng ta cần một lớp SessionStorage để đóng gói PHP session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SessionStorage
{
 function __construct($cookieName = 'PHP_SESS_ID')
 {
 session_name($cookieName);
 session_start();
 }
 
 function set($key, $value)
 {
 $_SESSION[$key] = $value;
 }
 
 function get($key)
 {
 return $_SESSION[$key];
 }
 
 // ...
}

… và lớp User:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User
{
 protected $storage;
 
 function __construct()
 {
 $this->storage = new SessionStorage();
 }
 
 function setLanguage($language)
 {
 $this->storage->set('language', $language);
 }
 
 function getLanguage()
 {
 return $this->storage->get('language');
 }
 
 // ...
}

Những lớp này rất đơn giản và dễ sử dụng:

1
2
3
$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();

Tất cả đều hoạt động tốt… cho đến khi bạn muốn thay đổi một thứ gì đó. Ví dụ bạn muốn đổi tên session? Dưới đây là 1 vài giải pháp:

*

Thêm trực tiếp tên khi gọi SessionStorage constructor trong lớp User:

1
2
3
4
5
6
7
8
9
      class User
      {
       function __construct()
       {
       $this->storage = new SessionStorage('SESSION_ID');
       }
       
       // ...
      }

*

Tạo 1 hằng số bên ngoài lớp User:

1
2
3
4
5
6
7
8
9
10
11
      class User
      {
       function __construct()
       {
       $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
       }
       
       // ...
      }
       
      define('STORAGE_SESSION_NAME', 'SESSION_ID');

*

Dùng tên của session là 1 tham số của User constructor:

1
2
3
4
5
6
7
8
9
10
11
      class User
      {
       function __construct($sessionName)
       {
       $this->storage = new SessionStorage($sessionName);
       }
       
       // ...
      }
       
      $user = new User('SESSION_ID');

*

Thêm 1 mảng các options cho lớp storage:

1
2
3
4
5
6
7
8
9
10
11
      class User
      {
       function __construct($storageOptions)
       {
       $this->storage = new SessionStorage($storageOptions['session_name']);
       }
       
       // ...
      }
       
      $user = new User(array('session_name' => 'SESSION_ID'));

Tất cả các giải pháp trên đều có vấn đề. Thêm trực tiếp tên session trong lớp User ko thực sự giải quyết vấn đề, sau này bạn muốn sửa lại bạn lại phải sửa lớp User. Sử dụng hằng số cũng là giải pháp tồi do lớp User bây giờ phụ thuộc vào giá trị mà hằng số được thiết lập. Cung cấp tên session dưới dạng tham số hay mảng các option là giải pháp tốt hơn, nhưng vẫn có vấn đề. Phương thức User constructor trở nên lộn xộn do tham số ko liên quan đến đối tượng.

Còn một vấn đề nữa ko thể giải quyết dễ dàng: làm thế nào để thay đổi lớp SessionStorage? Ví dụ, thay thế nó bằng một đối tượng mô phỏng để dễ test, hoặc chứa session trong database, bộ nhớ. Điều đó là ko thể với code hiện tại, trừ khi bạn thay đổi lớp User.

Sử dụng Dependency Injection. Thay vì tạo object SessionStorage trong lớp User, hãy đưa object SessionStorage vào trong object User bằng cách cung cấp nó như một tham số của phương thức khởi tạo:

1
2
3
4
5
6
7
8
9
class User
{
 function __construct($storage)
 {
 $this->storage = $storage;
 }
 
 // ...
}

Đó chính là Dependency Injection. Tất cả chỉ có vậy! Việc sử dụng lớp User bây giờ có chút thay đổi, trước tiên bạn cần tạo một đối tượng SessionStorage:

1
2
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);

Bây giờ, cấu hình cho đối tượng session storage trở nên đơn giản, và việc thay thế lớp session storage cũng khá dễ dàng. Mọi thứ có thể thực hiện mà ko cần thay đổi lớp User nhờ phân tách tốt hơn sự liên quan giữa các thành phần.

Website Pico Container mô tả Dependency Injection như sau:

“Dependency Injection là nơi các thành phần phụ thuộc vào nhau thông qua các phương thức khởi tạo, các phương thức, hay trực tiếp trong các field.”

Cũng như các design pattern khác, Dependency Injection cũng có một số anti-patterns. Website Pico Container có mô tả một số trong chúng.

Dependency Injection ko giới hạn trong phương thức khởi tạo:

*

Constructor Injection:

1
2
3
4
5
6
7
8
9
      class User
      {
       function __construct($storage)
       {
       $this->storage = $storage;
       }
       
       // ...
      }

*

Setter Injection:

1
2
3
4
5
6
7
8
9
      class User
      {
       function setSessionStorage($storage)
       {
       $this->storage = $storage;
       }
       
       // ...
      }

*

Property Injection:

1
2
3
4
5
6
      class User
      {
       public $sessionStorage;
      }
       
      $user->sessionStorage = $storage;

Thông thường, constructor injection được sử dụng cho các dependency bắt buộc, như trong ví dụ của chúng ta, còn setter injection được sử dụng cho các dependency ko bắt buộc, ví dụ như object cache.

Ngày nay, phần lớn các frameworks PHP hiện đại đều sử dụng Dependency Injection để cung cấp các thành phần riêng biệt nhưng có liên kết với nhau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage(array('database' => 'session', 'db_table' => 'session'));
$user = new sfUser($dispatcher, $storage, array('default_culture' => 'en'));
 
// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
 'auth' => 'login',
 'username' => 'foo',
 'password' => 'bar',
 'ssl' => 'ssl',
 'port' => 465,
));
 
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);

Nếu bạn quan tâm đến Dependency Injection, tôi khuyên bạn nên đọc bài giới thiệu của Martin Fowler hay bài trình bày của Jeff More. Bạn cũng có thể đọc bài trình bày tôi đã thực hiện cuối năm trước về Dependency Injection, ở đó tôi nói chi tiết hơn về ví dụ đã sử dụng trong bài viết này.

Đó là tất cả cho ngày hôm nay. Tôi hi vọng bạn đã hiểu rõ hơn về Dependency Injection. Trong bài tiếp theo, tôi sẽ nói về Dependency Injection Containers.

Written by quannh

March 28th, 2009 at 3:03 pm

Coding style (4): Comments

without comments

Comment thường không được coi trọng và viết đúng. Không comment làm cho chương trình khó đọc. Comment không đúng làm cho chương trình lộn xộn.
Việc comment cần tuân theo những nguyên tắc sau:
(code minh họa trong ngôn ngữ C, PHP)

- Comment đầu file

Đầu mỗi file (trừ file template trong lập trình web) cần phải có chú thích về file đó. Cụ thể như sau

1
2
3
4
5
6
7
8
9
/**
 * Sign Up Action [1]
 *
 * @package frontend [2]
 * @subpackage JobSeeker [3]
 * @author QuanNH <huu2uan@gmail.com> [4]
 * @copyright ABC Media JSC [5]
 * @version $Id$ [6]
 */

trong đó:

* [1]: chú thích file này dùng để làm gì, sử dụng như thế nào. Sau chú thích này là 1 dòng trống
* [2]: tên của application nếu có
* [3]: tên của module nếu có
* [4]: tên người viết và địa chỉ email. Nếu có nhiều người viết thì sẽ có nhiều dòng như vậy trong đó tên của người viết cuối sẽ được đặt ở trên, tên những người viết đầu tiên sẽ được đặt ở cuối
* [5]: tên công ty
* [6]: tên phiên bản. Giá trị $Id$ sẽ được SVN tự động thay thế khi export

- comment cho biến toàn cục

1
2
3
4
5
6
7
8
9
10
<?php

class Abc {
 /*
 * Thông tin cấu hình
 *
 * @var array
 */

 private $config;
}
1
2
3
4
5
<?php
$lastestResume // resume mới nhất
$jobSeekers // các job seeker
$jobSeeker // job seeker
?>

- comment cho function

đối với những function ngắn, ko phức tạp thì 1 dòng comment là đủ

1
2
3
4
5
// random: return an integer in the range [O. .r-1].
int random(int r)
{
  return (int) (Math.floor(Math.random()*r));
}

đối với các function phức tạp, cần có comment rõ ràng: chức năng của hàm, các tham số đầu vào, giá trị trả về, …

ví dụ về comment cho function

1
2
3
4
5
6
7
8
9
10
/**
 * Tìm object trong 1 array các object dựa theo method
 *
 * Dùng để tìm các object trong danh sách các kết quả trả về của câu truy vấn
 *
 * @param array $arr Danh sách các object
 * @param string $method
 * @param string $value Giá trị dùng để so sánh
 * @return mixed
 */

Comemnt trên có 3 phần:

1. Chú thích về tính năng của function. Sau chú thích này là 1 dòng trống
2. Chú thích về các tham số đầu vào (theo thứ tự). Mỗi tham số có 1 dòng chú thích. Bắt đầu bằng @param, sau đó đến TÊN KIỂU (array, string, boolean, int, ClassName, …), sau đó đó tên biến và cuối cùng là chú thích cho tên biến (biến này dùng để làm gì)
3. Giá trị trả về của function. Bắt đầu bằng @return, sau đó đến kiểu dữ liệu trả về (array, string, boolean, int, ClassName, …). Trong trường hợp function có nhiều kiểu dữ liệu thì sử dụng @return mixed, trường hợp function không trả về bất cứ giá trị gì, sử dụng @return void

- comment cho các khối chức năng trong function

Trong function thường có các khối chức năng nhỏ, mỗi khối chức năng này phải cách nhau 1 dòng và có chú thích đầu mỗi khối chức năng. Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Loại bỏ các field không nằm trong danh sách cho phép
$allowedFields = array('city_id', 'experience_id', 'career_level_id', 'degree_id', 'hear_about_id', 'country_id', 'email', 'password', 'name', 'address', 'home_phone', 'mobile_phone', 'gender', 'birth_year', 'website', 'activation_code', 'hear_text', 'target_job_1', 'target_job_2');
$jobSeekerInfo = $this->getRequest()->getParameterHolder()->getAll();
foreach ($jobSeekerInfo as $key => $value){
 if (!in_array($key,$allowedFields)){
 unset($jobSeekerInfo[$key]);
 }
}

// Mã hóa password sử dụng SHA256
$jobSeekerInfo['password'] = hash('sha256',$jobSeekerInfo['password']);

// Sinh mã kích hoạt nếu yêu cầu kích hoạt tài khoản
if ($this->config['ActivationCode'] == "yes"){
 $jobSeekerInfo['activation_code'] = md5(microtime().rand());
 $jobSeekerInfo['is_activated'] = false;
} else {
 $jobSeekerInfo['is_activated'] = true;
}

// Ngày đăng kí tài khoản
$jobSeekerInfo['sign_up_date'] = date("Y:m:d H:i:s", time());

————
source:
B.W.Kernighan, R.Pike – The Practice of Programming
An Vũ – Coding Style and Organization

Written by quannh

March 21st, 2009 at 12:31 am

Posted in programming style

Coding style (3): Idioms

without comments

Cũng giống như ngôn ngữ tự nhiên, ngôn ngữ lập trình cũng có các idiom, là các cách viết code chính tắc cho các trường hợp thông dụng.
Dưới đây là một số idiom (mã nguồn được viết trên ngôn ngữ C).
- Vòng lặp for
ví dụ đoạn code duyệt n phần tử của 1 mảng. Các cách viết sau đều đúng:

1
2
3
i=0;
while (i <= n-1)
  array[i++] = 1.0 ;

hoặc:

1
2
for (i = 0; i < n;)
  array[i++] = 1.0;

hoặc:

1
2
for (i = n; --i >= 0;)
  array[i] = 1.0;

Cách viết chính tắc như sau:

1
2
for (int i = 0; i < n; i++)
  array[i] = 1.0;

- 1 idiom phổ biến khác là gán giá trị cho biến trong biểu thức điều kiện của vòng lặp

1
2
while ((c = getchar()) != EOF)
  putchar(c);

- else-ifs
Khi có nhiều lựa chọn ta thường dùng chuỗi if…else if…else như sau:

1
2
3
4
5
6
7
8
9
if (condition 1)
  statement 1
else if (condition 2)
  statement 2
...
else if (condition n)
  statement n
else
  default-statement

Ví dụ cách viết các vòng lặp if lồng nhau

1
2
3
4
5
6
7
8
9
10
11
12
if (argc==3)
  if ((fin = fopen(argv[l], "r")) != NULL)
    if ((fout = fopen(argv[2], "w")) != NULL) {
      while ((c = getc(fin)) != EOF)
        putc(c, fout);
      fclose (fin); fclose(fout);
    } else
      printf ("Can't open output file %sn", argv[2]);
  else
    printf ("Can't open input file %sn" argv[1]);
else
  printf ("Usage: cp input file output filen");

có thể được viết sáng sủa hơn như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (argc != 3)
  printf ("Usage: cp inputfile outputfilen");
else if ((fin = fopen(argv[l], "r")) == NULL)
  printf ("Can't open inputfile %sn", argv[1]);
else if ((fout = fopen(argv[2], "w")) == NULL) {
  printf ("Can't open output file %sn", argv[2]);
  fclose(fin);
} else {
  while ((c = getc(fin)) != EOF)
    putc(c, fout);
  fclose(fin);
  fclose(fout) ;
}

một ví dụ khác với cấu trúc switch-case

1
2
3
4
5
6
7
switch (c) {
  case '-': sign = -1;
  case '+': c = getchar();
  case '.': break;
  default:  if (!isdigit(c))
              return 0;
}

cách viết sau tuy dài nhưng dễ đọc hơn

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (c) {
case '-':
  sign = -1;
case '+':
  c = getchar();
  break;
case '.':
  break;
default:
  if (!isdigit(c))
    return 0;
  break;
}

tuy nhiên cấu trúc else-if vẫn dễ nhìn hơn

1
2
3
4
5
6
7
8
if (c == '-') {
  sign = -1;
  c = getchar();
} else if (c == '+') {
  c = getchar();
} else if (c != '.' && !isdigit(c)) {
  return 0;
}

to be continued…
source: B.W.Kernighan, R.Pike – The Practice of Programming

Written by quannh

March 19th, 2009 at 5:51 am

Posted in programming style

Coding style (2): Expressions and Statements

without comments

Các quy tắc khi viết biểu thức:

- dùng dấu cách giữa các toán tử
Đây là 1 điều nhỏ nhặt nhưng có ích, code được chia thành các nhóm, dễ nhìn hơn
Ví dụ đoạn code sau:

1
2
for(n++;n<100;field[n++]='\0');
i='\0';return('n');

Sau khi format lại

1
2
3
for (n++; n < 100; field[n++] = '\0');
i = '\0';
return('n');

các viết vòng lặp for nên viết lại như sau

1
2
3
4
for (n++; n < 100; n++)
  field[n] = '\0';
i = '\0';
return 'n';

- viết code theo logic tự nhiên

cách viết ngược với logic thông thường luôn gây khó hiểu

1
if (!(block-id < actblks) || !(block-id >= unblocks))

sửa lại

1
if ((block-id >= actblks) || (block-id < unblocks))

- tránh dùng những biểu thức phức tạp
c, c++, java hỗ trợ các cách viết các biểu thức phức tạp

1
x += (xp=(2*k < (n-m) ? c[k+1] : d[k--]));

ta nên tách ra thành các biểu thức đơn giản hơn

1
2
3
4
5
if (2*k < n-m)
  xp = c[k+1];
else
  xp = d[k--];
x += xp;

to be continued…
ource: B.W.Kernighan, R.Pike – The Practice of Programming

Written by quannh

March 19th, 2009 at 4:02 am

Posted in programming style

Coding style (1): Đặt tên biến

without comments

Quy tắc đặt tên bạn thường phải tuân theo ở các ngôn ngữ là: chỉ gồm chữ cái, số và kí tự _ , -, ko được viết số ở đầu…
Chỉ cần ko phạm quy tắc này thì bạn có thể đặt tên biến thế nào tùy ý. Nhưng đặt tên sao cho ngắn gọn, dễ nhớ, chứa thông tin là vấn đề ko đơn giản.
Sau đây là một số nguyên tắc về cách đặt tên biến:

- Dùng tên đầy đủ cho biến global, và tên viết tắt cho biến local
+ Biến Global: được dùng ở mọi nơi trong chương trình, nên cần đủ dài để mô tả ý nghĩa, và nên kèm theo comment khi khai báo

1
int nPending = 0 ;     // current length of input queue

+ Biến local: ta nên sử dụng các kí tự viết tắt cho biến loại này, như: các biến i, j cho vòng lặp; s, t cho string.
Hãy so sánh 2 đoạn code sau:

1
2
for (theElementIndex = 0 ; theElementIndex < number0fElements; theElementIndex++)
  elementArray[theElementIndex] = theElementIndex;

1
2
for (i = 0 ; i < nelems; i++)
  elem[i] = i ;

rõ ràng đoạn code dưới trông sáng sủa và dễ nhìn hơn.

Có một vài quy tắc khác về cách đặt tên biến như: các hằng số luôn được viết hoa, các biến global thì viết hoa chữ cái đầu, ..Để biến dễ đọc ta có thể viết npending, numPending hay num_pending. Chọn cách nào là tùy mỗi người, nhưng bạn cần thống nhất theo style đó trong chương trình của mình.
Namespaces trong C++ và packages trong Java giúp quản lý scope của biến, do đó bạn có thể dễ dàng hơn trong việc đặt tên cho biến mà ko cần chọn tên quá dài.

- Nhất quán (Be consistent): tạo sự nhất quán trong cách đặt tên cho các biến có quan hệ với nhau

Dưới đây là code khai báo một lớp

1
2
3
class UserQueue {
  int noOfIternsInQ, frontOfTheQueue, queueCapacity;
  public int noOfUsersInQueue() { . . . }

Các biến này được đặt tên với từ “queue” được xuất hiện dưới dạng Q, Queue và queue. Nhưng các biến này thường được truy cập từ một biến (đối tượng) thuộc kiểu UserQueue nên tên của chúng ko cần thêm từ queue. Ví dụ với biến (đối tượng) queue của lớp trên, cách viết queue.queueCapacity sẽ gây trùng lặp, thừa thãi.

Ta có thể sửa lại như sau:

1
2
3
class UserQueue {
  int nIterns, front , capacity;
  public int nUsers () {. . .}

Code bây giờ trở thành:

1
2
queue.capacity++;
n = queue.nUsers();

- Đặt tên cho function
Tên function nên đặt theo quy tắc: “động từ + danh từ”, để mô tả ‘hành động’ của hàm đó, ví dụ:

1
now = date .getTime() ;

Đối với các function trả về giá trị boolean cần đặt tên để biết rõ giá trị trả về là true/false cho cái gì.

1
if(checkOctal(c))

sẽ mơ hồ hơn là

1
if(isOctal(c))

vì với cách đặt tên sau, ta hiểu hàm trả về true nếu tham số là octal, và false trong trường hợp ngược lại.

to be continued…
source: B.W.Kernighan, R.Pike – The Practice of Programming

Written by quannh

March 19th, 2009 at 1:40 am

Posted in programming style

Hello world!

without comments

1
2
<?php
  echo "Hello world!";

Written by admin

March 18th, 2009 at 11:39 am

Posted in VHCSoft