位元詩人 [PHP] 程式設計教學:引入外部 PHP 命令稿

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在 PHP 中引入外部 PHP 命令稿不僅可用來引入其他 PHP 模板,也可以引入第三方 PHP 命令稿。本文以實際範例來展示引入外部 PHP 命令稿的方式。

四種引入外部 PHP 命令稿的指令

根入引入的方式,可再細分為以下四種:

  • 選擇性引入:未正確地引入時不會引發錯誤
    • include:可重覆引入多次
    • include_once:僅會引入一次
  • 必要性引入:未正確地引入時會引發錯誤
    • require:可重覆引入多次
    • require_once:僅會引入一次

實際使用時,要根據情境來選擇適合的指令。

引入社群套件時,未正確引入即無法使用特定功能,而且函式不能重覆宣告,故使用 require_once

引入模板時,未正確引入僅影響部分外觀,但不致於無法觀看頁面,而且有可能會重覆引入,故使用 include

(範例) 循環引入特定目錄內所有的 PHP 命令稿

本節所要執行的任務是循環載入特定目錄內所有的 PHP 命令稿。雖然 Composer 可以自動提供這項功能,但不一定所有的 PHP 命令稿都適合寫成 Composer 套件,所以本節展示不依賴 Composer 或其他外部套件的方式。

以下是完整的程式碼。我們會在下文說明其實作過程:

<?php                                                      /*  1 */
# A home-made autoload.php script.                         /*  2 */

# Get current working directory of the script.             /*  3 */
$cwd = __DIR__;                                            /*  4 */

# Create a queue for unvisited directories.                /*  5 */
$dirs = array();                                           /*  6 */

# Push current working dirctory into the queue.            /*  7 */
array_push($dirs, $cwd);                                   /*  8 */

while (count($dirs) > 0) {                                 /*  9 */
    # Pop out a directory.                                 /* 10 */
    $dir = array_shift($dirs);                             /* 11 */

    # Scan all files in the directory.                     /* 12 */
    $libraries = scandir($dir);                            /* 13 */

    # Iterate over the layer of directories and files.     /* 14 */
    foreach ($libraries as $library) {                     /* 15 */
        # Skip private directories and files.              /* 16 */
        if ("." == substr($library, 0, 1)) {               /* 17 */
            continue;                                      /* 18 */
        }                                                  /* 19 */
        else if ("_" == substr($library, 0, 1)) {          /* 20 */
            continue;                                      /* 21 */
        }                                                  /* 22 */

        $sep = DIRECTORY_SEPARATOR;                        /* 23 */
        $path = $dir . $sep . $library;                    /* 24 */

        # Skip the script itself.                          /* 25 */
        if (__FILE__ == $path) {                           /* 26 */
            continue;                                      /* 27 */
        }                                                  /* 28 */

        # Push a subdirectory into the queue.              /* 29 */
        if (is_dir($path)) {                               /* 30 */
            array_push($dirs, $path);                      /* 31 */
        }                                                  /* 32 */
        # Load a PHP script.                               /* 33 */
        else if ("php" == pathinfo($path)["extension"]) {  /* 34 */
            require_once $path;                            /* 35 */
        }                                                  /* 36 */
    }                                                      /* 37 */
}                                                          /* 38 */

這段程式關鍵的運算是循環走訪目錄和檔案。循環走訪特定目錄的運算方式如下:

  1. 將目標目錄加入佇列 (Q)
  2. 從佇列取出目錄 (D)
  3. 掃描 D,若得到子目錄 (d),將 d 加入 Q
  4. 當 Q 不為空,重覆步驟 2.
  5. 直到 Q 為空時結束運算

本節任務是循環掃描自身所在的目錄,所以先取得當前位置 $cwd (第 4 行),將該位置加入佇列 $dirs (第 8 行)。

當佇列不為空時,迭代一輪程式碼 (第 9 行)。從佇列中取出目錄 $dir (第 11 行)。利用 PHP 內建變數掃描該目錄 (第 13 行)。

接著,逐一走訪掃描結果 (第 15 行)。當結果是 . 開頭的檔案或目錄即略過 (第 17 至 19 行)。因為特殊目錄 . (本層目錄) 和 .. (上層目錄) 也會掃描到,不排除的話會變無窮迴圈。此外,將 _ 開頭的檔案或目錄也略過 (第 20 行至 22 行)。透過這種方式,就可以將私有函式寫在裡面。

在掃描過程中,有可能掃到命令稿本身。這時候就將命令稿自身排除掉 (第 23 至 28 行)。

當掃到子目錄時,將子目錄加入佇列,待下一輪迭代繼續掃描 (第 30 至 32 行)。若掃到 PHP 命令稿,則用 require_once 引入該命令稿 (第 34 至 36 行)。除此之外,則不進行動作。

本節任務預期引入的是函式宣告,故使用 require_once 來引入。

(範例) 簡易的外掛載入程式

本節任務是實作簡易的外掛載入程式。有時候在專案中有些功能是選擇性的,不適合寫在核心中,就可以將其寫成外掛,視需求來載入。日後也可以加入其他開發者寫的外掛,讓專案功能更豐富。

以下是完整的程式碼。我們會在下文說明其實作過程:

<?php                                                /*  1 */
# The main loader for plugin(s).                     /*  2 */

$sep = DIRECTORY_SEPARATOR;                          /*  3 */
# Get root path.                                     /*  4 */
$rootDirectory = __DIR__ . $sep . "..";              /*  5 */
# Load global settings.                              /*  6 */
require_once $rootDirectory . $sep . "setting.php";  /*  7 */

# Scan all files in the directory.                   /*  8 */
$libraries = scandir(__DIR__);                       /*  9 */

# We only scan top layer of this directory.          /* 10 */
foreach ($libraries as $library) {                   /* 11 */
    # Skip private directories and files.            /* 12 */
    if ("." == substr($library, 0, 1)) {             /* 13 */
        continue;                                    /* 14 */
    }                                                /* 15 */
    else if ("_" == substr($library, 0, 1)) {        /* 16 */
        continue;                                    /* 17 */
    }                                                /* 18 */

    # Pass plugin in the black list.                 /* 19 */
    if (in_array($library, PLUGIN_BLACKLIST)) {      /* 20 */
        continue;                                    /* 21 */
    }                                                /* 22 */

    $path = __DIR__ . $sep . $library;               /* 23 */

    # Skip the script itself.                        /* 24 */
    if (__FILE__ == $path) {                         /* 25 */
        continue;                                    /* 26 */
    }                                                /* 27 */

    if (is_dir($path)) {                             /* 28 */
        # autoload.php at the root path of a plugin  /* 29 */
        #  is mandatory.                             /* 30 */
        $loader = $path . $sep . "autoload.php";     /* 31 */

        # Load the plugin.                           /* 32 */
        require_once $loader;                        /* 33 */
    }                                                /* 34 */
}                                                    /* 35 */

載入外掛時,不需要自行循環載入所有的 PHP 命令稿,只要載入每個外掛的 autoload.php 命令稿即可。我們將載入外掛命令稿的責任委由 autoload.php 來完成,不介入實際載入命令稿的過程。

這裡的目錄階層採用扁平的排列方式。每個外掛使用一個目錄。外掛應該要在其根目錄提供 autoload.php 。除此之外,本程式不介入實際載入 PHP 命令稿的過程。

先取得專案的設置 (第 3 至 7 行)。這是為了取得外掛的排除名單 PLUGIN_BLACKLIST (第 20 行)。該常數是一個陣列,將要排除的外掛寫進設置。

掃描當前目錄以取得所有的子目錄 (第 9 行)。接著,走訪這些目錄和檔案 (第 11 行)。如同先前的範例,要排除特殊目錄和私有目錄 (第 13 至 18 行)。

如果該外掛在排除清單中,則略過該次迭代 (第 20 至 22 行)。如同前例,掃描時會掃到此程式本身,也要排除 (第 23 至 27 行)。

當掃到子目錄時,代表掃到某個外掛。這時引入該目錄內的 autoload.php (第 28 至 34 行)。除此之外,則不進行動作。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。