PHP 5.3版特性中,重量级的特性包括 命名空间, 静态延迟绑定(late static binding) 以及 mysqlnd,当然还有其他的一些有趣的改进, 比如 __callStatic和dynamic static calls。这一节我们先来分析一下“命名空间”的细节。
PHP对命名空间的支持是一项人们期待已久的特性了。加入这一特性的主要目的是为了解决PHP中的超长类名问题。如果你准备开发一个大一点的库,那就不得 不使用比较长的类名来避免命名冲突的问题,比如下面这种怪物名字: Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive.
从5.3开始你可以使用命名空间来组织你的代码了。 不同的命名空间内可以包含相同名字的类、函数以及常量。 声明一个命名空间非常简单,只需要在文件的开始处加上namespace 语句,像下面这样
- /** classes/my/foo/MyClass.php */
-
- namespace my::foo;
-
- class MyClass {}//定义一个类
-
- // 当然,也可以定义函数和常量
- function myFunc() { }
- const MY_CONST = ‘foo’;
-
- ?>
有多种方法来访问
- /** test.php */
- include(‘classes/my/foo/MyClass.php’);
-
- // 可以随时通过完整的名称来访问一个类
- $foo = new my::foo::MyClass();
-
- // 还可以使用use语句来导入一个命名空间
- use my::foo;
- // 然后,通过foo来引用 my::foo这个命名空间
- $foo = new foo::MyClass();
-
- // 也可以只导入一个类
- use my::foo::MyClass;
- $foo = new MyClass;
-
- // 可以为命名空间或者命名空间中的类创建别名
- use my::foo as MyFoo;
- use my::foo::MyClass as MyFooClass;
- $foo = new MyFoo::MyClass();
- $foo = new MyFooClass();
-
- // 注意, 下面的两种写法是等价的:
-
- use my::foo;
- use my::foo as foo;
-
- // 也可以用同样的方法来访问函数和常量
- my::foo::myFunc();
- myFoo::myFunc();
- my::foo::MY_CONST;
- myFoo::MY_CONST;
-
- ?>
use语句只能导入命名空间和类,下面这种写法就不行: use my::foo::MY_CONST;。一个use语句的有效范围是从它被定义开始直到文件的结尾,你可以在全局范围内任何地方使用它。 你可以在多个文件中使用相同的命名空间。但是一个文件只应该包含一个命名空间 (这一行为可能会在最终版本中被改变,也可能用package来替换namespace关键字 :))。 尽管不能import一个函数或者常量, 但是仍然可以使用一些前缀来从命名空间中访问他们。在PHP 5.3的早期版本中,import 关键字被用来代替 use, 现在这一点已经被改变了。
“空”命名空间 (::)
如果你在函数或常量名前面使用 :: 前缀, 他们将会被从当前的引用规则中独立出来,这在命名空间中十分有用。
Life with namesapces (porting code to PHP 5.3)
如果你打算使用命名空间,那么下面几处缺陷是你应该注意的:
类的命名规则
首先,要避免在类名中使用PHP的保留字,仔细看下面的代码:
- /** classes/my/form/element/static.php */
- class MyFormElementStatic {}
- ?>
而在使用命名空间后,代码会变成这样:
- /** classes/my/form/element/static.php */
- namespace my::form::element;
- class Static {}
- ?>
但是因为Static 是一个保留字,所以这段代码最终会导致一个致命错误。如果你有很多类似名字的类,那么对你来说这确实是一个坏消息。
Autoloading
从5.3版本开始, __autoload()将使用类的全路径名称,这就意味着如果你已经使用了__autoload(),就要对它做一下适当的修改,举个简单的例子
- /** test.php */
- function __autoload($className)
- {
- require ‘classes/’.str_replace(‘::’, DIRECTORY_SEPARATOR, $className).‘.php’;
- }
-
- $foo = new my::foo::MyClass();
- ?>
或者选择 SPL的形式 来完成类似的功能
- /** classes/my/core/classloader.php */
- namespace my::core;
-
- function classLoader($className)
- {
- require ‘classes/’.str_replace(‘::’, DIRECTORY_SEPARATOR, $className).‘.php’;
- }
-
- spl_autoload_register(‘my::core::classLoader’);
- ?>
-
- /** test.php */
- require ‘classes/my/core/classLoader.php’;
- $foo = new my::foo::MyClass();
-
- ?>
get_class(), get_parent_class(), 及其他.
当你使用这类函数的时候也要注意,他们也同样返回一个类的全路径名称。
反射 API
反射 API 同样也要反映出这种新的语言特性,这部分的计划是这样的:
* 创建一个包含下面这些方法的ReflectionNamespace类: getName(), getClasses(), getFunctions(), getFiles()
* 扩展ReflectionClass 和ReflectionFunction 类并加入getNamespace() 这一新方法。
下面这些知识也是你应该了解的:
常量: __NAMESPACE__
在这一版本中我们引入了一个新的常量: __NAMESPACE__,它包含了当前引用的命名空间的名字,比如你可以使用下面的方式来实现一个SPL风格的loader
- /** classes/my/core/classloader.php */
- namespace my::core;
-
- function classLoader($className)
- {
- require ‘classes/’.str_replace(‘::’, DIRECTORY_SEPARATOR, $className).‘.php’;
- }
-
- spl_autoload_register(__NAMESPACE__.‘::classLoader’);
-
- ?>
PHP对命名空间内的元素的解析流程
我不想对这部分内容作过多的解释,你可以在 PHP namespaces README 这里阅读到详细的规则(fidy注:作者对这部分的解释确实比较“简略”,建议还是读一下那个readme,其中的关键部分我会在后面贴出来)。 记住一个要点,就是PHP会首先参照当前的命名空间来解析一个元素, 让我们来看例子
- namespace my::foo;
- …
- mysql_connect();
- …
- ::mysql_connect();
- ?>
当你调用mysql_connect()的时候,PHP会首先到my::foo 下面去找这个函数。如果找到了就调用它,没找到的话再去调用PHP内部集成的mysql_connect()函数,而其他命名空间中定义的 mysql_connect()函数你是没办法访问到的。而使用::bar() 这种写法则可以访问到全局命名空间中的函数,它可能是PHP的内部函数,也可能是用户自定义的。
- namespace my::foo;
-
- core::bar();
-
- ::core::bar();
- ?>
在core::bar()这种情况下, PHP 会首先尝试调用my::foo::core 下面的函数bar(),如果没有找到,则尝试调用my::foo命名空间下的类core(如果存在的话)的静态方法bar(),最后尝试调用PHP的内置 类core的bar()方法。
::core::bar()这种情况下,PHP会首先尝试调用core命名空间下的 函数 bar() ,然后尝试调用全局范围内类core的静态方法bar()。
现在PHP对命名空间的支持还没有完成,很可能将来会做一些必要的改动。
补充:PHP namespaces README中的相关内容
Names inside namespace are resolved according to the following rules:1) all qualified names are translated during compilation according to
current import rules. So if we have "use A::B::C" and then "C::D::e()"
it is translated to "A::B::C::D::e()".
2) unqualified class names translated during compilation according to
current import rules. So if we have "use A::B::C" and then "new C()" it
is translated to "new A::B::C()".
3) inside namespace, calls to unqualified functions that are defined in
current namespace (and are known at the time the call is parsed) are
interpreted as calls to these namespace functions.
4) inside namespace, calls to unqualified functions that are not defined
in current namespace are resolved at run-time. The call to function foo()
inside namespace (A::B) first tries to find and call function from current
namespace A::B::foo() and if it doesn't exist PHP tries to call internal
function foo(). Note that using foo() inside namespace you can call only
internal PHP functions, however using ::foo() you are able to call any
function from the global namespace.
5) unqualified class names are resolved at run-time. E.q. "new Exception()"
first tries to use (and autoload) class from current namespace and in case
of failure uses internal PHP class. Note that using "new A" in namespace
you can only create class from this namespace or internal PHP class, however
using "new ::A" you are able to create any class from the global namespace.
6) Calls to qualified functions are resolved at run-time. Call to
A::B::foo() first tries to call function foo() from namespace A::B, then
it tries to find class A::B (__autoload() it if necessary) and call its
static method foo()
7) qualified class names are interpreted as class from corresponding
namespace. So "new A::B::C()" refers to class C from namespace A::B.
Examples
--------
namespace A;
foo(); // first tries to call "foo" defined in namespace "A"
// then calls internal function "foo"
::foo(); // calls function "foo" defined in global scope
?>
namespace A;
new B(); // first tries to create object of class "B" defined in namespace "A"
// then creates object of internal class "B"
new ::B(); // creates object of class "B" defined in global scope
?>
namespace A;
new A(); // first tries to create object of class "A" from namespace "A" (A::A)
// then creates object of internal class "A"
?>
namespace A;
B::foo(); // first tries to call function "foo" from namespace "A::B"
// then calls method "foo" of internal class "B"
::B::foo(); // first tries to call function "foo" from namespace "B"
// then calls method "foo" of class "B" from global scope
?>
The worst case if class name conflicts with namespace name
namespace A;
A::foo(); // first tries to call function "foo" from namespace "A::A"
// then tries to call method "foo" of class "A" from namespace "A"
// then tries to call function "foo" from namespace "A"
// then calls method "foo" of internal class "A"
::A::foo(); // first tries to call function "foo" from namespace "A"
// then calls method "foo" of class "A" from global scope
?>