Tổ chức mã nguồn với include, require, namespace

    0

    Khi xây dựng một ứng dụng thực sự với khối lượng code lớn và đảm nhiệm nhiều chức năng khác nhau, một yêu cầu bắt buộc là phải có cách tổ chức mã nguồn phù hợp. Để hỗ trợ tổ chức mã nguồn, PHP cung cấp hai cách phân chia: phân chia về mặt vật lý (thành các file mã nguồn), và phân chia về mặt logic (thành các không gian tên).

    Tổ chức mã nguồn trong PHP

    Tất cả các ví dụ chúng ta thực hiện từ đầu đến giờ đều rất đơn giản và nằm gọn trong một file script duy nhất. Mọi biến, hằng và hàm được định nghĩa và sử dụng trong chính file script nơi chúng được định nghĩa. Mô hình tổ chức code này chỉ phù hợp khi bắt đầu học lập trình.

    Code của các ứng dụng thực sự không bao giờ nằm gọn được trong một file như vậy:

    • Khi khối lượng code tăng lên bạn sẽ phải phân chia code ra nhiều file khác nhau để dễ dàng quản lý. Bạn thậm chí còn phải tách code ra nhiều file đặt trong nhiều thư mục khác nhau.
    • Code trong một ứng dụng luôn được phân chia thành nhiều nhóm khác nhau, như giao diện, logic, xử lý dữ liệu. Do vậy việc phân tách chúng là một yêu cầu bắt buộc để tổ chức và quản lý.
    • Bạn cần sử dụng file script do người khác đã xây dựng sẵn.
    • Bạn cần tái sử dụng file script do mình đã xây dựng trước đây.

    Cách thức tổ chức quản lý mã nguồn trong PHP bao gồm hai mức độ:

    • Mức độ vật lý: phân chia mã nguồn ra các file script riêng rẽ. Bạn cần sử dụng các lệnh như include, require, include_once, require_once để “ghép” các file mã nguồn vật lý khi chương trình hoạt động.
    • Mức độ logic: phân chia hàm và class vào các không gian tên (namespace) riêng biệt giúp tránh xung đột tên gọi. Không gian tên cho phép tạo ra và sử dụng các hàm và class trùng tên nhau.

    Trong các phần tiếp theo của bài học này chúng ta sẽ tìm kiểu cả hai loại kỹ thuật tổ chức mã nguồn này.

    Sử dụng code trong file khác, include và require

    Để sử dụng code trong một file script khác, PHP cung cấp hai phương án: include require.

    Hãy cùng thực hiện một ví dụ. Tạo ra 3 file config.php, functions.php và index.php (trong cùng thư mục) với nội dung như sau:

    <?php
    
    define('A_CONSTANT', 'THIS IS A CONSTANT FROM OTHER FILE');
    
    $a_variable = 'This is a variable from other file';
    <?php
    
    function a_function() {
        echo "This is a function from other file";
    }
    <?php
    
    include 'config.php'; // hoặc require 'config.php';
    include 'functions.php'; // hoặc require 'functions.php';
    
    echo A_CONSTANT . "\r\n";
    echo $a_variable ."\r\n";
    a_function();
    THIS IS A CONSTANT FROM OTHER FILE
    This is a variable from other file
    This is a function from other file

    Sử dụng include hoặc require trong ví dụ này đem lại cùng một hiệu quả, với cùng một cú pháp.

    Khi sử dụng include hoặc require, tất cả khai báo biến và hàm, cũng như các đoạn code trong script ngoài tương ứng sẽ được thực thi ở vị trí của lời gọi hàm include/require.

    Sau vị trí này, tất cả các biến và hàm của script ngoài sẽ được truy xuất như thể chúng được viết trong cùng một file với script hiện tại. Bạn có thể gán giá trị mới cho các biến đó.

    Lời gọi include hoặc require có thể để dấu () như lời gọi hàm, hoặc không dùng (như lời gọi lệnh echo). Ví dụ:

    // hai lệnh sau là tương đương
    include('functions.php');
    include 'functions.php';
    
    // hai lệnh sau là tương đương
    require('config.php');
    require 'config.php';

    Nếu chọn phương án nào thì sử dụng thống nhất.

    Khi gặp include/require, PHP sẽ thay thế lời gọi hàm này bởi văn bản của file script tương ứng. Như vậy, dễ hình dung include/require thực tế sẽ “ghép” các file code lại để thực thi.

    Khi này, tất cả lệnh trong script (bị gọi) được thực thi như thể các lệnh được viết ở vị trí của include/require.

    Điều này cũng có nghĩa là, nếu bạn gọi include/require bên trong một hàm, tất cả những lệnh trong script (bị gọi) sẽ biểu hiện giống hệt như khi nó được viết trong thân hàm. Ví dụ, biến trong script bị gọi sẽ trở thành biến cục bộ của hàm.

    Sự khác biệt giữa include và require

    Sự khác biệt cơ bản nhất khi sử dụng giữa include và require ở chỗ:

    • Nếu không tìm thấy file, include chỉ đưa ra cảnh báo (E_WARNING) và vẫn tiếp tục thực hiện script.
    • Nếu không tìm thấy file, require phát ra lỗi (E_COMPILE_ERROR) và dừng thực thi script. Chương trình đến đây là hết!

    Như vậy, việc lựa chọn giữa include và require nằm ở vai trò của file script đang muốn gọi.

    Nếu file đặc biệt quan trọng, không có nó thì không thể thực thi code ở file gọi, khi đó bạn dùng require.

    Những trường hợp còn lại có thể dùng include.

    Sử dụng include_once và require_once

    Nếu bạn vô tình include/require lại script ngoài, cả quá trình thực thi script ngoài sẽ thực hiện lại từ đầu! Nghĩa là các biến trong script ngoài sẽ được gán lại giá trị ban đầu.

    Nếu bạn đã cập nhật biến (từ script ngoài), những gì bạn điều chỉnh sẽ mất đi.

    Nếu bạn định nghĩa một hàm và include/require file tương ứng hai lần, bạn sẽ gặp lỗi tái định nghĩa hàm.

    Nếu bạn cần đảm bảo chắc chắc rằng script ngoài chỉ được phép chạy một lần duy nhất, bạn cần dùng include_once hoặc require_once thay cho include hoặc require.

    Khi sử dụng include_once/require_once lần đầu tiên sẽ có cùng tác dụng với include/require. Từ lần gọi thứ hai PHP sẽ không tải file nữa.

    File và đường dẫn trong include/require

    Trong hàm require hoặc include, khi chỉ định tên file và đường dẫn tới script ngoài cần lưu ý các điểm sau.

    (1) Có thể chỉ định tải file trên cùng một máy tính vật lý

    Nếu script chính và script ngoài nằm trong cùng một thư mục, bạn chỉ cần cung cấp tên file: include('functions.php');

    Nếu script chính và script ngoài nằm trong các thư mục khác nhau, bạn có thể sử dụng đường dẫn tương đối (tính từ thư mục của script chính) hoặc đường dẫn tuyệt đối (tính từ thư mục gốc).

    Khi chỉ định thư mục có thể sử dụng dấu phân tách / cho cả windows và linux. Ví dụ:

    // đường dẫn tương đối
    include 'lib/common-functions.php'; // trong thư mục con lib/
    include 'lib/functions/common-functions.php'; // trong thư mục con lib/functions/
    
    // đường dẫn tuyệt đối
    include 'C:/projects/lib/common-functions.php';

    Nhìn chung nên hạn chế sử dụng đường dẫn tuyệt đối.

    Khi chỉ định đường dẫn tương đối có thể dùng ký tự .. (hai dấu chấm) để chỉ định thư mục cha. Ví dụ:

    include('../common-functions.php'); // tải file nằm trong thư mục cha của thư mục hiện hành

    (2) Có thể tải file từ một server khác

    Ví dụ,

    include('http://www.example.com/common-functions.php');

    Đây là lệnh yêu cầu tải file script common-functions.php từ một server.

    Cần lưu ý: nếu chỉ định tải một file php từ một máy chủ khác, và máy chủ này được cấu hình cho php, cái bạn thu được không phải là file script (viết bằng php), mà là kết quả chạy script php đó.

    (3) File script ngoài không cần phần mở rộng .php

    Điều này liên quan đến việc include và require chỉ đọc nội dung của file. Một trong những phần mở rộng thường gặp là .inc, ví dụ config.inc (thay vì config.php).

    Ví dụ,

    include('http://www.example.com/common-functions.inc');
    include('common-functions.inc');

    Việc đặt phần mở rộng khác .php có một lợi ích nếu tải file script ngoài từ một máy chủ khác: bạn sẽ luôn thu được file mã (viết bằng php), vì server sẽ không thực thi file này (mặc dù chứa lệnh php).

    (4) Đường dẫn tương đối trong script bị gọi sẽ thay đổi phụ thuộc vào script gọi.

    Đây là một điều khá oái oăm nhưng không khó hiểu.

    Khi include/require, thực sự có thể hình dung là code của file bị gọi sẽ được chèn vào đúng vị trí của lệnh include/require. Do vậy, nếu trong code bị gọi có đường dẫn tương đối, nó cũng trở thành đường dẫn tương đối trong file gọi.

    Sử dụng namespace trong PHP

    Khi khối lượng code lớn, nhất là khi sử dụng lại code của người khác, một khả năng nữa cũng xảy ra: trùng tên gọi. Do đó, việc quản lý định danh khi code phân phối trên nhiều file trở thành một yêu cầu quan trọng.

    Lấy ví dụ, bạn có 2 file thư viện, lib1.php và lib2.php như sau:

    <?php // lib1.php
    function welcome($name){
        echo "Hello, $name. Welcome to heaven!";
    }
    <?php // lib2.php
    function welcome($name){
        echo "Privet, $name. Welcome to hell!";
    }

    Giờ trong file index.php bạn include cả hai file thư viện:

    <?php // index.php
    include 'lib1.php';
    include 'lib2.php';
    $name = 'Barack Obama';
    welcome($name); 

    Khi chạy script index.php bạn sẽ gặp lỗi ‘Fatal error: Cannot redeclare welcome()‘. Logic rất đơn giản, khi include hai file, coi như bạn chạy hai lần lệnh khai báo hàm welcome!

    Vậy làm thế nào để sử dụng được cả hai hàm welcome trong hai thư viện?

    PHP cung cấp một công cụ: namespace. Namespace là cách thức tổ chức quản lý về mặt logic của hàm và class (trong khi việc phân chia ra các file script là tổ chức quản lý về mặt vật lý).

    Hãy điều chỉnh code của lib1.php và lib2.php như sau:

    <?php // lib1.php
    namespace America;
    function welcome($name){
        echo "Hello, $name. Welcome to heaven!";
    }
    <?php // lib2.php
    namespace Russia;
    function welcome($name){
        echo "Privet, $name. Welcome to hell!";
    }

    Giờ bạn có thể sử dụng cả hai hàm welcome trong index.php như sau:

    <?php
    
    require 'Lib/lib1.php';
    require 'Lib/lib2.php';
    
    $name = 'Barack Obama';
    America\welcome($name); // cách gọi hàm welcome trong namespace America
    Russia\welcome($name); // cách gọi hàm welcome từ namespace Russia

    Hãy đề ý lời gọi hàm giờ phải chỉ định cả namespace chứa hàm: America\welcome();, Russia\welcome();

    Như vậy chúng ta có thể tạo ra/include/sử dụng các hàm trùng tên nhau.

    Một số lưu ý khi sử dụng namespace

    (1) Lệnh tạo namespace namespace <tên>; bắt buộc phải là lệnh đầu tiên (ngay sau tag mở).

    (2) Namespace không có tác dụng đối với biến.

    Namespace có thể hình dung như một không gian riêng (chứa hàm và class). Trong khi đó, biến trong PHP luôn gắn với một trong hai không gian: global (toàn cục) và local (bên trong hàm). Tức là, biến trong PHP không liên kết với namespace.

    Nếu một biến khai báo trong script, dù script đó nằm trong namespace, biến luôn thuộc về không gian toàn cục (global).

    (3) Nếu một namespace quá dài hoặc khó nhớ khó viết, bạn có thể đặt biệt danh cho namespace. Ví dụ:

    use America as A, Russia as R;
    
    $name = 'Barack Obama';
    A\welcome($name); // gọi America qua biệt danh A
    R\welcome($name); // gọi Russia qua biệt danh R

    (4) Nếu muốn sử dụng tên gọi ngắn gọn của hàm từ namespace khác, bạn có thể sử dụng lệnh use function như sau:

    use function 
        America\welcome,
        Russia\welcome as r_welcome; // phải sử dụng biệt danh cho Russia\welcome vì trùng tên (ngắn gọn) với America\welcome.
    
    $name = 'Barack Obama';
    welcome($name); // đây là lời gọi tới hàm America\welcome
    r_welcome($name); // đây là biệt danh của hàm Russia\welcome

    Ở đây cần lưu ý, do tên gọi ngắn gọn của hai hàm trùng nhau, bạn chỉ có thể sử dụng một trong hai hàm với tên ngắn gọn. Trong ví dụ trên khi gọi use function America\welcome, bạn quyết định rằng tên gọi ngắn gọn welcome sẽ dành cho hàm America\welcome.

    Với hàm còn lại bạn cần đặt biệt danh cho hàm:

    use function Russia\welcome as r_welcome;

    Biệt danh của hàm là một tên gọi khác do bạn tùy chọn, miễn là không trùng với tên hàm sẵn có.

    Chúng ta sẽ quay lại với namespace khi học về cách xây dựng class trong PHP.

    Kết luận

    Trong bài học này chúng ta đã làm quen với hai cách thức tổ chức code trong PHP:

    • Ở mức độ vật lý bằng cách tách code thành các file riêng biệt.
    • Ở mức độ logic bằng cách tách hàm (và class) thành các không gian tên (namespace) riêng biệt.
    • Để ghép nối các file code có thể sử dụng hàm include (include_once) hoặc require (require_once).
    • Để sử dụng hàm từ không gian tên khác cần sử dụng lệnh use function, hoặc sử dụng tên đầy đủ của hàm có dạng <tên_namespace>\<tên_hàm>().

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Subscribe
    Notify of
    guest
    0 Thảo luận
    Inline Feedbacks
    View all comments