魔术常量
魔术常量是 PHP 中一类特殊的预定义常量,它们的值会根据在代码中的使用位置而动态变化。例如 __LINE__ 返回当前行号,__FILE__ 返回当前文件路径。所有魔术常量都在编译时解析,与运行时常量不同。
基础概念
魔术常量之所以"魔术",是因为它们的值不是固定的,而是取决于它们在代码中出现的具体位置。它们在编译时被解析,因此性能优于运行时函数调用(如 debug_backtrace())。
PHP 魔术常量总览
| 常量 | 说明 | 示例值 |
|---|---|---|
__LINE__ | 文件中的当前行号 | 42 |
__FILE__ | 文件的完整路径和文件名 | /app/src/Service.php |
__DIR__ | 文件所在的目录 | /app/src |
__FUNCTION__ | 当前函数名 | handleRequest |
__CLASS__ | 当前类名(含命名空间) | App\Services\UserService |
__TRAIT__ | 当前 Trait 名(含命名空间) | App\Traits\Loggable |
__METHOD__ | 当前方法名 | App\Services\UserService::getUser |
__NAMESPACE__ | 当前命名空间名 | App\Services |
__PROPERTY__ | 属性挂钩中的属性名(PHP 8.4+) | 属性名称字符串 |
编译时解析
所有魔术常量都在编译时解析,不是运行时。这意味着它们的值在代码编译完成后就已确定,与运行时的上下文无关。
文件相关魔术常量
__FILE__ 和 __DIR__
<?php
declare(strict_types=1);
// __FILE__ 返回当前文件的完整路径
echo __FILE__ . "\n";
// 输出示例: /var/www/html/project/src/Service.php
// __DIR__ 返回文件所在目录(等价于 dirname(__FILE__))
echo __DIR__ . "\n";
// 输出示例: /var/www/html/project/src
// 实际应用:构建路径
$configPath = __DIR__ . "/../config/app.php";
$templatePath = __DIR__ . "/templates/layout.php";
echo "Config: " . realpath($configPath) . "\n";
echo "Template: " . realpath($templatePath) . "\n";__DIR__ 与 dirname(__FILE__) 的区别
__DIR__ 等价于 dirname(__FILE__),但更简洁高效。注意:除非是根目录,否则 __DIR__ 不包含末尾的斜杠。
__LINE__
<?php
declare(strict_types=1);
// __LINE__ 返回当前行号
echo "Line " . __LINE__ . "\n"; // Line 6
$debugInfo = [
"file" => __FILE__,
"line" => __LINE__, // 行号在编译时确定
"timestamp" => date("Y-m-d H:i:s"),
];
// 调试输出
function logDebug(string $message, array $context = []): void
{
$line = __LINE__;
$logEntry = "[" . date("H:i:s") . "] [Line {$line}] {$message}";
if (!empty($context)) {
$logEntry .= " | " . json_encode($context, JSON_UNESCAPED_UNICODE);
}
echo $logEntry . "\n";
}
logDebug("User login attempt", ["ip" => "192.168.1.1"]);函数和方法相关魔术常量
__FUNCTION__ 和 __METHOD__
<?php
declare(strict_types=1);
class UserService
{
/**
* __FUNCTION__ 只返回函数/方法名
* __METHOD__ 返回类名::方法名(含命名空间)
*/
public function getUserById(int $id): ?array
{
echo "Function: " . __FUNCTION__ . "\n"; // getUserById
echo "Method: " . __METHOD__ . "\n"; // App\Services\UserService::getUserById
return null;
}
}
$service = new UserService();
$service->getUserById(1);__FUNCTION__ vs __METHOD__
__FUNCTION__:仅返回函数名或方法名__METHOD__:返回完整的"类名::方法名"格式(包含命名空间)
在匿名函数中
<?php
declare(strict_types=1);
// 在匿名函数中,__FUNCTION__ 返回 {closure}
$handler = function (string $input): string {
echo __FUNCTION__ . "\n"; // {closure}
echo __LINE__ . "\n"; // 行号
return strtoupper($input);
};
echo $handler("hello") . "\n";
// 在普通命名函数中
function processData(string $data): void
{
echo __FUNCTION__ . "\n"; // processData
echo __METHOD__ . "\n"; // processData(不在类中,没有类名前缀)
}类和命名空间相关魔术常量
__CLASS__、__TRAIT__ 和 __NAMESPACE__
<?php
declare(strict_types=1);
namespace App\Models;
use App\Traits\Timestampable;
/**
* __CLASS__ 返回当前类的完整名称(含命名空间)
*/
trait Timestampable
{
public function getTraitName(): string
{
// __TRAIT__ 返回 Trait 名称(含命名空间)
return __TRAIT__; // App\Traits\Timestampable
}
}
class User
{
use Timestampable;
public function getClassName(): string
{
// __NAMESPACE__ 返回当前命名空间
echo "Namespace: " . __NAMESPACE__ . "\n"; // App\Models
echo "Class: " . __CLASS__ . "\n"; // App\Models\User
// ::class 魔术常量返回完全限定的类名
echo "Class constant: " . User::class . "\n"; // App\Models\User
return __CLASS__;
}
public function getMethodInfo(): string
{
echo "Method: " . __METHOD__ . "\n";
// App\Models\User::getMethodInfo
return __METHOD__;
}
}
$user = new User();
$user->getClassName();
$user->getMethodInfo();
echo "Trait name: " . $user->getTraitName() . "\n";__CLASS__ 在继承和 Trait 中的行为
__CLASS__ 始终指向代码所在类
__CLASS__ 返回的是代码编写所在的类名,而不是实际调用时的对象类名。在 Trait 方法中使用 __CLASS__ 时,返回的是使用该 Trait 的类名。
<?php
declare(strict_types=1);
namespace App\Base;
class BaseService
{
public function showClass(): void
{
echo "Class in BaseService: " . __CLASS__ . "\n";
// 输出: App\Base\BaseService(始终是代码所在类)
}
public function showDynamicClass(): void
{
echo "Actual class: " . get_class($this) . "\n";
// 输出实际对象类名
}
}
namespace App\Services;
use App\Base\BaseService;
class UserService extends BaseService
{
}
$service = new UserService();
$service->showClass(); // App\Base\BaseService(代码所在类)
$service->showDynamicClass(); // App\Services\UserService(实际对象类)详细说明
__PROPERTY__(PHP 8.4+)
PHP 8.4 引入了属性挂钩(Property Hooks),__PROPERTY__ 魔术常量仅在属性挂钩内部有效,等同于被挂钩的属性名称:
<?php
declare(strict_types=1);
// PHP 8.4+ 属性挂钩
class Circle
{
public float $radius {
get => $this->radius;
set(float $value) {
if ($value < 0) {
throw new InvalidArgumentException(
"Property '{__PROPERTY__}' must be non-negative"
);
}
$this->radius = $value;
}
}
}
// __PROPERTY__ 在属性挂钩中返回 "radius"ClassName::class 常量
::class 魔术常量
ClassName::class 返回类的完全限定名称(包含命名空间)。它在编译时解析,比运行时的 get_class() 或 ReflectionClass 更高效。
<?php
declare(strict_types=1);
namespace App\Services;
class UserService
{
}
// ::class 返回完全限定类名
echo UserService::class . "\n"; // App\Services\UserService
// 用于类型检查
$className = UserService::class;
if ($service instanceof $className) {
echo "Is UserService instance\n";
}
// 用于动态实例化
$service = new $className();魔术常量在 include 文件中的行为
关键:魔术常量在定义位置解析
当函数参数使用魔术常量作为默认值时,值在函数定义位置解析,而不是在函数调用位置:
<?php
declare(strict_types=1);
// helpers.php
function showLocation(string $file = __FILE__, int $line = __LINE__): void
{
echo "Defined in: {$file} (Line {$line})\n";
echo "Local constants: " . __FILE__ . "; " . __LINE__ . "\n";
}
// main.php
require_once __DIR__ . "/helpers.php";
showLocation(); // 参数使用默认值,值是 helpers.php 中的位置
showLocation(__FILE__, __LINE__); // 参数显式传递,值是 main.php 中的位置实战示例
日志记录工具
<?php
declare(strict_types=1);
namespace App\Utils;
class Logger
{
private string $logFile;
public function __construct(string $logDir)
{
$this->logFile = rtrim($logDir, "/") . "/app_" . date("Y-m-d") . ".log";
}
/**
* 记录调试信息(自动附加文件名和行号)
*/
public function debug(string $message, array $context = []): void
{
// 注意:这里的 __FILE__ 和 __LINE__ 是此方法定义的位置
// 需要调用者传递其文件和行号
$this->write("DEBUG", $message, $context);
}
/**
* 记录信息(调用者需传递位置信息)
*/
public function info(
string $message,
string $file = __FILE__,
int $line = __LINE__,
array $context = []
): void {
$this->write("INFO", $message, $context, $file, $line);
}
private function write(
string $level,
string $message,
array $context = [],
string $file = "",
int $line = 0
): void {
$timestamp = date("Y-m-d H:i:s");
$entry = "[{$timestamp}] [{$level}]";
if ($file) {
$shortFile = basename($file);
$entry .= " [{$shortFile}:{$line}]";
}
$entry .= " {$message}";
if (!empty($context)) {
$entry .= " " . json_encode($context, JSON_UNESCAPED_UNICODE);
}
$entry .= "\n";
file_put_contents($this->logFile, $entry, FILE_APPEND);
}
}自动加载路径构建
<?php
declare(strict_types=1);
// 使用 __DIR__ 构建项目路径
define("APP_ROOT", __DIR__ . "/..");
define("APP_PATH", APP_ROOT . "/app");
define("CONFIG_PATH", APP_ROOT . "/config");
define("STORAGE_PATH", APP_ROOT . "/storage");
define("VIEWS_PATH", APP_PATH . "/Views");
// 自动加载配置文件
$configFiles = glob(CONFIG_PATH . "/*.php");
foreach ($configFiles as $file) {
$config = require $file;
// 处理配置...
}
echo "App Root: " . APP_ROOT . "\n";
echo "Config Path: " . CONFIG_PATH . "\n";
echo "Storage Path: " . STORAGE_PATH . "\n";异常类中使用魔术常量
<?php
declare(strict_types=1);
namespace App\Exceptions;
use RuntimeException;
class ValidationException extends RuntimeException
{
private string $field;
private mixed $value;
public static function missingField(string $field): self
{
// __METHOD__ 包含完整的类和方法路径
return new self(
"Field '{$field}' is required",
0,
null,
$field,
null
);
}
public static function invalidValue(string $field, mixed $value): self
{
return new self(
"Invalid value for field '{$field}'",
0,
null,
$field,
$value
);
}
public function __construct(
string $message,
int $code,
?\Throwable $previous,
string $field,
mixed $value
) {
$this->field = $field;
$this->value = $value;
parent::__construct($message, $code, $previous);
}
public function getField(): string
{
return $this->field;
}
public function getValue(): mixed
{
return $this->value;
}
}
// 使用
try {
throw ValidationException::missingField("email");
} catch (ValidationException $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "Field: " . $e->getField() . "\n";
}注意事项
1. 魔术常量不区分大小写
<?php
declare(strict_types=1);
// 以下写法等价
echo __FILE__ . "\n";
echo __file__ . "\n";
echo __File__ . "\n";
// 推荐使用大写形式,更易识别2. 不要在配置中硬编码路径
使用 __DIR__ 代替硬编码的路径,确保项目的可移植性:
<?php
declare(strict_types=1);
// 不推荐:硬编码路径
// require "/var/www/html/config/app.php";
// 推荐:使用 __DIR__
require __DIR__ . "/../config/app.php";3. __DIR__ 与 $_SERVER['DOCUMENT_ROOT'] 的区别
__DIR__:PHP 文件在文件系统中的实际位置$_SERVER['DOCUMENT_ROOT']:Web 服务器的文档根目录
两者可能不同,特别是在使用虚拟目录或 FPM 配置时。
最佳实践
- 使用
__DIR__构建路径:避免硬编码,保证可移植性 - 使用
::class获取类名:编译时解析,比get_class()高效 - 用
__METHOD__做日志标记:包含完整的类和方法信息 - 大写书写魔术常量:
__FILE__比__file__更易识别 - 注意魔术常量在参数默认值中的位置:在定义位置解析
- 在 include 中正确使用:
__FILE__始终指向当前文件
下一节
学习了魔术常量之后,基础语法部分的学习即将完成。接下来我们将进入表达式和运算符的学习。首先了解什么是表达式以及表达式的求值规则。请阅读 表达式基础。