PHP和包
包是一组相关类的集合,这些类以某种方式组合在一起。包可以把系统的一部分和其他部分分隔开了。PHP还不支持包的概念,但是支持命名空间。
- 命名空间
命名空间就是一个容器,你可以将类、函数和变量放在其中。在命名空间中,你可以无条件地访问这些项。在命名空间之外,必须导入或引用命名空间,才能访问它所包含的项。
namespace my;
require_once "useful/Outputter3.php";
class Outputter{
//输出数据
}
//useful/Outputter3.php
namespace useful;
class Outputter{
//
}
通常你可能需要创建层次更深的命名空间。您可以从创建一个组织或项目标识符开始,然后通过包进一步限定。PHP支持声明嵌套的命名空间。为此,您只需使用反斜杠字符将每一层分开就可以了。
namespace com\getinstance\util;
class Debug{
static function helloWorld(){
print "hello from Debug\n";
}
}
如果打算提供代码库,我可能会使用一个域:getinstance.com,然后使用该域名作为命名空间。反转域名,这样就可以按最通用到最具体的方式运行。标识了代码库以后,接下来定义包。调用方法取决于从何处调用。如果从命名空间内部调用方法,你可以直接调用该方法:
Debug::helloWorld();
这称为非限定名。因为我已经位于com\getinstance\util命名空间中了,所以不必在类名前加任何种类的路径。如果打算从命名空间环境之外访问类:
namespace main;
\com\getinstance\util\Debug::helloWorld();
这个前导的反斜杠告诉PHP从根命名空间而不是从当前命名空间开始搜索。利用use关键字,你可以为当前命名空间中的其他命名空间起别名:
namespace main;
use com\getinstance\util;
util\Debug::helloWorld();
导入com\getinstance\util命名空间,并隐式地为其使用了别名util。注意,我并没有使用前导反斜线字符作为开始。这里从全局命名空间而不是从当前命名空间搜索要使用的参数。如果不想引用命名空间,可以导入Debug类本身:
namespace main;
use com\getinstance\util\Debug;
util\Debug::helloWorld();
但是如果main命名空间中已经包含了Debug类,又会遇到类命名冲突的问题。我们可以显式使用别名:
namespace main;
use com\getinstance\util\Debug as uDebug;
class Debug{
static function helloWorld(){
print "hello from Debug\n";
}
}
uDebug::helloWorld();
如果在命名空间中编写代码,而你想访问的类保存在全局空间中,那么你可以在该名称前加反斜杠:
//global.php: 无命名空间
class Lister
{
public static function helloWorld()
{
print "hello from global\n";
}
}
namespace com\getinstance\util;
require_once 'global.php';
class Lister{
public static function helloWorld()
{
print "hello from ".__NAMESPACE__."\n";
}
}
Lister::helloWorld();//访问本地
\Lister::helloWorld();//访问全局
可以在同一个文件中声明多个命名空间,还可以使用命名空间关键字加大括号形式的语法:
namespace com\getinstance\util{
class Debug
{
static function helloWorld()
{
print "hello from Debug\n";
}
}
}
namespace main{
com\getinstance\util\Debug::helloWorld();
}
如果必须将多个命名空间组合到一个文件中,你们推荐上面这种做法,但通常认为为每个文件定义一个命名空间是最好的做法。
大括号语法提供的一项功能就是可以在一个文件中切换到全局空间:
namespace {
class Lister{
//...
}
}
namespace com\getinstance\util{
class Lister{
//...
}
Lister::helloWorld();//访问本地
\Lister::helloWorld();//访问全局
}
如果打开命名空间块的时候不指定名称,你就可以进入全局空间。
注解:不能在同一个文件中同时使用大括号命名空间语法和行命名空间语法。
- 使用文件系统模拟包
我们可以使用提供了包结构的文件系统来组织类:
require_once ('business/Customer.php');
require_once ('util/WebTools.php');
也可以使用include_once达到相同的效果。使用require()调用文件发生错误时,将会停止整个程序;调用include()遇到相同的错误,则会生成警告并停止执行包含文件,跳出调用代码然后继续执行。require()和require_once()用于包含库文件时更加安全,而include()和include_once()则适用于加载模板这样的操作。(require()和require()都是语句而不是函数,这意味着使用它们都是时候可以省略括号)
require_once()接受文件路径作为参数,然后把文件包含到当前脚本中。函数只能在文件没有被包含过的情况下才能包含它。这种只使用一次的方法在访问库代码时特别有用,因为它防止了类和方法的重复定义。和使用require()相比,require_once()需要额外的开销。
- PEAR风格的命名方式
在没有命名空间的支持下,一个办法是使用PEAR包的命名规则来解决命名冲突。PEAR使用文件系统来定义包。然后每个类都根据包路径来命名,每个路径名以下划线来分隔。例如,PEAR有一个XML包,包里有一个RPC子包,RPC包中有一个Server.php文件。在Server.php中定义的类就被命名为XML_RPC_Server。
- 包含路径
组织组件时,你必须记住两件事。第一件事是文件系统中文件和目录存放的位置,此外还要考虑组件之间互相访问的方式。当我们包含一个文件时,可以使用相对路径,也可以使用绝对路径来引用该文件。使用相对路径包含库时,通常会使用冗长的require_once()语句。
使用绝对路径时,我们把库绑定到了特定的文件系统。无论何时在新服务器上安装写好的程序时,所有的require语句都需要为新的文件路径而改变。
使用相对路径时,我们把脚本工作目录和库类目录的位置关系固定住了。这样如果不修改require()语句,就无法重新定位文件系统中的文件,而且不通过复制文件很难实现项目间共享。
要使程序代码不直接依赖于类库代码程序中包含的类库,需要将调用类库的代码和类库代码进行解耦(decouple,使依赖不再过于紧密),即让
business/User.php
可以在系统的任何地方被引用。我们只需把包放到include_path指定引用的目录中即可。通常,可以在php的核心配置文件php.ini中设置include_path。它定义了一个目录列表,在Unix中以冒号分隔,在Windows中则以分号分隔。
include_path = ".:/usr/local/lib/php-libraries"
如果使用Apache作为web服务器,还可以在服务器应用程序的配置文件(通常是httpd.conf)或使用下面的语法在Apache配置文件(通常是.htaccess)中设置include_path:
php_value include_path value .:/usr/local/lib/php-libraries
在使用文件系统的函数(如fopen()或require)时,如果没有找到相应的文件目录,就会在include_path指定的目录中自动查找——从include_path中指定的第一个目录开始找起(要使用本功能,需要指定fopen()函数的第三参数为true)。直到找到目标文件时,停止搜索,由文件函数执行任务。
因此,把包目录放到包含目录以后,只需要在require()语句中指定包和文件。你可以修改php.ini文件,增加目录到include_path中来构建自己的类库。(注意,对于php服务器模块需要重启服务器使修改生效)。如果没有修改权限,可以在程序中使用set_include_path()语句函数来设置包含路径。set_include_path()函数接受包含路径(和在php.ini中出现的一样)并仅为当前进程改变include_path设置。php.ini文件可能已经为include_path定义了一个有效的值,因此我们可以不用覆盖这个值,直接使用get_include_path()函数来访问它并加入自己的目录。下面的代码将目录添加到当前的包含路径:
set_include_path(get_include_path().":/home/john/phplib/");
- 自动加载
PHP5引入了__autoload()拦截器方法来自动包含类文件。
function __autoload($classname)
{
include_once("$classname.php");
}
$product = new ShopProduct('The Darkning', 'Harry', 'Hunter', 12.99);
如果没有包含定义了ShopProduct类的文件,那么实例化就会失败。在这里,PHP引擎知道我们定义了__autoload()函数并将ShopProduct字符串作为参数传递给它。
命名空间需要测试反斜线字符,如果该字符存在的话,就添加一个转换:
function __autoload($classname)
{
if (preg_match('/\\\\/',$classname)){
$path = str_replace('\\',DIRECTORY_SEPARATOR,$classname);
}else{
$path = str_replace('_',DIRECTORY_SEPARATOR,$classname);
}
require_once("$path.php");
}
__autoload()方法是一种根据类和文件的结构,管理类库文件包含的有效方法。