[PHP có thể bạn chưa biết] - Auloading (ok)

https://kipalog.com/posts/PHP-co-the-ban-chua-biet----Auloading

Hm, lâu rồi mình cũng không viết bài nào mới. Chủ yếu là do dạo này tập ngủ sớm mà lại đi làm về khá muộn nên leo lên giường đi ngủ luôn đâm cũng lười. Đây hứa hẹn sẽ là lần trở lại của mình đầy ăn hại lợi hại của mình.

Mình sẽ bắt đầu một series mới là PHP có thể bạn chưa biết. Lý do để bắt đầu series này là vì mình thích thì mình viết thôi.

Đây là bài đầu tiên (hi vọng sẽ có bài thứ 2 - LOL) trong series. Trong bài này mình sẽ giới thiệu về Autoloading trong PHP

Autoloading là gì

Trước khi vào autoload là gì thì mình sẽ nói về việc không có autoload trước. Mình có một ví dụ sau.

File app/Controllers/Model.php:

<?php

namespace App\Entities;

class Model
{
    protected $attributes = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
        echo "Filling attributes: " . json_encode($attributes)  . PHP_EOL;
    }

    public function save()
    {
        echo static::class . " have been saved" . PHP_EOL;
    }

    public static function create($attributes = [])
    {
        $instance = new static($attributes);
        $instance->save();
        return $instance;
    }
}

File app/Controllers/Post.php:

<?php

namespace App\Entities;

class Post extends Model
{

}

File app/Controllers/PostController.php

<?php

namespace App\Controllers;

use App\Entities\Post;

class PostController
{
    public function store()
    {
        return Post::create([
            'title' => 'Blah blah',
            'content' => 'Bloh, bloh'
        ]);
    }
}

Nội dung của file index.php như sau

<?php

use App\Controllers\PostController;

$controller = new PostController();
$controller->store();

Cơ bản thì khi chúng ta gọi tới file index.php, File này sẽ khởi tạo class App\Controller\PostController và gọi vào method store của PostController. Trong method PostController::store sẽ khởi tạo một post với title và content được truyền vào.

Như các bạn đã biết, hoặc chưa biết thì khi chúng ta chạy thử bằng lệnh

php index.php

sẽ nhận được một lỗi như sau

Lỗi này là do PHP không tìm thấy class App\Controller\PostController do chúng ta thiếu lệnh require các class này trước khi gọi. Giờ chúng ta sẽ tiến hành sửa lỗi này. Để phức tạp hóa vấn đề, thì mình sẽ tạo một file mới là autoload.php. File này đơn giản sẽ gọi lệnh require cho tất cả các file chúng ta cần sử dụng.

<?php

require_once 'app/Entities/Model.php';
require_once 'app/Entities/Post.php';
require_once 'app/Controllers/PostController.php';

và require file autoload.php vào trong file index.php

<?php

require_once 'autoload.php';

use App\Controllers\PostController;

$controller = new PostController();
$controller->store();

Thử chạy lại xem sao

php index.php

Ok, ngon, đã chạy.

Ơ thế nó liên quan qué gì tới autoload. Như các bạn thấy thì khi muốn dùng class hoặc function hoặc variable từ file khác thì chúng ta phải thực hiện load file đó vào trước khi sử dụng. Nếu với các dự án nhỏ thì không sao, nhưng với những dự án lớn hàng trăm, hàng nghìn classes thì việc maintain các đoạn code load file là một công việc cực kỳ mệt mỏi và nhàm chán. Vậy là autoload ra đời. Nôm na thì autoload là một cách dành cho người lười dùng để load các class trong PHP một cách tự động dựa vào cấu trúc thư mục và tên class.

Trăm nghe không bằng một thấy, trăm thấy không bằng một thử, lại tiếp tục ví dụ tiếp cho các bạn dễ hình dung.

Giờ mình sẽ chỉnh sửa lại file autoload.php thành

<?php

function my_app_autoloader($class)
{
    $root = 'app/';
    $prefix = 'App\\';

    // bỏ prefix
    $classWithoutPrefix = preg_replace('/^' . preg_quote($prefix) . '/', '', $class);
    // Thay thế \ thành /
    $file = str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutPrefix) . '.php';

    $path = $root . $file;
    if (file_exists($path)) {
        require_once $path;
    }
}

spl_autoload_register('my_app_autoloader');

Như các bạn thấy thì, toàn bộ các namespace của mình trong code ví dụ đều có prefix là App\\ và nằm trong folder app/. Trong function my_app_autoloader sẽ nhận vào tên đầy đủ của class cần được load ví dụ App\Controllers\PostController, mình tiến hành bỏ đi prefix chỉ giữ lại phần phía sau, mình sẽ được một chuỗi là Controllers\PostController đây là chuỗi tương đương với tên một folder và tên file. Mình tiến hành thanh thế dấu ngăn cách namespace \ thành dấu ngăn cách folder DIRECTORY_SEPARATOR và nối thêm định dạng file (.php). Việc còn lại chỉ là kiểm tra đường dẫn Controllers/PostController.php có trong folder app/ không và dùng lệnh require_once để load file lên.

Đây là kết quả.

Vậy là chúng ta đã có một autoloader đơn giản. Từ giờ khi bạn muốn thêm một class mới chỉ cần đặt tên file và namspace theo cấu trúc folder là chúng ta có thể không cần phải để ý gì tới việc load file.

Trên đây mình đã giới thiệu về autoload các class trong PHP. Đấy là bản chất của kỹ thuật này, giúp các bạn có thể hiểu được cách mà cách thư viện, Framework đã load file một cách tự động lên như thế nào mà không cần tới hàng nghìn lệnh require.

Autoload bằng composer

Composer là một công cụ dùng để quản lý dependencies trong PHP, ngoài việc quản lý dependencies, các bạn có thể dùng để đăng ký việc autoload các thư viện của mình, mà không cần viết thêm các custom method để load file như trong ví dụ trước. Vẫn sử dụng tiếp ví dụ trước. Mình sẽ load các file này một cách tự động bằng cách dùng composer.

Mình khai báo một file composer.json như sau.

{
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
}

sau đó mình tiến hành dump-autoload bằng lệnh composer dump-autoload composer sẽ tiến hành tạo folder vendor và vendor/autoload.php giờ mình sửa lại file index.php để require file vendor/autoload.php của composer vừa tạo ra.

<?php

require_once 'vendor/autoload.php';

use App\Controllers\PostController;

$controller = new PostController();
$controller->store();

Done! Chạy thử và kết quả y chang, nhưng chúng ta không phải thêm bất kỳ một custom function nào mà dùng composer như một autoloader.

Composer hỗ trợ chúng ta 4 cách khai báo autoload. Bao gồm PSR-4, PSR-0, classmapfiles.

PSR-4 và PSR-0 là cách chuẩn PSR về cách đặt tên file và folder theo quy tắc cho các PHP Framework, PSR-4 là chuẩn thay thế cho PSR-0. classmap là cho phép load toàn bộ các class có trong một folder chỉ định. và files cho phép chỉ định load các file được khai báo. Dưới đây là một số ví dụ về các cách khai báo với composer.json

{
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        },
        "psr-0": {
            "Monolog\\": "src/",
            "Vendor\\Namespace\\": "src/",
            "Vendor_Namespace_": "src/"
        },
        "classmap": ["src/", "lib/", "Something.php"],
        "files": ["src/MyLibrary/functions.php"]
    }
}

Các bạn vui lòng đọc chi tiết tại https://getcomposer.org/doc/04-schema.md#autoload

Thôi, đi làm không đến muộn sếp mắng.

Last updated