PHP和包

包由一组相关的类以某种方式组合而成,可以用于隔离系统中的各个部分。有些编程语言可以识别出包,并为它们提供不同的命名空间。虽然PHP没有原生的包概念,但它自PHP5.3起引入了命名空间。

命名空间

在PHP5.3之前,类名是全局有效的。也就是说,如果我们给一个类起名为ShoppingBasket,那么这个类在整个系统中都是可用的。这就造成了两大问题:首先,这可能导致命名冲突。

<?php

require_once __DIR__ . "/Outputter.php";

class Outputter
{
    // 输出数据
}

现在假设被包含的Outputter.php中有如下定义

<?php

class Outputter
{
    // 这是一个测试文件 没有任何实际功能
}

结果

Cannot declare class Outputter, because the name is already in use in Outputter.php

在PHP引入命名空间之前,有一种方法可以绕过这个问题,那就是在类名前加上包名,这样能够确保类名是唯一的。

// Outputter.php

class useful_Outputter
{
    // 这是一个测试文件 没有任何实际功能
}

这种方法的问题是,随着项目越来越庞大,类名也会变得越来越长。

命名空间来救场

PHP5.3引入了命名空间。从本质上看,命名空间就是一个桶,我们可以向其中放入类、函数和变量。在命名空间中,我们可以无限制地访问这些元素。但在外部,必须先导入命名空间或引用它才能访问其内部的东西。

<?php
namespace my;

require_once __DIR__ . "/Outputter.php";

class Outputter
{
    // 输出数据
}
<?php
namespace useful;

class Outputter
{
    // 这是一个测试文件 没有任何实际功能
}

namespace关键字会创建一个命名空间,且必须在代码文件的第一条有效语句(注释不算)中声明命名空间。这里创建了两个命名空间myuseful。通常需要创建更深层次的命名空间。我们可以使命名空间以组织或项目的标识符开头,接着通过包名进一步限定。PHP支持嵌套的命名空间。为此,简单地用反斜杠分隔各层命名空间即可。

<?php
namespace popp\ch05\batch04\util;

class Debug
{
    public static function helloWorld()
    {
        print "hello from Debug";
    }
}

通常会用与产品或组织有关的名称来定义库。那么现在应该如何调用方法呢?事实上,这取决于从哪里调用方法。如果在命名空间内调用方法,那么可以直接这样做:

Debug::helloWorld();

这称为“非限定名”。因为我们已经在popp\ch05\batch04\util这个命名空间中,所以不用在类名前添加任何路径。如果要从命名空间的外部访问类,那么可以按照以下方式:

\popp\ch05\batch04\util\Debug::helloWorld();

现在请看下面的代码:

namespace main;
popp\ch05\batch04\util\Debug::helloWorld();

会报错:

Fatal error: Uncaught Error: Class 'main\popp\ch05\batch04\util\Debug' not found in ...

这是因为这里使用的是相对命名空间,PHP会在当前命名空间main下查找popp\ch05\batch04\util,但是没找到,于是程序就报错了。就像在使用绝对URL和绝对文件路径时以分隔符开头一样,我们也可以使用绝对命名空间。但是,如果我按照《深入PHP》这本书所述使用绝对命名空间修改如下:

namespace main;
\popp\ch05\batch04\util\Debug::helloWorld();

还是会报错:

Fatal error: Uncaught Error: Class 'popp\ch05\batch04\util\Debug' not found in ...

这是因为没有将util文件引入进来,命名空间其实是逻辑上的,并不会真正加载那个 php 文件进来。文件是文件,语法解析是语法解析。所以需要:

<?php
namespace main;
// 引入文件
require_once __DIR__ . "/util.php";
\popp\ch05\batch04\util\Debug::helloWorld();

结果:

hello from Debug

对于这个的相关讨论可以看下这个链接https://www.v2ex.com/t/657493

也可以使用use关键字

<?php
namespace main;
require_once __DIR__ . "/util.php";
use popp\ch05\batch04\util;
util\Debug::helloWorld();

这里use后面的命名空间并没有使用以反斜杠开头,这是因为use关键字会从全局空间开始查找参数,而不是当前命名空间。你也可以写成下面这种形式:

<?php
namespace main;
require_once __DIR__ . "/util.php";
use popp\ch05\batch04\util\Debug;
Debug::helloWorld();

现在有一个问题,如果调用Debug类的命名空间中已经存在一个Debug类,那么会发生什么呢?

// util 文件
<?php
namespace popp\ch05\batch04\util;

class Debug
{
    public static function helloWorld()
    {
        print "hello from popp\\ch05\\batch04\\util\\Debug";
    }
}
// main 文件
<?php
namespace main;
require_once __DIR__ . "/util.php";
use popp\ch05\batch04\util\Debug;

class Debug
{
    public static function helloWorld()
    {
        print "hello from main";
    }
}

Debug::helloWorld();

这会发生一个错误:

Cannot declare class main\Debug because the name is already in use

这种情况就可以给命名空间的类起一个别名

<?php
namespace main;
require_once __DIR__ . "/util.php";
use popp\ch05\batch04\util\Debug as coreDebug;

class Debug
{
    public static function helloWorld()
    {
        print "hello from main";
    }
}

Debug::helloWorld();
coreDebug::helloWorld();

use语句中使用as语句可以为Debug类起一个别名coreDebug

如果你正在命名空间内编写代码,而且想要访问位于全局空间(非命名空间)中的类,那么可以在类名前加上一个反斜杠来访问它。
以下是一个定义在全局空间下的类:

<?php

class Lister
{
    public static function helloWorld()
    {
        print "hello from global";
    }
}

以下是命名空间的代码:

<?php
namespace popp\ch05\batch04\util;


require_once __DIR__ . "/util.php";

class Lister
{
    public static function helloWorld()
    {
        print "hello from " . __NAMESPACE__ . "<br/>";
    }
}

Lister::helloWorld(); // 局部访问
\Lister::helloWorld(); //全局访问

结果:

hello from popp\ch05\batch04\util
hello from global

注意本例中的__NAMESPACE__常量,可以通过它来输出当前的命名空间,有助于调试程序。

另外,可以在同一个文件中声明多个命名空间。以下是通过namespace关键字和大括号声明命名空间的一种语法。

<?php
namespace com\getinstance\util {
    class Lister
    {
        public static function helloWorld()
        {
            print "hello from global";
        }
    }
}

namespace other {
    \com\getinstance\util\Lister::helloWorld();
}

如果必须在同一个文件中定义多个命名空间,那么推荐以上这种声明语法。但通常而言,在每个文件中定义一个命名空间是一种最佳实践。

注意:不能在同一个文件中同时使用大括号和行命名空间语法,只能选择其一并贯彻整个文件

标签: none

评论已关闭