(三) PHP5的新特性(续)
PHP5的发布计划
在前面的文章中我们提到,“根据ZEND公司2003年4月1日发布的讯息的话,现在的应该就是Beta Release了”,但是开发者内部讨论的结果是,Beta为时尚早,而且有可能不是Beta Release.
对这方面动向有兴趣的可以参照 news://news.php.net/ 上所公布的信息 php.version5.dev:372
在这个文件中,PHP5的发布计划又重新回到了一张白纸,而另一方面,Zend Engine2的开发正在着手进行中。PHP5的Release其实大体就是盼望着“快点到年终吧”。
PHP5的新特性
接着我们来看一下在前面所讲到的其他一些关于类的新增的机能
■名空间
PHP5支持名空间。因此,我们可以在名空间内装入类、变量、常量、函数。
在PHP4的Scope中,只有global、函数内、类内这三个种类,所以要特别注意如果不注意的话,将会很容易“污染”global空间。假如使用名空间的话我们就能够在package里分离变量命名空间,因此应该就能比较容易的做成独立的package。
使用实例如下:
PHP代码:
--------------------------------------------------------------------------------
namespace This {
class Hoge {
}
const aConstant = 'This Constant';
function aFunction() {}
var $aVariable = 'This Variable';
}
$obj = new This::Hoge;
echo This::aConstant . "
\n";
This::aFunction();
echo This::$aVariable . "
\n";
--------------------------------------------------------------------------------
假设要访问名空间内的对象的话,就应该这样做:
名空间名::对象名
但是PHP5的名空间不会套入与C++相异的样子。
■Class内常量
使用关键字const,能够在类、名空间内定义常量。这里因为是常量,因此一定要在常量名的前面加上$。Class内的常量,比这个类中的global常量的优先级要高。
在这里const是预约语,因此在class名和函数名中使用const的时候要做必要的修正。
PHP代码:--------------------------------------------------------------------------------
define('constant_value', 'global constant');
class MyClass {
const constant_value = 'class constant';
function printConstant() {
print constant_value;
}
}
echo MyClass::constant_value . "
\n";
MyClass:rintConstant();
?>
--------------------------------------------------------------------------------
在这个例子里,MyClass: rintConstant()是显示常量constant_value的值,但是,constant_value存在于global空间和Class内这两个地方。在这种情况下,MyClass内的常量constant_value的优先级较高,被显示为「class constant」。
■对象变量
即使是在类没有被实例化状态下,对象变量也能准确的按照指定的值被初始化。访问的方法如下:
类名::$变量名
PHP代码:--------------------------------------------------------------------------------
class Hoge {
static $my_static = 5;
}
print Hoge::$my_static;
?>
--------------------------------------------------------------------------------
■统一构建器
在生成对象的时候,能够自动被调用的方法被称作“构建器”。
PHP4中的构建器,是与Class名相同的方法名。这是与Java和C++相同的地方,因此,对于一些用惯了的人来说,不会有别扭感。但是,如果要从子类中调用父类的构建器的话,在PHP中就必须特意写上父类的名字。
在PHP中,父类的构建器不能被自动调用,因此,情况就比较多。
在PHP5中,统一采用了__constructor这个构建器名称,不管class名是什么,凡是被称为__construct()的,都被当作构建器来处理。
另外,考虑到同PHP4的互换性,假如存在于Class名相同的以前的构建器名,那么,优先使用那个构建器。
PHP代码:--------------------------------------------------------------------------------
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructor\n";
}
}
$obj = new BaseClass();
$obj = new SubClass();
?>
--------------------------------------------------------------------------------
■析构函数
与构建器相反,能够在对象释放时自动被调用的方法被称为析构函数。
PHP4支持析构函数,通过登录在PHP运行终止时用register_shutdown_function()调用的函数,只有类似的实行方法。PHP5正式支持析构函数,能够在类中指定对象释放时的动作。
析构函数就是名为__destruct的方法。当对象内部的参照计数器变成0的时候,__destruct()被调用,然后对象所使用的内存被释放出来。
PHP代码:
--------------------------------------------------------------------------------
class MyDestructableClass {
function __construct() {
print "In constructor\n";
$this->name = 'MyDestructableClass';
}
function __destruct() {
print 'Destroying ' . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
?>
--------------------------------------------------------------------------------
另外,与构建器相同的地方是,父类的析构函数不能被自动的调用,必要的时候,需要用命令:
parent::__destruct();
■访问
在PHP4中,如果访问了一个不存在的属性,那么系统就会自动生成与之相对应的新属性。
PHP代码:
--------------------------------------------------------------------------------
class Hoge {
}
$obj = new Hoge;
$obj->prop = "This is new property";
--------------------------------------------------------------------------------
如上所示,当把值代入一个不存在的属性时,那个代入点就会自动生成一个新属性。同样的,访问一个不存在的属性,就如同被代入NULL值的变量一样,不会发生错误。
在PHP5中追加了一点,就是能够对访问任意的属性进行控制。在类中如果存在__set()、__get()这样的方法,替代上述的动作此处的方法将能够被调用。例如:
PHP代码:
--------------------------------------------------------------------------------
class Hoge {
function __set($name, $value) {
print "__set() is called with ($name, $value)\n";
$this->$name = $value;
}
}
$obj = new Hoge;
$obj->a = '123';
$obj->a = '456';
$obj->b = '789';
?>
--------------------------------------------------------------------------------
在这里,__set 方法被作为未定义属性的代入方法,在显示值之后将值代入未定义属性。
PHP代码:
--------------------------------------------------------------------------------
$obj->a = '123';
--------------------------------------------------------------------------------
执行这一句时,因为在这个时候不存在属性a,因此,作为代替,__set 方法被调用。
__set() is called with (a, 123)
其次,
$obj->a = '456';
再一次的代入$obj->a,这一次,由于属性a已经存在,所以__set 没有被调用,和通常一样把值代入到了属性a中去了。
$obj->b = '789';
这一回,我们把值代入另一个属性b中,同a的第一次情况一样,
__set() is called with (b, 789)
同__set 方法相反,__get 方法是在对不存在的属性的引用时调用的。将这两者结合起来,再来看一下对于属性的访问,实际上,利用它能够写出在不同的场合都能做出不同响应的类来。
PHP代码:
--------------------------------------------------------------------------------
class Hoge {
public $properties;
function __set($name, $value) {
$this->properties[$name] = $value;
}
function __get($name) {
return $this->properties[$name];
}
}
$obj = new Hoge;
$obj->a = '123';
$obj->b = '456';
echo $obj->a;
echo $obj->b;
print_r($obj);
?>
--------------------------------------------------------------------------------
在这个例子里,对类中所有属性的访问被装入了$properties中,这样,使我们加入的属性不直接的附在对象之下。这是个不太能容易理解的例子,例如,试着把这个例子中的保存到$ properties改成存入文件或是数据库会很有趣吧。实际上,在对象里面,我们能够简单的实现让许多的复杂的操作。
与__set, __get多少有些不同,但是__call也能用来书写不存在的方法,当我们向如下例子一样调用对象的方法的时候,
$object->methodname();
如果这个类中不存在methodname这个方法,通常情况下,就会出现如下错误:
Fatal error: Call to undefined method Class::methodname()
但是,如果这个类中存在__call这个方法,作为替代,__call就被调用。__call的参数有两个,第一个参数是被叫出的方法名,第二个参数是保持了的被调用的参数的数组。考虑到有很多的使用方法,除了以下的例子外,还可以使用其它的方法。
PHP代码:
--------------------------------------------------------------------------------
class Proxy {
private $object;
function __call($name, $params) {
if (isset($this->object)) {
if (method_exists($this->object, $name)) {
return call_user_func_array(array($this->object, $name), $params);
}
else {
return "method not exists.";
}
}
}
function __construct($object) {
$this->object = $object;
}
}
class Hoge {
function add($var1, $var2) {
return $var1 + $var2;
}
}
$p = new Proxy(new Hoge);
$result = $p->add(1, 2);
echo "result: $result
\n";
$result = $p->sub(5, 3);
echo "result: $result
\n";
?>
--------------------------------------------------------------------------------
全新的对象模型
PHP 中的对象处理部分已完全重写,具有更佳的性能和更多的功能。在PHP的以前版本中,对象与内建变量类型(如integer和string)的处理方法相同,其弊端是当变量被赋值为对象或对象作为参数传递时,得到的是对象复制品。而在新版本中,对象通过句柄进行引用,而不是通过它的值。(句柄可以认是为是对象的标识符)
很多PHP程序员可能未意识到以前的对象模型的“复制怪癖”,因此以前的PHP程序将不需要做任何更改,或只做很小的改动即可运行
私有和保护成员
PHP 5引入了私有和保护成员变量,它们可以定义类属性在何时可以被访问。
例
类的保护成员变量能在该类的扩展类中被访问,而私有成员变量只能在本类中被访问。
class MyClass {
private $Hello = "Hello, World!\n";
protected $Bar = "Hello, Foo!\n";
protected $Foo = "Hello, Bar!\n";
function printHello() {
print "MyClass::printHello() " . $this->Hello;
print "MyClass::printHello() " . $this->Bar;
print "MyClass::printHello() " . $this->Foo;
}
}
class MyClass2 extends MyClass {
protected $Foo;
function printHello() {
MyClass::printHello(); /* Should print */
print "MyClass2::printHello() " . $this->Hello; /* Shouldn't print out anything */
print "MyClass2::printHello() " . $this->Bar; /* Shouldn't print (not declared)*/
print "MyClass2::printHello() " . $this->Foo; /* Should print */
}
}
$obj = new MyClass();
print $obj->Hello; /* Shouldn't print out anything */
print $obj->Bar; /* Shouldn't print out anything */
print $obj->Foo; /* Shouldn't print out anything */
$obj->printHello(); /* Should print */
$obj = new MyClass2();
print $obj->Hello; /* Shouldn't print out anything */
print $obj->Bar; /* Shouldn't print out anything */
print $obj->Foo; /* Shouldn't print out anything */
$obj->printHello();
?>
---------------------------------------------------------------------------------
私有和保护方法
在PHP 5(ZEND引擎2)中,还引入了私有和保护方法。
例:
class Foo {
private function aPrivateMethod() {
echo "Foo::aPrivateMethod() called.\n";
}
protected function aProtectedMethod() {
echo "Foo::aProtectedMethod() called.\n";
$this->aPrivateMethod();
}
}
class Bar extends Foo {
public function aPublicMethod() {
echo "Bar::aPublicMethod() called.\n";
$this->aProtectedMethod();
}
}
$o = new Bar;
$o->aPublicMethod();
?>
以前代码中的用户自定义类或方法中虽未定义"public," "protected" 或 "private"等关键字,但无需编辑即可运行。
抽象类和方法
PHP 5还引入了抽象类和方法。抽象方法只声明方法定义, 不供实际运行。包含抽象方法的类需要声明为抽象类。
例:
abstract class AbstractClass {
abstract public function test();
}
class ImplementedClass extends AbstractClass {
public function test() {
echo "ImplementedClass::test() called.\n";
}
}
$o = new ImplementedClass;
$o->test();
?>
抽象类不能实例化。以前代码中的用户自定义类或方法中虽未定义"abstract”关键字,但无需编辑即可运行。
接口
ZEND引擎2.0引入了接口。一个类可以运行任意的接口列表。
Example
例:
interface Throwable {
public function getMessage();
}
class Exception implements Throwable {
public function getMessage() {
// ...
}
?>
以前代码中的用户自定义类或方法中虽未定义"interface”关键字,但无需编辑即可运行。
类类型定义
在保留类无需定义类型的同时,PHP 5引入了类类型定义来声明希望把哪个类通过参数传递给一个方法。
Example
例:
interface Foo {
function a(Foo $foo);
}
interface Bar {
function b(Bar $bar);
}
class FooBar implements Foo, Bar {
function a(Foo $foo) {
// ...
}
function b(Bar $bar) {
// ...
}
}
$a = new FooBar;
$b = new FooBar;
$a->a($b);
$a->b($b);
?>
这些类类型定义在不象一些需要类型预定义的语言在编译中进行检查,而是在运行时进行。这意味着:
function foo(ClassName $object) {
// ...
}
?>
等价于:
function foo($object) {
if (!($object instanceof ClassName)) {
die("Argument 1 must be an instance of ClassName");
}
}
?>
本语法只用于对象或类,不适用于内建类型。
---------------------------------------------------------------------------------
final
PHP 5引入了“final”关键字定义在子类中不能被覆盖的成员或方法。
例:
class Foo {
final function bar() {
// ...
}
}
?>
以前代码中的用户自定义类或方法中虽未定义"final"关键字,但无需编辑即可运行。
对象克隆
PHP 4在对象被复制时,用户不能决定拷贝的机制。在复制时,PHP 4只一位一位地复制一个和原来对象一模一样的复制品。
我们并不是每次都要建立一个完全一样的复制品。一个很好的需要一种复制机制的例子是,当有一个代表一个GTK窗口的对象,它拥有该窗口的所有资源,当你建立一个拷贝时,你可能需要一个新的窗口,它拥有原窗口的所有属性,但需要拥有新窗口的资源。另外一个例子是你有一个对象引用了另外一个对象,当你复制父对象时,你希望建立那个引用对象的新实例,以使复制品引用它。
对一个对象的拷贝通过调用对象的__clone()方法完成:
$copy_of_object = $object->__clone();
?>
当开发者请求建立一个对象的新的拷贝时,ZEND引擎会检查是否定义了__clone()方法。如果未定义的话,它会调用一个默认的__clone()方法来复制该对象的所有属性。如果定义了该方法,该方法会负责在拷贝中设置必要的属性。为方便起见,引擎会提供一个函数从源对象中导入所有的属性,这样它就可以先得到一个具有值的源对象拷贝,只需要对需要改变的属性进行覆盖即可。
例:
class MyCloneable {
static $id = 0;
function MyCloneable() {
$this->id = self::$id++;
}
function __clone() {
$this->name = $that->name;
$this->address = "New York";
$this->id = self::$id++;
}
}
$obj = new MyCloneable();
$obj->name = "Hello";
$obj->address = "Tel-Aviv";
print $obj->id . "\n";
$obj = $obj->__clone();
print $obj->id . "\n";
print $obj->name . "\n";
print $obj->address . "\n";
?>
统一的构造方法名
ZEND引擎允许开发者定义类的构造方法。具有构造方法的类在新建时会首先调用构造方法,构造方法适用于在正式使用该类前进行的初始化。
在PHP4 中,构造方法的名称与类名相同。由于在派生类中调用父类的作法比较普遍,因此导致在PHP4中当类在一个大型的类继承中进行移动时,处理方式有点笨拙。当一个派生类被移动到一个不同的父类中时,父类的构造方法名必然是不同的,这样的话派生类中的有关调用父类构造方法的语句需要改写。
PHP 5 introduces a standard way of declaring constructor methods by calling them by the name __construct().
PHP5引入了方法名__construct()来定义构造方法。
Example
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructor\n";
}
}
$obj = new BaseClass();
$obj = new SubClass();
?>
为向下兼容,PHP5当在类不能找到__construct()方法时,会通过老的方法也就是类名来查找构造方法。这意味着唯一可能产生兼容性问题的是在以前的代码中已经使用了一个名为__construct()的方法名。
---------------------------------------------------------------------------------