Học PHP/Lớp và đối tượng

Tủ sách mở Wikibooks

Trong bất kỳ ngôn ngữ lâp trình nào, việc nắm rõ tất cả các cách thức, phương thức hoạt động của biến, hàm, lớp, đối tượng như thế nào là yếu tố then chốt trong việc coding để sử dụng nó đúng với ý đồ của các developer. Trong đó Class và Object là một trong những nội dung cần phải hiểu rõ vì nó rất quan trọng trong việc phát triển web.

Class và Object trong PHP 4[sửa]

Một Class là một tập các biến, hằng (được gọi là thuộc tính) và hàm (đươc gọi là phương thức). Biến được định nghĩa bởi từ khóa var và hàm được định nghĩa với từ khóa function. Ta nhìn vào ví dụ sau:

 <?php
 class Cart {
    var $items;
    function add_item($artnr, $num) {
        $this->items[$artnr] += $num;
    }
    function remove_item($artnr, $num) {
        if ($this->items[$artnr] > $num) {
            $this->items[$artnr] -= $num;
            return true;
        } elseif ($this->items[$artnr] = $num) {
            unset($this->items[$artnr]);
            return true;
        } else {
            return false;
        }
    }
 }
 ?> 
 

Trong ví dụ trên ta đã tạo ra một class tên là Cart, trong class đó có biến $items và 2 hàm tên là add_item()remove_item().

Việc viết class có thể được chia ra trong nhiều block PHP (các dấu chỉ dẫn mỗi block là một <?php,?>)nhưng bạn chỉ được phép phân ra trong phương thức (hàm) của nó, ví dụ:

 <?php
 class test {
    function test() {
        ?>
        <?php
        print 'Duoc phep viet nhu the nay trong mot class';
    }
 }
 ?> 
 

Ví dụ trên chúng ta đã phân mot class ra thành 2 block, được phân ra trong phương thức test như bạn thấy. Nhưng trong ví dụ sau thì không thể

 <?php
 class test {
 ?>
 <?php
    function test() {
        print 'Some thing';
    }
 }
 ?> 
 

Phân class trên thành 2 block như trên là sai bởi vì nó không phải được phân ra trong phương thức.

  • Chú ý:
    • Bạn không thể tạo lớp tên là stdClass vì nó đã được sử dụng trong Zend.
    • Không thể sử dụng các hàm có tên _sleep_wakeup đã được sử dụng trong một số lớp của PHP.
    • Không được sử dụng các hàm bắt đầu với từ _.

Để code của chúng ta rõ ràng, dễ hiểu khi chúng ta viết một class nên xây dựng cho class đó một hàm tạo (contructor). Các bạn có thể thấy trong ví dụ sau:

class Student{
   var $school;
   var $name;
   var $birthday;
   function Student(){
     $this->school = "Sai Gon University";
   }
   function MyFunction(){
     echo "This student learning at Sai Gon University";
   } 
}

Như bạn thấy trong ví dụ trên ta đã viết một lớp Student có hàm tạo Student được khởi tạo thuộc tính $school. Vậy khi ta khởi tạo một đối tượng Student thì nó đã có thuộc tính khởi tạo trước đó là $school. vd:

$st = new Student();
$st->name = "Nguyen Van A";
$st->birthday = "01-01-1989";

Chú ý rằng khi khởi tạo thuộc tính cho đối tượng không được viết như sau

$st->$name = "Nguyen Van A";

Không đúng bởi vì khi bạn viết như trên thì $st->$name sẽ trở thành $st->"".

Trong định nghĩa lớp thì lớp đó sẽ không thể biết được tên của đối tượng nào sẽ được truy xuất, do đó để gán giá trị của thuộc tính cho đối tượng chúng ta dùng $this. $this được hiểu rằng chúng ta muốn gán giá trị cho thuộc tính của chính đối tượng đó. Ví dụ trên khi ta khởi tạo một đối tượng $st thì nó đã có sẵn thuộc tính $school do trong hàm tạo chúng ta đã khởi tạo cho nó qua $this->school = "Sai Gon University";.

Nếu chúng ta muốn viết một lớp mà nó kế thừa các hàm, biến từ một lớp khác thì chúng ta sử dụng từ khóa extends. Ví dụ:

class New_Student extends Student{
   var $department;
   function AnotherFunction{
   }
}

Ví dụ trên ta đã tạo ra một class New_Student kế thừa từ lớp Student và có thêm thuộc tính $department và phương thức AnotherFunction. Cách tạo một đối tượng của lớp kế thừa này không có gì thay đổi so với cách khởi tạo đối tượng bình thường.

$new_st = new New_Student;
$new_st->department = "Information Technology";
$new_st->MyFunction();

Việc kế thừa một lớp thì lớp đó phải được tạo trước. Trong ví dụ của chúng ta, ta đã khởi tạo một đối tượng $new_st trong class New_Student, nó sẽ được kế thừa tất cả các phương thức và thuộc tính của class Student, ví dụ trên ta đã kế thừa phương thức MyFunction của class Student.

  • Đôi lúc chúng ta vẫn cần những hàm và biến của lớp nguyên thủy của nó. Để làm được điều đó ta dùng toán tử ::. Các bạn xem ví dụ sau:
 <?php
 class Student{
    var $school;
    var $name;
    var $birthday;
    function Student(){
        $this->school = "Sai Gon University";
    }
    function getInfor(){
        echo "Truong: ".$this->school."<br>";
        echo "Ten: ".$this->name."<br>";
        echo "Ngay sinh: ".$this->birthday."<br>";
        
    }
 }
 class ITStudent extends Student{
    var $department;
    function ITStudent(){
        $this->department = "Information Technology";
    }
    function getInfor(){
        parent::getInfor();
        echo "Khoa: ".$this->department."<br>";
    }
 }
 $b = new ITStudent;
 $b->name = "Le Thi B";
 $b->birthday = "22-12-07";
 $b->getInfor();
 ?>
 

Trong hàm getInfor() của class ITStudent chúng ta đã sử dụng toán tử ::, từ parent để chỉ tham chiếu đến hàm getInfor() của class Student. Kết quả của ví dụ trên sẽ như sau:

Trong kết quả của ví dụ trên ta thấy thuộc tính $school của ITStudent không có bởi vì đó là thuộc tính được khởi tạo trong hàm tạo của class Student, mà đối tượng thuộc class Student không được khởi tạo do đó thuộc tính đó chưa được gán giá trị.

  • Tham chiếu trong Constructor:

Ta xét ví dụ sau:

<?php
class Foo {
   function Foo($name){
       global $globalref;
       $globalref[] = &$this;
       $this->setName($name);
       $this->echoName();
   }
   function echoName() {
       echo "
", $this->name; } function setName($name) { $this->name = $name; } } $bar1 = new Foo('set in constructor'); $bar1->echoName(); $globalref[0]->echoName(); ?>

Kết quả sẽ là

Ví dụ trên ta đã sử dụng một mãng là $globalref[] chữa các giá trị là tham chiếu của đối tượng, nghĩa là giá trị ban đầu, giá trị thực của chính đối tượng đó. Để hiểu rõ hơn, ta xét lại ví dụ trên với một vài thay đổi.

 chua duoc
 
  • So sánh đối tượng

Trong PHP4, 2 đối tượng được gọi là bằng nhau nếu chúng có cùng các thuộc tính, giá trị và cùng lớp, khi so sánh 2 đối tượng ta dùng toán tử===.

Class và Object trong PHP 5[sửa]

Trong PHP 5, đối tượng được xây dựng theo mô hình mới cho phép chạy nhanh hơn và nhiều đặc tính hơn.

Autoloading objects[sửa]

Trong PHP 4 việc ta sử dụng một class chưa định nghĩa trước là điều không thể. Điều đó đã được khắc phục trong PHP 5 bằng cách chúng ta định nghĩa một hàm _autoload, nó sẽ tự động gọi trong trường hợp class đó chưa định nghĩa. Vd:

<?php
function __autoload($class_name) {
    require_once $class_name . '.php';
}
$obj  = new MyClass1();
$obj2 = new MyClass2(); 
?> 

Ví dụ trên sẽ tự động load 2 class MyClass1 và MyClass2 theo thứ tự từ 2 file MyClass1.php và MyClass2.php Chú ý: Chúng ta sẽ không bắt được lỗi từ hàm _autoload() trong catch block

Hàm tạo và hàm hủy (constructor và destructor)[sửa]

Hàm tạo: __construct([mixed $args[, $...]])[sửa]

PHP 5 cho phép bạn tạo hàm tạo theo một phương thức mới. vd:

<?php
class A{
    function __construct(){
       echo "Init"; 
    }
}
$a = new A();
?>

Nếu PHP 5 không tìm thấy được hàm tạo theo phương thức mới _construct() thì nó sẽ tìm phương thức khởi tạo theo phương thức cũ, nghĩa là nó sẽ tìm hàm với tên là tên của lớp.

Hàm hủy: __destruct()[sửa]

PHP 5 đã định nghĩa thêm phương thức hủy giống như những ngôn ngữ lập trình hướng đối tượng khác. Hàm hủy sẽ được gọi khi các tham chiếu đến đối tượng được removed hoặc các đối tượng được phá hủy. vd:

<?php
class ConstructAndDestruct{
    function __construct(){
        echo "Init obj";
    }
    function __destruct(){
        echo "destroying obj";
    }
}
$a = new ConstructAndDestruct();
?>

Visibility[sửa]

Trong PHP 5, các thuộc tính và phương thức được định nghĩa thêm thuộc tính mô tả với các từ khóa public, protected, private, giống như trong các ngôn ngữ lập trình hướng đối tượng như C++, C#. Public được hiểu như là phương thức và thuộc tính đó được sử dụng ở mọi nơi, thuộc tính và phương thức được mô tả với từ khóa protected sẽ bị giới hạn truy xuất trong việc kế thừa và lớp cha. Private chỉ được sử dụng các phương thức và thuộc tính trong lớp đó. Ta xet ví dụ sau:

<?php
class MyClass
{
   public $a = 5;
   protected $b = 6;
   private $c = 7;
   public $s;
   public function sum()
   {   
       return $this->s = $this->a + $this->b + $this->c;
   }
   protected function sumProtected()
   {
      $sums = $this->a + $this->b;
      return $sums;
   }
}
class NewClass extends MyClass{
   public $su = 0;
   function add(){
       echo "Add to b:";
       $this->su = $this->sum() + $this->b;
       echo $this->su."<br>";
       //dùng được phương thức sau:
       echo $this->sumProtected()."<br>";
   }
}
$obj1 = new MyClass;
echo $obj1->sum()."<br>";
//không thể gọi được thuộc tính: $obj1->b;
//và phương thức: $obj1->sumProtected();
$obj2 = new NewClass();
$obj2->add();
//không thể gọi được thuộc tính: $obj2->b;
//và phương thức: $obj2->sumProtected();
?>

Kết quả như sau:
http://farm4.static.flickr.com/3003/3047855057_bd305c1fcc_o.jpg


Như chúng ta thấy trong hàm sum() của lớp MyClass ta sử dụng được giá trị của a,b,c là vì chúng đều nằm trong cùng một lớp, và hàm add() trong lớp NewClass được kế thừa từ lớp MyClass sử dụng được giá trị của b là vì b được khai báo với mô tả protected nên nó cho phép ở lớp kế thừa. Nhưng với khai báo private như biến c thì trong lớp NewClass sẽ không sử dụng được. Mặc định nếu không khai báo mô tả trước một phương thức hay một hàm thì nó xem như mô tả public. Tương tự với mô tả thuộc tính (biến) ta có thể mô tả đối với hàm. Ta thêm có thể thấy được mô tả public đối với hàm sum() trong MyClass.

Static[sửa]

Một thuộc tính được khai báo với từ khóa static, thì các đối tượng thuộc lớp đó sẽ không thể truy xuất được thuộc tính đó. Cũng vì vậy $this cũng không thể sử dụng trong phương thức static. Ta xem ví dụ sau

<?php
class MyClass
{
   public static $a = 5;
   public static $b = 6;
   public $c = 7;
   private $s;
   public function sum()
   {   
       return $this->s = $this->a + $this->b + $this->c;
   }
}
$ob = new MyClass;
echo $ob->a; //Không lấy được giá trị a
echo MyClass::$b; //Hoặc dùng echo self::$b để lấy được giá trị b h
?>

Ví dụ trên ta sẽ không lấy được giá trị của a vì a là một biến static nhưng ta có thể lấy được giá trị của nó qua toán tử :: như ta lấy đượ Nhưng đối với phương thức được khai báo static thì đối tượng thuộc lớp đó vẫn có thể truy xuất được.Ví dụ:

<?php
class MyClass
{
   private static $a = 5;
   public static $b = 6;
   public $c = 7;
   private $s;
   public static function show()
   {   
       echo "Welcome to PHP";
   }
}
$ob = new MyClass;
$ob->show();
?>

Kết quả của ví dụ trên sẽ in ra chuỗi "Welcome to PHP".

Constants[sửa]

Chúng ta có thể định nghĩa một hằng trên một lớp. Hằng khác với biến ở chổ nó là giá trị không thay đổi và khai báo không có $ ở trước. Nếu định nghĩa một hằng thì tên hằng đó phải không được trùng tên với biến, lớp, hàm và kết quả của một phép toán hay kết quả của một hàm.

  • Để định nghĩa một hằng cú pháp như sau:

const myConst = 'value of myConst'; Việc lấy giá trị của hằng cũng gần giống như biến tuy nhiên ta chỉ lấy được qua toán tử :: hoặc thông qua một phương thức. Ví dụ:

<?php
class MyClass
{
   const myConst = 'My Constant';
   function showConst()
   {   
       echo MyClass::myConst;
   }
}
$ob = new MyClass;
$ob->showConst();
echo MyClass::myConst;
?>

Abstraction[sửa]

PHP 5 giới thiệu đến chúng ta các lớp và phương thức trừu tượng (abstract). Các lớp được định nghĩa abstract thì nó sẽ không cho phép tạo các instance, nghĩa là ta sẽ không thể tạo được các đối tượng thuộc lớp đó. Các phương thức được định nghĩa abstract chỉ đơn giản là viết mô tả phương thức đó, không định nghĩa chúng, nghĩa là ta chỉ khai báo tên hàm theo đúng cấu trúc mà bên trong thân hàm không định nghĩa gì hết. Vd:

<?php
abstract class AbstractClass{
   abstract protected function printA();
   abstract protected function printB();
   public function showAll(){
     $this->printA();
     $this->printB();
   }
}
class extClass extends AbstractClass{
   protected function printA(){
       echo 'A';
   }
   protected function printB(){
       echo 'B';
   }
}
$ob = new extClass;
//$ob->printA();
//$ob->printB();
$ob->showAll();
?>

Chúng ta thấy các phương thức trong class AbstractClass chỉ toàn là các mô tả, nói đúng hơn chỉ đơn giản là trong lớp trừu tượng chỉ mô tả cấu trúc của lớp là chính.

Cần chú ý là khi một lớp kế thừa từ một lớp abstract thì tất cả các phương thức từ lớp con phải có kiểu mô tả đúng với kiểu mô tả của lớp cha, ví dụ như trong ví dụ trên phương thức printA() đều được mô tả là protected trên cả lớp abstract và cả lớp kế thừa

Object Interfaces[sửa]

Object Interfaces cho phép bạn chỉ định phương thức nào của lớp phải được thực thi, mà không phải định nghĩa phương thức đó làm việc như thế nào. Các bạn xem ví dụ sau:

<?php
interface interf{
   public function funcA();
   public function funcB();
}
class myClass implements interf
{
   public function funcA(){
       echo 'A';
   }
   public function funcB(){
       echo 'B';
   }
   public function func(){
       echo 'All';
   }
}
$a = new myClass;
$a->funcA();
?>

Trong ví dụ đó ta chỉ định rằng trong lớp myClass phải có 2 hàm được thực thi là funcA() và funcB() như trong định nghĩa interface đã chỉ định, nếu trong lớp đó không có một trong 2 hàm được chỉ định đó thì sẽ bị báo lỗi khi chạy. Lỗi báo như sau nếu ta không có hàm funcB():

 Fatal error: Class myClass contains 1 abstract method and must therefore be declared abstract or 
 implement the remaining methods (interf::funcB) in ...
 

Trong đó từ khóa implements dùng chỉ định để thực thi interface interf

Chú ý:

  • Tất cả các phương thức được mô tả trong interface phải được mô tả public
  • Một lớp có thể thực thi nhiều hơn một interface nhưng phải đảm bảo rằng các phương thức trong các interface đó không được trùng tên nhau. Vd:
<?php
interface interf1{
   public function funcA();
   public function funcB();
}
interface interf2{
   public function funcC();
   public function funcD();
}
class myClass implements interf1,interf2
{
   public function funcA(){
       echo 'A';
   }
   public function funcB(){
       echo 'B';
   }
   public function func(){
       echo 'All';
   }
   public function funcC(){}
   public function funcD(){}
}
$a = new myClass;
$a->funcA();
?>

Trong lớp trên ta thực thi 2 interface là interf1interf2, và các phương thức trong 2 interface đó phải khác nhau, nếu trùng thì sẽ bị báo lỗi.jhuhuhuhuh

Overloading[sửa]

Tất cả các phương sử dụng để gọi hoặc truy xuất chúng ta đều có thể overload qua các phương thức _call, _set, _get. Trong PHP 5 chúng ta có thể overload thêm 2 phương thức _isset() và _unset() bằng 2 phương thức _isset và _unset. Tất cả các phương thức chúng ta muốn overload thì phải được mô tả chúng public và không được định nghĩa static. Các phương thức được overload được mô tả như sau:

  • void __set ( string $name, mixed $value ): Được thực hiện khi chúng ta dùng lệnh gán giá trị cho biến.
  • mixed __get ( string $name ): Thực hiện khi chúng ta dùng lệnh unset hoặc gán một giá trị mới cho biến.
  • bool __isset ( string $name ): Thực hiện khi chúng ta dùng đến phương thức isset()
  • void __unset ( string $name ): Thực hiện khi chúng ta dùng phương thức unset();

Vd:

 <?php
 class myClass
 {
    public $t;
    private $a = array("x" => 100, "y" => 200);
    public function __get($nm)
    {
        echo "Getting [$nm]"."<br>";
        if (isset($this->a[$nm])) {
            $r = $this->a[$nm];
            print "Returning: $r"."<br>";
            return $r;
        } else {
            echo "Nothing!"."<br>";
        }
    }
    public function __set($nm, $val)
    {
        echo "Setting [$nm] to $val".'<br>';

        if (isset($this->a[$nm])) {
            $this->a[$nm] = $val;
            echo "OK!\n"."<br>";
        } else {
            echo "Not OK!"."<br>";
        }
    }
    public function __isset($nm)
    {
        echo "Checking if $nm is set"."<br>";

        return isset($this->a[$nm]);
    }

    public function __unset($nm)
    {
        echo "Unsetting $nm"."<br>";

        unset($this->a[$nm]);
    }
 }
 $obj = new myClass;
 $obj->x = 300;
 ?> 

 

Kết quả của phép gán được phương thức _set overload lại như sau:

Setting [x] to 300
OK! 

Tương tự khi chúng ta sử dụng hàm unset() để unset giá trị của [x] (vd unset($obj->x);) thì hàm overload của unset() sẽ thực hiện, và kết quả của hàm unset() sẽ là

Setting [x] to 300
OK!
Unsetting x

Khi đó thuộc tính x đã không còn, khi ta thực hiện lệnh gán giá trị lại cho x (vd $obj->x = 400;) thì phép gán sẽ được overload lại như sau:

Setting [x] to 300
OK!
Unsetting x
Setting [x] to 400
Not OK!

Object Iteration[sửa]

PHP 5 cung cấp cho chúng ta một phương pháp để chúng ta có thể sử dụng tính lặp lại của một list các thuộc tính, giá trị tương tự như câu lệnh foreach(), mặc định các thuộc tính public trong lớp mới được lặp. Sự khác biệt trong sử dụng phương thức lặp của PHP 5 so với PHP 4 là nó sử dụng các đối tượng ArrayObject và ArrayIterator.