2020年3月12日 星期四

PHP 的組態設定

在撰寫本文時,使用的是利用 homebrew 安裝的 PHP 7.4.3 (以及 PHP 內建的 PHP-FPM)、httpd 2.4.1。

提到 PHP 的組態設定,第一個想到的一定是 PHP 的初始化組態檔 -- php.ini,它負責 PHP 各種指令 (Directives) 的預設組態設定。 但是除了 php.ini 之外,PHP 還提供了許多組態設定的方式:當 SAPI 為 Apache 2.0 Handler 時,使用者可以透過 httpd 組態檔 httpd.conf 以及每個檔案夾下的超文件取存 (Hypertext) 設定檔 .htaccess 進行組態設定;當 SAPI 為 CGI/FastCGI 時,從 PHP 5.3.0 開始可以透過使用者組態檔 .user.ini (user_ini.filenamephp.ini 的預設值) 對每一個檔案夾進行組態設定;在執行時期則可以使用組態設定函式 ini_set() 在腳本中進行組態設定。

雖然這些方式都可以進行組態設定,但是可以設定的內容卻不盡相同。PHP 將組態設定指令區分為 5 類 (針對 PHP_INI_* 分成 4 類,在指令列表中多了php.ini only),下表列出了這 5 類可以設定的方式:

分類設定方式
PHP_INI_USER 指令可以在使用者的腳本 (使用 ini_set()) 或是在 Windows registry 設定。從 PHP 5.3 開始,指令可以在 .user.ini 設定。PHP 5.0 開始沒有指令屬於這個分類。
PHP_INI_PERDIR 指令可以在 php.ini.htaccesshttpd.conf 或是 .user.ini (從 PHP 5.3 開始) 設定。
PHP_INI_SYSTEM 指令可以在 php.ini 或是 httpd.conf 設定。
PHP_INI_ALL 指令可以在所有的地方設定。
php.ini only 指令只可以在 php.ini 設定。


PHP 組態設定的指令非常的多,如果有需要可以參考指令列表的介紹。


初始化組態檔 php.ini


當 PHP 啟動時,初始化組態檔 php.ini 會被讀取,並且完成相關的指令設定。在 PHP 與伺服器之間的應用程式介面一文中介紹過:
1)使用 SAPI 模組 (Apache 2.0 Handler) 時,PHP 會在網頁伺服器的程序中執行,不需要針對每個請求啟動一個新的程序,所以 php.ini 只會在網頁伺服器啟動時被讀取一次;但是當更改組態檔的內容後,需要重新啟動網頁伺服器才會生效。
2) 使用 CGI 時,每次收到新的請求時,都要啟動一個執行 php-cgi 的程序,讀取 php.ini;所以在更改組態檔內容後,有新的請求就會生效。
3) 使用 FastCGI 時,FastCGI 伺服器會建立一個主要程序以及多個工作者子程序,不需要針對每個請求啟動一個新的程序,所以php.ini 只會在 FastCGI 伺服器啟動時被讀取一次;更改完組態檔內容後,需要重新啟動 FastCGI 伺服器才會生效。
4) 使用 CLI 時,每次收到新的請求時都要讀取 php.ini;在更改組態檔內容後,有新的請求就會生效。


另外,在使用 CGI/FastCGI 時,如果要對全網站或是部份檔案夾進行設定,分別要在 php.ini 中的 [HOST=www.example.com] 或是 [PATH=/www/mysite] 區塊進行設定。布林 (boolean) 組態指令的開啟值可以是 1、On、True 或是 Yes;關閉值可以是 0、Off、False 或是 No。

httpd 組態檔 httpd.conf 以及 .htaccess


當 PHP 做為 httpd 的模組使用時,使用者可以透過 httpd.conf 以及 .htaccess 進行組態設定。要能夠利用這兩個檔案設定組態,使用者還需要對檔案夾開啟 "AllowOverride Options" 或是 "AllowOverride All" 權限。httpd.conf 可以設定 PHP_INI_ALL、PHP_INI_PERDIR、以及 PHP_INI_SYSTEM 等類型的指令;.htaccess 則只能夠設定 PHP_INI_ALL 以及 PHP_INI_PERDIR。在 httpd.conf 更改 PHP 組態設定需要重啟 httpd,.htaccess 則是在更改組態設定後,有新的請求就會生效。

httpd.conf 可以使用下列四項指令進行組態設定:


指令用途說明
php_value name value用來設定指令的數值。PHP_INI_ALL、PHP_INI_PER。不能用來設定布林組態指令。
php_flag name on|off用來設定布林組態指令。PHP_INI_ALL、PHP_INI_PER。
php_admin_value name value用來設定指令的數值。不能夠在 .htaccess 中設定,任何指令使用php_admin_value 設定後,不能夠被 .htaccessini_set() 覆寫;要清除先前的設定值,值要設為 none。
php_admin_flag name on|off用來設定布林組態指令。不能夠在 .htaccess 中設定,任何指令使用php_admin_value 設定後,不能夠被 .htaccessini_set() 覆寫。


設定範例 1 (httpd.conf):

<ifmodule mpm_prefork_module>
    LoadModule php7_module /usr/local/opt/php/lib/httpd/modules/libphp7.so
    AddHandler application/x-httpd-php .php
    AddType text/html .php
    php_flag display_errors Off
</ifmodule>


上面的設定會將錯誤顯示更改為不顯示 (display_errors Off)。

設定範例 2 (.htaccess):

php_flag display_errors On
php_value error_reporting 32759


範例 1 在 httpd.conf 中使用 php_flag 而不是 php_admin_flag,所以範例 2 可以在 .htaccess 中改寫錯誤顯示的設定為開啟。另外,PHP 常數只可以在 PHP 內使用,無法在 PHP 外 (例如,httpd.conf 或是 .htaccess) 使用。如果要在這兩個檔案使用,必需要使用位元遮罩 (Bitmask) 數值代替。這裡的 32759 代表的是 E_ALL & ~E_NOTICE。

PHP-FPM 組態檔 php-fpm.conf 以及程序池組態檔



適用於 PHP-FPM 的組態檔,設定的方式同 php.ini,詳細的設定請參考

使用者組態檔 .user.ini


從 PHP 5.3.0 開始,當 SAPI 為 CGI/FastCGI 時,PHP 支援基於每個檔案夾的使用者組態 .user.ini;同時將原有 PECL 的 htscanner 延伸套件廢棄。因為每個請求都會由網頁伺服器轉交給 FastCGI 伺服器 (例如,PHP-FPM) 處理,.user.ini 的組態設定方式和 php.ini 相同。當使用 CGI/FastCGI 時,不可以再使用 php_value、php_flag、php_admin_value、以及 php_admin_flag 進行設定,因為這 4 個組態設定指令是專門提供給 PHP 模組 (Apache 2.0 Handler) 使用的,而當採用 CGI/FastCGI 時,httpd 就不會再載入這個模組。

如果檔案夾中的 .htaccess 中有使用 php_value 或是 php_flag,在瀏覽時該檔案夾內的網頁時會出現 500 Internal Server Error。在 httpd 的 error log 則會出現:

/temp/.htaccess: Invalid command 'php_flag', perhaps misspelled or defined by a module not included in the server configuration


同樣地,當沒有使用 PHP 模組時,不可以在 httpd.conf 使用 php_value、php_flag、php_admin_value 或是 php_admin_flag 進行組態設定。



注意:
1. .user.ini 存放在公開的檔案夾中,它的內容可以被使用者讀取,不要在檔案內進行敏感性的設定。建議可以在 .htaccess 禁止使用者讀取:
<Files ".user.ini">
    Require all denied
</Files>


2. PHP-FPM 的程序池裡的工作者程序需要一小段時間才能夠全部都讀取到重新設定後的 .user.ini (user_ini.cache_ttl 的預設值是 5 分鐘),如果要設定值立刻生效,可以重新啟動 PHP-FPM。

組態設定函式 ini_set()


使用組態設定函式 ini_set() 進行組態設定是最靈活,但是可以設定的指令最少的方式。在腳本中使用 ini_set() 對特定的組態選項進行設定後,組態選項將會在腳本執行期間保留此新值,並且在腳本結束後恢復原來的值。因此可以根據腳本執行上的需求,對每個腳本進行不同的組態設定。ini_set() 只能夠設定 PHP_INI_ALL 類型的指令 (因為唯一的 PHP_INI_USER 指令 tidy.clean_output 從 PHP 5.0 開始變成了PHP_INI_PERDIR 了)。

ini_set() 的函式定義如下:
ini_set ( string $varname , string $newvalue ) : string


如果設定成功會傳回設定選項的舊值,失敗則會傳回 FALSE。

範例 3:
<?php
declare(strict_types = 1);

echo ini_get('display_errors');
if (!ini_get('display_errors')) {
    ini_set('display_errors', '1');
}
echo ini_get('display_errors');
ini_set('error_reporting', '32759'); //32759 為 E_ALL & ~E_NOTICE 的位元遮罩值


範例 3 會使用 ini_get() 檢查錯誤顯示的設定,並且設定為顯示錯誤資訊,方便進行程式除錯。


注意:因為用 ini_get() 的參數宣告為 string,當使用強型別 (declare(strict_types = 1);) 時,'E_ALL'、'E_WARNING'、'E_NOTICE' 等整數常數會被當成是字串傳入;此時只能夠使用數值進行組態值設定。若要使用常數設定 error_reporting,可以使用 error_reporting() 函式:
error_reporting(E_ALL & ~E_NOTICE);


沒有留言: