+4

PHP magic methods

Mở đầu

Nếu bạn đang đọc đến dòng này, có nghĩa là bạn đang đọc bài viết của mình trên Viblo tại địa chỉ https://viblo.asia/posts

Bạn tự hỏi tại sao mình lại mở đầu như vậy, tất nhiên là ko phải vì hâm rồi, mình viết để cho ai đó đọc được bài này trên 1 trang khác biết rằng trang đó đang clone trang Viblo này thôi =))

Giờ là đến phần chính, chúng ta sẽ tìm hiểu về các Magic methods trong PHP.

Nội dung

Giả sử ta có 1 class như thế này:

class Tweet {

}

Và tiếp theo chúng ta sẽ xem magic method cụ thể trong class này như thế nào.

Construct && Destruct

Trong php thì magic method __construct() rất là phổ biến mà chúng ta hay thường gặp nhất.

Hàm __construct() được gọi 1 cách tự động khi mà object được khởi tạo lần đầu tiên. Điều này có nghĩa chúng ta có thể inject các thông số và phụ thuộc để thiết lập object.

public function __construct($id, $text)
{
  $this->id = $id;
  $this->text = $text;
}

$tweet = new Tweet(123, 'Hello world');

Khi ta tạo ra một instance mới của đối tượng Tweet, các param được truyền vào object thông qua __construct() method. Như chúng ta thấy, không cần phải gọi phương thức vì nó sẽ được tự động gọi.

Khi có 1 object đc extend, thì object này cũng có __construct(), và chúng ta sẽ muốn dùng cả construct của cha lẫn con:

class Entity {

  protected $meta;

  public function __construct(array $meta)
  {
    $this->meta = $meta;
  }

}

class Tweet extends Entity {

  protected $id;
  protected $text;

  public function __construct($id, $text, array $meta)
  {
    $this->id = $id;
    $this->text = $text;

    parent::__construct($meta);
  }

}

Sau khi tạo đối tượng, đôi khi chúng ta ghét nó mà muốn destroy nó đi, thì method __destruct() được gọi. Giống construct, destruct được gọi tự động, php sẽ care vụ này.

public function __destruct()
{
  $this->connection->destroy();
}

Sau cuộc mây mưa với object, construct làm tung tóe mọi thứ, thì destruct sẽ làm sạch tất cả, ví dụ cắt như kết nối tới đâu đó, bla bla...

Nhưng thực sự mà nói thì chúng ta chả mấy khi cần đến cái method này.

Getting && Setting

Khi làm việc với object, chúng ta luôn cần phải truy cập các thuộc tính của object, đó là điều đương nhiên, chứ object sinh ra để làm gì :v

$tweet = new Tweet(123, 'hello world');
echo $tweet->text; // 'hello world'

Tuy nhiên, nói chung, các thuộc tính của một đối tượng sẽ được thiết lập protected và do đó cố gắng truy cập vào một property theo cách này sẽ gây ra lỗi (gì đó).

Do đó, __get() method đc sử dụng đến:

public function __get($property)
{
  if (property_exists($this, $property)) {
    return $this->$property;
  }
}

Phương pháp __get() chấp nhận tên của các property mà ta đang tìm kiếm như một tham số. Trong đoạn mã trên, đầu tiên ta kiểm tra xem property có tồn tại trên các đối tượng hiện tại hay không. Nếu có, ta có thể trả lại chúng từ các đối tượng.

Chúng ta không cần phải gọi phương thức __get() vì PHP sẽ tự động gọi chúng khi ta cố gắng truy cập vào một property mà không phải public.

Nếu chúng ta cố gắng thiết lập một property không thể access, __set() method sẽ được kích hoạt. Phương thức này lấy property mà chúng ta đã cố gắng truy cập và các giá trị mà ta đang cố để thiết lập như là hai đối số.

Dúng như này chẳng hạn:

public function __set($property, $value)
{
  if (property_exists($this, $property)) {
    $this->$property = $value;
  }
}

$tweet->text = 'Setting up my twttr';
echo $tweet->text; // 'Setting up my twttr'

Kiểm tra tồn tại không 1 thuộc tính

Chúng ta vấn hay dùng isset() để kiểm tra có key nào trong 1 mảng hay không? isset($a['b']). Còn đối với 1 object mà có 1 thuộc tính không public thì cũng dùng tương tự như thế:

public function  __isset($property)
{
  return isset($this->$property);
}

isset($tweet->text); // true

Unset 1 thuộc tính

Tương tự isset(), unset() khi dùng để unset 1 thuộc tính không public thì sẽ dùng như thế này:

public function __unset($property)
{
  unset($this->$property);
}

To String

__toString() method sẽ trả về 1 string từ object:

public function __toString()
{
  return $this->text;
}

$tweet = new Tweet(1, 'hello world');
echo $tweet; // 'hello world'

Điều này có nghĩa là khi nào mà ta dùng object như 1 string, ví dụ echo $tweet thì method này sẽ được gọi.

Sleep && wakeup

Ví dụ, nếu ta muốn lưu trữ một đối tượng trong cơ sở dữ liệu, đầu tiên ta sẽ serialise nó, lưu nó, và sau đó khi ta muốn có nó một lần nữa ta lại unserialise nó. Đó là cách thường làm với hàm serialize().

Phương pháp __sleep() cho phép ta xác định các thuộc tính của đối tượng cần được serialise.

$tweet = new Tweet(123, 'Hello world', new PDO ('mysql:host=localhost;dbname=twttr', 'root'));

Ví dụ trên ta có 1 object chứa cả 1 connect tới database, khi mà serialise cái object này, chúng ta ko muốn cả cái connect DB đi theo, __sleep() method chỉ đơn giản là trả về 1 mảng các thuộc tính cần để serialise:

public function __sleep()
{
  return array('id', 'text');
}

Khi lấy object trở lại từ DB, chúng ta cần re-establish cả DB connection, lúc này ta cần đến __wakeup() method:

public function __wakeup()
{
  $this->storage->connect();
}

Call

__call() method sẽ được gọi nếu chúng ta muốn có 1 cái gì đó được xử lý và gán lại vào object (mình hiểu là thế 😄), ví dụ với cái class Tweet của chúng ta:

class Tweet extends {

  protected $id;
  protected $text;
  protected $meta;

  public function __construct($id, $text, array $meta)
  {
    $this->id = $id;
    $this->text = $text;
    $this->meta = $meta;
  }

  protected function retweet()
  {
    $this->meta['retweets']++;
  }

  protected function favourite()
  {
    $this->meta['favourites']++;
  }

  public function __get($property)
  {
    var_dump($this->$property);
  }

  public function __call($method, $parameters)
  {
    if (in_array($method, array('retweet', 'favourite')))
    {
      return call_user_func_array(array($this, $method), $parameters);
    }
  }
}

$tweet = new Tweet(123, 'hello world', array('retweets' => 23, 'favourites' => 17));

$tweet->retweet();
$tweet->meta; // array(2) { ["retweets"]=> int(24) ["favourites"]=> int(17) }

Chúng ta có thể thấy như thế này:

  • khởi tạo object với các thuộc tính: id = 123, text = hello word, và meta là 1 mảng có retweets = 23, favourites = 17, method __construct() đó.
  • chạy thằng retweet() method, lúc này method __call() sẽ chịu trách nhiệm gọi retweet
  • $tweet->meta: lúc này thì method __get() thực hiện nhiệm vụ.

Cloning

Khi chúng ta tạo các bản sao của 1 object trong PHP, chúng có liên kết với object gốc ban đầu, do đó thay đổi object ban đầu sẽ dẫn tới các bản sao cũng thay đổi.

$sheep1 = new Sheep();
$sheep2 = $sheep1;

$sheep2->name = "Polly";
$sheep1->name = "Dolly";

echo $sheep1->name; // Dolly
echo $sheep2->name; // Dolly

Do đó, muốn có 1 bản sao hoàn toàn độc lập thì ta dùng clone

$sheep1 = new Sheep();
$sheep2 = clone $sheep1;

$sheep2->name = "Polly";
$sheep1->name = "Dolly";

echo $sheep1->name; // Dolly
echo $sheep2->name; // Polly

Invoke

__invoke() method cho phép chúng ta sử dụng 1 object như thế nó là 1 function:

class User {

  protected $name;
  protected $timeline = array();

  public function __construct($name)
  {
    $this->name = $name;
  }

  public function addTweet(Tweet $tweet)
  {
    $this->timeline[] = $tweet;
  }

}

class Tweet {

  protected $id;
  protected $text;
  protected $read;

  public function __construct($id, $text)
  {
    $this->id = $id;
    $this->text = $text;
    $this->read = false;
  }

  public function __invoke($user)
  {
    $user->addTweet($this);
    return $user;
  }

}

$users = array(new User('Ev'), new User('Jack'), new User('Biz'));
$tweet = new Tweet(123, 'Hello world');
$users = array_map($tweet, $users);

var_dump($users);

array_map sẽ lặp qua mảng các $user rồi sử dụng $tweet như là 1 hàm, sau đó mỗi $tweet sẽ được thêm vào timeline của mỗi $user

Kết Luận

  • Cảm ơn Google Search đã tài trợ bài viết này.
  • Chúng ta không cần sử dụng tất cả các magic method nhưng trong những trường hợp cụ thể, mỗi 1 magic method mang lại cách giải quyết vấn đề 1 cách hiệu quả :v

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí