PHP 命名空间(namespace) 详解

命名空间是PHP中一种封装事物的方法,类似于文件系统的目录结构。它可以解决两类问题:

  • 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突
  • 为很长的标识符名称创建一个别名的名称,提高源代码的可读性

定义命名空间

默认情况下,所有常量、类和函数名都放在全局空间下。命名空间通过关键字 namespace 来声明,必须在文件所有代码之前声明(除了declare语句)。

基本语法:

<?php
// 定义代码在 'MyProject' 命名空间中
namespace MyProject;

const PROJECT_NAME = 'My Application';
class Database {
    public function connect() {
        return "Connected to database";
    }
}

function logMessage($message) {
    echo "Log: " . $message;
}

// 使用当前命名空间中的元素
$db = new Database();
echo $db->connect();
logMessage("Application started");
?>

在同一个文件中定义多个命名空间:

<?php
namespace MyProject1;

const VERSION = '1.0';
class User {
    public function getName() {
        return "User from MyProject1";
    }
}

namespace MyProject2;

const VERSION = '2.0';
class User {
    public function getName() {
        return "User from MyProject2";
    }
}

// 使用大括号语法定义命名空间
namespace MyProject3 {
    class Logger {
        public function log($msg) {
            echo "MyProject3 Logger: " . $msg;
        }
    }
}

// 全局命名空间
namespace {
    // 可以在这里编写不在任何命名空间中的代码
    $user1 = new MyProject1\User();
    $user2 = new MyProject2\User();

    echo $user1->getName() . "\n";
    echo $user2->getName() . "\n";
    echo "MyProject1 Version: " . MyProject1\VERSION . "\n";
}
?>

正确的声明位置:

<?php
declare(encoding='UTF-8'); // 在命名空间之前唯一合法的代码

namespace MyApp;

class Application {
    // 类代码
}
?>

错误的声明位置:

<html>
<?php
namespace MyProject; // 致命错误 - 命名空间前出现了HTML代码
?>

子命名空间

PHP命名空间支持层次化结构,类似于文件系统的目录结构。

<?php
namespace MyCompany\Project\Utils;

class StringHelper {
    public static function truncate($string, $length) {
        if (strlen($string) > $length) {
            return substr($string, 0, $length) . '...';
        }
        return $string;
    }
}

namespace MyCompany\Project\Database;

class Connection {
    public static function getConnection() {
        return "Database connection established";
    }
}

// 使用子命名空间
$helper = new \MyCompany\Project\Utils\StringHelper();
$db = new \MyCompany\Project\Database\Connection();

echo $helper->truncate("This is a long text", 10) . "\n";
echo $db->getConnection() . "\n";
?>

命名空间使用方式

PHP命名空间中的类名可以通过三种方式引用:

类型 语法 解析方式
非限定名称 $a = new Foo(); 解析为当前命名空间下的 Foo
限定名称 $a = new Sub\Foo(); 解析为当前命名空间下的 Sub\Foo
完全限定名称 $a = new \Full\Path\Foo(); 总是解析为指定的完全路径

完整示例:

file1.php 文件代码:

<?php
namespace Foo\Bar\subnamespace;

const FOO = 1;
function foo() {
    echo "Function foo from subnamespace\n";
}
class Foo
{
    static function staticMethod() {
        echo "Static method from Foo\Bar\subnamespace\Foo\n";
    }
}
?>

file2.php 文件代码:

<?php
namespace Foo\Bar;
include 'file1.php';

const FOO = 2;
function foo() {
    echo "Function foo from Foo\Bar\n";
}
class Foo
{
    static function staticMethod() {
        echo "Static method from Foo\Bar\Foo\n";
    }
}

/* 非限定名称 */
foo(); // 解析为 Foo\Bar\foo
Foo::staticMethod(); // 解析为类 Foo\Bar\Foo 的静态方法
echo FOO; // 解析为常量 Foo\Bar\FOO

echo "\n--- 限定名称 ---\n";
/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\Foo::staticMethod(); // 解析为类 Foo\Bar\subnamespace\Foo
echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO

echo "\n--- 完全限定名称 ---\n";
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\Foo::staticMethod(); // 解析为类 Foo\Bar\Foo
echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
?>

访问全局函数和类:

<?php
namespace MyApp;

// 在命名空间内定义同名函数和类
function strlen($str) {
    return "Custom strlen: " . \strlen($str);
}

class Exception extends \Exception {
    public function __construct($message) {
        parent::__construct("MyApp Exception: " . $message);
    }
}

// 访问全局函数和类
$length = \strlen('hello'); // 调用全局函数strlen
$globalException = new \Exception('Global exception'); // 实例化全局类Exception
$customException = new Exception('Custom exception'); // 实例化当前命名空间的Exception

echo strlen('test') . "\n";
echo $length . "\n";
?>

命名空间和动态语言特征

在动态访问元素时,必须使用完全限定名称。

<?php
namespace MyCompany\Utils;

class Logger {
    function __construct() {
        echo __METHOD__ . "\n";
    }
}

function helper() {
    echo __FUNCTION__ . "\n";
}

const APP_NAME = "MyApp";

// 动态访问
$classname = 'MyCompany\Utils\Logger';
$obj = new $classname; // 输出 MyCompany\Utils\Logger::__construct

$function = 'MyCompany\Utils\helper';
$function(); // 输出 MyCompany\Utils\helper

echo constant('MyCompany\Utils\APP_NAME') . "\n"; // 输出 MyApp

// 使用双引号时需要注意转义
$classname = "\\MyCompany\\Utils\\Logger";
$obj2 = new $classname;
?>

namespace关键字和__NAMESPACE__常量

PHP提供了两种访问当前命名空间的方式:__NAMESPACE__魔术常量和namespace关键字。

<?php
namespace MyProject\Sub;

echo '当前命名空间: "' . __NAMESPACE__ . '"' . "\n"; // 输出 "MyProject\Sub"

class Example {
    public function showNamespace() {
        echo '类所在的命名空间: ' . __NAMESPACE__ . "\n";
    }
}

// 使用namespace关键字
$obj = new namespace\Example(); // 相当于 new MyProject\Sub\Example
$obj->showNamespace();

// 动态创建类名
$className = __NAMESPACE__ . '\\Example';
$obj2 = new $className();
$obj2->showNamespace();
?>

全局命名空间中的__NAMESPACE__:

<?php
// 全局代码,不在任何命名空间中
echo '当前命名空间: "' . __NAMESPACE__ . '"' . "\n"; // 输出空字符串

class GlobalClass {
    public function test() {
        echo '全局类方法';
    }
}
?>

使用命名空间:别名/导入

PHP支持通过use关键字为类或命名空间创建别名。

基本导入示例:

<?php
namespace MyApp;

use MyCompany\Project\Utils\StringHelper as Helper;
use MyCompany\Project\Database\Connection; // 不使用别名
use \ArrayObject; // 导入全局类

$helper = new Helper();
echo $helper->truncate("Long text here", 8) . "\n";

$conn = new Connection();
echo $conn->getConnection() . "\n";

$arrayObj = new ArrayObject([1, 2, 3]); // 使用全局ArrayObject类
print_r($arrayObj);
?>

一行多个use语句:

<?php
namespace MyApp;

use MyCompany\Project\Utils\StringHelper as Helper,
    MyCompany\Project\Database\Connection as DB;

$helper = new Helper();
$db = new DB();
?>

导入和动态名称:

<?php
namespace MyApp;
use MyCompany\Project\Utils\StringHelper as Helper;

$obj = new Helper(); // 正常工作
$class = 'Helper';
$obj2 = new $class(); // 错误!动态名称不受use影响

// 正确的方式
$fullClass = 'MyCompany\Project\Utils\StringHelper';
$obj3 = new $fullClass(); // 正常工作
?>

使用命名空间:后备全局函数/常量

在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称,例如:

1、在命名空间中访问全局类

<?php
namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); // $b 是类 Exception 的一个对象

$c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
?>

对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。

2、 命名空间中后备的全局函数/常量

<?php
namespace A\B\C;

const E_ERROR = 45;
function strlen($str)
{
    return \strlen($str) - 1;
}

echo E_ERROR, "\n"; // 输出 "45"
echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL

echo strlen('hi'), "\n"; // 输出 "1"
if (is_array('hi')) { // 输出 "is not array"
    echo "is array\n";
} else {
    echo "is not array\n";
}
?>

命名空间解析规则

类名称解析:

<?php
namespace A\B\C;

class Exception extends \Exception {
    // 自定义异常类
}

// 在当前命名空间中查找类
$a = new Exception('hi'); // 使用 A\B\C\Exception

// 使用完全限定名称访问全局类
$b = new \Exception('hi'); // 使用全局Exception类

// 错误示例 - 在当前命名空间查找ArrayObject
// $c = new ArrayObject(); // 致命错误,找不到A\B\C\ArrayObject

// 正确方式
$c = new \ArrayObject(); // 使用全局ArrayObject类
?>

函数和常量解析:

<?php
namespace MyApp\Utils;

const DEBUG = true;
function strlen($str) {
    return "Custom: " . \strlen($str);
}

echo strlen('hello') . "\n"; // 使用当前命名空间的strlen函数
echo \strlen('hello') . "\n"; // 使用全局strlen函数

echo DEBUG . "\n"; // 使用当前命名空间的DEBUG常量
echo \PHP_VERSION . "\n"; // 使用全局PHP_VERSION常量

if (\defined('MyApp\Utils\DEBUG')) {
    echo "DEBUG constant is defined\n";
}
?>

全局空间

所有没有定义在命名空间中的代码都在全局空间中。

<?php
// 全局空间
class GlobalClass {
    public function test() {
        return "Global class method";
    }
}

function globalFunction() {
    return "Global function";
}

namespace MyApp;

class LocalClass {
    public function test() {
        // 访问全局类
        $global = new \GlobalClass();
        return $global->test();
    }
}

$local = new LocalClass();
echo $local->test() . "\n";

// 调用全局函数
echo \globalFunction() . "\n";
?>

实际应用场景

自动加载与命名空间:

<?php
// 自动加载函数
spl_autoload_register(function ($class) {
    // 将命名空间中的反斜线转换为目录分隔符
    $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

// 现在可以自动加载任何符合PSR-4标准的类
use MyCompany\Project\Controllers\UserController;
use MyCompany\Project\Models\User;

$controller = new UserController();
$user = new User();
?>

组织大型项目结构:

<?php
namespace MyApp\Controllers;

use MyApp\Models\User;
use MyApp\Services\EmailService;
use MyApp\Utils\Validator;

class UserController {
    private $userModel;
    private $emailService;

    public function __construct() {
        $this->userModel = new User();
        $this->emailService = new EmailService();
    }

    public function register($userData) {
        if (Validator::validateEmail($userData['email'])) {
            $userId = $this->userModel->create($userData);
            $this->emailService->sendWelcomeEmail($userData['email']);
            return $userId;
        }
        return false;
    }
}
?>