Pattern Lazy Initialization em PHP 5

PHP
Enviado por DeCo em Qua, 29/03/2006 - 15:55.PHP

Esta pattern descreve como recuperar dados do banco somente se for necessário e na hora de uso sem afetar o resto do sistema. O principal conceito é dar a sensação para os outros objetos de que o dado está disponível e pronto para usar ocultando o carregamento tardio sem que isso afete quem usa.

Exemplo básico:
Essa forma é bem comum e usada em várias linguagens.

  1. <?php
  2.  
  3. // classe de categoria qualquer
  4. class Category
  5. {
  6.     protected $id = null;
  7.    
  8.     public function __construct($id = null)
  9.     {
  10.         $this->id = (int) $id;
  11.     }
  12. }
  13.  
  14. // uma classe que irá fazer lazy da categoria para outros objetos
  15. class Post
  16. {
  17.     protected $id = null;
  18.    
  19.     protected $category_id = null;
  20.    
  21.     protected $title = 'Titulo';   
  22.    
  23.     /**
  24.      * Buffer, aqui será guardada a categoria após ser carregada
  25.      * @var Category
  26.      */
  27.     protected $category = null;
  28.  
  29.     public function __construct($id = null)
  30.     {
  31.         if ($id) {
  32.             $this->id = (int) $id;
  33.             $this->category_id = 4; // carrega os dados do banco
  34.         }
  35.     }
  36.    
  37.     public function setTitle($title)
  38.     {
  39.         $this->title = $title;
  40.     }
  41.     public function getTitle()
  42.     {
  43.         return $this->title;
  44.     }
  45.    
  46.     /**
  47.      * Retorna o objeto da categoria do post
  48.      *
  49.      * @return Category
  50.      */
  51.     public function getCategory()
  52.     {
  53.         // lazy, verifica se já foi carregada, ou carrega...
  54.         if ($this->category === null) {
  55.             $this->category = new Category($this->category_id);
  56.         }
  57.         return $this->category;
  58.     }
  59. }
  60.  
  61. ?>

Não tem muito o que falar sobre o método acima.

Modo Avançado:
O PHP possui um recurso interessante para fazer um lazy bem mais elaborado. O método mágico __get dos objetos permite tratar o acesso a atributos que não existem e retornar um valor. Em nosso caso, vamos utiliza-lo para fazer lazy carregando o objeto em um atributo.

  1. <?php
  2.  
  3. class Comment
  4. {
  5.     protected $id;
  6.    
  7.     protected $post_id = null;
  8.    
  9.     /**
  10.      *
  11.      * @var Post
  12.      */
  13.     public $post;
  14.    
  15.     public function __construct($id = null)
  16.     {
  17.         if ($id) {
  18.             $this->id = (int) $id;
  19.             $this->post_id = 5;
  20.         }
  21.        
  22.         // remove o membro para funcionar no __get
  23.         unset($this->post);
  24.     }
  25.    
  26.     /**
  27.      * Método mágico do PHP que é utilizado para acessar "atributos inexistentes"
  28.      * Esse foi o motivo do unset($this->post), após a criação ele será acessado
  29.      * diretamente da variavel.
  30.      *
  31.      * @return mixed
  32.      */
  33.     protected function __get($name)
  34.     {
  35.         switch ($name) {
  36.             case 'post':
  37.                 // lazy, carrega o objeto no membro e retorna
  38.                 if ($this->post_id) {
  39.                   
  40.                   // debug :)
  41.                   echo "Lazy\n";
  42.                   $this->post = new Post($this->post_id);
  43.                   return $this->post;
  44.                 } else {
  45.                     return null;
  46.                 }
  47.                 break;
  48.        
  49.             default:
  50.                 break;
  51.         }
  52.     }
  53. }
  54.  
  55. // Exemplo de uso
  56. $comment = new Comment(3);
  57. echo $comment->post->getTitle() . "\n";
  58. echo $comment->post->getTitle() . "\n";
  59.  
  60. ?>

Output:

Lazy
Titulo
Titulo

Existe também o método __set($name, $value) e da mesma forma só funciona se o atributo não existir. Note que você não precisa usar a váriavel de acesso com o mesmo nome do atributo.

  1. <?php
  2. class Test
  3. {
  4.     protected $var = null;
  5.     protected function __get($name)   
  6.     {
  7.         if ($name == 'test') {
  8.             if ($this->var == null) {
  9.                 $this->var = 'Aham!';
  10.             }
  11.             return $this->var;
  12.         }
  13.     }
  14. }
  15.  
  16. $test = new Test();
  17. echo $test->test;
  18. ?>

Output:

Aham!

Dessa forma o membro nunca irá existir e sempre passará pela verificação do __get. É útil para tornar atributos readonly combinando com o __set ou atualizando os dados caso o objeto for substituido (ex: $this->post_id = $value->getKey()).