Bài 15. Lập trình hướng đối tượng trong PHP

OOP giúp tổ chức mã lớn thành các lớp có dữ liệu và hành vi rõ ràng, là nền tảng của mọi framework PHP. Bài này học lớp, đối tượng, constructor, kiểm soát truy cập, kế thừa, interface, trait, static, enum, readonly, namespace và autoload.

60 phútTrung cấp
Bạn sẽ học được
  • Viết lớp với thuộc tính, phương thức, constructor
  • Dùng public/private/protected, readonly, static
  • Kế thừa, lớp trừu tượng, interface, trait
  • Dùng enum, namespace, autoload theo PSR-4
Thuật ngữ cần biết
Class / Object
Bản thiết kế / thực thể cụ thể tạo bằng new.
Constructor
Phương thức __construct chạy khi tạo đối tượng.
Interface
Hợp đồng: lớp cài đặt phải có các phương thức khai báo.
Trait
Khối mã dùng lại được trong nhiều lớp.
Namespace
Không gian tên để tránh trùng tên lớp.

Khái niệm và lý thuyết

PHP 8 có OOP đầy đủ và hiện đại: constructor property promotion khai báo thuộc tính ngay trong tham số constructor, readonly cho thuộc tính chỉ gán một lần, enum cho tập giá trị cố định, kiểu union và nullable. Bốn nguyên lý: đóng gói (private + getter), kế thừa (extends), đa hình (ghi đè phương thức), trừu tượng (abstract/interface).

Lớp, đối tượng, constructor

<?php
declare(strict_types=1);

class SinhVien {
    private array $diem = [];

    public function __construct(
        public readonly string $ma,
        private string $hoTen,
        public ?string $email = null,
    ) {}

    public function getHoTen(): string { return $this->hoTen; }
    public function setHoTen(string $t): void {
        if (mb_strlen(trim($t)) < 3) throw new InvalidArgumentException("Tên quá ngắn");
        $this->hoTen = trim($t);
    }
    public function themDiem(string $mon, float $d): static {
        $this->diem[$mon] = $d;
        return $this;                         // cho phép gọi nối chuỗi
    }
    public function diemTB(): float {
        return $this->diem ? round(array_sum($this->diem) / count($this->diem), 2) : 0.0;
    }
    public function __toString(): string { return "$this->ma - $this->hoTen ({$this->diemTB()})"; }
}

$sv = new SinhVien("SV001", "Lò Văn An");
$sv->themDiem("Toán", 8)->themDiem("Tin", 9.5);
echo $sv;                 // SV001 - Lò Văn An (8.75)
// $sv->ma = "x";         // Lỗi: readonly
// $sv->hoTen;            // Lỗi: private

Kế thừa, abstract, interface, trait

<?php
interface CoTheXuat { public function xuat(): string; }

trait GhiLog {
    public function log(string $m): void { error_log(static::class . ": $m"); }
}

abstract class Nguoi {
    public function __construct(protected string $hoTen) {}
    abstract public function vaiTro(): string;
    public function gioiThieu(): string { return "$this->hoTen - " . $this->vaiTro(); }
}

class GiangVien extends Nguoi implements CoTheXuat {
    use GhiLog;
    public function __construct(string $hoTen, private string $hocVi) { parent::__construct($hoTen); }
    public function vaiTro(): string { return "Giảng viên ($this->hocVi)"; }
    public function xuat(): string { return json_encode(["ten" => $this->hoTen], JSON_UNESCAPED_UNICODE); }
}

$gv = new GiangVien("Nguyễn Duy Hiếu", "TS");
echo $gv->gioiThieu();          // đa hình: vaiTro() của lớp con
var_dump($gv instanceof Nguoi, $gv instanceof CoTheXuat);

static, enum, hằng lớp

<?php
enum TrangThai: string {
    case DangHoc = "dang_hoc";
    case BaoLuu  = "bao_luu";
    case TotNghiep = "tot_nghiep";
    public function nhan(): string {
        return match ($this) { self::DangHoc => "Đang học", self::BaoLuu => "Bảo lưu", self::TotNghiep => "Đã tốt nghiệp" };
    }
}
class Lop {
    public const SI_SO_TOI_DA = 60;
    private static int $dem = 0;
    public static function tao(string $ten): self { self::$dem++; return new self($ten); }
    public function __construct(public string $ten, public TrangThai $tt = TrangThai::DangHoc) {}
    public static function soLop(): int { return self::$dem; }
}
$l = Lop::tao("K66 Tin");
echo $l->tt->nhan(), " ", Lop::SI_SO_TOI_DA, " ", Lop::soLop();
$tt = TrangThai::from("bao_luu");   TrangThai::tryFrom("x");  // null nếu không có

Namespace và autoload

<?php
// src/Model/SinhVien.php
namespace App\Model;
class SinhVien { /* ... */ }

// index.php
spl_autoload_register(function (string $lop) {
    $tep = __DIR__ . "/src/" . str_replace(["App\\", "\\"], ["", "/"], $lop) . ".php";
    if (is_file($tep)) require $tep;
});
use App\Model\SinhVien;
$sv = new SinhVien("SV001", "An");

Chuẩn PSR-4: tên namespace khớp thư mục, mỗi lớp một tệp. Dự án thật dùng Composer (composer init, "autoload": {"psr-4": {"App\\": "src/"}}) để tự sinh autoloader và cài thư viện.

Mẹo hay

Hãy “tư duy đối tượng” theo bảng CSDL: mỗi bảng (sinhvien, lop) thành một lớp Model, mỗi bản ghi là một đối tượng. Đây là bước đệm cho MVC (bài 17).

Lưu ý

Đừng đặt mọi thứ public. Thuộc tính private + phương thức public là cách bảo đảm dữ liệu luôn hợp lệ (ví dụ điểm 0–10).

Thực hành
  1. Viết lớp TaiKhoan có số dư private, phương thức nap/rut ném ngoại lệ khi không hợp lệ.
  2. Tạo abstract Hinh với dienTich(); lớp Tron, ChuNhat; in diện tích danh sách hình (đa hình).
  3. Viết enum XepLoai có phương thức tuDiem(float): self.
  4. Tổ chức lại bài 9 thành lớp SinhVien, LopHoc dùng namespace và autoload.
Tóm tắt bài học
  • class, new, __construct với property promotion, readonly.
  • private/protected/public, getter/setter, $this, static.
  • extends, abstract, interface, trait, enum.
  • namespace + autoload (PSR-4, Composer) để tổ chức dự án.