期待已久的PHP5终于在当地时间7月13日正式发布了。无论对于PHP语言本身还是PHP的用户来讲,PHP5发布都算的上是一个里程碑式的版本。在PHP5发布之前的各个PHP版本就以简单的语法、丰富的库函数以及极快的脚本解释执行速度,赢得了许多开发者的青睐,几乎成了*NIX平台上首选的WEB开发语言。然而,站在语言本身角度,PHP的语法,特别是OO方面的语法设计并不完善,当然这和PHP语言的作者一开始的设计目的有关。众说周知,PHP最开始只是一个用Perl写成的一个模板系统,其后的发展思路也是尽可能为快速开发WEB程序提供方便。大量的库函数加入其中,而语言模型的发展则相对缓慢。虽然在PHP4中加入了面向对象的设计,但其语言模型并不完善,缺乏诸如构造函数、析构函数、抽象类(接口)、异常处理等基本元素。这极大限制了利用PHP来完成大规模应用程序的能力。
而PHP5的诞生,则从根本上改变了PHP的上述弊端。Zend II 引擎的采用,完备对象模型、改进的语法设计。终使得PHP成为一个设计完备、真正具有面向对象能力的脚本语言。我们预计,PHP5的出现,将会给整个PHP社区带来新的一轮各种库、应用开发改进的高峰。
PHP5 新特性(总概)
PHP5.0版本强化了以下功能。首先,完全实现面向对象。PHP4版本中以深拷贝为缺省值的对象代入,在PHP5中变为以浅拷贝为缺省值。因此,PHP5并非与PHP4完全兼容,不过,提供名为PHP兼容模式的功能。 其次是XML功能,PHP5.0版本支持可直观地访问XML数据、名为SimpleXML的XML处理用界面。同时还强化了XML Web服务支持,而且标准支持SOAP扩展模块。 数据库方面,PHP新版本提供旨在访问MySQL的新界面--MySQLi。除此前的界面外,还可以使用面向对象界面和预处理语句(Prepared Statement)等MySQL的新功能。另外,PHP5.0上还捆绑有小容量RDBMS--SQLite。
PHP5 新函数
来源 / Zend 翻译 / EasyChen@phpe
PHP5中新添了一些函数,下边是它们的列表: Arrays: array_combine() - 使用一个数组作为索引,另一个数组作为值来创建一个新数组 array_diff_uassoc() - 使用用户提供的回调函数比较数组的相异程度(使用额外的索引检查) array_udiff() - 使用回调函数比较数组的相异程度 array_udiff_assoc() - 比较数组的相异程度(使用额外的索引检查)。数据通过回调函数比较。 array_udiff_uassoc() - 比较数组的相异程度(使用额外的索引检查)。数据通过回调函数比较;索引检查也由回调函数完成。 array_walk_recursive() - 调用一个用户定义函数对数组的每个元素进行遍历。 InterBase: ibase_affected_rows() - 返回前一个query中生效的row数 ibase_backup() - 在service manager中初始化一个backup任务,并立即返回 ibase_commit_ret() - 在不关闭的情况下,Commit一个事务 ibase_db_info() - 取得数据库的状态 ibase_drop_db() - Drop一个数据库 ibase_errcode() - 返回错误代码 ibase_free_event_handler() - 取消一个已注册的事件句柄 ibase_gen_id() - 添加named generator并返回新值 ibase_maintain_db() - 执行维护命令 ibase_name_result() - 将一个名字指定到一个结果集 ibase_num_params() - 返回prepared query中参数的个数 ibase_param_info() - 返回一个prepared query中一个参数的信息 ibase_restore() - 在service manager中初始化一个restore任务并立即返回 ibase_rollback_ret() - Rollback一个事务,并保留事务的上下文(context) ibase_server_info() - 取得一个数据库的信息 ibase_service_attach() - 连接到service manager ibase_service_detach() - 和service manager断开 ibase_set_event_handler() - 注册一个事件的回调函数 ibase_wait_event() - 等待数据库事件 iconv: iconv_mime_decode() - 解码一个MIME头 iconv_mime_decode_headers() - 对多个MIME头一次解码 iconv_mime_encode() - 形成一个MIME头 iconv_strlen() - 返回字符串的字符数 iconv_strpos() - 查找字符在串中第一次出现的位置 iconv_strrpos() - 查找字符在串中最后一次出现的位置 iconv_substr() - 取得字符串的一部分 Streams: stream_copy_to_stream() - 在stream间复制数据 stream_get_line() - 用指定分隔符从stream中取得行 stream_socket_accept() - 接受由stream_socket_server() 创建的socket连接 stream_socket_client() - 打开 Internet 或者 Unix domain socket连接 stream_socket_get_name() - 取得本地或者远程 sockets的名字 stream_socket_recvfrom() - 从socket接受数据,无论是否连接 stream_socket_sendto() - 发送信息给一个socket,无论是否连接 stream_socket_server() - 创建一个Internet或者Unix domain socket Other: convert_uudecode() - 解码一个通过uuencode的字符 convert_uuencode() - 编码一个字符为uuencode curl_copy_handle() - 复制一个cURL句柄,连同它的全部偏好(preferences) dba_key_split() - 将一个键值(key)从string形式分割为数组形式 dbase_get_header_info() - 取得一个dBase数据库的头信息 dbx_fetch_row() - 从一个DBX_RESULT_UNBUFFERED标志置位的query-result取得row fbsql_set_password() - 改变指定用户的密码 file_put_contents() - 将一个string写入一个文件 FTP_alloc() - 为将被上传的文件分配空间 get_declared_interfaces() - 返回一个包含了所有已声明的接口的数组 get_headers() - 取得服务器对一个HTTP请求返回的全部头数据 headers_list() - 返回发送的(或者准备发送的)头信息列表 http_build_query() - 生成一个URL编码的query串 idate() - 将本地时间/日期格式化为整型 image_type_to_extension() - 取得由getimagesize(), exif_read_data(), exif_thumbnail(), exif_imagetype() 生成的图像类型的文件扩展名 imagefilter() - 自定义图像过滤 imap_getacl() - 取得指定mailbox的ACL ldap_sasl_bind() - 使用SASL绑定LDAP目录 mb_list_encodings() - 返回所有支持的编码列表数组 pcntl_getpriority() - 取得进程的属性 pcntl_wait() - 等待或返回fork子进程的由waitpid() 系统调用定义的状态 pg_version() - 返回一个客户端,协议和服务器版本的数组 php_check_syntax() - 检查指定文件的语法 php_strip_whitespace() - 返回去除了注释和空白格的源代码 proc_nice() - 改变当前进程的属性 pspell_config_data_dir() - 更改语言数据文件 pspell_config_dict_dir() - 更改主单词列表 setrawcookie() - 发送一个不带URL编码值得cookie snmp_read_mib() - 读取和解析一个MIB文件到MIB树 sqlite_fetch_column_types() - 返回一个包含特定表的列属性的数组 str_split() - 将字符转化为数组 strpbrk() - 查找一个串中字符的任何子集 substr_compare() - 字符串比较,二进制安全 time_nanosleep() - 延迟N纳秒
PHP5 对象体系
PHP5推出的对象体系相信是大家最为期待的。PHP5借鉴了Java2的对象模型,提供了较为强大的面向对象编程支持,使用PHP来实现OO将变得轻松和自然。
对象传递
PHP5使用了Zend引擎II,对象被储存于独立的结构Object Store中,而不像其它一般变量那样储存于Zval中(在PHP4中对象和一般变量一样存储于Zval)。在Zval中仅存储对象的指针而不是内容(value)。当我们复制一个对象或者将一个对象当作参数传递给一个函数时,我们不需要复制数据。仅仅保持相同的对象指针并由另一个zval通知现在这个特定的对象指向的Object Store。由于对象本身位于Object Store,我们对它所作的任何改变将影响到所有持有该对象指针的zval结构----表现在程序中就是目标对象的任何改变都会影响到源对象。.这使PHP对象看起来就像总是通过引用(reference)来传递,因此PHP中对象默认为通过“引用”传递,你不再需要像在PHP4中那样使用&来声明。
垃圾回收机制
某些语言,最典型的如C,需要你显式地要求分配内存当你创建数据结构。一旦你分配到内存,就可以在变量中存储信息。同时你也需要在结束使用变量时释放内存,这使机器可以空出内存给其它变量,避免耗光内存。
PHP可以自动进行内存管理,清除不再需要的对象。PHP使用了引用计数(reference counting)这种单纯的垃圾回收(garbage collection)机制。每个对象都内含一个引用计数器,每个reference连接到对象,计数器加1。当reference离开生存空间或被设为NULL,计数器减1。当某个对象的引用计数器为零时,PHP知道你将不再需要使用这个对象,释放其所占的内存空间。
例如:
class Person{
}
function sendEmailTo(){
}
$haohappy = new Person( );
// 建立一个新对象: 引用计数 Reference count = 1
$haohappy2 = $haohappy;
// 通过引用复制: Reference count = 2
unset($haohappy);
// 删除一个引用: Reference count = 1
sendEmailTo($haohappy2);
// 通过引用传递对象:
// 在函数执行期间:
// Reference count = 2
// 执行结束后:
// Reference count = 1
unset($haohappy2);
// 删除引用: Reference count = 0 自动释放内存空间
?>
以上是PHP5在内存管理上的变化,也许大家不怎么感兴趣。下面我们来看看PHP5中的对象模型和PHP4有什么具体的不同之处:
新增功能
改进功能
1) Private and Protected Members 私有和保护类成员(属性,方法)
2) Abstract Classes and Methods 抽象类和抽象方法
3) Interfaces 接口
4) Class Type Hints 类型指示 =
5) final final关键字 =
6) Objects Cloning 对象复制 =
7) Constructors and Destructors 构造函数和析构函数
8) Class Constants 类常量 =
9) Exceptions 异常处理
10) Static member 静态类成员
11) __METHOD__ constant __METHOD__常量 =
12) Reflection 反射机制
以下向大家介绍第4、5、6、8、11点语言特性:
Class Type Hints 类型指示
大家都知道,PHP是一种弱类型的语言。在使用变量前不需要定义,不需要声明变量的数据类型。这在编程中带来很多便利,但也带了一些隐患,特别当变量的类型变化时。在PHP5增加了类型指示,可以在执行过程中自动对类方法的参数类型进行判断。这类似于Java2中的RTTI,配合reflection可以让我们很好地控制对象。
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);
?>
在强类型语言中,所有变量的类型将在编译时进行检查,而在PHP中使用类型指示来对类型的检查则发生在运行时。如果类方法参数的类型不对,将会报出类似“Fatal error: Argument 1 must implement interface Bar…”这样的错误信息。
以下代码:
function foo(ClassName $object) {
// ...
}
?>
相当于:
function foo($object) {
if (!($object instanceof ClassName)) {
die("Argument 1 must be an instance of ClassName");
}
}
?>
final / final关键字
PHP5中新增加了final关键字,它可以加在类或类方法前。标识为final的类方法,在子类中不能被覆写。标识为final的类,不能被继承,而且其中的方法都默认为final类型。
Final方法:
class Foo {
final function bar() {
// ...
}
}
?>
Final类:
final class Foo {
// class definition
}
// 下面这一行是错误的
// class Bork extends Foo {}
?>
Objects Cloning / 对象复制
前面在内存管理部份说过,PHP5中默认通过引用传递对象。像使用$object2=$object1这样的方法复制出的对象是相互关联的。如果我们确实需要复制出一个值与原来相同的对象而希望目标对象与源对象没有关联(像普通变量那样通过值来传递),那么就需要使用clone关键字。如果还希望在复制的同时变动源对象中的某些部份,可以在类中定一个__clone()函数,加入操作。
//对象复制
class MyCloneable {
static $id = 0;
function MyCloneable() {
$this->id = self::$id++;
}
/*
function __clone() {
$this->address = "New York";
$this->id = self::$id++;
}
*/
}
$obj = new MyCloneable();
$obj->name = "Hello";
$obj->address = "Tel-Aviv";
print $obj->id . "\n";
$obj_cloned = clone $obj;
print $obj_cloned->id . "\n";
print $obj_cloned->name . "\n";
print $obj_cloned->address . "\n";
?>
以上代码复制出一个完全相同的对象。
然后请把function __clone()这一个函数的注释去掉,重新运行程序。则会复制出一个基本相同,但部份属性变动的对象。
Class Constants / 类常量
PHP5中可以使用const关键字来定义类常量。
class Foo {
const constant = "constant";
}
echo "Foo::constant = " . Foo::constant . "\n";
?>
__METHOD__ constant __METHOD__常量
__METHOD__ 是PHP5中新增的“魔术”常量,表示类方法的名称。
魔术常量是一种PHP预定义常量,它的值可以是变化的,PHP中的其它已经存在的魔术常量有__LINE__、__FILE__、__FUNCTION__、__CLASS__等。
class Foo {
function show() {
echo __METHOD__;
}
}
class Bar extends Foo {
}
Foo::show(); // outputs Foo::show
Bar::show(); // outputs Foo::show either since __METHOD__ is
// compile-time evaluated token
function test() {
echo __METHOD__;
}
test(); // outputs test
?>
PHP5 的XML新特性
PHP4 的 XML
早期的PHP版本就已经开始支持XML了,而这只是一个基于SAX的接口,它可以轻松的解析任何XML文档。随着PHP4中加入了DOMXML扩展模块,XML被更好的支持了。后来XSLT做为补充被加了进来。在整个PHP4的阶段,其它一些功能如HTML,XSLT和DTD验证也被加到了DOMXML扩展中,不幸的是,由于XSLT和DOMXML扩展始终处于实验阶段,API部分也被不止一次的修改,它们还是不能以默认方式安装。此外,DOMXML扩展没有遵循W3C制定的DOM标准,而有自己的命名方法。虽然在PHP4.3中这部分得到了改善并且许多内存泄漏和其它一些功能也得以修复,但它始终没有发展到一个稳定的阶段,一些深入的问题已经几乎不可能修复。只有SAX扩展被已默认方式安装,其它的一些扩展从未得到广泛的使用。
基于所有这些原因,PHP的XML开发者决定在PHP5重写全部代码,并遵循使用标准。
PHP5的XML 在PHP5中所有支持XML的部分几乎全部重新编写.现在的所有XML扩展都是基于GNOME项目的LIBXML2库。这将允许在不同的扩展模块之间互相操作,核心开发者只需要在一个底层的库上进行开发。例如,复杂的内存管理只实现一次就可以让所有XML相关扩展得到改善。
除了继承PHP4中闻名的SAX解析器之外,PHP5还支持遵循W3C标准的DOM和基于LIBXSLT引擎的XSLT。同时还加入了PHP独有的SimpleXML扩展和符合标准的SOAP扩展。随着XML越来越被重视,PHP开发者决定在默认安装方式中加入更多对XML的支持。这就意味着你现在可以使用SAX,DOM和SimpleXML,而这些扩展将会在更多的服务器上安装。然后对于XSLT和SOAP的支持,还需要在PHP编译时被显式的配置。
数据流的支持
现在所有的XML扩展都支持PHP数据流,即使你不从PHP中直接访问。例如,在PHP5中你可以从一个文件或从一条指令访问数据流。基本上你能够在任何可以访问普通文件的地方访问PHP数据流。
PHP4.3中简要的介绍了数据流,在PHP5中已经得到了进一步的提高,包含文件存取,网络存取和其它操作,如共享一套功能函数。你甚至可以使用PHP代码来实现你自己的数据流,这样数据存取将变得非常简单。关于这部分的更多细节请参考PHP文档。
SAX
SAX的全称是Simple API for XML,它是用于解析XML文档的接口,是基于回调形式的。从PHP3开始就已经支持了SAX,到现在也没有太大的变化。在PHP5中,API接口并没有改变,所以你的代码仍然可以运行。唯一不同的是它不再基于EXPAT库,而是基于LIBXML2库。
这个变化带来了一些对命名空间支持上的问题,这个问题在LIBXML2.2.6版本中已经得到解决。但是LIBXML2以前的版本中并没有解决,因此如果你使用了xml_parse_create_ns();强烈建议在你的系统上安装LIBXML2.2.6。
DOM
DOM (文档对象模型)是由W3C制定的一套访问XML文档树的标准。在PHP4可以使用DOMXML来对此进行操作,DOMXML的最主要问题是它不符合标准的命名方法。而且在很长一段时间内还存在内存泄漏问题(PHP4.3已经修复了这个问题)。
新的DOM扩展是基于W3C标准完成的,包含方法和属性名称。如果你在其它语言中熟悉DOM,例如在JavaScript中,那么在PHP中编写类似的功能将变得非常容易。你不必每次都查看文档,因为方法和参数都是相同的。
由于使用了新的W3C标准,基于DOMXML的代码将不能运行。在PHP中的API有很大的不同。但是如果你的代码中使用了类似W3C标准的方法命名方式,移植并不是很困难。你只需要将载入函数和保存函数修改,删除函数名中的下划线(DOM标准使用首字母大写)。其它各处的调节当然也是必须的,但是主要逻辑部分可以保持不变。
读取DOM
在这篇文章的大多数例子中我们将使用同一个XML文件,zend.com上有非常简单的RSS版本。将下面的文本粘贴到一个文本文件中并保存为articles.xml。
-
http://www.zend.com/zend/week/week172.php -
http://www.zend.com/zend/tut/tut-hatwar3.php
要将这个例子载入到一个DOM对象,首先要创建一个DOMDocument对象,然后载入XML文件。
$dom = new DomDocument();
$dom->load("articles.xml");
正像上面所提及的,你可以使用PHP的数据流来载入一个XML文档,你应该这样写:
$dom->load("file:///articles.xml");
(或者其它类型的数据流)
如果你想将XML文档输出到浏览器或做为标准标出,使用:
print $dom->saveXML();
如果你想把它保存成文件,请使用:
print $dom->save("newfile.xml");
(注意这样做会将文件大小发送到stdout)
当然这个例子没有太多的功能,让我们来做些更有用的。我们来取得所有的title元素。有很多方法可以办到,最简单的就是使用getElementsByTagName($tagname):
$titles = $dom->getElementsByTagName("title");
foreach($titles as $node) {
print $node->textContent . "\n";
}
textContent属性并不是W3C标准,它可以让我们很方便的快速读取一个元素的所有文本节点,使用W3C的标准读取是下面这样:
$node->firstChild->data;
(这时候你要确保firstChild结点是你需要的文本结点,否则你还得遍历所有子结点来查找)。
另外一个要注意的问题是getElementsByTagName()返回一个DomNodeList,对象,而不是像PHP4中get_elements_by_tagname()那样返回一个数组,但是正像你在这个例子中看到的那样,你可以使用foreach语句轻松的遍历它。你也可以直接使用$titles->item(0)来访问结点。该方法将返回第一个title元素。
另一个取得所有title元素的办法是从根结点遍历,你可以看到,这个方法更复杂,但是如果你需要的不只是title元素的时候,这个方法也就更灵活。
foreach ($dom->documentElement->childNodes as $articles) {
//如果节点是一个元素(nodeType == 1)并且名字是item就继续循环
if ($articles->nodeType == 1 && $articles->nodeName == "item") {
foreach ($articles->childNodes as $item) {
//如果节点是一个元素,并且名字是title就打印它.
if ($item->nodeType == 1 && $item->nodeName == "title") {
print $item->textContent . "\n";
}
}
}
}
XPath
XPaht 就像是XML的SQL,使用XPath你可以在一个XML文档中查询符合一些模式语法的特定结点。想使用XPath获得所有title结点,只需要这么做:
$xp = new domxpath($dom);
$titles = $xp->query("/articles/item/title");
foreach ($titles as $node) {
print $node->textContent . "\n";
}
?>
这样和使用getElementsByTagName()方法差不多,但是Xpath要强大的多,例如,如果我们有一个title元素是article的子元素(而不是item的子元素),getElementsByTagName()就会将它返回。而使用/articles/item/title语法,我们只会得到在指定深度和位置的title元素。这只是一个简单的例子,再深入一点可能是这样:
/articles/item[position() = 1]/title 返回第一个item元素的所有
/articles/item/title[@id = '23'] 返回所有含有id属性并且值为23的title
/articles//title 返回所有articles元素下面的title(译者注://代表任意深度)
你也可以查询含有特殊兄弟元素的点,含有特殊文本内容的元素,或者使用命名空间等等。如果你必须大量的查询XML文档,适当的学习使用XPath会节省你很多时间,它使用简单,执行速度快,比标准的DOM需要更少的代码。
向DOM中写入数据
文档对象模型并不是只能读取和查询,你也可以操作和写入。(DOM标准有点冗长,因为编写者想尽量支持能够想像到的每一个环境,但是它工作的非常好)。看看下面这个例子,它在我们的article.xml文件中添加了一个新元素。
$item = $dom->createElement("item");
$title = $dom->createElement("title");
$titletext = $dom->createTextNode("XML in PHP5");
$title->appendChild($titletext);
$item->appendChild($title);
$dom->documentElement->appendChild($item);
print $dom->saveXML();
首先,我们创建了所有需要的结点,一个item元素,一个title元素和一个包含item标题的文本结点,然后我们将所有的结点链接起来,把文本结点加到title元素上,把title元素加到item元素上,最后我们把item元素插入到articles根元素上。现在,我们的XML文档中有一个新的文章列表了。
扩展类(class)
好了,上面的例子都可以在PHP4下面用DOMXML扩展来做(只是API有一些不同),能够自己扩展DOM类是PHP5的一个新特性,这使得书写更多可读性强的代码变得可能。下面是用DOMDocument类重新写的整个例子:
class Articles extends DomDocument {
function __construct() {
//必须调用!
parent::__construct();
}
function addArticle($title) {
$item = $this->createElement("item");
$titlespace = $this->createElement("title");
$titletext = $this->createTextNode($title);
$titlespace->appendChild($titletext);
$item->appendChild($titlespace);
$this->documentElement->appendChild($item);
}
}
$dom = new Articles();
$dom->load("articles.xml");
$dom->addArticle("XML in PHP5");
print $dom->save("newfile.xml");
HTML
PHP5中一个经常不被注意到的特性是libxml2库对HTML的支持,你不仅可以使用DOM扩展载入结构良好(well-formed)的XML文档,还可以载入非结构良好的(not-well-formed)HTML文档,把它当做标准的DOMDocument对象,使用所有能用的方法和特性,比如XPath和SimpleXML。
当你需要访问一个你无法控制站点的内容时,HTML的性能就显示十分有用了。在 XPath, XSLT 或 SimpleXML的帮助下,你省掉了许多代码,像使用正则表达式比较字符串或者SAX解析器。当HTML文档结构不是很好的时候,这个办法尤其有用(这是个频繁的问题!)。
下面的代码获得并解析php.net的首页,将返第一个title元素的内容。
$dom = new DomDocument();
$dom->loadHTMLFile("http://www.php.net/");
$title = $dom->getElementsByTagName("title");
print $title->item(0)->textContent;
请注意当指定元素没有找到时,你的输出可能会包含错误。如果你的网站还在使用PHP输出HTML4代码,有一个好消息要告诉你,DOM扩展不仅能载入HTML文档,而且还能将他们保存为HTML4格式的文件。在你添加完DOM文档后,使用$dom->saveHTML()来保存。要注意的是,为了使输出的HTML代码符合W3C标准,最好不用使用 整齐的扩展?(tidy extension)。Libxml2 库支持的HTML并不会考虑到每个可能发生的事情,也不能很好的处理非通用格式的输入。
验证
XML文档的验证越来越重要了。例如,如果你从一些国外资源中获得了一个XML文档,在你处理之前你需要检验它是否符合某个确定的格式。幸运的是你不需要在PHP中写自己的验证程序,因为你可以使用三个应用最广泛的标准之一(DTD,XML Schema 或RelaxNG)来完成它。.
- DTD是一个产生于SGML时代的标准,缺少一些XML的新特性(如命名空间),而且由于它不是用XML写的,它也很难被解析和转换。
- XML Schemai是由W3C制定的一个标准,它应用广泛,几乎包含了所有验证XML文档所需要的内容。
- RelaxNG 是复杂的XML Schema标准的对头,是由自由者组织创建的,由于它比XML Schema更容易实现,越来越多的程序开始支持RelaxNG了
如果你没有遗留下来的计划文档或者非常复杂的XML文档,那么使用RelaxNG吧。它书写和阅读都比较简单,越来越多的工具也支持它。甚至还有一个工具叫Trang,它可以从XML范本中自动创建一个RelaxNG文档。而且只有RelaxNG(和老化的DTDS)被libxml2完全支持,尽管libxml2也即将完全支持ML Schema。
验证XML文档的语法相当简单:
$dom->validate('articles.dtd');
$dom->relaxNGValidate('articles.rng');
$dom->schemaValidate('articles.xsd');
目前,所有这些都只会简单的返回true或false,错误会被做为PHP警告输出。显然想返回给用户友好的信息这并不是一个好主意,在PHP5.0以后的版本里会有所改善。到底该怎么实现目前还在讨论之中,但是错误报告肯定会处理的更好。
SimpleXML
SimpleXML 是PHP的XML家族中最后一个被加入的成员,加入SimpleXML扩展的目的是为了提供一个使用标准对象属性和迭代器访问XML文档的更简单的方法。该扩展没有太多的方法,虽然如此它还是相当强大的。从我们的文档的取得所有title节点比原来需要更少的代码。
$sxe = simplexml_load_file("articles.xml");
foreach($sxe->item as $item) {
print $item->title ."\n";
}
这是在干什么?首先将articles.xml载入到一个SimpleXML对象。然后取得所有$sxe中的item元素,最后$item->title返回title元素的内容,就是这样。你也可以使用关联数组查询属性,使用: $item->title['id']。
看到了吧,这后面真是太神奇了,有许多不同的办法可以得到我们想要的结果,例如, $item->title[0]返回和例子中相同的结果,另一方面,foreach($sxe->item->title as $item)只返回第一个title,并不是所有在文档中的title元素。(就像我在XPath中预期的那样)。
SimpleXML 实际上是使用了Zend引擎2新特性的第一个扩展。因此也成了这些新特性的测试点,你要知道在开发阶段bugs和不可预料的错误可不是少数。
除了上面例子中所使用的遍历所有节点的方法,在SimpleXML中也有一个XPath接口,它为访问单个结点提供了更简单的办法。
foreach($sxe->xpath('/articles/item/title') as $item) {
print $item . "\n";
}
不可否认,这段代码也不比前面例子中的短,但是提供了更复杂或更深的嵌套XML文档,你会发现和SimpleXML一起使用XPath会节省你很多的输入。
向 SimpleXML 文档写入数据
你不仅可以解析和读取SimpleXML,而且还可以改变SimpleXML文档。至少我们加入一些扩展:
$sxe->item->title = "XML in PHP5 "; //title元素的新内容。
$sxe->item->title['id'] = 34; // title元素的新属性。
$xmlString = $sxe->asXML(); // 将SimpleXML对象做为序列化的XML字符串返回
print $xmlString;
互用协作性
由于SimpleXML也是基于libxml2库的,你可以在几乎不影响速度的情况下轻松的将SimpleXML对象转化成DomDocument对象。(文档不用进行内部复制),由于这个机制,你拥有了二个对象的最好部分,使用一个适合你手头工作的工具吧,它是这样使用的:
$sxe = simplexml_import_dom($dom);
$dom = dom_import_simplexml($sxe);
XSLT
XSLT是用来将XML文档转换为其它XML文档的语言,XSLT本身是用XML编写的,属于功能性语言家族,在程序处理上和面对对象语言(像PHP)有所不同。PHP4中有二种XSLT处理器:Sablotron(在广泛使用的XSLT扩展中)和Libxslt(在domxml扩展中),这两种API不互相兼容,并且使用方法也不相同。PHP5只支持libxslt处理器,之所以选择它是因为它是基于Libxml2的,因此也更符合PHP5的XML概念。
理论上将Sablotron绑定到PHP5上也是可能的,但是不幸的是没人来做。因此,如果你正在使用Sablotron,你不得不在PHP5中切换到libxslt处理器。Libxslt 是带有JavaScript异常处理支持的Sablotron,甚至可以使用PHP强大的数据流来重新实现Sablotron独有的计划处理(scheme handlers)。此外,libxslt 是 最快的XSLT处理器之一,所以你还免费得到了速度的提升。(执行速度是Sablotron的二倍)。
和本文讨论的其它扩展一样,你可以在XSL扩展,DOM扩展和vice versa之间交换XML文档,实际上,你必须得这么做,因为EXT/XSL扩展并没有载入和保存XML文档的接口,只能使用DOM扩展。一开始学习XSLT转换,你不需要掌握太多的内容,这里也不存在W3C标准,因为这个API中从Mozilla“借”过来的。
首先你需要一个XSLT样式表,将下列文本粘贴到一个新文件并且保存灰articls.xsl
然后用PHP脚本调用它::
/* 将XML和XSL文档载入到DOMDocument对象*/
$xsl = new DomDocument();
$xsl->load("articles.xsl");
$inputdom = new DomDocument();
$inputdom->load("articles.xml");
/* 创建XSLT处理器,并导入样式表*/
$proc = new XsltProcessor();
$xsl = $proc->importStylesheet($xsl);
$proc->setParameter(null, "titles", "Titles");
/* 转换并输出XML文档 */
$newdom = $proc->transformToDoc($inputdom);
print $newdom->saveXML();
?>
上面的例子首先使用DOM的方法load()载入XSLT样式表articles.xsl,然后创建了一个新的XsltProcessor对象,该对象导到了后面要使用了XSLT样式表对象,参数可以这样设置setParameter(namespaceURI, name, value),最后XsltProcessor对象使用transformToDoc($inputdom)开始转换并返回一个新的DOMDocument对象。
. 这个API的优点在于你可以使用同一个样式表转换许多XML文档,只需要将它载入一次然后重复使用它,因为transormToDoc()函数可以应用于不同的XML文档。
除了transormToDoc(),还有二个用于转换的方法:transformToXML($dom)返回一个字符串,transformToURI($dom, $uri)将转换之后的文档保存到文件或一个PHP数据流。注意如果你想使用XSLT的一个语法如 或 indent="yes",你不能使用transformToDoc(),因为DOMDocument对象 不能保存该信息,只能当你将转换后的结果直接保存到字符串或文件中时才能这样做。
调用PHP函数
XSLT扩展最后一个新加的特性是能够在XSLT 样式表内部调用任何PHP函数,主张正统的XML支持者一定不会喜欢这个功能(这样的样式表有点复杂,很容易混淆逻辑和设计),在某些地方却是十分有用的。当涉及到函数时XSLT就变得很有限,即使想实现用不同的语言输出一个日期也是非常麻烦的。但是使用这个功能,处理这些就和只使用PHP一样容易。下面是向XSLT添加一个函数的代码:
function dateLang () {
return strftime("%A");
}
$xsl = new DomDocument();
$xsl->load("datetime.xsl");
$inputdom = new DomDocument();
$inputdom->load("today.xml");
$proc = new XsltProcessor();
$proc->registerPhpFunctions();
// 载入文档并使用$xsl来处理
$xsl = $proc->importStylesheet($xsl);
/* 转换并输出XML文档 */
$newdom = $proc->transformToDoc($inputdom);
print $newdom->saveXML();
?>
下面是XSLT样式表datetime.xsl,它会调用这个函数。
下面是要使用样式表转换的XML文档,today.xml(同理,articles.xml也会得到同样结果)。
上面的样式表,PHP脚本和所有的XML文件会用当前系统设置的语言输出星期的名字。你可以给php:function()添加更多的参数,添加的参数会被传递给PHP函数。这里有一个函数php:functionString(),这个函数自动将所有输入的参数转换为字符串,所以你不需要在PHP里进行转换。
注意你需要在转换之前调用$xslt->registerPhpFunctions(),否则PHP函数调用将因为安全原因不会被执行(你始终相信你的XSLT样式表吗?)。目前访问系统还没有实现,也许在将来PHP5的版本中会实现这个功能。
摘要
PHP对XML的支持已经向前迈进了一大步,它符合标准,功能强大,互用协作性强,被作为默认选项安装,已被授权使用。新加入的SimpleXML扩展提供了简单快速访问XML文档的方法,可以节省你很多的代码,尤其是当你有结构化文档或者可以使用强大的XPath时。
感谢libxml2—PHP5 XML扩展所使用的底层库,使用DTD,RelaxNG或XML Schema验证XML文档现在已经被支持了。
XSL支持也得到了翻新,现在使用Libxslt库,比原来的Sablotron库在性能上有很大提高,而且,在XSLT样式表内部调用PHP函数可以让你写出更强大的XSLT代码。
如果你已经在PHP4或其它语言中使用了XML,你会喜欢PHP5的XML特性的,XML在PHP5中有了很大的变化,符合标准,和其它工具,语言是同等的。
PHP5.0 新特性(ZT)
(一) Zend 2.0的诞生 现在的PHP4所使用的基本文法是被称之为Zend 引擎的脚本编译引擎。这个就是PHP4的优良机能的原因之一,是作为对PHP3的改进而生成的一种语言。大家一直认为,PHP4的性能根据当初的目标,比PHP3有了很大的提升,在网络编程的世界里占据了很大的份额。
开发了Zend 引擎的Zend公司是在开发PHP4的同时,由PHP3的主要开发者Zeev Suraski和Andi Gutmans所创立的企业合并而来的。Zend的名称是由Zeev和Andi的名字合起来组成的。Zend公司的商业模式是,持续不断的为open source提供zend 引擎的PHP内核 (core),同时提升周边产品开发和贩卖的利益。以open source software作为基盘的商业,在世界范围内大多数正在苦战的企业中,算是比较好的典型例子了。
■PHP4的局限
托PHP4成功的福,这个用途的适用范围逐渐变广起来。作为企业级的用途而使用PHP的说法时有所闻。因此,就有了这样一个问题,构筑大规模网站的时候,代码的再利用性十分差。具体来说就是,PHP4的面向对象性能很弱,因此习惯于使用Java等的技术人员对此有很多的抱怨。
逐步的改善PHP4的面向对象的性能,大幅度的更改基本文法,开发者达成了更新PHP记述方法的开拓目的。
■Zend 2.0开始开发 随后,Zend公司PHP中心的开发者们在2001年7月发表了作为下一代PHP语言引擎的Zend 2.0引擎的构想。以[Zend Engine version 2.0: Feature Overview and Design] ( http://www.zend.com/engine2/ZendEngine-2.0.pdf)作为目标的同时,面向对象的性能大幅度的强化了。 目前的PHP4 Zend 引擎的扩张情况与昔日的PHP3如出一辙。这就意味着,要提升新的语言引擎的主版本号,明确方法目标,迎接来自开发团体的称赞。
Ze2的开发,与以往的Zend引擎一样,都是运行在open source的模式下的。最新的源代码在CVS上被全面的公开,因为是面向开放的开发者的,关于开发的议论非常的活跃。
现在Ze2被决定采用于PHP的下一个版本PHP5中。最终发布的时间现在还未定,但是假如根据Zend公司2003年4月1日发布的Newsletter的话,现在的应该就是Beta Release了。
(二) PHP5的新特性
接下来请按照顺序看一下被强化的PHP5的性能。首先是最为重要的面向对象性能,类的实体特性在大幅度的被修改着。这里说的仅是关于类的新特性。
· 对象的参照过渡是默认的(default) · 引入访问属性的限制 · 引入访问方法的限制 · 抽象类和抽象方法 · 接口 · final声明 · 名空间 · 类内常量 · 类变量 · 统一构建器 · 析构函数(Distructor) · 其他附属特性
以上内容是根据2003年4月22日CVS上登录版本资料所写的,在正式的发布之前,也有变动的可能性。
■对象的默认参照过渡
在PHP4中,在以变量$var1为类的实体对象的时候,如果$var2 = $var1;那么,在$var2中,$var1的复制被代入。明显的,$var2为了指向与$var1相同的对象,就要写成$var2 =& $var1,必须要加上&作为参照。 而在PHP5,对象的代入将成为自动的参照过渡。也就是说, $var2=$var1,两者指向相同的对象。如果想要同php4一样,带入copy,那么就会运用到导入__clone()的方法。 $var2 = $var1->__clone();此处,clone前面是两个连续的“_” (这仅仅是类的实体的特性)
■引入访问属性的限制
在PHP4的类中,连同属性和方法在内,可以自由的访问类的内外任何地方,而没有限制。因此,用户就无法防范属性的无意中的更改。
而在PHP5中,同C++和Java一样,导入了private, protected, public三个等级的访问限制,使得类的设计者能够对属性和方法的使用方法进行限定。以下是各种访问限制的意思。
· Public: 可以自由的在类的内外任何地方进行参照、变更 · Private: 只能在这个类的方法中进行参照、变更 · Protected:能够在这个类以及继承了这个类的另一个类的方法中进行参照、变更。另外,在继承的类中,能够写入访问指定。
在PHP4中的“var”,同以往一样与public有着相同的意思。下面就来举一个例子,让我们来看看访问限制是怎样起作用的。
PHP代码:-------------------------------------------------------------------------------- class Hoge1 { private $var1 = 'A'; protected $var2 = 'B'; protected $var3 = 'C';
function setLower() { $this->var1 = 'a'; $this->var2 = 'b'; $this->var3 = 'c'; } function var1() { return $this->var1; } function var2() { return $this->var2; } function var3() { return $this->var3; } }
--------------------------------------------------------------------------------
在这个类中,带有$var1, $var2, $var3三个属性。$var1被声明为private, $var2和$var3是protected.在此处
PHP代码:-------------------------------------------------------------------------------- $hoge=new Hoge1; echo’var1:’.$hoge->var1.” n”
--------------------------------------------------------------------------------
如果尝试参照不允许从外部进行访问的private属性,那么就会出现如下错误:
Fatal error: Cannot access private property hoge1::$var1 in /path/to/script.php on line XX
对于protected的$var2也是相同的。
但是,因为$hoge的方法是没有private和protected的,所以下面的代码能够正常运作,返回内部私有和保护变量的值。
PHP代码:-------------------------------------------------------------------------------- echo 'var1: ' . $hoge->var1() . " \n"; // var1: A echo 'var2: ' . $hoge->var2() . " \n"; // var2: B echo 'var3: ' . $hoge->var3() . " \n"; // var3: C
$hoge->setLower();
echo 'var1: ' . $hoge->var1() . " \n"; // var1: a echo 'var2: ' . $hoge->var2() . " \n"; // var2: b echo 'var3: ' . $hoge->var3() . " \n"; // var3: c
--------------------------------------------------------------------------------
其次,为了能够看到protected的属性的状态,我们试着创造了继承了Hoge1的类Hoge2
PHP代码:-------------------------------------------------------------------------------- class Hoge2 extends Hoge1 { public $var3 = '3';
function d_var1() { return $this->var1; } function d_var2() { return $this->var2; } function d_var3() { return $this->var3; } }
--------------------------------------------------------------------------------
在类Hoge2中,只有$var3被声明为public。在属性是protected的情况下,从子类进行访问有何种限制,是由子类的属性声明决定的。在Hoge2中,因为$var3被声明是public,因此无论是从何处都可以访问Hoge2的$var3(实体是Hoge1的$var3)。因为$var1在Hoge1中是private,因此,在Hoge2子类中Hoge1的$var1不会被继承,而在Hoge2中有可能会做出名为$var1的属性,因此,必须要明确区分Hoge1::$var1和Hoge2::$var1。
PHP代码:-------------------------------------------------------------------------------- $hoge = new Hoge2;
echo 'var1: ' . $hoge->var1 . " \n"; // var1: // echo 'var2: ' . $hoge->var2 . " \n"; // Error echo 'var3: ' . $hoge->var3 . " \n"; // var3: 3
echo 'var1: ' . $hoge->d_var1() . " \n"; // var1: echo 'var2: ' . $hoge->d_var2() . " \n"; // var2: B echo 'var3: ' . $hoge->d_var3() . " \n"; // var3: 3
--------------------------------------------------------------------------------
$hoge->var1是与Hoge1::var1没有关系的变量,因此不会有任何显示,因为var2有protected访问限制,所以如果不通过method就直接参照$var2,就会出现致命错误。
■引入访问方法的限制
与上述相同,此处也分为private, protected, public三种。
· Public: 能够从任何地方调用 · Private: 只能够从这个类的method内调用 · Protected: 只能够从这个类以及subclass的method中调用
此处的意思同Java和C++相同,请不要搞混。
■抽象(abstract)的类和抽象的方法
支持与Java相同的抽象类和抽象方法。抽象方法只提供了方法名的调用方式,而没有提供实体。另外,持有抽象方法的类,必须抽象宣言类本身。如果想要直接作成抽象类的对象,那么就会出现如下的致命错误。
Fatal error: Cannot instantiate abstract class ClassName
产生错误的实际的例子如下所示:
PHP代码:--------------------------------------------------------------------------------
abstract class MyAbstract { abstract public function test(); public function test2() { echo "MyAbstract::test2() called. \n"; } }
class MyImplement extends MyAbstract { public function test() { echo "MyImplement::test() called. \n"; } }
$obj = new MyImplement; $obj->test();
?>
--------------------------------------------------------------------------------
■接口(interface)
支持与Java相同的接口(interface)。接口是适合所描述的外部调用形式而设计组合起来的。 接口的实体不能够记录。相反的,实现接口的类必须持有与这个接口的方法相对应的实体。另外,类能够实现多个接口,因此,有可能实现多重继承。
PHP代码:--------------------------------------------------------------------------------
interface Throwable { public function getMessage(); }
interface Serializable { public function toString(); }
class MyException implements Throwable, Serializable { public function getMessage() { return 'this is MyException message'; }
public function toString() { return 'MyException: this is MyException message'; } }
$e = new MyException; echo $e->getMessage(); echo $e->toString(); ?>
--------------------------------------------------------------------------------
■final声明
同Java一样,PHP5支持final声明。如果对于一个方法追加final声明,这个方法将肯定在子类不能重载(Override)。如果方法被final声明了,但是还在子类中重载,就会出现如下错误:
PHP代码:-------------------------------------------------------------------------------- Fatal error: Cannot override final method fuga::foo()
--------------------------------------------------------------------------------
产生错误的例子:
PHP代码:--------------------------------------------------------------------------------
class Fuga { final function foo() { echo "this is final function\n"; } }
class Hoge extends Fuga { function foo() { echo "this is not final function\n"; } } ?>
--------------------------------------------------------------------------------
(三) PHP5的新特性(续)
PHP5的发布计划
在前面的文章中我们提到,“根据ZEND公司2003年4月1日发布的讯息的话,现, 在的应该就是Beta Release了”,但是开发者内部讨论的结果是,Beta为时尚早,而且有可能不是Beta Release.
在这个文件中,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()的方法名。 --------------------------------------------------------------------------------- 析构方法 定义析构方法是十分有用的。析构方法可以记录调试信息,关闭数据库连接,还有做其它的扫尾工作。PHP4中并无此机制,尽管PHP已支持注册在请求结束时需要运行的函数。 PHP 5 introduces a destructor concept similar to that of other object-oriented languages, such as Java: When the last reference to an object is destroyed the object's destructor, which is a class method name %__destruct()% that recieves no parameters, is called before the object is freed from memory. PHP5引入了与其它面向对象语言如Java语言相似的析构方法:当最后一个该对象的引用被清除时,系统将会在该对象从内存中释放前调用名为__destruct()的析构方法。 例: class MyDestructableClass { function __construct() { print "In constructor\n"; $this->name = "MyDestructableClass"; }
function __destruct() { print "Destroying " . $this->name . "\n"; } }
$obj = new MyDestructableClass(); ?> 和构造方法相似,引擎将不调用父类的析构方法,为调用该方法,你需要在子类的析构方法中通过parent::__destruct()语句进行调用。 常量 PHP 5 引入了类常量定义: class Foo { const constant = "constant"; }
echo "Foo::constant = " . Foo::constant . "\n"; ?>
PHP5允许常量中有表达式,但在编译时常量中的表达式将被计算.,因此常量不能在运行中改变它的值。 class Bar { const a = 1<<0; const b = 1<<1; const c = a | b; } ?> 以前代码中的用户自定义类或方法中虽未定义"const”关键字,但无需编辑即可运行。 例外 PHP 4 had no exception handling. PHP 5 introduces a exception model similar to that of other programming languages. PHP4中无例外处理,PHP5引用了与其它语言相似的例外处理模型。 例: class MyExceptionFoo extends Exception { function __construct($exception) { parent::__construct($exception); } }
try { throw new MyExceptionFoo("Hello"); } catch (MyException $exception) { print $exception->getMessage(); } ?> 以前代码中的用户自定义类或方法中虽未定义'catch', 'throw' 和 'try'关键字,但无需编辑即可运行。 函数返回对象值 In PHP 4 it wasn't possible to dereference objects returned by functions and make further method calls on those objects. With the advent of Zend Engine 2, the following is now possible: 在PHP4中,函数不可能返回对象的值并对返回的对象进行方法调用,通过ZEND引擎2中,这一切变得可能: class Circle { function draw() { print "Circle\n"; } }
class Square { function draw() { print "Square\n"; } }
function ShapeFactoryMethod($shape) { switch ($shape) { case "Circle": return new Circle(); case "Square": return new Square(); } }
ShapeFactoryMethod("Circle")->draw(); ShapeFactoryMethod("Square")->draw(); ?> 静态类中的静态成员变量现在可初始化 Example class foo { static $my_static = 5; }
print foo::$my_static; ?> 静态方法 PHP5引入了关键字'static'来定义一个静态方法,这样可以从对象外进行调用。 例: class Foo { public static function aStaticMethod() { // ... } }
Foo::aStaticMethod(); ?> 虚拟变量$this在静态方法中无效。 instanceof PHP5引入了关键字instanceof来确定一个对象是否是某一个对象的实例,或某一个对象的派生,或使用了某一个接口。 例: class baseClass { }
$a = new baseClass;
if ($a instanceof basicClass) { echo "Hello World"; } ?> 静态函数变量 所有的静态变量现在在编译时进行处理,这允许开发者通过引用来指定静态变量。这个变化提高了效率但意味着不可能对静态变量进行间接引用。 函数中通过按地址传送方式的参数允许定义默认值 例: function my_function(&$var = null) { if ($var === null) { die("$var needs to have a value"); } } ?> __autoload() 在初始化一个未定义的类时,引擎将自动调用__autoload()拦截器函数。该类名将作为__autoload()拦截器函数唯一参数传递给它。 例: function __autoload($className) { include_once $className . ".php"; }
$object = new ClassName; ?> 方法和属性调用的重载 通用 __call(), __get() 和 __set()方法可以进行方法和属性调用的重载。
例: __get() 和 __set() class Setter { public $n; public $x = array("a" => 1, "b" => 2, "c" => 3);
function __get($nm) { print "Getting [$nm]\n";
if (isset($this->x[$nm])) { $r = $this->x[$nm]; print "Returning: $r\n"; return $r; } else { print "Nothing!\n"; } }
function __set($nm, $val) { print "Setting [$nm] to $val\n";
if (isset($this->x[$nm])) { $this->x[$nm] = $val; print "OK!\n"; } else { print "Not OK!\n"; } } }
$foo = new Setter(); $foo->n = 1; $foo->a = 100; $foo->a++; $foo->z++; var_dump($foo); ?> 例: __call() class Caller { var $x = array(1, 2, 3);
function __call($m, $a) { print "Method $m called:\n"; var_dump($a); return $this->x; } }
$foo = new Caller(); $a = $foo->test(1, "2", 3.4, true); var_dump($a); ?>
SQLite in PHP
关键词: SQLite, MySQL, PHP4, PHP5, Pear, 触发器, 视图, SQL
(1)SQLite介绍
SQLite第一个Alpha版本诞生于2000年5月. 至今已经有4个年头了. 而在今年的5月SQLite也迎来了一个新的里程: SQLite 3.
下面是你访问SQLite官方网站: www.sqlite.org 时第一眼看到关于SQLite的特性.
1. ACID事务
2. 零配置 – 无需安装和管理配置
3. 储存在单一磁盘文件中的一个完整的数据库
4. 数据库文件可以在不同字节顺序的机器间自由的共享
5. 支持数据库大小至2TB
6. 足够小, 大致3万行C代码, 250K
7. 比一些流行的数据库在大部分普通数据库操作要快
8. 简单, 轻松的API
9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
10. 良好注释的源代码, 并且有着90%以上的测试覆盖率
11. 独立: 没有额外依赖
12. Source完全的Open, 你可以用于任何用途, 包括出售它
从代码架构图你可以轻松的看出, 是的, SQLite非常简单. 对, SQLite的设计思想就是简单:
1. 简单的管理
2. 简单的操作
3. 简单的在程序中使用它
4. 简单的维护和客制化
因为简单所以它快速, 但虽然简单, 却仍非常可靠. 适合SQLite的应用场所有, 网站,嵌入式设备和应用, 应用程序文件格式, 代替特别的文件, 内部或临时数据库, 命令行数据集分析工具, 在演示或测试中代替企业级数据库, 数据库教学, 试验SQL语言扩展等. 但并不是所有都合适, 比如在使用Server/Client结构的时候,高负荷的网站,高并发等情况下并不建议使用SQLite.
本文重点在于介绍SQLite在PHP中的应用, PHP作为Web应用中一个重要力量一直在不断的前进和发展. 在马上就要Release的PHP的第五个版本中, 不再将MySQL作为默认支持, 而转为将SQLite的扩展作为默认支持. 从某种程度上说MySQL的广泛应用有PHP的很大功劳. 虽然说PHP改变默认支持有MySQL的授权改变的原因, 但选择SQLite也是有原因的, 理由就在于上面所提到的那些特性. 其实MySQL从来就不是完全免费的, 你无法用于商业用途. 而SQLite是完全的open的.
(2) SQLite SQL
SQLite的SQL从很大程度上实现了ANSI SQL92标准. 特别的SQLite支持视图, 触发器, 事务, 支持嵌套SQL. 这些都会在下面应用的过程中讲到, 故这边先暂时放下, 而主要说说SQLite所不支持的一些SQL.
1. 不支持Exists, 虽然支持in(in是Exists的一种情况)
2. 不支持多数据库, 如: create table db1.table1 as select * from db2.table1;
3. 不支持存储过程
4. 不支持Alter View/Trigger/Table
5. 不支持Truncate, 在SQLite中Delete不带Where字句时和Truncate的效果是一样的.
6. 不支持Floor和Ceiling函数, 还有其他蛮多的函数
7. 没有Auto Increment(自增)字段, 但是SQLite其实是支持Auto Increment的, 即在将该字段设置为” INTEGER PRIMARY KEY”的时候.
8. 不支持If Exists
……
详细的SQL支持可以访问: http://www.sqlite.org/lang.htm
(3) SQLite的数据类型
首先你会接触到一个让你惊讶的名词: Typelessness(无类型). 对! SQLite是无类型的. 这意味着你可以保存任何类型的数据到你所想要保存的任何表的任何列中, 无论这列声明的数据类型是什么(只有在一种情况下不是, 稍后解释). 对于SQLite来说对字段不指定类型是完全有效的. 如:
|
Create Table ex1(a, b, c); |
诚然SQLite允许忽略数据类型, 但是仍然建议在你的Create Table语句中指定数据类型. 因为数据类型对于你和其他的程序员交流, 或者你准备换掉你的数据库引擎. SQLite支持常见的数据类型, 如:
|
CREATE TABLE ex2(
a VARCHAR(10),
b NVARCHAR(15),
c TEXT,
d INTEGER,
e FLOAT,
f BOOLEAN,
g CLOB,
h BLOB,
i TIMESTAMP,
j NUMERIC(10,5)
k VARYING CHARACTER (24),
l NATIONAL VARYING CHARACTER(16)
); |
前面提到在某种情况下, SQLite的字段并不是无类型的. 即在字段类型为”Integer Primary Key”时.
(4) SQLite的Wrapper
由于SQLite有别于其他数据库引擎的TCP/IP或RPC访问方式, 完全地是本地的操作, 从某种角度来说你可以说SQLite和MS的Access很相似, 但是更小更强大. 所谓Wrapper即使对SQLite提供的接口进行封装, 使其他语言可以访问, 使用SQLite.
SQLite本身是提供C和Tcl的接口的. 所以可以非常轻易的和PHP相结合. 除了PHP的Wrapper以外, 还有许多世界各地的程序员提供了各种语言的SQLite的接口封装, 如Python, C++, Java, .Net…… 所流行的语言基本都有.
(5) PHP的环境下使用SQLite
1. PHP下的安装
在PHP5中, SQLite已作为默认支持的模块.在PHP4中你需要进行安装. 首先去http://pecl.php.net/package/SQLite 去下载到SQLite的扩展, 注意Windows下的版本需要去http://snaps.php.net/win32/PECL_STABLE/php_sqlite.dll 下载, 当然你也可以下载代码自己编译.事实上在linux下只需要使用命令: ‘pear install sqlite’就可以完成安装,而在Win下需要修改php.ini, 同样的使PHP4支持SQLite.
此时你已经无需再安装任何东西了, 而你也已经完全支持SQLite了, 一个简单, 快速, 可靠的数据库.
如果你需要一个管理软件, 那么你可以尝试使用SQLiteManager (www.sqlitemanager.org), 一个与PHPMyAdmin类似的针对SQLite的数据库管理系统.
2. 第一个使用SQLite的PHP程序.
我们创建一个叫binzy的数据库, 并创建一个叫Binzy的Table, 有2个字段, 分别是ID, Title. 而其中ID为INTEGER PRIMARY KEY, 即自增三主键. 并在其中插入了2条数据”Binzy”, “Jasmin”.
打开并显示数据:
|
if ($db = sqlite_open('../binzy.db', 0666, $sqliteerror))
{ // 打开
SQLite$result = sqlite_query($db,'select * from Binzy'); // 查询while($row = sqlite_fetch_array($result)) // 获得结果
{
print 'ID=>'.$row['MyID'].', Name=>'.$row['Name'].' ';
}
} else {die ($sqliteerror);} |
接下来Insert一条记录, 其中我们会使用到SQLite的事务.
|
if ($db = sqlite_open('../binzy.db', 0666, $sqliteerror)) {
sqlite_query($db,'BEGIN TRANSACTION'); // 开始事务
if (@sqlite_query($db,'insert into Binzy (Name) values (\'Binzy&Jasmin\')'))
{
print 'Execute Successfully';
sqlite_query($db,'COMMIT TRANSACTION'); // 提交事务
}
else
{
print sqlite_error_string(sqlite_last_error($db));
sqlite_query($db,'ROLLBACK TRANSACTION'); // 回滚事务
}
} else {
die ($sqliteerror);
} |
是的, 如果你已经熟悉使用PHP对MySQL之类的数据库进行操作, 那么SQLite几乎是一样的, 而且更为简洁.
3. 使用Pear::DB (PHP4中)
上面的例子中我们是使用PHP的函数直接对SQLite进行访问, 这样的访问方式是不推荐使用的. 更好的方式是使用某种数据访问抽象层, 如Pear的DB. 下面是2中查询例子的重写. 使用某个数据访问抽象层会更方便更安全, 并且可以在需要进行数据库迁移的时候尽可能减小成本.
|
require_once('DB.php');
$dbh = DB::connect('sqlite://@localhost/../binzy.db?mode=0666'); // 打开
$dbh->setFetchMode(DB_FETCHMODE_ASSOC);
if (!DB::isError($dbh))
{
$result = $dbh->query('select * from Binzy'); // 查询
if (!DB::isError($result))
{
while($row = $result->fetchRow()) // 读取
{ print 'ID=>'.$row['MyID'].', Name=>'.$row['Name'].' ';
}
$dbh->disconnect();
}
else
{
print($dbh->message);
$dbh->disconnect();
}
}
else
{
print($dbh->message);
$dbh->disconnect();
} |
4. 使用Creole (PHP5中)
Creole是由phpdb.org开发的面向PHP5的数据访问抽象层. 关于Creole可参考本期中的《Creole :新兴数据抽象层》.
Pear::DB并没有针对PHP5进行改变, 只是因为PHP5对PHP4良好的兼容性, 使得Pear::DB在PHP5下仍能很好的工作. 所以在你使用PHP5的时候推荐使用Creole.
|
require_once('creole/Creole.php');
$Connection = null;
try{
$Connection = Creole::getConnection('sqlite://@localhost/../binzy.db?mode=0644'); // 获得Connection
$rs = $Connection->executeQuery('select * from Binzy'); // Get ResultSet while($rs->next())
{
print 'ID=>'.$rs->getInt('myid').', Name=>'.$rs->getString('name').' ';
}
$Connection->close();
}
catch(SQLException $exception) // Catch Exception
{
$Connection->close();
print $exception->getMessage();
} |
(6) 总结
随着PHP5的即将到来, 给我们带来了许多新的语言特性, 使PHP更加适合于构建强大健壮的各类系统. 而随着PHP5一起走进PHP开发人员视线的SQLite则给我们带来了有别于MySQL的惊喜. 是的, 他简单却又强大, 稳定. 而在刚刚过去的六月底新版本的SQLite3已经Release了第一个测试版本, 不仅仅带来了新的文件结构, 也带来了许多新的特性. |