function_exists() 檢查函數是否已定義(內置函數、用戶函數、擴展函數)。是擴展依賴驗證、可選功能降級、版本兼容、安全審計的核心工具。
bool function_exists ( string $function_name )
TRUE:函數存在(含禁用函數、不可調用函數)。FALSE:函數不存在。| 情況 | function_exists | is_callable | 說明 |
|---|---|---|---|
| 正常函數 | true | true | |
| disable_functions 禁用 | true | false | 僅檢查定義,不檢查可調用性 |
| Suhosin 黑名單 | true | false | 需額外檢查 suhosin 配置 |
| 方法名 | false | true | 需用 method_exists() |
| 閉包/可調用對象 | false | true |
<?php
// 檢查 IMAP 擴展
if (function_exists('imap_open')) {
echo "IMAP 功能可用";
// 啟用郵件相關功能
} else {
echo "IMAP 不可用,使用 POP3/SMTP 替代";
// 禁用郵件功能或提示用戶安裝擴展
}
// 檢查 GD 擴展關鍵函數
$gd_functions = ['imagecreatetruecolor', 'imagepng', 'imagejpeg', 'imagefttext'];
$gd_ok = true;
foreach ($gd_functions as $fn) {
if (!function_exists($fn)) {
$gd_ok = false;
break;
}
}
if (!$gd_ok) die("GD 擴展不完整,無法生成驗證碼/圖表");
?>
<?php
// PHP 7.3+ json_validate, 低版本自定義
if (!function_exists('json_validate')) {
function json_validate(string $json, int $depth = 512, int $flags = 0): bool {
json_decode($json, false, $depth, $flags);
return json_last_error() === JSON_ERROR_NONE;
}
}
// PHP 8.0+ str_contains, 低版本 polyfill
if (!function_exists('str_contains')) {
function str_contains(string $haystack, string $needle): bool {
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
}
// 現在全項目統一用 json_validate(), str_contains()
?>
<?php
function is_function_callable($func) {
if (!function_exists($func)) return false;
// 檢查 disable_functions
$disabled = explode(',', ini_get('disable_functions'));
$disabled = array_map('trim', $disabled);
if (in_array($func, $disabled, true)) return false;
// 檢查 Suhosin
if (extension_loaded('suhosin')) {
$suhosin_bl = @ini_get('suhosin.executor.func.blacklist');
if ($suhosin_bl) {
$bl = array_map('trim', explode(',', $suhosin_bl));
if (in_array($func, $bl, true)) return false;
}
}
return is_callable($func);
}
// 審計危險函數
$dangerous = ['exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'pcntl_exec'];
foreach ($dangerous as $fn) {
echo "$fn: " . (is_function_callable($fn) ? '可調用 ⚠️' : '安全 ✅') . "\n";
}
?>
<?php
function run_hook($hook_name, $data) {
$func = "hook_{$hook_name}";
if (function_exists($func)) {
return $func($data);
}
return $data; // 無鉤子則透傳
}
// 插件定義
function hook_user_register($user) {
// 發送歡迎郵件、初始化配置...
return $user;
}
// 核心調用
$user = run_hook('user_register', $user);
?>
<?php
var_dump(function_exists('')); // false
var_dump(function_exists(null)); // false (PHP 8.0+ TypeError, 早期 false)
// 不要傳遞動態變量未驗證直接調用
?>
<?php
class FunctionRegistry {
private $cache = [];
private $disabled_cache = null;
public function isAvailable(string $func): bool {
if (isset($this->cache[$func])) return $this->cache[$func];
if (!function_exists($func)) return $this->cache[$func] = false;
// 檢查禁用
if ($this->disabled_cache === null) {
$this->disabled_cache = array_flip(array_map('trim', explode(',', ini_get('disable_functions'))));
}
if (isset($this->disabled_cache[$func])) return $this->cache[$func] = false;
// Suhosin 檢查...
return $this->cache[$func] = is_callable($func);
}
public function getAvailable(array $funcs): array {
return array_filter($funcs, [$this, 'isAvailable']);
}
}
?>
function_exists + is_callable 雙重檢查。ini_get、字符串分割開銷。method_exists($obj, 'method') 或 is_callable([$obj, 'method'])。