Sự khác nhau giữa con trỏ this và self trong php

Bài viết này trình bày về lớp và đối tượng trong PHP. Lưu ý 1 số phần nội dung có thể rất khó hiểu với 1 số người mới học, chúng tôi sẽ cố gắng trình bày chi tiết và giải thích cặn kỹ nhất có thể, nếu có câu hỏi, xin vui lòng để lại comment bên dưới.

PHP 5 có sự thay đổi về mô hình đối tượng cho phép nhiều tính năng tốt hơn so với PHP 4 bao gồm phạm vi truy cập (visibility), trừu tượng (abstract), các phương thức và lớp cuối, các phương thức thần bí (magic methods), giao diện (interface), nhân bản (cloning) và dạng ám chỉ (typehinting).

PHP xem các đối tượng như các tham chiếu hoặc các xử lý, nghĩa là mỗi biến chứa 1 đối tượng tham chiếu hơn là 1 sao chép toàn bộ đối tượng.

Lớp (từ khóa class)
Một lớp được định nghĩa bắt đầu với từ khóa class, theo sau là tên của 1 lớp, và cặp dấu ngoặc tròn bên trong chứa các thuộc tính (properties) và các phương thức (methods) thuộc về lớp.

Tên lớp phải bắt đầu bằng 1 ký tự hoặc dấu gạch chân (_), theo sau là bất kỳ ký tự số, chữ cái hoa thường, hay dấu gạch chân và không trùng tên với các từ khóa của PHP. Tên lớp có thể diễn giải bằng biểu thức chính quy như sau: ^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$.

Một lớp có thể chứa các hằng số, biến (hay gọi là thuộc tính) và các hàm (hay gọi là phương thức). Ví dụ về 1 lớp cơ bản như sau:

<?php class BasicClass { // định nghĩa 1 thuộc tính --- dammio.com public $var1 = 'giá trị mặc định của thuộc tính'; // định nghĩa 1 phương thức --- dammio.com public function displayVar() { echo $this->var1; } } ?>

Biến giả $this luôn sẵn có khi 1 phương thức được gọi từ bên trong đối tượng. $this là 1 tham chiếu để gọi đối tượng (thường là đối tượng chứa phương thức, nhưng cũng có thể là đối tượng khác, nếu phương thức được gọi mang tính tĩnh từ bối cảnh của 1 đối tượng thứ hai. Cho đến PHP 7.0.0, việc gọi 1 phương thức không tĩnh (static) từ 1 bối cảnh không tương thích mang lại kết quả $this không được định nghĩa bên trong phương thức.

Tiếp theo là 1 số ví dụ về biến giả $this. Giả sử error_reporting (báo cáo lỗi) bị vô hiệu hóa trong ví dụ này; nếu không thì đoạn mã sau sẽ phát sinh ra các cảnh báo lỗi rất chặt chẽ tùy thuộc vào phiên bản PHP.

<?php class A { function foo() { if (isset($this)) { echo '$this được định nghĩa ('; echo get_class($this); echo ")\n"; } else { echo "\$this chưa được định nghĩa.\n"; } } } class B { function bar() { A::foo(); } } $a = new A(); // khởi tạo biến $a là 1 biến đối tượng lớp A --- dammio.com $a->foo(); // truy xuất đến phương thức foo() bằng dấu mũi tên -> A::foo(); // dùng 2 dấu :: chúng ta cũng có thể truy xuất đến phương thức foo() theo cách truy xuất tĩnh $b = new B(); // khởi tạo biến $b là 1 biến đối tượng lớp B $b->bar(); // truy xuất đến phương thức bar() trong lớp B bằng dấu mũi tên (->) B::bar(); // truy xuất theo kiểu tĩnh đến phương thức bar() ?> Kết quả của các ví dụ trên ở PHP 5: $this được định nghĩa (A) $this chưa được định nghĩa. $this được định nghĩa (B) $this chưa được định nghĩa. Kết quả của các ví dụ trên ở PHP 7: $this được định nghĩa (A) $this chưa được định nghĩa. $this chưa được định nghĩa. $this chưa được định nghĩa.

Từ khóa new
Để tạo 1 thể hiện của 1 lớp, chúng ta dùng từ khóa new. Một đối tượng sẽ luôn được tạo với 1 khởi tạo (constructor) được định nghĩa, nếu không sẽ phát sinh lỗi. Các lớp nên được định nghĩa trước khi tạo thể hiện (một số trường hợp thì việc này là bắt buộc).

Nếu 1 chuỗi chứa tên 1 lớp dùng từ khóa new, 1 thể hiện mới của lớp đó sẽ được tạo. Nếu 1 lớp là 1 không gian tên (namespace), tên đầy đủ của nó phải được dùng khi thực hiện điều này. Ví dụ sau mô tả chi tiết cách dùng khởi tạo 1 thể hiện thông qua 1 biến ($className) có giá trị y chang tên 1 lớp (SimpleClass).

<?php $instance = new SimpleClass(); // tạo thể hiện mới của lớp SimpleClass // Điều này có thể thực hiện với 1 biến --- dammio.com $className = 'SimpleClass'; // đặt giá trị biến $classname là 'SimpleClass' $instance = new $className(); // new $className() được thay thế bằng // giá trị của biến $className là 'SimpleClass' ở dòng trên, vì vậy ở đây // chúng ta cũng có thể viết lại câu lệnh là $instance = new SimpleClass(); ?>

Trong bối cảnh lớp, có thể tạo 1 đối tượng mới bằng new self và new parent. Khi gán 1 thể hiện đã được tạo của 1 lớp sang 1 biến mới, biến mới sẽ truy cập cùng 1 thể hiện như là 1 đối tượng mà được gán. Hành vi này giống như khi chuyển các thể hiện tới 1 hàm. Một phiên bản sao chép của 1 đối tượng đã được tạo có thể thực hiện bằng cách nhân bản (cloning) nó.

<?php $instance = new SimpleClass(); $assigned = $instance; $reference = & $instance; $instance->var = 'Hello dammio.com'; // $assigned sẽ có giá trị này $instance = null; // $instance và $reference có giá trị null var_dump($instance); var_dump($reference); var_dump($assigned); ?> Kết quả: NULL NULL object(SimpleClass)#1 (1) { ["var"]=> string(16) "Hello dammio.com" }

Trong ví dụ trên, chúng ta có 3 biến $instance, $assigned$reference; trong đó $reference là 1 con trỏ của $instance. Chúng ta gán biến thuộc tính tên là var cho biến đối tượng $instance với giá trị ‘Hello dammio.com’. Tuy nhiên, theo nhiều khuyến cáo, chúng ta không nên tạo biến thuộc tính theo kiểu này vì nó rất khó quản lý và có thể phát sinh lỗi khi sử dụng lớp SimpleClass ở nhiều tập tin khác nhau. Hàm var_dump() dùng để lấy thông tin về cấu trúc, giá trị, kiểu dữ liệu biến của 1 biến.

Thuộc tính và phương thức
Các phương thức và thuộc tính có không gian tên riêng biệt, vì vậy có thể có tên 1 thuộc tính và 1 phương thức trùng nhau. Tùy thuộc vào cách sử dụng mã nguồn, chúng ta có thể gọi đến 1 thuộc tính hay 1 phương thức mặc dùng chúng cùng tên. Ví dụ sau sẽ mô tả điều này.

class Foo { public $bar = 'thuộc tính --- dammio.com'; // biến thuộc tính $bar public function bar() { return 'phương thức --- dammio.com'; // hàm (phương thức) bar() trả về giá trị. } } $obj = new Foo(); // khởi tạo thể hiện mới echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL; // xuất giá trị thuộc tính ($obj->bar // gọi phương thức $obj->bar() // PHP_EOL là hiển thị văn bản xuống 1 dòng (EOL = end of line) Kết quả của ví dụ trên: thuộc tính --- dammio.com phương thức --- dammio.com

Từ khóa extends
Một lớp có thể 1 lớp khác thông qua từ khóa extends. Trong PHP, 1 lớp con chỉ được kế thừa 1 lớp cha duy nhất. Các thuộc tính và phương thức có thể được ghi đè bằng cách tạo lại các thuộc tính và phương thức này với cùng tên ở lớp con. Tuy nhiên, nếu lớp cha dùng 1 phương thức với từ khóa final thì lớp này không thể bị ghi đè.

<?php class ParentClass { function ShowMessage() { echo "Parent class - dammio.com<br/>"; } } class ChildClass extends ParentClass { // Định nghĩa lại phương thức, ghi đè lên phương thức cùng tên ở lớp ParentClass function ShowMessage() { echo "ChildClass class - dammio.com<br/>"; parent::ShowMessage(); } } $extended = new ChildClass(); $extended->ShowMessage(); ?> // Ví dụ trên cho ra kết quả // ChildClass class - dammio.com // Parent class - dammio.com

Trong ví dụ trên, chúng ta có lớp ChildClass kết thừa lớp ParentClass thông qua từ khóa extends. Phương thức ShowMessage() trong lớp ChildClass cũng ghi đè lên phương thức cùng tên ở lớp ParentClass. Để truy xuất phương thức ShowMessage() ở lớp ParentClass, chúng ta dùng cú pháp parent::.

Phương thức __construct() và phương thức __destruct()
Phương thức __construct là phương thức khởi tạo của 1 lớp, trong khi đó phương thức __destruct là phương thức hủy tất cả giá trị thuộc tính và phương thức của lớp.

Trong ví dụ sau, lớp SubClass kế thừa lớp BaseClass và khi khởi tạo đối tượng, các hàm _construct của 2 lớp sẽ khởi tạo để in các giá trị đầu ra.

<?php class BaseClass { function __construct() { print "Phương thức khởi tạo lớp BaseClass - dammio.com<br/>"; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print "Phương thức khởi tạo lớp SubClass - dammio.com<br/>"; } } $obj = new BaseClass(); $obj = new SubClass(); ?> // Kết quả của đoạn code trên // Phương thức khởi tạo lớp BaseClass - dammio.com // Phương thức khởi tạo lớp BaseClass - dammio.com // Phương thức khởi tạo lớp SubClass - dammio.com

Ví dụ sau mô tả xây dựng 1 lớp chứa phương thức khởi tạo và phương thức hủy lớp.

<?php class DestructableClass { function __construct() { print "Phương thức khởi tạo<br/>"; $this->name = "dammio.com"; } function __destruct() { print "Hủy giá trị " . $this->name . "<br/>"; } } $obj = new DestructableClass(); ?> // Kết quả // Phương thức khởi tạo // Hủy giá trị dammio.com

Phạm vi truy cập (Visibility)
Tương tự như các ngôn ngữ lập trình hướng đối tượng khác, PHP cũng dùng các phạm vi truy cập như public, protected và private. Trong PHP, mặc định nếu chưa định nghĩa phạm vi truy cập cho 1 lớp thì lớp đó có phạm vi truy cập là public.

Nếu các thuộc tính, phương thức được định nghĩa public thì chúng có thể được truy xuất bất cứ đâu. Protected chỉ định nghĩa các thuộc tính, phương thức được truy xuất trong việc kế thừa giữa 2 lớp cha, con. Còn lại private thì chỉ dành việc truy xuất cho chính lớp đó.

Ví dụ sau mô tả cách truy xuất thuộc tính $public, $protected và $private theo các kiểu khác nhau.

<?php <?php class DammioClass { public $public = 'Public <br/>'; protected $protected = 'Protected <br/>'; private $private = 'Private <br/>'; function ShowMessage1() { echo $this->public; echo $this->protected; echo $this->private; } private function ShowMessage2() { echo $this->public; echo $this->protected; echo $this->private; } } $obj = new DammioClass(); echo $obj->public; // Hiển thị thuộc tính public echo $obj->protected; // Lỗi Fatal echo $obj->private; // Lỗi Fatal $obj->ShowMessage1(); // Hiển thị các public, protected và private $obj->ShowMessage2(); // Không chạy vì kiểu truy xuất hàm là private ?>

Tương tự, ở ví dụ tiếp theo chứa các phạm vi truy cập khác nhau ở trường hợp kế thừa giữa 2 lớp DammioClass và ChildClass.

<?php class DammioClass { public $public = 'Public <br/>'; protected $protected = 'Protected <br/>'; private $private = 'Private <br/>'; function ShowMessage1() { echo $this->public; echo $this->protected; echo $this->private; } private function ShowMessage2() { echo $this->public; echo $this->protected; echo $this->private; } } class ChildClass extends DammioClass { // Chỉ có thể ghi đè lên thuộc tính public và protected public $public = 'Public - ChildClass <br/>'; protected $protected = 'Protected - ChildClass <br/>'; function ShowMessage1() { echo $this->public; echo $this->protected; echo $this->private; } } $obj = new ChildClass(); echo $obj -> public; // Hiển thị echo $obj -> protected; // Lỗi Fatal echo $obj -> private; // Lỗi chưa định nghĩa ở lớp ChildClass $obj->ShowMessage1(); // Hiển thị public và protected, thuộc tính private lỗi chưa định nghĩa ?>

Kết luận: Bài viết đưa các khái niệm và ví dụ cho việc tạo lớp, phương thức, thuộc tính và phạm vi truy cập. Mời các bạn theo dõi về lớp và đối tượng trong PHP ở bài tiếp theo.