Khi bạn đang mơ thì người khác đang nỗ lực.

Lập trình hướng đối tượng trong PHP - Phần 1: Cơ bản về class, object

Khi mới làm quen với lập trình chúng ta thường bắt đầu với các ngôn ngữ như C, Pascal là những ngôn ngữ lập trình cấu trúc với việc thực hiện mã lệnh tuần tự kèm theo các câu lệnh điều kiện và các vòng lặp. Kiểu lập trình này giúp chúng ta có thể nhanh chóng tiếp cận và thực hành, nhưng với các dự án dần dà nó có những yếu điểm trong phát triển phần mềm.

1. Lập trình hướng đối tượng là gì?

OOP viết tắt của Object-Oriented Programming - Lập trình hướng đối tượng ra đời giải quyết các vấn đề mà lập trình truyền thống gặp phải. Lập trình hướng đối tượng không chỉ đơn giản là các cú pháp, câu lệnh mới mà còn là một cách tư duy mới khi giải quyết một vấn đề. Thực tế khi làm một việc gì đó, chúng ta sẽ quan tâm đến hai điều: vật bị tác động và hành động. Với lập trình cũng vậy, nếu chúng ta tập trung vào hành động thì đó là lập trình hướng thủ tục còn nếu tập trung vào các vật thể thì đó là lập trình hướng đối tượng. Với cả hai cách giải quyết vấn đề, đều cho chúng ta một kết quả như nhau, chỉ có một điều khác nhau là cách chúng ta tập trung vào cái gì?

Hai thứ cần quan tâm khi giải quyết vấn đề

Trong lập trình hướng đối tượng OOP, có hai thuật ngữ rất quan trọng là lớp (class) và đối tượng (object). Class là định nghĩa chung cho một vật, để dễ tưởng tượng bạn có thể nghĩ đến class là một bản thiết kế trong khi đó đối tượng là một thực hiện cụ thể của bản thiết kế. Ví dụ, object là một ngôi nhà cụ thể thì class là bản thiết kế ngôi nhà đó. Lập trình hướng đối tượng là cách bạn thiết kế các class và sau đó thực hiện chúng thành các đối tượng trong chương trình khi cần. Lập trình hướng đối tượng có 4 tính chất chính:

  • Tính kế thừa
  • Tính trìu tượng
  • Tính đóng gói
  • Tính đa hình

Với OOP, ứng dụng ra thành các phần nhỏ, ví dụ website có thể làm nhiều thứ như tương tác với database, quản lý form, gửi email, tạo mã HTML… mỗi thứ đó có thể là một module hay một class. Bằng cách tách biệt các thành phần không liên quan, các class sẽ độc lập, dễ bảo trì và cập nhật, debug lỗi cũng vì thế đơn giản hơn.

Một vấn đề cũng hay gặp với những người mới làm quen với OOP là khi chia nhỏ các thành phần, các lớp được định nghĩa quá rộng, ví dụ thay vì định nghĩa một class để tương tác với một cơ sở dữ liệu MySQL, bạn tạo ra một class tương tác với cơ sở dữ liệu không xác định, class này hoạt động như một lớp database chung chung, nhưng tính năng của nó lại hướng về một thứ cố định.

Trong OOP, các class là các thành phần riêng biệt được đóng kín và ẩn đi cách thức hoạt động bên trong nó, một thuộc tính của lớp có thể làm việc gì đó nếu bạn cần làm mà bạn không biết cách nó hoàn thành như thế nào. Bên cạnh những ưu điểm, OOP cũng có những vấn đề của nó, OOP không phải là cách tốt hơn trong lập trình, nó đơn giản chỉ là một hướng đi khác trong giải quyết vấn đề.

Trong một số trường hợp nó tốt hơn, nhưng cũng có những trường hợp OOP là tồi hơn. Có nhiều lập trình viên thấy rằng lập trình hướng đối tượng sử dụng các đối tượng có thể kém hiệu quả hơn phương pháp lập trình hướng thủ tục. Hiệu năng giữa hai phương pháp là không đáng kể trong nhiều trường hợp, nhưng có những nguy cơ tiềm ẩn từ những việc khác. Những thông tin trên đây có thể làm cho bạn hơi mung lung, khó hiểu về lập trình hướng đối tượng, nhưng bạn hãy bình tĩnh và chỉ cần một câu ngắn gọn để nhớ về OOP:

Lập trình hướng đối tượng chỉ đơn giản là cách tư duy khác để giải quyết vấn đề mà trong đó chúng ta tập trung vào các đối tượng thay vì tập trung vào hành động.

2. Các khái niệm cơ bản trong lập trình hướng đối tượng

2.1 Class - Bản thiết kế

Trong lập trình hướng đối tượng, chúng ta sẽ tập trung vào các vật (danh từ) ví dụ như ngôi nhà, xe ô tô... và với một vật thì bản thiết kế hay mô hình trìu tượng là cái đầu tiên nghĩ đến và class chính là cái chúng ta đang nói đến. Một class User có thể lưu trữ các thông tin như tên, id, địa chỉ email… Các chức năng của User có thể là đăng nhập, đăng xuất, thay đổi mật khẩu… Class được định nghĩa với từ khóa class và theo sau là tên của class, tên class không được đặt trùng với các từ khóa được sử dụng bởi hệ thống, thông thường tên class tuân thủ theo kiểu Pascal Case (chữ cái đầu viết hoa). Sau tên lớp, các định nghĩa về lớp được đặt trong hai dấu ngoặc nhọn:

class ClassName {
   // Nội dung của lớp
}

Các lớp có thể chứa các biến (variable) và hàm (function), trong lập trình hướng đối tượng, biến trong class được gọi là thuộc tính (property) và hàm được gọi là phương thức (method). Các thuộc tính và phương thức là thành phần của class.

class ClassName {
  function functionName() {
    // Code của phương thức
  }
}

Phương thức được định nghĩa trong một class giống như một hàm ở ngoài lớp, chúng có thể có các tham số, giá trị mặc định và trả về giá trị khi kết thúc… Thuộc tính trong các lớp khác biệt một chút so với các biến ở ngoài lớp. Đầu tiên, các thuộc tính phải được khai báo với các từ khóa để biết phạm vi truy nhập như public, private, protected. Các giá trị này chỉ có ý nghĩa khi thực hiện thừa kế các class, chúng ta sẽ nói đến chi tiết trong các phần tiếp theo:

class ClassName {
  public $var1, $var2;
  function functionName() {
    // Code của phương thức
  }
}

Trong khai báo một class, các thuộc tính được khai báo đầu tiên, sau đó đến các phương thức trong class. Một sự khác biệt nữa giữa thuộc tính (property) và biến thông thường (variable) là thuộc tính được khởi tạo với một tập giá trị thuần túy chứ không được khởi tạo với giá trị là kết quả của một biểu thức.

class GoodClass {
  public $var1 = 123;
  public $var2 = 'string';
  public $var3 = array(1, 2, 3);
}
class BadClass {
  // Khai báo thuộc tính với kết quả của biểu thức là lỗi ngay
  public $today = get_date();
  public $square = $num * $num;
}

Một số chú ý là thuộc tính không nhất thiết phải có giá trị khởi tạo, chú ý nữa các đoạn code khác ngoài việc khai báo thuộc tính phải được thực hiện ở trong phương thức, không thể thực hiện các câu lệnh bên ngoài một phương thức.

class BadClass {
  public $num = 2;
  public $square;
  $square = $num * $num; // Không được viết code ngoài phương thức.
}

Trên đây chúng ta đã có được các kiến thức về class, một khái niệm cơ sở quan trọng của OOP. Các đoạn code trong lý thuyết chỉ để minh họa, chúng ta cần có các ví dụ cụ thể hơn giúp hiểu rõ lý thuyết đưa ra. Một cách học lập trình tốt là học đến đâu thực hành đến đấy. Trong loạt bài về "Lập trình hướng đối tượng trong PHP", chúng ta cũng sẽ làm như vậy, mỗi phần lý thuyết sẽ được tiếp theo bởi các ví dụ. Phần tiếp theo đây, chúng ta cùng tạo ra một môi trường thực hành.

2.2 Các bước cài đặt môi trường thực hành lập trình hướng đối tượng trong PHP

Bước 1: Tải và cài đặt XAMPP, đây là một phần mềm tổng hợp bên trong đã có sẵn PHP, Apache (máy chủ web).

Bước 2: Tạo thư mục C:/xampp/htdocs/OOP để chứa các code thực hành.

Bước 3 (tùy chọn): Ở bước 2 là chúng ta đã có thể truy cập các file php từ trình duyệt với đường dẫn http://localhost/OOP, tuy nhiên nếu bạn muốn truy nhập theo kiểu domain ảo dạng http://oop.dev chúng ta thực hiện mở file hosts nằm trong C:\Windows\System32\drivers\etc và thêm vào:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
127.0.0.1       localhost
127.0.0.1       oop.dev

Sau đó, mở file httpd-vhosts.conf trong thư mục C:\xampp\apache\conf\extra để map domain ảo này với thư mục mã nguồn:

<VirtualHost *:80>
    DocumentRoot "C:/xampp/htdocs/OOP"
    ServerName oop.dev
</VirtualHost>

Khởi động lại Apache trong XAMPP Control Panel là bạn đã có thể sử dụng được tên miền ảo http://oop.dev cho các ví dụ thực hành. Ok, chúng ta đã cài đặt xong môi trường và bắt tay tạo ra một ví dụ về class. Ví dụ HelloWorld để khởi đầu, nhiệm vụ chỉ đơn giản là in ra màn một câu chào bằng các thứ tiếng khác nhau. Trong lập trình hướng đối tượng, mỗi class có thể được lưu trên một file khác nhau hoặc đưa toàn bộ các code vào một file giống lập trình thủ tục cũng được, chúng ta sẽ tạo ra một file HelloWorld.php nằm trong thư mục C:/xampp/htdocs/OOP với nội dung sau:

<?php
/* Định nghĩa class HelloWorld
 * "Hello, world" được in ra màn hình với các thứ tiếng khác nhau
 */
class HelloWorld {
    // Phương thức đưa ra lời chào với mặc định là tiếng Anh
    function sayHello($language = 'English') {
        echo '<p>';
        // Tùy thuộc ngôn ngữ sẽ in ra màn hình các lời chào bằng ngôn ngữ khác nhau.
        switch ($language) {
            case 'French':
                echo 'Bonjour, monde!';
                break;
            case 'German':
                echo 'Hallo, Welt!';
                break;
            case 'Vietnamese':
                echo 'Xin chào, các bạn trên toàn cầu!';
                break;
            case 'Chinese':
                echo '你好,世界!';
                break;
            case 'English':
            default:
                echo 'Hello, world!';
            break;
        }
        echo '</p>';
    } // End of sayHello() method.
} // End of HelloWorld class.

Như vậy, bạn đã định nghĩa được một class HelloWorld không có thuộc tính mà chỉ có duy nhất một phương thức sayHello(). Phương thức này sử dụng để in ra các câu chào bằng các thứ tiếng khác nhau, nó có một tham số đầu vào là $language và được mặc định là ngôn ngữ tiếng Anh. Bạn có thể thực thi file HelloWorld.php bằng cách mở trình duyệt và vào đường dẫn http://oop.dev/HelloWorld.php, bạn nào không tạo domain ảo thì vào bằng đường dẫn http://localhost/OOP/HelloWorld.php.

Ví dụ tạo class HelloWorld

Ặc, trắng tinh lỗi gì sao. Không phải đâu, do chúng ta mới tạo ra một Class mà chưa sử dụng Class này lên trống trơn là đúng thôi. Trong phần tiếp theo chúng ta sẽ tìm hiểu cách sử dụng Class.

2.3 Đối tượng - thực thi cụ thể của bản vẽ

Trong phần 2.1 chúng ta đã tạo ra một bản vẽ là một class, tiếp theo chúng ta sẽ sử dụng bản vẽ để tạo ra các đối tượng là những thực hiện cụ thể của bản vẽ. Trở lại với ví dụ về class User, một thực thể của class này là một người dùng xác định, ví dụ thông tin người dùng này như sau username là kiendang, ID người dùng là 1234 và địa chỉ email là kiendang@allaravel.com. Một class có thể có nhiều các thực thể khác nhau, cũng như một bản vẽ nhà có thể thực hiện xây dựng ra nhiều các ngôi nhà cụ thể khác nhau. Các đối tượng (ngôi nhà) này là giống nhau về khung (ba tầng, một tum) nhưng khác nhau về nét đặc trưng (khác về màu sơn, chất liệu cửa khác nhau...). Trong PHP việc tạo một đối tượng từ class là rất đơn giản, chúng ta sử dụng từ khóa new:

$object = new ClassName();

Bây giờ biến $object đã tồn tại và có dạng là ClassName (thay vì dạng string, integer, array…), chúng ta nói $object là một thực thể của ClassName. Chúng ta cũng có thể thực thi các phương thức trong class bằng cú pháp:

$object->methodName();

Trong đó, -> được gọi là toán tử đối tượng, được sử dụng để truy xuất các thuộc tính hoặc phương thức của đối tượng. Nếu một phương thức có tham số, bạn có thể truyền các tham số cho chúng, các tham số được phân cách bởi dấu phẩy.

$object->methodName('value', 32, true);

Truy xuất giá trị thuộc tính của đối tượng cũng thông qua toán tử đối tượng ->

$object->propertyName;

Chú ý, đằng sau toán tử đối tượng là tên thuộc tính, không sử dụng dấu $ khi truy nhập vào thuộc tính của lớp, đây là lỗi thường gặp với người mới làm quen lập trình hướng đối tượng.

$object->$propertyName; // Lỗi

Khi kết thúc việc sử dụng đối tượng, bạn có thể xóa chúng đi như với các biến thông thường. Trong các ứng dụng PHP, nếu bạn không xóa các đối tượng khi sử dụng thì ứng dụng cũng tự động xóa sau khi thực hiện xong các đoạn mã.

unset($object);

Phần lý thuyết chỉ có như vậy, chúng ta cùng thực hành tiếp với class HelloWorld đã tạo ra ở trên. Bạn hãy cùng tôi thực hành sử dụng class này tạo ra các thực thể (instance). Mở thư mục OOP tạo file mới là hello.php với nội dung:

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Hello, World!</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php
    require('HelloWorld.php');

    // Tạo đối tượng từ class HelloWorld
    $obj = new HelloWorld();

    // Gọi đến phương thức sayHello
    $obj->sayHello();

    // In ra "Hello, World" bằng các thứ tiếng khác
    $obj->sayHello('Vietnamese');
    $obj->sayHello('Chinese');
    $obj->sayHello('French');

    // Xóa đối tượng khi không dùng đến
    unset($obj);
    ?>
</body>
</html>

Để sử dụng được class HelloWorld được khai báo trong file OOP/HelloWorld.php chúng ta cần câu lệnh require trước khi tạo ra một đối tượng từ class này. Tiếp theo, với đối tượng $obj được tạo ra từ class HelloWorld, chúng ta gọi phương thức sayHello() để in câu chào ra màn hình, nếu không truyền tham số nó sẽ lấy giá trị mặc định là tiếng Anh để in ra lời chào, các lời chào tiếp theo được in ra bằng tiếng Việt, tiếng Trung Quốc và tiếng Pháp. Kết thúc, khi không sử dụng đến đối tượng này nữa chúng ta thực xóa chúng đi. Kết quả là bạn truy nhập vào http://oop.dev/hello.php sẽ được như sau:

Ví dụ sử dụng Class

2.4 Biến $this

Class HelloWorld đã làm được một số việc và là một ví dụ dễ hiểu để khởi đầu, class này có duy nhất một phương thức và không có bất kỳ thuộc tính nào. Như đã trình bày trong phần trước, khái niệm về thuộc tính:

  • Thuộc tính là một biến.
  • Thuộc tính phải khai báo phạm vi truy nhập với các từ khóa public, protected, private.
  • Nếu thuộc tính có khởi tạo giá trị thì phải là giá trị thuần túy, không được là kết quả của biểu thức.

Trên đây là các nguyên tắc khi định nghĩa một thuộc tính, khi sử dụng thuộc tính cần thêm một chút thông tin. Khi truy nhập vào thuộc tính của class chúng ta cần thông qua toán tử đối tượng ->

$object->propertyName;

Có một vấn đề là chúng ta muốn truy cập thuộc tính trong chính class đó. Bạn không thể sử dụng:

class BadClass {
  public $var;
  function do() {
    // Sử dụng biến kiểu này không đúng.
    print $var;
  }
}

Phương thức do() không thể truy xuất thuộc tính $var, giải pháp là sử dụng một biến đặc biệt là $this. Biến $this trong một class tham chiếu đến thực thể hiện tại của class. Trong một phương thức, bạn có thể tham chiếu đến thực thể của class và các thuộc tính bằng cách sử dụng cú pháp $this->attributeName. Trong phần thực hành với biến $this, chúng ta đến với một ví dụ tạo ra một class hình chữ nhật với các thuộc tính chiều rộng, chiều cao và các phương thức như tính chu vi, diện tích... Tạo file Rectangle.php trong thư mục OOP.

<?php 
/* Định nghĩa class Rectangle
* Các thuộc tính của class: width(chiều rộng), height(chiều cao).
* Các phương thức của lớp:
* - setSize()
* - getArea()
* - getPerimeter()
* - isSquare()
*/
class Rectangle {
    // Khai báo các thuộc tính
    public $width = 0;
    public $height = 0;

    // Phương thức này thiết lập các kích thước của hình chữ nhật
    function setSize($w = 0, $h = 0) {
        $this->width = $w;
        $this->height = $h;
    }

    // Phương thức này tính diện tích hình chữ nhật
    function getArea() {
        return ($this->width * $this->height);
    }

    // Phương thức này tính chu vi hình chữ nhật
    function getPerimeter() {
        return ( ($this->width + $this->height) * 2 );
    }

    // Phương thức này kiểm tra xem hình chữ nhật này có phải là hình vuông
    function isSquare() {
        if ($this->width == $this->height) {
            return true; // Hình chữ nhật
        } else {
            return false; // Không phải hình chữ nhật
        }
    }
} // End of Rectangle class.

Trong class Rectangle chúng ta có sử dụng đến biến $this và nó trỏ đến đối tượng hiện tại của class Rectangle, tuy nhiên khi chúng ta chưa tạo ra đối tượng thì biến $this cũng chỉ để định nghĩa. Cũng giống như các thực hành ở trên. Chúng ta đã tạo ra class và tiếp theo sẽ sử dụng class này, tạo file rectangle_calculate.php với nội dung:

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Rectangle Calculation</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php
    require('Rectangle.php');

    // Hình chữ nhật cần tính toán
    $width = 42;
    $height = 7;

    echo "<h2>Hình chữ nhật có chiều rộng $width và chiều cao $height</h2>";

    // Tạo đối tượng từ class Rectangle
    $rect = new Rectangle();

    // Gán chiều rộng và chiều cao cho đối tượng
    $rect->setSize($width, $height);

    echo '<p>Chu vi hình chữ nhật: ' . $rect->getPerimeter() . '</p>';
    echo '<p>Diện tích hình chữ nhật: ' . $rect->getArea() . '</p>';

    if ($rect->isSquare()) {
       echo '<p>Hình chữ nhật này là hình vuông!</p>';
    } else {
       echo '<p>Hình chữ nhật này không phải là hình vuông!</p>';
    }

    // Xóa đối tượng sau khi sử dụng
    unset($rect);
    ?>
</body>
</html>

Khi tạo ra đối tượng từ class Rectangle, chúng ta có thể thiết lập giá trị chiều rộng và chiều cao cho đối tượng, khi đó biến $this tham chiếu đến đối tượng này. Kiểm tra xem ví dụ hoạt động như thế nào trong đường dẫn http://oop.dev/rectangle\_calculate.php

Ví dụ sủ dụng biến $this

3. Các phương thức được xây dựng sẵn trong Class

Trong class có sẵn một số phương thức (còn gọi là magic method) giúp cho việc sử dụng class thuận tiện hơn, có một số phương thức liên quan đến việc khởi tạo và hủy bỏ một đối tượng, cũng có một số phương thức giúp làm việc với các đối tượng như tạo một bản sao đối tượng, nhân bản đối tượng... Các phương thức này có một số khác biệt với phương thức chuẩn:

  • Tên luôn bắt đầu với hai dấu gạch dưới __ và tên phương thức được cố định bởi hệ thống.
  • Thực hiện một số công việc đặc biệt tại những thời điểm khác nhau trong vòng đời của đối tượng.

3.1 Phương thức khởi tạo __construct()

Phương thức khởi tạo (contructor) là một phương thức đặc biệt, tên phương thức luôn là __construct(). Phương thức này được gọi mỗi khi một đối tượng được tạo ra từ class. Phương thức này không trả về giá trị, do đó không sử dụng câu lệnh return trong __construct(). Khai báo phương thức khởi tạo như sau:

class ClassName {
  public $var;
  function __construct() {
    // Các công việc cần thực hiện khi khởi tạo đối tượng
  }
}

Phương thức khởi tạo thực hiện các công việc cần thiết để khởi tạo đối tượng như kết nối với cơ sở dữ liệu, thiết lập cookie hoặc khởi tạo các giá trị ban đầu. Phương thức khởi tạo cũng có thể có tham số và giá trị các tham số này được truyền vào khi tạo đối tượng.

class User {
  function __construct($id) {
    // Các công việc cần thực hiện khi khởi tạo đối tượng
  }
}
$me = new User(1234);

Trong ví dụ Rectangle.php ở phần 1.3, chúng ta sẽ tạo ra một hàm khởi tạo và trong đó thiết lập luôn giá trị chiều rộng, chiều cao của đối tượng hình chữ nhật.

<?php 
/* Định nghĩa class Rectangle
* Các thuộc tính của class: width(chiều rộng), height(chiều cao).
* Các phương thức của lớp:
* - setSize()
* - getArea()
* - getPerimeter()
* - isSquare()
*/
class Rectangle {
    // Khai báo các thuộc tính
    public $width = 0;
    public $height = 0;

<strong>    // Hàm khởi tạo
    function __construct($w = 0, $h = 0) {
       $this->width = $w;
       $this->height = $h;
    }</strong>

    // Phương thức này thiết lập các kích thước của hình chữ nhật
    function setSize($w = 0, $h = 0) {
        $this->width = $w;
        $this->height = $h;
    }

    // Phương thức này tính diện tích hình chữ nhật
    function getArea() {
        return ($this->width * $this->height);
    }

    // Phương thức này tính chu vi hình chữ nhật
    function getPerimeter() {
        return ( ($this->width + $this->height) * 2 );
    }

    // Phương thức này kiểm tra xem hình chữ nhật này có phải là hình vuông
    function isSquare() {
        if ($this->width == $this->height) {
            return true; // Hình chữ nhật
        } else {
            return false; // Không phải hình chữ nhật
        }
    }
} // End of Rectangle class.

Như vậy khi tạo ra đối tượng chúng ta có thể khởi tạo các kích thước của đối tượng luôn, thực hiện trong rectangle_calculate.php:

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Rectangle Calculation</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php

    require('Rectangle.php');

    // Hình chữ nhật cần tính toán
    $width = 100;
    $height = 100;

    echo "<h2>Hình chữ nhật có chiều rộng $width và chiều cao $height</h2>";

    // Tạo đối tượng từ class Rectangle và gán kích thước
    $rect = new Rectangle($width, $height);

    echo '<p>Chu vi hình chữ nhật: ' . $rect->getPerimeter() . '</p>';
    echo '<p>Diện tích hình chữ nhật: ' . $rect->getArea() . '</p>';

    if ($rect->isSquare()) {
       echo '<p>Hình chữ nhật này là hình vuông!</p>';
    } else {
       echo '<p>Hình chữ nhật này không phải là hình vuông!</p>';
    }

    // Xóa đối tượng sau khi sử dụng
    unset($rect);
    ?>
</body>
</html>

Chạy lại đường dẫn http://oop.dev/rectangle\_calculate.php chúng ta thấy hàm khởi tạo __contruct đã được sử dụng để thiết lập kích thước hình chữ nhật khi tạo đối tượng.

Ví dụ hàm khởi tạo construct

3.2 Phương thức hủy __destruct()

Phương thức hủy __destruct() ngược với phương thức khởi tạo __contruct(), nó được gọi đến khi đối tượng bị hủy bỏ.

$obj = new ClassName();
unset($obj); // Gọi đến phương thức hủy __destruct() và thực hiện hủy bỏ đối tượng

Hoặc nó được gọi khi đoạn mã được thực thi xong (tại thời điểm này PHP giải phóng các biến khỏi bộ nhớ). Khai báo phương thức hủy như sau:

class ClassName {
  // Khai báo các thuộc tính và phương thức
  function __destruct() {
    // Các công việc cần thực hiện khi hủy bỏ đối tượng
  }
}

Hàm hủy __destruct cũng là các công việc kết thúc vòng đời của một đối tượng, ví dụ tiếp theo này chúng ta sẽ cùng tìm hiểu vòng đời của một đối tượng. Tạo ra file object_lifecycle.php với nội dung như sau:

<!doctype html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Object lifecyle</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>
    <?php 
    // Định nghĩa class
    class Demo {
        // Hàm khởi tạo
        function __construct() {
            echo '<li>Khởi tạo các thiết lập cho đối tượng</li>';
        }

        // Hàm hủy
        function __destruct() {
            echo '<li>Hủy bỏ đối tượng</li>';
        }

        function doSomeThing() {
            echo '<li>Đối tượng đang sống</li>';
        }
    } // End of Demo class.

    echo '<ol>';
    // Tạo đối tượng
    echo '<li>Tạo đối tượng mới</li>';
    $demo = new Demo();

    // Thực hiện doSomeThing
    $demo->doSomeThing();

    // Xóa đối tượng
    echo '<li>Chuẩn bị xóa đối tượng</li>';
    unset($demo);
    echo '<li>Ứng dụng kết thúc</li>';
    ?>
</body>
</html>

Vòng đời đối tượng

Bạn thấy đấy, trong các sự kiện xảy ra trong vòng đời của một đối tượng, chúng ta đều có thể thực hiện một việc gì đấy. Chú ý, ở đây chúng ta chủ động thực hiện xóa đối tượng khi sử dụng xong bằng lệnh unset($demo), trong trường hợp không xóa đối tượng, ứng dụng sẽ tự động xóa đối tượng khi kết thúc thực thi đoạn mã. Nếu bạn bỏ dòng unset($demo) thì thứ tự của sự kiện thứ 5 và thứ 6 ở trên sẽ đảo cho nhau.

3.3 Phương thức __get(), __set() và __isset() làm việc với thuộc tính trong class

Với toán tử đối tượng -> chúng ta có thể truy nhập một thuộc tính của đối tượng, tuy nhiên nếu thuộc tính chưa được khai báo trong class thì làm thế nào? Phương thức __get và __set() giúp chúng ta khai báo các giá trị thích hợp cho các thuộc tính không được khai báo của một class. Chúng ta cùng xem ví dụ sau:

<?php 
class Test { 
    protected $members = array(); 
    public function __get($arg) { 
        if (array_key_exists($arg, $this->members)) { 
            return ($this->members[$arg]); 
        } else { return ("No such luck!\n"); } 
    } 
    public function __set($key, $val) { 
        $this->members[$key] = $val; 
    } 
    public function __isset($arg) { 
        return (isset($this->members[$arg])); 
    } 
} 
$x = new Test(); 
print $x->speed_limit; 
$x->speed_limit = "65 MPH\n"; 
if (isset($x->speed_limit)) { 
    printf("Speed limit is set to %s\n", $x->speed_limit); 
} 
$x->speed_limit = NULL; 
if (empty($x->speed_limit)) { 
    print "The method __isset() was called.\n"; 
} else { 
    print "The __isset() method wasn't called.\n"; 
}

Trong ví dụ này, class Test sử dụng cách khai báo thuộc tính động theo kiểu mảng thuộc tính, thuộc tính speed_limit không được khai báo khi định nghĩa lớp Test, tuy nhiên khi truy xuất thuộc tính này, phương thức __get() sẽ được gọi đến. Nó kiểm tra nếu thuộc tính gọi đến nằm trong mảng thuộc tính thì trả về giá trị trong mảng, nếu không có thuộc tính này thì trả về dòng thông báo. Phương thức __isset() được dùng xử lý các công việc khi thực hiện lệnh isset() với thuộc tính một đối tượng.

3.4 Phương thức __call(), __callStatic() xử lý khi gọi phương thức của đối tượng

Các phương thức __get(), __set() ở trên để xử lý khi truy cập các thuộc tính của đối tượng, còn với phương thức của đối tượng thì sao, chúng ta có phương thức __call() và __callStatic(). Các phương thức này được gọi đến khi chúng ta cố tình gọi một phương thức không tồn tại trong một đối tượng. Phương thức __call() được gọi đến khi gọi một phương thức thông thường không tồn tại, __callStatic() được gọi đến khi gọi một phương thức static không tồn tại. Ví dụ:

<?php 
class Test { 
    function __call($name, $argv) { 
        print "name:$name\n"; 
        foreach ($argv as $a) { 
            print "\t$a\n"; 
        } 
    }
    function __callStatic($name, $argv) { 
        print "name:$name\n"; 
        foreach ($argv as $a) { 
            print "\t$a\n"; 
        } 
    }
} 
$x = new Test(); 
$x->non_existing_method(1, 2, 3);
Test::non_existing_method('Gọi thử phương thức static không tồn tại');
?>

Trong đoạn mã trên, class Test không khai báo phương thức nào, tuy nhiên khi tạo ra một đối tượng từ class này chúng ta vẫn có thể gọi một phương thức bất kỳ không tồn tại trong đối tượng này. Khi thực hiện $x->non_existing_method(1, 2, 3); phương thức __call() của class Test được thực hiện, còn khi gọi Test::non_existing_method() thì phương thức __callStatic() sẽ được thực hiện. ### 3.5 Phương thức __toString() xử lý việc in đối tượng ra màn hình

Như tên gọi của nó, phương thức này được gọi đến khi bạn sử dụng các lệnh in để in đối tượng ra màn hình. Chúng ta cùng xem ví dụ sau:

<?php 
class Test { 
    protected $member; 
    function __construct($member) { 
        $this->member = $member; 
    } 
    function __toString() { 
        return ("Test member.\n"); 
    } 
} 
$x = new Test(1); 
print $x; 
?>

3.6 Phương thức __clone()

Trong PHP, đối tượng là một biến tham chiếu đến một vùng bộ nhớ, do vậy khi chúng ta tạo ra một đối tượng khác bằng cách gán với đối tượng cũ thì chúng ta chỉ có duy nhất một thực thể trong bộ nhớ. Bạn hãy xem ví dụ sau:

$fred = new Person("Fred", 35);
$barney = $fred; // $barney và $fred trỏ đến cùng một đối tượng trong bộ nhớ
$barney->setName("Barney");
printf("%s và %s là đôi bạn thân.\n", $barney->getName(), $fred->getName());
// Kết quả
// Barney và Barney là đôi bạn thân.

Không như chúng ta nghĩ phải không? Để thực hiện tạo ra một đối tượng khác thật sự, chúng ta phải sử dụng câu lệnh clone().

$fred = new Person("Fred", 35);
$barney = clone($fred);
$barney->setName("Barney");
printf("%s và %s là đôi bạn thân.\n", $barney->getName(), $fred->getName());
Barney và Fred là đôi bạn thân.

Phương thức __clone() được gọi đến khi bạn sử dụng câu lệnh clone(), phương thức này có thể dùng xử lý các công việc bạn muốn thực hiện khi clone, ví dụ có những thuộc tính bạn muốn thiết lập lại khi clone chẳng hạn.

4. Mô hình hóa thiết kế class với UML

4.1 Các thành phần class trong sơ đồ UML

UML viết tắt của Unified Modeling Language là một cách để mô hình hóa trong thiết kết hướng đối tượng. Một class có 3 thành phần là tên class, các thuộc tính và các phương thức. UML thể hiện một class bởi một hình chữ nhật gồm 3 phần. Với các thuộc tính, dạng dữ liệu của thuộc tính được liệt kê sau tên thuộc tính

userId:number
username:string

Nếu thuộc tính có giá trị mặc định, bạn có thể đưa vào UML.

width:number = 0

Để định nghĩa một phương thức trong sơ đồ class, chúng ta bắt đầu với tên phương thức, tiếp theo là các tham số và dạng dữ liệu tham số và cuối cùng là dạng giá trị phương thức sẽ trả về.

sayHello(language:string):void

Ví dụ sử dụng UML để mô tả class Rectangle trong phần 1.3

Sơ đồ UML cho class Rectangle

4.2 Lợi ích sử dụng UML để thiết kế class

Đầu tiên, nếu bạn phác thảo các thiết kế trước khi viết code, code của bạn sẽ đúng ngay từ khi bắt đầu. Hay nói một cách khác, nếu bạn nỗ lực trong quá trình thiết kế trực quan, thiết kế sẽ có đầy đủ các yêu cầu do đó giảm được số lần phải sửa đổi các class sau này. Thứ hai, nguyên lý của OOP là đóng gói: tách biệt và ẩn đi cách một việc nào đó được hoàn thành. UML với việc liệt kê danh sách các thuộc tính, phương thức và tham số có thể được sử dụng như bản hướng dẫn người dùng sử dụng các class.

5. Lời kết

Trong phần đầu tiên giới thiệu về lập trình hướng đối tượng trong PHP chúng ta đã được làm quen với những khái niệm cơ bản nhất cũng như khai phá tư duy giải quyết vấn đề theo cách hướng đối tượng. Các phần cơ bản này là tiền đề cho những lý thuyết tiếp theo của OOP như kế thừa trong class sẽ được trình bày trong phần 2. Dự định loạt bài viết sẽ thực hiện với 3 phần đủ để cung cấp cho bạn đọc các kiến thức về Lập trình hướng đối tượng. Đón xem phần tiếp theo bạn nhé. Loạt bài viết này gồm 4 phần, bạn đang ở phần 1:


CÁC BÀI VIẾT KHÁC

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Các câu lệnh kiểm tra repository trong Git

Unit Testing phần 1 - Giới thiệu PHPUnit

7 Bình luận trong "Lập trình hướng đối tượng trong PHP - Phần 1: Cơ bản về class, object"

  1. Luna

    2 years ago

    Phản hồi
    Cho mình hỏi sự khác nhau giữa các khái niêm class, instance và object? Mình toàn bị nhầm các khái niệm này trong lập trình hướng đối tượng.
  2. Culit

    2 years ago

    Phản hồi
    Class, Object và Instance là các thuật ngữ cơ bản nhất của OOP. Trong bài viết cũng đã nói class là một bản thiết kế, mẫu thiết kế để tạo ra đối tượng (object), một class chứa các thuộc tính, phương thức. Thực thể (instance) là một đơn vị, một đối tượng được tạo ra từ class. Đối tượng (object) có thể là một hoặc nhiều thực thể được tạo ra từ class. Một ví dụ trong thực tế nhé, class là bản thiết kế nhà, tất cả các ngôi nhà được xây dựng từ bản thiết kế là các đối tượng và một ngôi nhà cụ thể trong đó là một thực thể. Khi mới học mình cũng rất hay nhầm các thuật ngữ này.
  3. An Trần

    2 years ago

    Phản hồi
    Trong lập trình mình thấy có các thuật ngữ rất giống nhau như function, procedure, method. Bạn nào có thể diễn giải cụ thể từng thuật ngữ này giúp mình không?
    1. Z99

      2 years ago

      Phản hồi
      Học trong lập trình Pascal thì có phân biệt function và procedure ở chỗ nó có trả về giá trị hay không? Hàm (function) sẽ trả về một giá trị cho được gọi đến, thủ tục (procedure) thì không trả về giá trị nào cả, chỉ thực hiện các công việc trong đó thôi.
    2. Culit

      2 years ago

      Phản hồi
      Bổ sung thêm cho Z99, phương thức (method) chỉ sử dụng với class, một method có thể là function hoặc procedure do phương thức trong class có thể trả về giá trị hoặc không
  4. Thuan Pham

    1 year ago

    Phản hồi
    Trong OOP em còn nghe nói đến khái niệm về entity, cho em hỏi entity có phải là cách gọi khác của instance phải ko ạ?
    1. Culit

      1 year ago

      Phản hồi
      Entity là để chỉ những đối tượng thật ngoài cuộc sống như Car, Tree, Student, Employee..., nó duy nhất và có thể tồn tại độc lập. Trong lập trình thì nó là biểu hiện của một instance khác với một instance khá. Các entity thường được sử dụng để mapping giữa một đối tượng với một bảng trong database. Khái niệm object, instance nó hàm chứa rộng hơn chỉ những thứ có thể không có thật ngoài đời, ví dụ Test, Requirement...

Thêm bình luận