PHP 编程高级教程(一)

原文:Pro PHP Programming

协议:CC BY-NC-SA 4.0

零、PHP 简介

欢迎阅读另一本关于 PHP 伟大编程语言的书。这本书是独一无二的,因为它侧重于更高端的材料和更先进的,前沿的话题。在快节奏的互联网世界中,我们尽可能地让它保持现代。我们把读者从这种伟大的编程语言的中级水平带到更高级的水平。

PHP 的起源

PHP 最初是由拉斯马斯·勒德尔夫先生领导和设计的一个项目。1995 年 6 月,他发布了个人主页工具 1.0 版本(其最初的产品名称)。它是一个小的功能集合,帮助在当时蓬勃发展的互联网上自动创建和维护简单的主页。从那以后,PHP 突飞猛进地发展到今天的版本 5.3.4(在撰写本文时)。PHP 是从一开始就开源的第一批 web 开发编程语言之一。Lerdorf 很有远见,看到了一种工具和语言的需求和潜力,这种工具和语言可以随着互联网社区的发展而发展,并扩展到更远的地方。

什么是 PHP?

那么,PHP 到底是什么?它现在的版本和《̴feel》看起来像什么?简单来说,PHP 仅仅是一个 HTML 标记生成器。如果你看一个 PHP 生成的网页的源代码,你只会看到 HTML 标签;也许还有一些 JavaScript,但是没有原始的 PHP 代码。当然,这是一种过于简单化的观点,这种语言已经占据了 web 开发中使用的语言的 35%到 59 %(取决于来源)。无论你选择哪个数字,PHP 都是当今市场上最流行的 web 开发语言。

当我使用“在市场上”这个术语时,你也必须意识到 PHP 是免费的。是的,免费!它是一个开源产品,所以实际上并没有真正的市场。因此,就受欢迎程度和使用范围而言,它做得非常好,因为它不是由一个实体或个人领导和操纵的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 要了解更多关于开源的信息,一定要阅读 Eric S. Raymond 的《大教堂和集市》,比较开源产品(集市)和闭源产品(大教堂)。你可以在这里找到:[www.catb.org/~esr/writings/cathedral-bazaar/.](http://www.catb.org/~esr/writings/cathedral-bazaar)

实际上,Zend Corporation(zend.com)可能是 PHP 世界的领导者,因为它已经开发了许多额外的产品来支持和增强 PHP,而且它是 PHP 发展方向的关键角色,因为该公司的两位创始人 Zeev Suraski 和 Andi Gutmans 从该产品的第 3 版开始就接受了挑战。

PHP 在语言结构上也是非常开放和宽容的,因为它是松散类型的。这意味着变量不必像其他一些编程语言那样,在使用之前定义它们所保存的数据类型。相反,它会查询数据,并尝试根据变量当时保存的内容来确定其数据类型。这意味着,例如,在代码文件执行期间,名为$information 的变量可以有许多不同的值。在某些方面,这也可能是一个缺点,因为数据可能在代码运行期间发生变化,因此否定了一些代码段,这些代码段可能需要一个整数,但却接收到一个字符串。

PHP 也可以用面向对象编程(OOP)设计来编写。类、属性和方法;继承、多态和封装都是语言的一部分。这增加了代码的健壮性和可重用性,并且总体上更易于使用。当然,面向对象的编程方法在技术领域已经存在很长时间了,PHP 也已经采用并扩展了它的集成好几年了。

PHP 拥有的另一个有价值的特性是,它可以从命令提示符(Linux 或 Windows)运行,因此可以在预定的无人值守(CRON)脚本中使用。这种增加的灵活性非常好,因为您(程序员)在服务器环境中工作时,不必学习另一种语言来完成不同的任务。您可以使用与管理文件系统相同的语言来生成网页(如果您愿意的话)。

PHP 也有很多集成点;至少可以说,这是一种非常开放的语言。PHP 可以用于除了直接的 web 开发之外的许多事情。通过适当的连接库将它与数据库源结合起来,您就可以拥有非常动态的 web 表现,甚至是 web 应用。将它与一个附加的库(例如 tcpdf)结合起来,您就可以动态地生成 Adobe PDF 文档。这仅仅是两个例子,我们将在本书中涉及很多这些附加库,所以请继续关注!

本书的高级概述

那么,我们希望通过这本书为你这个阅读程序员完成什么呢?我们已经尽了最大努力让这篇文章成为当前的前沿价值,这样你就能了解并使用 PHP 的一些最新特性和集成。我们没有在语言的更简单的主题上花费时间,例如什么是变量或者如何编写 for / next 循环。

我们希望你成为一名更高级的 PHP 程序员,这些材料甚至可以帮助你准备好参加并通过 Zend 认证工程师考试。以下是每章内容的简要总结。

第一章:面向对象

这第一章的目的是让你为本书剩余部分将要出现的许多概念和代码示例做好准备。我们介绍了 OOP 的一些基本概念以及它是如何在 PHP 中实现的,然后马上进入一些更高级的问题。在深入后面的章节之前,请确保你真正理解了这一章。

第二章:例外和引用

在这里,我们继续一些 OOP 概念,并进入带有 try / catch 块的异常编码。这是处理 PHP 代码中潜在错误的一种更优雅的方式,一旦掌握了这种方式,它将是一种非常强大的方法。接下来是关于引用编码的讨论,以及它对于你可能使用的类和函数的意义。

第三章 : PHP on the Run(移动 PHP)

这个世界变得越来越依赖移动设备;我们看到更小更强大的设备一直在发布。苹果、RIM、HTC 和许多其他公司都试图占领这个利润丰厚的市场。但是需要有适用于这些设备的应用,在这一章中,我们将向您展示 PHP 发展和适应的一些方式,以适应这种移动性的转变。

第四章:社交媒体和 PHP

在类似的技术发展中,社交媒体使用的快速扩张也在很大程度上得益于 PHP。例如,《脸书》的大部分前瞻性内容都是用 PHP 编写的。许多其他网站,如 Flickr,雅虎的一部分!,甚至很多博客应用都严重依赖 PHP。在这一章中,我们来看一些与这些社交媒体网站整合的界面。

第五章:前沿 PHP

在撰写本文时的当前版本 5.3.4 中,PHP 在其实际语言中增加了许多新特性。这些特性中有许多是为期待已久的 6.0 版本设计的,但是因为一些特性比其他特性更早准备好,所以这个初始集合被发布为 5.3 版本。在这一章中,我们将会看到这些新特性中的一些“精华”,以及如何在你的网络项目中使用它们。

第六章:表单设计与管理

在这里,我们花一点时间来回顾一下在设计和管理数据输入表单时可以实现的特性和技术。控制输入其中的数据,对错误数据(例如无效的日期格式)做出响应,以及如何优雅地将数据输入 web 系统。

第七章和第八章:数据库交互

当然,目前 web 开发的一个主要方面是存储和显示来自数据源的数据的能力。在这两章中,我们看了许多不同的数据处理方式。从 NoSQL 的小型数据库到 MySQLi 这样的大型数据库引擎,以及我们通过使用 PDO 和 Sphinx 这样的工具收集到的技术。

第九章:神谕

谈到超大数据集,PHP 和 Oracle 有一种特殊的联系。在这一章中,我们将探讨这种关系特有的问题,以及如何充分利用他们的“结合”

第十章 : PHP 库

正如已经提到的,PHP 非常开放,可以与其他库一起工作。在第十章中,我们来看看这些库中更流行和先进的一些。能够动态生成 PDF 表单、使用 RSS 订阅源、生成专业电子邮件以及与 Google maps 集成只是本章将讨论的一些库集成。

第十一章:基本 PHP 安全

当然,如果我们没有涵盖 web 安全方面的最新技术,这就不是一部完整的书。第十一章涵盖了这个大话题。我们看看最安全的(目前)加密算法称为 SHA-1。其他主题包括保护输入到 web 系统的数据以及从 web 系统输出的数据。

第十二章:与 Zend Studio 的团队发展

这一章有点跑题,因为它不纯粹是一个 PHP 话题。在这里,我们来看看如何使用一个更流行的集成开发环境(ide)来进行 PHP 开发,Zend Studio for Eclipse。有了 Zend Studio,我们看看一个开发团队如何以敏捷的方式一起工作(你听说过极限编程吗?)我们将看看如何利用 SVN、Bugzilla 和 MyLyn 的协同工作,让团队的工作在许多方面更有成效。

第十三章:重构单元测试

这实际上是上一章所讲内容的延伸。这里有更多关于如何做才能使 PHP 开发在编程方面更加敏捷的报道。重构和单元测试是这里的重点,您将学习如何在日常编码项目中很好地利用它们。

第十四章 : XML 和 PHP

自从 XML 第一次成为流行语以来,它的使用已经变得越来越主流。在这一章中,我们来看看如何使用 SimpleXML 来消费来自外部的 XML。我们还讨论了从我们自己的系统中生成 XML 数据供他人使用的能力。

第十四章 : JSON / Ajax

再一次,我们从纯粹的 PHP 向前迈了一小步,看看 JSON 库,以及我们如何将它与 Ajax 一起使用,以使我们的 web 应用更具响应性。

第十五章:结论

在最后一章中,我们将会看到本书中没有的 PHP 附加资源。在这里,我们看了许多可用的网络资源和一些杂志和会议,它们可以加深你对这个伟大的语言和社区的知识和理解。

PHP 的未来

这是一个我觉得很难写的话题。由于 PHP 是一个真正的开源产品,所以很难预测社区在不远的将来会走向何方。然而,我对这个社区有绝对的信心;在我做 PHP 程序员的这些年里,我还没有真正看到这个集体犯过一次错误。我知道我们生活的移动方面将继续增长和扩展,PHP 已经采取措施完全接受这一事实。在不久的将来还会发生什么?在智能手机和数据互操作性方面,可能会与电话有更多的集成。可能会进一步扩展到语音识别技术和网络应用——谁知道呢?从我目前的经验来看,我确实知道 PHP 及其支持社区将继续把握技术世界的脉搏,他们不会让我们失望。

展望 PHP 的未来是一件令人欣慰的事情;这就像看着美丽的日出,知道接下来的一天只会越来越好。

一、面向对象

本章的目的是介绍面向对象的基本概念。“PHP 是面向对象的”这句话实际上意味着什么最简单的答案是 PHP 允许用户数据类型的定义和层次结构。这本书是关于 PHP 5.3 的,它为 PHP 对象设备引入了一些新元素。从版本 4 开始,PHP 经历了一个相当彻底的变化,其中也包括了基本的面向对象(OO)功能。例如,在 PHP 4 中,不可能定义方法和成员的可见性。在 PHP 5.3 中,增加了名称空间。

本章我们将介绍类、继承、对象创建和接口定义的概念。我们还将介绍一些不太基础的东西,比如迭代器。那么,我们开始吧。

类别

类只是用户定义的类型。在面向对象语言中,一个类作为创建该类的对象或实例(功能副本)的模板。一个类包含属于它的所有项目的共同特征的描述。一个类(或多个类)的目的是封装对象定义和行为,对最终用户隐藏其实际实现,并使最终用户能够以记录的和预期的方式使用类对象。封装还使程序变得更小,更易于管理,因为对象已经包含了处理它们所需的逻辑。还有一个叫做自动加载的特性,它有助于将脚本分成更小、更易管理的片段。

在我们看到一个 PHP 类的简单例子之前,让我们先介绍一些术语:

  • 类成员或属性:变量,类的数据部分
  • 类方法:在类中定义的函数

现在我们将为二维平面中的一个点定义一个类,用它的笛卡尔坐标定义(见清单 1-1 )。因为它纯粹是为教学目的而设计的,所以这门课有几个严重的缺点。我们建议您不要将它用作您自己的任何代码的代码基础。

***清单 1-1。*一架 2D 飞机

<?php class Point {     public $x;     public $y;
    function __construct($x,$y) {         $this->x=$x;         $this->y=$y;     }     function get_x() {         return($this->x);     }     function get_y() {         return($this->y);     }     function dist($p) {         return(sqrt( pow($this->x-$p->get_x(),2)+                      pow($this->y-$p->get_y(),2)));     } } // Class ends here $p1=new Point(2,3); $p2=new Point(3,4); echo $p1->dist($p2),"\n"; $p2->x=5; echo $p1->dist($p2),"\n"; ?>

这个类不是微不足道的;有相当多的东西需要分析和修复。首先,如我们之前所述,这个类描述了平面中的一个点,由它的笛卡尔坐标$x$y定义。有一个关键词public,我们稍后将返回。还有一个构造函数方法__construct,,当通过调用操作符new在内存中创建一个类Point的新对象(或实例)时,将调用该方法。换句话说,当执行第$p1=new Point(2,3)行时,方法__construct被自动引用和执行,并且类名后面的参数(在括号中)被传递给__construct方法以备可能使用。

方法__construct引用了变量$this。变量$this是引用类实例本身的 OO 方式。它总是指向当前的焦点对象。它相当于“我”这个变量的一个变体存在于几乎所有基于 OO 的语言中,尽管在一些语言中它被称为“self”。

类构造函数是初始化(实例化)给定类的对象的方法。在这种特殊情况下,它指定坐标。坐标(名为$x$y的变量)是这个类的成员。还定义了其他几种方法,两种get方法和一种叫做dist的方法,用于计算两点之间的距离。

接下来要观察的是关键词public。将成员标记为“公共”允许对标记为公共的数据成员进行完全访问。在我们的脚本中,有一行写着$p2->x=5;。我们其中一个点的 x 坐标被直接操纵。这种访问是不可能控制的,除了最简单的情况外,在所有情况下都是非常不鼓励的。好的实践是编写getset方法,以可控的方式读写类成员。换句话说,使用getset方法,可以控制数据成员的值。对于公共成员,get 和 set 函数是多余的,因为可以直接设置成员,如$p2->x=5所示。但是,对于公共成员,无法控制成员的值。Setget函数可以直接为每个成员编写,但 PHP 也提供了所谓的“神奇方法”,可以用来代替必须为每个成员编写两个函数。

使用关键字privateprotected可以更好地保护成员。这两个关键字的确切含义将在下一节解释。同样值得注意的是public是默认的可见性。如果没有指定成员或方法的可见性,则默认为public。笔迹

class C { $member;        function method() {...} …. }

完全等同于写:

 class C {        public $member;        pubic  function method() {…} …. }

与公共类成员相比,私有类成员或方法仅对同一类的方法可见。未连接到该类的方法不能访问任何私有成员,也不能调用任何其他私有方法。如果类成员$x$y的关键字“public”替换为关键字“private ”,并尝试访问,结果将是

PHP Fatal error:  Cannot access private property Point::$x in script2.1 on line 25

换句话说,我们在第 25 行的小技巧,即$p2->x=5,将不再有效。构造函数没有任何问题,函数get_x()get_y()也没有问题,因为它们是类成员。这是一件好事,因为不再可能直接操纵类对象,潜在地以一种该类不应该做的方式从根本上改变它们的行为。简而言之,这个班级更加独立,就像一条受控制的高速公路——只有有限的入口和出口坡道。

公有和私有成员现在都清楚了,但是什么是受保护的成员和方法呢?受保护的方法和成员可由它们所属类的方法访问,也可由从它们所属的基类继承的类的方法访问。我们将在下一节中对此进行更深入的研究。

继承和重载

如本章开头所述,可以用分层的方式来组织类。等级是通过继承建立的。为了演示继承,让我们开发另一个名为employee的类。一个公司的员工有一部分是经理,这将是从更一般的employee类继承而来的一个类。继承也被称为专门化。所以,事不宜迟,让我们看看这个类(参见清单 1-2 )。

***清单 1-2。*employee的例子

*<?php* class employee {     protected $ename;     protected $sal;    function __construct($ename, $sal = 100) {         $this->ename = $ename;         $this->sal = $sal;     }
`    function give_raise(KaTeX parse error: Expected '}', got 'EOF' at end of input: …unt) {         this->sal+= $amount;
        printf(“Employee %s got raise of %d dollars\n”, $this->ename, $amount);
        printf(“New salary is %d dollars\n”, $this->sal);
    }
    function __destruct() {
        printf(“Good bye, cruel world: EMPLOYEE:%s\n”, $this->ename);
    }
}

class manager extends employee {
    protected KaTeX parse error: Expected group after '_' at position 20: …;     function _̲_construct(ename, $sal, KaTeX parse error: Expected group after '_' at position 25: …       parent::_̲_construct(ename, s a l ) ;          sal);          sal);        this->dept = KaTeX parse error: Expected 'EOF', got '}' at position 11: dept;     }̲     function g…amount) {
        parent::give_raise($amount);
        print “This employee is a manager\n”;
    }
    function __destruct() {
        printf(“Good bye, cruel world: MANAGER:%s\n”, $this->ename);
        parent::__destruct();
    }
} // Class definition ends here.

$mgr = new manager(“Smith”, 300, 20);
$mgr->give_raise(50);
$emp = new employee(“Johnson”, 100);
$emp->give_raise(50);
?>`

这个类只是一个人为的例子;它不是用来作为一个模板。值得注意的是,__construct方法在两个类中都是公共的。如果它不是公共的,就不可能创建任何一个类的新对象。执行时,该脚本将产生以下结果:

Employee Smith got raise of 50 dollars New salary is 350 dollars This employee is a manager Employee Johnson got raise of 50 dollars New salary is 150 dollars Good bye, cruel world: EMPLOYEE:Johnson Good bye, cruel world: MANAGER:Smith Good bye, cruel world: EMPLOYEE:Smith

这个小例子非常适合解释继承的概念。每个经理都是雇员。注意短语“is a”表示继承关系的特征。在这种情况下,类employee是类 employee 的父类。与日常生活相反,PHP 中的类只能有一个父类;不支持多重继承。

此外,父函数可以使用类manager中显示的parent::构造来寻址。当创建子类的对象时,不会自动调用父类的构造函数;在子类的构造函数中调用它是程序员的责任。

这同样适用于析构函数方法。析构函数方法与构造函数方法正好相反。构造函数在内存中建立对象时调用,而析构函数在不再需要该对象时调用,或者在该对象上显式调用“unset”函数时调用。显式调用 unset 函数并不是常见的做法;它通常用于节省内存。这也意味着当脚本执行完成时,会自动为所有对象调用析构函数。析构函数方法通常用于清理资源,例如关闭打开的文件或断开与数据库的连接。最后,注意我们的manager类的析构函数方法拥有对成员ename的完全访问权,尽管它实际上是employee类的成员。这正是受保护成员的目的。如果ename是 employee 类的私有成员,我们的小例子就不会成功。

方法get_raise在两个类中都存在。PHP 知道为哪个对象调用哪个方法;这是面向对象基本原则的一个方面:封装。对象$x属于manager类,而give_raise方法在生成其正常输出后生成了输出,“这个雇员是经理”。我们可以这样重新表述:类manager中的give_raise函数重载或取代了employee类中的give_raise方法。请注意,术语“重载”在 PHP 中的含义与 C++或 Python 中的含义不同,在 c++或 Python 中,它表示名称相同但参数类型不同的函数(不是类方法)。回到 PHP:如果方法标记为final,则不能重载。如果 employee 类中的方法give_raise是这样声明的

final function give_raise($amount) { …. }

在类管理器中重载它是不可能的。我们建议您在这个小脚本中尝试基本的 OO 概念,并通过将各种成员和方法标记为私有、受保护或公共来看看结果。

最后,当谈到继承时,还需要提到抽象类。抽象类不能被实例化;不能创建属于它们的对象。它们主要用作模板,以强制所有从它们继承的类具有期望的结构。如果用关键字“abstract”标记,则该类是抽象的,如下所示:

abstract class A { …. }

无法创建此类的对象;PHP 将抛出一个运行时错误并停止脚本执行。也可以声明抽象类的抽象方法。这是这样做的

abstract class A { abstract protected method(...); }

这样做是为了强制扩展抽象类的类实现指定的方法。

抽象类通常被用作扩展它们的类的模板。抽象类的一个很好的例子可以在标准 PHP 库(SPL)中找到。排序堆的类(SplMinHeapSplMaxHeap)扩展了抽象类SplHeap,并以不同的方式实现了compare方法。SplMinHeap会将元素从最小到最大排序,而SplMaxHeap会将元素从最小到最大排序。这两个类的共同特征都包含在抽象类SplHeap中,这里有文档说明:

http://ca2.php.net/manual/en/class.splheap.php

与其发明抽象类的人为例子,不如让我们看看它们在 SPL 是如何使用的。下面是一个如何使用SplMinHeap类的简单例子

<?php $heap = new SplMinHeap(); $heap->insert('Peter'); $heap->insert('Adam'); $heap->insert('Mladen'); foreach ($heap as $h) {     print "$h\n"; } ?>

执行时,输出将是:

Adam Mladen Peter

名字是按字母顺序排序的——与它们被插入堆的方式不太一样。稍后我们将看到如何在循环中使用一个SplMaxHeap类的对象,就像它是一个数组一样。

现在,让我们把注意力转向更实用的 OO 编程技术。例如,您可能想知道我们如何让类对 PHP 脚本可用。类通常被编写成可以一遍又一遍地重用。显而易见的答案是,我们创建单独的文件,然后用requireinclude指令包含这些文件,但随着文件的增加,这很快就会变得笨拙或麻烦。事实证明,PHP 有一个工具可以帮助解决这个问题——即名为__autoload的函数。该函数以类名作为参数,每当 PHP 在当前执行的脚本中找不到类定义时,就会调用该函数。从本质上来说,__autoload函数是一个“没有找到类”异常错误的陷阱处理程序。我们稍后将回到异常。我们在清单 1-2 中的例子现在可以在两个文件中重写(参见清单 1-3 )。

***清单 1-3。*清单 1-2 在两个文件中重写

`File script1.3.php:

<?php function __autoload ($class) {     require_once("ACME$class.php"); } $x = new manager("Smith", 300, 20); $x->give_raise(50); $y = new employee("Johnson", 100); $y->give_raise(50); ?>`

【acmemanager . php 档案:

<?php class employee {     protected $ename;     protected $sal;     // Note that constructor is always public. If it isn't, new objects cannot `    // be created.
    function __construct($ename, KaTeX parse error: Expected '}', got 'EOF' at end of input: …100) {         this->ename = e n a m e ;          ename;          ename;        this->sal = KaTeX parse error: Expected 'EOF', got '}' at position 10: sal;     }̲     function g…amount) {
        $this->sal+= $amount;
        printf(“Employee %s got raise of %d dollars\n”, $this->ename, $amount);
        printf(“New salary is %d dollars\n”, $this->sal);
    }
    function __destruct() {
        printf(“Good bye, cruel world: EMPLOYEE:%s\n”, $this->ename);
    }
} // End of class “employee”

class manager extends employee {
    protected KaTeX parse error: Expected group after '_' at position 20: …;     function _̲_construct(ename, $sal, KaTeX parse error: Expected group after '_' at position 25: …       parent::_̲_construct(ename, s a l ) ;          sal);          sal);        this->dept = KaTeX parse error: Expected 'EOF', got '}' at position 11: dept;     }̲     function g…amount) {
        parent::give_raise($amount);
        print “This employee is a manager\n”;
    }
    function __destruct() {
        printf(“Good bye, cruel world: MANAGER:%s\n”, $this->ename);
        parent::__destruct();
    }
} // End of class “manager”`

这段代码完全等同于清单 1-2 中的原始 script1.2.php,除了它更容易阅读,因为最重要的部分包含在清单 1-3 中的文件 script1.3.php 中。另一个文件,ACMEmanager.php,只包含类声明。如果我们对类声明的内部并不真正感兴趣,我们不需要阅读它们;我们只需要知道声明类的对象是如何工作的。另外,请注意,该文件是以正在被实例化的第一个类命名的。当加载该文件时,类employee也将被定义,因为两个类的定义在同一个文件中。

第二件要注意的事情是类名以“ACME”为前缀。这是为了提醒读者创建专门的、基于项目的类库的可能性。这里实现的函数__autoload使用了require_once而不是include指令。原因是 PHP 的行为,如果require指令请求的文件不可用,PHP 将终止脚本。如果 include 指令简单包含的文件不可用,执行将继续进行。在没有可用的类定义的情况下,执行依赖于类定义的脚本是没有意义的。

此外,类定义文件不应该定义结尾的?>。这是因为在组装页面之前,它们通常可以被自动加载或包含在“header”文件中,并且在开始时,?>和 EOF 之间的任何额外空白都将被注入到页面的 html 输出流中。PHP 很高兴没有尾随的?>,省略它是一个最佳实践。这可能是使用header()函数将 HTTP 头发送回浏览器时导致“输出已经开始”错误的最大原因,对于不知道这是一个死亡陷阱的人来说。

杂用“神奇”的方法

大多数被统称为“神奇方法”的方法处理的是缺少的成员和没有在类本身中定义的方法。原因是广泛采用的在关联数组中定义成员的做法,而不是将它们定义为单独的类变量。这种定义数据成员的方式易于遵循、扩展和修改,这有助于定义一组类成员的流行。没有“神奇的功能”,就不可能以透明和可理解的方式访问这些成员。

值得一提的第一对特殊方法由__get__set方法组成。

* get*和 _ _set 方法

当一个值被赋给一个不存在的成员时,方法__set被调用。当试图访问一个不存在的成员时,调用方法__get。清单 1-4 就是一个例子。

清单 1-4。_set_get方法的例子

`# Demonstration of __get and __set functions.

Non-existing property “speed_limit” is being set and read.

<?php class test1 {     protected $members = array();     public function __get($arg) {         if (array_key_exists($arg, $this->members)) {             return ($this->members[$arg]);         } else { return ("No such luck!\n"); }     }     public function __set($key, $val) {         $this->members[$key] = $val;     }     public function __isset($arg) {         return (isset($this->members[$arg]));     } } $x = new test1(); print $x->speed_limit; $x->speed_limit = "65 MPH\n"; if (isset($x->speed_limit)) {     printf("Speed limit is set to %s\n", $x->speed_limit); } $x->speed_limit = NULL; if (empty($x->speed_limit)) {     print "The method __isset() was called.\n"; } else {     print "The __isset() method wasn't called.\n"; } ?>`

执行时,该脚本将产生以下结果:

No such luck! Speed limit is set to 65 MPH The method __isset() was called.

类成员speed_limit没有被定义,但是引用它并没有产生错误,因为当我们引用不存在的成员时执行了__get方法,当对不存在的成员赋值时执行了__set方法。一种常见的做法是将所有成员(或属性)定义在一个关联数组中,并像将它们定义为单独的类属性一样引用它们。这种做法使得类更容易扩展。

_ _ 是一套方法

除了__get__set方法,清单 1-4 还演示了__isset函数的使用,该函数用于检查一个不存在的属性(通常定义为数组元素)是否被设置(有值)。当然,还有__unset功能。当在一个不存在的属性上调用unset时,就会调用它。使用empty()函数检查变量是否为空时,也会调用__isset方法。empty()函数测试参数是否被设置,以及它的长度是否大于 0。如果参数未设置或其长度等于零,则返回 true 否则,它返回 false。特别是,当参数设置为空字符串时,empty()函数也将返回 true。

_ _ 叫

最后,当谈到不存在的成员时,调用不存在的方法时会调用__call函数。根据我的经验,这种方法相对来说很少使用,但是清单 1-5 只是一个小例子,只是为了完整。

***清单 1-5。*当调用一个不存在的方法时,调用__call函数。

`<?php

Demonstrating the use of “__call” method

class test2 {
    function __call($name, KaTeX parse error: Expected '}', got 'EOF' at end of input: …   print "name:name\n";
        foreach ($argv as KaTeX parse error: Undefined control sequence: \t at position 25: …        print "\̲t̲a\n";
        }
    }
}
$x = new test2();
$x->non_existing_method(1, 2, 3);
?>`

执行时,该脚本将产生以下结果:

name:non_existing_method         1         2         3

当然,方法non_existing_method没有被定义,但是调用成功了。

*_ _ toString()*法

这里将要提到的最后一个“神奇”方法,也是唯一一个与不存在的成员或方法无关的方法是__toString()。当一个对象被转换为字符串时使用它——要么显式地,通过使用 explicit cast (string)对其进行转换,要么隐式地,通过将它作为一个参数传递给一个需要字符串参数的函数,如“print”清单 1-6 就是一个例子。

***Listing 1-6.** An Example of the _toString()` Method*

<?php # Demonstrating the use of "__toString" method class test2 {     protected $memb;     function __construct($memb) {         $this->memb = $memb;     }     function __toString() {         return ("test2 member.\n");     } } $x = new test2(1); print $x; ?>`

执行时,该脚本将产生以下输出:

test2 member.

打印出__toString函数的返回值。当在字符串上下文中使用底层对象时,调用此函数。当需要打印由陌生成员组成的复杂对象时,例如网络或数据库连接或其他二进制对象,这个函数非常有用。

复制、克隆和比较对象

在本章的开始,我讨论了什么是类以及如何创建和处理复杂的对象。现在是时候讨论内部对象处理的一些方面了。当使用诸如$x=new class(....)的语句创建对象时,变量$x是对该对象的引用。当我们执行类似$x=$y的东西时会发生什么?很简单:句柄$x指向的原始对象被扔掉,调用它的析构函数,让$x指向对象$y。清单 1-7 是一个演示该行为的小脚本。

***清单 1-7。*执行$x=$y

`<?php

Demonstrating shallow copy.

class test3 {
    protected KaTeX parse error: Expected group after '_' at position 23: … `    function _̲_construct(memb) {
        $this->memb = $memb;
    }
    function __destruct() {
        printf(“Destroying object %s…\n”, $this->memb);
    }
}
$x = new test3(“object 1”);
$y = new test3(“object 2”);
print “Assignment taking place:\n”;
$x = $y;
print “End of the script\n”;
?>`

执行该脚本时,它会产生以下输出:

Assignment taking place: Destroying object object 1... End of the script Destroying object object 2...

当执行$x=$y时,对象 1 在赋值期间被销毁。为什么物体 2 被摧毁了?答案很简单:每当对象超出范围时,就调用析构函数。当脚本完成时,所有幸存的对象都将超出作用域,每个对象都将被调用析构函数。这也是在两个print命令之间包含赋值的原因。另外,请注意析构函数只执行一次,尽管有两个对底层对象的引用,$x$y。每个对象调用一次析构函数,而不是每个引用调用一次。这种复制对象的方式被称为“浅层”复制,因为没有创建对象的真正副本;只有引用被更改。

除了之前看到的“浅层”复制,还有“深层”复制,产生了新的对象。这种“深度”复制是通过使用“克隆”操作符来完成的,如清单 1-8 所示。

***清单 1-8。*使用克隆操作符进行深度复制

`<?php

Demonstrating deep copy.

class test3a {
    protected $memb;
    protected KaTeX parse error: Expected group after '_' at position 22: …;     function _̲_construct(memb, KaTeX parse error: Expected '}', got 'EOF' at end of input: …= 0) {         this->memb = m e m b ;          memb;          memb;        this->copies = $copies;
    }     function __destruct() {
        printf(“Destroying object %s…\n”, KaTeX parse error: Expected 'EOF', got '}' at position 18: …is->memb);     }̲     function _…this->memb.= “:CLONE”;
        $this->copies++;
    }

function get_copies() {
        printf(“Object %s has %d copies.\n”, $this->memb, $this->copies);
    }
}
$x = new test3a(“object 1”);
$x->get_copies();
$y = new test3a(“object 2”);
$x = clone $y;
$x->get_copies();
$y->get_copies();
print “End of the script, executing destructor(s).\n”;
?>`

深度复制是在显示$x = clone $y的那一行完成的。当这一行被执行时,对象$y的一个新副本被创建,并且函数__clone被调用以帮助按照脚本需要的方式安排新副本。该脚本的输出如下所示:

Object object 1 has 0 copies. Destroying object object 1... Object object 2:CLONE has 1 copies. Object object 2 has 0 copies. End of the script, executing destructor(s). Destroying object object 2... Destroying object object 2:CLONE...

驻留在$x中的新创建的副本具有成员值“对象对象 2:克隆”,并且副本数量被设置为 1,这是__clone方法的动作的结果。另外,请注意,构造函数被执行了两次,一次是针对原始的,一次是针对克隆的。克隆不像引用赋值那样经常使用,但是在需要的时候有这种可能性是很好的。

对象是如何比较的?根据比较标准,有几种情况需要考虑。什么时候我们称两个对象变量$x$y为“相等”有以下三种逻辑上同样有效的可能性:

  • 同一类的对象的所有成员都是平等的。
  • 对象是对同一类的同一对象的引用。
  • 使用一些其他定制的标准。

标准的相等运算符==测试第一种可能性。当且仅当 x 和 x 和 xy 的对应成员彼此相等时,表达式$x==$y才成立。

第二种可能性,即$x$y是对同一个对象的引用,由特殊操作符===测试(3 个连续的等号)。当且仅当$x$y都引用同一个对象时,表达式$x===$y成立。注意,通常的赋值,如$x=$y,将有表达式$x===$y返回 true,而克隆将破坏等式。如果没有自定义的__clone方法,原始的和克隆的将是相等的,相等的定义与==操作符相同。

对于第三种可能性,即平等的自定义定义,我们能做些什么?那么,在这种情况下,我们必须编写一个自定义函数,并比较返回值。当编写采用特定类的参数的函数时,可以通过在形式参数名称前列出参数类型来强制参数为所需的类型。看起来会像这样:

function test_funct(test3a $a) {….}

在这种情况下,我们要求参数$a的类型为test3a。这只能通过输入关键字array而不是对象名来实现。PHP5 仍然是一种弱类型语言,不支持将参数类型强制为经典类型,比如int

接口、迭代器和抽象类

面向对象世界中另一个经典的对象类型是接口。接口是描述一个类可以选择实现的一组方法的对象。界面看起来像这样:

interface interf {   public function f1($x,$y,...,);   public function f2(....);   ….   public function fn(...); }

注意,没有方法代码的说明,只有名称和参数的数量。一个类可以选择实现一个接口,就像这样:

class c extends parent implements interf { (all functions listed in the interface must now be defined) … )

接口可以相互继承,就像类一样。语法也是一样的:

interface interf2 extends interf1 {    function f1(...); }

新接口interf2将包含来自接口interf1的所有功能,加上由interf2定义的新功能。清单 1-9 就是一个例子。

***清单 1-9。*新界面的例子interf2

<?php interface i1 {     public function f1($a); }    interface i2 extends i1 {     public function f2($a); } class c1 implements i2 {     private $memb;     function __construct($memb) {         $this->memb = $memb;     }     function f2($x) {         printf("Calling F2 on %s with arg: %s\n", $this->memb, $x);     } }
$x = new c1("test"); $x->f2('a');

当执行该脚本时,会产生一个错误,因为来自接口i1的函数f1未被定义。错误输出如下:

Fatal error: Class c1 contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (i1::f1) in /home/mgogala/work/book/script2.6.php on line 17

接口是 Java 编程中的标准结构,在 PHP 等脚本语言中不太常见。我们将要看到的例子是关于接口Iterator,它是 PHP 语言不可或缺的一部分。迭代器是实现内部 PHP 接口Iterator的类的对象。界面Iterator定义如下:

interface Iterator {     public function rewind();        // Returns the iterator the beginning     public function next();          // Get to the next member     public function key();           // Get the key of the current object.     public function current();       // Get the value of the current object     public function valid();         // Is the current index valid? }

任何实现接口Iterator的类都可以在for循环中使用,它的对象被称为迭代器。清单 1-10 就是一个例子。

**清单 1-10。**任何实现接口Iterator的类都可以在for循环中使用

`<?php
class iter implements iterator {
    private $items;
    private $index = 0;
    function __construct(array KaTeX parse error: Expected '}', got 'EOF' at end of input: …ems) {         this->items = KaTeX parse error: Expected 'EOF', got '}' at position 12: items;     }̲     function r…this->index = 0;
    }
    function current() {
        return ( t h i s − > i t e m s [ this->items[ this>items[this->index]);
    }
    function key() {
        return (KaTeX parse error: Expected 'EOF', got '}' at position 19: …s->index);     }̲     function n…this->index++;
        if (isset( t h i s − > i t e m s [ this->items[ this>items[this->index])) {
            return ( t h i s − > i t e m s [ this->items[ this>items[this->index]);
        } else {
            return (NULL);
        }
    }

function valid() {
        return (isset( t h i s − > i t e m s [ this->items[ this>items[this->index]));
    }
} x = n e w i t e r ( r a n g e ( ′ A ′ , ′ D ′ ) ) ; f o r e a c h ( x = new iter(range('A', 'D')); foreach ( x=newiter(range(A,D));foreach(x as $key => KaTeX parse error: Expected '}', got 'EOF' at end of input: …    print "key=key\tvalue=$val\n";
}`

这是一个非常简单但非常典型的 PHP 迭代器的例子。执行时,该脚本会产生以下输出:

key=0   value=A key=1   value=B key=2   value=C key=3   value=D

脚本的主要部分,也是整个练习的起因,是最底部的循环。这种语法通常用于数组,但是$x不是数组,它是类iter的对象。迭代器是可以表现为数组的对象。这是通过实现接口Iterator来实现的。这在什么情况下适用?文件的行或从游标返回的行可以很容易地被遍历。注意,我们仍然不能使用表达式$x[$index];计数变量仅用于在数组中前进。通过实现Iterator接口来做到这一点是一件相当简单的事情。清单 1-11 就是一个例子。

清单 1-11 。实现界面Iterator

<?php class file_iter implements iterator {     private $fp;     private $index = 0;     private $line;     function __construct($name) {         $fp = fopen($name, "r");         if (!$fp) {             die("Cannot open $name for reading.\n");         }         $this->fp = $fp;         $this->line = rtrim(fgets($this->fp), "\n");     }     function rewind() {         $this->index = 0;         rewind($this->fp);         $this->line = rtrim(fgets($this->fp), "\n");     }     function current() {         return ($this->line);     }     function key() {         return ($this->index);     }
    function next() {         $this->index++;         $this->line = rtrim(fgets($this->fp), "\n");         if (!feof($this->fp)) {             return ($this->line);         } else {             return (NULL);         }     }     function valid() {         return (feof($this->fp) ? FALSE : TRUE);     } } $x = new file_iter("qbf.txt"); foreach ($x as $lineno => $val) {     print "$lineno:\t$val\n"; }

“qbf.txt”文件是一个小文本文件,包含著名的 pangram,即包含字母表中所有字母的短语:

quick brown fox jumps over the lazy dog

该脚本将读取该文件并在屏幕上打印出来,每行前面都有行号。它使用常规的文件操作,如fopenfgetsrewind。那个。rewind函数不仅仅是迭代器接口中的方法名;它也是文件操作的核心功能。它修改文件句柄以指向文件的开头。

行号从 0 开始,以使文件尽可能类似于数组。到目前为止,我们已经看到文件和数组变成了迭代器。任何具有“下一个”和“我说完了吗?”的实体类型方法可以用一个迭代器结构来表示并循环。一个这样的例子是. database 游标。它有一个名为“fetch”的“get next”方法,并且它还能够通过使用句柄状态来判断最后一条记录何时被检索到。数据库光标的迭代器类的实现与清单 1-11 中文件的实现非常相似。这个file_iter类只是一个例子。PHP5 包含一组名为标准 PHP 库的内部类,类似于 C++ fame 的 STL。属于 SPL 的一个更加复杂的类是SplFileObject,是的,它实现了迭代器类。我们的整个脚本可以更简单地写成这样:

<?php $x = new SplFileObject("qbf.txt","r"); foreach ($x as $lineno => $val) {     if (!empty($val)) {print "$lineno:\t$val"; } } ?>

请注意,新的行字符不再从行中删除,我们必须测试这些行是否为空。如果我们忽略了对空行的测试,SplFileObject类将会越过文件的结尾。然而,它仍然是一个让生活变得更容易的课程。唯一真正有用的函数是SplFileClass中缺少的fputcsv,它以 CSV 格式输出数组。然而,它很容易写。

SPL 中还有其他有用的类和接口。SPL 的完整描述超出了本书的范围,但是感兴趣的读者可以在这里找到文档:

www.php.net/manual/en/book.spl.php

还有一组标准的类,为数据库游标和查询实现迭代器。:这组类称为 ADOdb,它使程序员能够使用foreach循环遍历查询结果,就像遍历文件或数组一样。ADOdb 类集将在本书后面更详细地介绍。

抽象类和接口的区别是什么?两者都被用作从它们继承或实现它们的其他类的模板,但是抽象类更严格,并且在更严格的意义上定义结构。除了抽象方法之外,抽象类还可以有真实的成员和方法——甚至是最终方法,它们不能重载,只能按原样使用。

类范围和静态成员

到目前为止,我们只处理了在对象范围内定义的成员和方法;每个对象都有自己独立的成员和方法。还有存在于类范围内的成员和方法,这意味着它们是类的所有对象所共有的。我们试图解决的问题如下:我们如何计算在脚本中创建的特定类的对象?我们显然需要一个特定于类的计数器,而不是特定于对象的。在类范围而不是每个对象的范围内声明的变量和方法称为静态变量。图 1-12 是一个例子。

***清单 1-12。*静态变量的例子

<?php class test4 {     private static $objcnt;     function __construct() {         ++self::$objcnt;     }     function get_objcnt() {         return (test4::$objcnt);     }     function bad() {         return($this->objcnt);     } } $x = new test4(); printf("X: %d object was created\n", $x->get_objcnt()); $y = new test4(); printf("Y: %d objects were created\n", $y->get_objcnt()); print "Let's revisit the variable x:\n"; printf("X: %d objects were created\n", $x->get_objcnt()); print "When called as object property, PHP will invent a new member of X...\n"; printf("and initialize it to:%d\n", $x->bad()); ?>

执行该脚本时,输出如下所示:

X: 1 object was created Y: 2 objects were created Let's revisit the variable x: X: 2 objects were created When called as object property, PHP will invent a new member of X... and initialize it to:0

变量test4:$objcnt是存在于类范围内的静态变量。当它增加到 2 时,在创建$y的过程中,这个变化在$x中也是可见的。另一方面,如果试图将它作为一个对象属性来访问,如在函数bad中,PHP 将发明一个新的公共对象属性,并将其命名为objcnt。生活变得更加令人困惑。一个对象在类范围内被声明为静态的事实与其可见性没有任何关系。可以有公共、私有和受保护的静态对象,其限制与普通方法和成员相同。另外,请注意,同一个变量在构造函数中被称为self::$objcnt,而它被称为test4::$objcnt。关键字self是“this class”的缩写,但它总是指定义它的类。换句话说,self没有用继承来传播,它保持不变。清单 1-13 是一个小例子。

***清单 1-13。*关键字self总是指定义它的类

`<?php
class A {
    protected static KaTeX parse error: Expected group after '_' at position 24: …;     function _̲_construct() { …prop*= 2;
    }
    function get_prop() {
        return (self::$prop);
    }
}
class B extends A {
    protected static KaTeX parse error: Expected group after '_' at position 24: …;     function _̲_construct() { …prop*= 3;
    }
    #    function get_prop() {
    #        return(self::$prop);
    #    }

}
$x = new A();
$y = new B();
printf(“A:%d\n”, $x->get_prop());
printf(“B:%d\n”, $y->get_prop());
?>`

如果class B中的get_prop函数的代码被注释掉,两行都将打印出数字 4,因为这两个函数都将在class A的上下文中被调用。如果 be 类中的get_prop函数未被注释,则显示printf("B:%d\n"$y->get_prop())的行;将打印 9 号。我个人的偏好是总是用正确的类名调用类变量。它减少了混乱,使代码更容易阅读。

除了静态成员,还有静态方法。它们在类上下文中也被称为:class::static_method(...)。需要注意的是,没有任何类型的序列化;这是用户的唯一责任。

总结

在这一章中,你学到了很多关于 PHP 类和对象的知识。您现在应该熟悉类、方法和成员的概念,以及构造函数、析构函数、继承、重载、接口、抽象类、静态方法和迭代器。这一章绝不是 PHP5 对象特性的完整参考,但它涵盖了要点,应该给你一个坚实的基础。位于[www.php.net](http://www.php.net)的官方文档是一个很好的资源,涵盖了本章省略的所有内容。

二、异常和引用

在这一章中,我们将探索异常和引用,这是现代面向对象编程(OOP)的两个基本方面。同步事件除外。“同步”这个词意味着它们是对代码本身事件的反应,而不是对外部事件(如信号)的反应。例如,当操作员按下键盘上的 Ctrl-C 时,一个信号被发送到正在执行的程序。异常用于以有序、符合标准的方式处理错误。当程序(或者在 PHP 中是脚本)试图执行除以零时,会引发一个异常。异常可以被引发(或抛出)和捕获。引发异常实际上意味着将程序控制权交给程序中用来处理这些事件的部分。像 PHP 这样的现代编程语言有办法以逻辑和有序的方式做到这一点。

异常情况

异常是类Exception或任何扩展类Exception的类的对象。你会记得在前一章中,继承经常被描述为类之间的“是”层次关系。摘自文档的Exception类的定义如下:

Exception { /* Properties */ protected string $message ; protected int $code ; protected string $file ; protected int $line ; /* Methods */ public __construct ([ string $message = "" [, int $code = 0                                [, Exception $previous = NULL ]]] ) final public string getMessage ( void ) final public Exception getPrevious ( void ) final public int getCode ( void ) final public string getFile ( void ) final public int getLine ( void ) final public array getTrace ( void ) final public string getTraceAsString ( void ) public string __toString ( void ) final private void __clone ( void ) }

因此,异常是在错误事件发生时至少包含以下信息的对象:错误消息、错误代码、抛出异常的文件以及抛出异常的行。不足为奇的是,异常对于调试程序和确保程序正确运行非常有用。或者,你可以把异常想象成一些小球,当一些“不好的”事情发生时,它们被抛出正在运行的程序,并且可以被捕获来分析发生了什么。清单 2-1 显示了一个例子。

***清单 2-1。*异常例子

`<?php
class NonNumericException extends Exception {
    private $value;
    private KaTeX parse error: Expected group after '_' at position 61: …;     function _̲_construct(value) {
        $this->value = KaTeX parse error: Expected 'EOF', got '}' at position 12: value;     }̲     public fun…this->msg, KaTeX parse error: Expected 'EOF', got '}' at position 19: …s->value);     }̲ } try {     a = “my string”;
    if (!is_numeric(KaTeX parse error: Expected '}', got 'EOF' at end of input: …mericException(argv[1]);
    }
    if (!is_numeric(KaTeX parse error: Expected '}', got 'EOF' at end of input: …mericException(argv[2]);
    }
    if ($argv[2] == 0) {
        throw new Exception(“Illegal division by zero.\n”);
    }
    printf(“Result: %f\n”, $argv[1] / $argv[2]);
}

catch(NonNumericException KaTeX parse error: Expected '}', got 'EOF' at end of input: exc) {     exc->info();
    exit(-1);
}
catch(Exception KaTeX parse error: Undefined control sequence: \n at position 29: …int "Exception:\̲n̲";     code = e x c − > g e t C o d e ( ) ;      i f ( ! e m p t y ( exc->getCode();     if (!empty( exc>getCode();    if(!empty(code)) {
        printf(“Erorr code:%d\n”, $code);
    }
    print KaTeX parse error: Undefined control sequence: \n at position 22: …etMessage() . "\̲n̲";     exit(-1)…a\n";
?>`

当使用不同的命令行参数执行时,该脚本会产生以下输出:

`./script3.1.php 4 2

Result: 2.000000
Variable a=my string

./script3.1.php 4 A

Error: the value A is not numeric!

./script3.1.php 4 0

Exception:
Illegal division by zero.`

这个小脚本充满了关于异常的注意事项。$argv数组是一个预定义的全局数组,包含命令行参数。还有一个预定义的全局变量$argc,包含命令行参数的数量,就像 C 语言中一样。现在,让我们把注意力放在异常和它们的语法上。首先,我们定义了一个Exception类,它本质上忽略了Exception类的现有结构,甚至没有在扩展类的构造函数中调用parent::__construct方法。这被认为不是一个好的编程实践,这里只是作为一个例子。其结果是我们的异常没有熟悉的getMessagegetCode函数,这将使它们更难使用和调用。

异常的通常语义不适用于我们的类,例如,如果有人决定使用getMessage()方法,这可能会导致问题。接下来,我们创建了一个try块,其中可能会出现异常。我们正在测试紧随try块之后的catch块中的异常,也称为异常处理程序。try块不是普通的作用域块;在try块内定义的变量将保持在块外定义。特别是,当 4 除以 2 时,变量$a在第一次执行中的除法完成后被打印。

其次,观察throw语句的语法:抛出的是一个异常对象。catch块中的异常处理程序非常类似于接受一个参数的函数,一个异常对象。异常处理程序的顺序也很重要:PHP 会将手头的异常传递给第一个能够处理所抛出类型的异常的异常处理程序。异常类型Exception的处理程序必须总是排在最后,因为它是一个“通吃”程序,可以捕捉任何类型的异常。

当抛出异常时,PHP 会寻找第一个适用的处理程序并使用它。如果默认异常类的处理程序放在NonNumericException类的处理程序之前,后者将永远不会被执行。

异常处理程序块,或catch块,看起来像函数。这不是巧合。PHP 还有一个叫做set_exception_handler的“神奇”方法,在这个方法中可以设置一个“捕获所有”异常处理程序来捕获所有未被捕获的异常。让我们重写清单 2-1 中的脚本(参见清单 2-2 )。

***清单 2-2。*改写的脚本来自清单 2-1

<?php function dflt_handler(Exception $exc) {     print "Exception:\n";     $code = $exc->getCode();
`    if (!empty($code)) {
        printf(“Erorr code:%d\n”, $code);
    }
    print $exc->getMessage() . “\n”;
    exit(-1);
}
set_exception_handler(‘dflt_handler’);

class NonNumericException extends Exception {
    private $value;
    private KaTeX parse error: Expected group after '_' at position 61: …;     function _̲_construct(value) {
        $this->value = KaTeX parse error: Expected 'EOF', got '}' at position 12: value;     }̲     public fun…this->msg, KaTeX parse error: Expected 'EOF', got '}' at position 19: …s->value);     }̲ } try {     if…argv[1])) {
        throw new NonNumericException(KaTeX parse error: Expected 'EOF', got '}' at position 15: argv[1]);     }̲     if (!is_nu…argv[2])) {
        throw new NonNumericException(KaTeX parse error: Expected 'EOF', got '}' at position 15: argv[2]);     }̲     if (argv[2] == 0) {
        throw new Exception(“Illegal division by zero.\n”);
    }
    printf(“Result: %f\n”, $argv[1] / $argv[2]);
}

catch(NonNumericException KaTeX parse error: Expected '}', got 'EOF' at end of input: exc) {     exc->info();
    exit(-1);
}
?>`

这个脚本的结果将与来自清单 2-1 的原始脚本的结果相同。在set_exception_handler函数中声明的异常处理程序是一个接受类Exception的一个参数的函数,在所有声明的异常处理程序之后执行:

./script3.1b.php 4 A Error: the value A is not numeric!

这显然是来自我们的NonNumericException处理程序,而不是来自默认处理程序。如果在执行 script3.1b.php 时用 0 代替字母“A ”,我们会得到原来的结果:

./script3.1b.php 4 0 Exception: Illegal division by zero.

这是默认的异常处理程序。默认异常处理程序什么时候有用?它们在处理其他人写的类时特别有用,比如上一章的SplFileObject类。如果出错,SplFileClass的对象将抛出Exception类的异常,就像 ADOdb 一样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意:当出现错误时,PEAR 库中的类将抛出PEAR_Exception类的对象。PEAR_Exception拥有普通Exception类的所有元素,变量$trace被添加到 lot 中。PEAR_Exception也会尝试显示堆栈跟踪,当它被抛出和捕获时。

清单 2-3 是一个试图使用SplFileObject类打开一个不存在的文件的脚本的例子。还有一个默认的异常处理程序,它将捕捉由SplFileObject抛出的异常,尽管在代码中没有显式的try { ..} catch {...}块。

**清单 2-3。**一个脚本试图使用SplFileObject打开一个不存在的文件的例子

<?php function dflt_handler(Exception $exc) {     print "Exception:\n";     $code = $exc->getCode();     if (!empty($code)) {         printf("Erorr code:%d\n", $code);     }     print $exc->getMessage() . "\n";     print "File:" . $exc->getFile() . "\n";     print "Line:" . $exc->getLine() . "\n";     exit(-1); } set_exception_handler('dflt_handler'); $file = new SplFileObject("non_existing_file.txt", "r"); ?>

执行该脚本时,结果如下所示:

Exception: SplFileObject::__construct(non_existing_file.txt): failed to open stream: No such file or directory File:/home/mgogala/work/book/Chapter3/script3.2.php Line:15

当处理别人写的类时,默认的catch all处理程序可能是一个非常有用的工具,尽管它是不加选择的。默认的catch all处理程序将处理所有未被捕获的异常,它通常用于终止程序并允许简单的调试。当然,程序员可能希望安排对某些情况的特殊处理,并做类似这样的事情:

try {     $file = new SplFileObject("non_existing_file.txt", "r"); } catch (Exception $e) {     $file=STDIN; }

如果打开文件进行读取有问题,则返回标准输入。当然,这不再是类SplFileObject的对象,所以程序员必须考虑可能的结果。由于默认的catch all处理程序是最后执行的,对于没有被其他任何处理程序捕捉到的异常,谨慎处理并编写我们自己的异常处理程序没有任何障碍。

还有一点要提:异常的嵌套。PHP 不支持它,除非try块也是嵌套的。换句话说,在这种情况下,如果从ExcA的处理程序中调用异常,那么ExcB的处理程序将不会被调用:

class ExcA extends Exception {...} class ExcB extends Exception {...} try {... throw new ExcA(..) } catch(ExcA $e) {  throw new ExcB(); } catch(ExcB $e) {  // Will not be called, if thrown from the ExcA }

嵌套异常的唯一方法是嵌套try块。关于异常嵌套,PHP5 与 Java 或 C++相同。

引用

PHP 中另一种重要的对象称为引用。PHP 引用不是指针。与 Perl 不同,PHP 没有可用于通过解引用来寻址对象的“引用”类型。在 PHP 中,“引用”一词只是对象的另一个名称。考虑清单 2-4 中的脚本。

***清单 2-4。*引用是 PHP 中的对象

<?php class test5 {     private $prop;     function __construct($prop) {         $this->prop = $prop;     }     function get_prop() {         return ($this->prop);     }     function set_prop($prop) {         $this->prop = $prop;     } } function funct(test5 $x) {     $x->set_prop(5); } `$x = new test5(10);
printf(“Element X has property %s\n”, x − > g e t p r o p ( ) ) ; f u n c t ( x->get_prop()); funct( x>getprop());funct(x);
printf(“Element X has property %s\n”, $x->get_prop());

a r r = r a n g e ( 1 , 5 ) ; f o r e a c h ( arr = range(1, 5); foreach ( arr=range(1,5);foreach(arr as KaTeX parse error: Expected '}', got 'EOF' at end of input: a) {     a*= 2;
}
foreach ($arr as KaTeX parse error: Expected '}', got 'EOF' at end of input: …) {     print "a\n";
}
?>`

执行该脚本时,会产生以下输出:

Element X has property 10 Element X has property 5 1 2 3 4 5

对于一个对象变量$x,其值通过在方法funct中操作而改变,对于数组变量$arr,其值不会通过在foreach循环中操作元素而改变。这个令人困惑的谜题的答案在于 PHP 通过复制传递参数。这意味着,对于数字、字符串或数组之类的非对象类型,将创建另一个完全相同的对象实例,而对于对象类型,将创建一个引用或对象的另一个名称。当类test5的参数$x被传递给方法funct时,相同对象的另一个名称被创建。通过操纵新变量,我们操纵了原始对象的内容,因为新变量只是现有对象的另一个名称。详情请参阅第一章。虽然 PHP 不像 Perl,不允许直接访问引用,但它仍然允许在一定程度上控制如何复制对象。让我们通过引用来介绍复制(见清单 2-5 )。

***清单 2-5。*参照复制

<?php print "Normal assignment.\n";     $x = 1;     $y = 2;     $x = $y;     $y++;     print "x=$x\n"; print "Assignment by reference.\n";     $x = 1;     $y = 2;     $x = & $y;     $y++;     print "x=$x\n"; ?>

执行该脚本时,结果如下所示:

Normal assignment. x=2 Assignment by reference. x=3

这个脚本由两部分组成:普通赋值和引用赋值。在第一部分中,进行常规赋值,创建变量$y的新副本并将其赋值给$x,丢弃之前的内容。增加$y对变量$x完全没有影响。第二部分,通过引用做赋值,变量$x之前的内容也被扔掉了,但是变量被做成了变量$y的别名(又名“引用”)。将变量$y增加 1 在变量$x中也是可见的,变量$x显示的是3而不是2,赋值后是$x的值。

同样的操作也可以应用于循环。在清单 2-4 中,我们有以下代码片段:

$arr = range(1, 5); foreach ($arr as $a) {     $a*= 2; } foreach ($arr as $a) {     print "$a\n";

结果是从 1 到 5 没有变化的数字。现在让我们使用引用操作符&重写这段代码:

$arr = range(1,5); foreach ($arr as &$a) {         $a *= 2; } print_r($arr);

结果是一个改变的数组:

Array (     [0] => 2     [1] => 4     [2] => 6     [3] => 8     [4] => 10 )

换句话说,通过将&加到$a,我们没有创建数组元素的副本,就像在表达式foreach($arr as $a)中所做的那样。相反,我们创建了对数组成员的引用,这意味着我们在循环中对$a所做的任何事情都将修改实际的数组成员,而不是副本。不可能引用一个函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意使用foreach循环的驱动数组时要小心。如果代码更改了驱动数组,可能会导致不可预测和意外的结果。

然而,从函数返回引用并通过引用传递参数是可能的。当希望函数能够修改原始变量时,通过引用传递参数。语法与循环相同:通过引用传递的变量只是简单地以&字符(&)为前缀。:清单 2-6 是一个小例子。

***清单 2-6。*可以从函数中返回引用,并通过引用传递参数

`<?php
a = 5 ; f u n c t i o n f 1 ( a = 5; function f1( a=5;functionf1(x) {
     x + = 3 ;      p r i n t " x = x+= 3;     print "x= x+=3;    print"x=x\n";
}
function f2(&KaTeX parse error: Expected '}', got 'EOF' at end of input: x) {     x+= 3;
    print “x=$x\n”;
}

f1( a ) ; p r i n t " a = a); print "a= a);print"a=a\n";
f2( a ) ; p r i n t " a = a); print "a= a);print"a=a\n";
?>`

当执行这个小片段时,结果如下所示:

x=8 a=5 x=8 a=8

当调用函数f1时,参数通过值传递。函数中的print语句打印了值8,但是原始变量没有被修改;它保留了值5。当函数f2被调用时,原始变量被修改,从变量a的最后一次打印输出可以看出。

引用也可以从函数中返回。这不应该是为了提高性能,因为 PHP 会自动这么做。重申一下:引用只是现有变量的另一个名字。引用可以用来绕过关键字privateprotected提供的可见性保护。清单 2-7 就是一个例子。

***清单 2-7。*引用可用于规避能见度保护

<?php class test6 {     private $x;     function __construct($x = 10) {         $this->x = $x;     }     function &get_x() {  // Observe the "&" in the function![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg) declaration         return $this->x;     }     function set_x($x) {         $this->x = $x;     } } $a = new test6(); $b = &$a->get_x(); // $b is a reference to $x->a. It![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg) circumvents protection                                 // provided by the "private"![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg) qualifier. print "b=$b\n"; $a->set_x(15); print "b=$b\n";        // $b will change its value, after![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg) calling "set_x" $b++; print '$a->get_x()='.$a->get_x() . "\n"; // $a->x will change![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg) its value after $b being                                                              ![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg)   // incremented ?>

执行时,输出如下所示:

b=10 b=15 $a->get_x()=16

这里,变量$b变成了对$a->x的引用, 是类test6的私有成员。这是通过将函数get_x()声明为返回引用来实现的。当然,对私有变量的公共引用破坏了可见性控件的目的。。通过引用返回值通常是一件非常奇特的事情。应该仔细考虑,因为很容易通过从函数返回引用来允许非预期的访问。

总结

在这一章中,你学习了 PHP 中的异常和引用。在其他现代编程语言中也可以找到这两种语言。异常的目的是为错误处理提供一个简单明了的机制;引用的目的主要是为了提高代码执行速度,偶尔也是为了让一些编程技巧成为可能。这两种语言元素都非常有用,可以为程序员提供许多好处。异常处理程序使错误检查变得更加优雅,这将在关于数据库集成的章节中展示。

三、PHP 移动开发

移动开发每年都受到越来越多的关注。iPhone、Android 和黑莓不仅是强大的智能手机设备,也是争夺利润丰厚的市场份额的有价值品牌。每个智能手机制造商都希望为自己的产品开发应用,以期吸引用户。除了智能手机,我们还有平板电脑,如 iPad、PlayBook 和 Galaxy,以及阅读设备,如 Kindle 和 Nook。即使是标准手机也改进了浏览器支持和功能。

每一个接入互联网的移动设备都可以浏览由 PHP 在服务器端提供的在线内容或应用。出于这个原因,我们需要一种方法在更小的屏幕上有意义地呈现我们的内容。在本章中,我们将介绍通过 HTTP 请求用户代理字符串、WURFL 和 Tera-WURFL 进行的设备检测,所有这些将在本章的后面定义。

目前存在数以千计的移动设备,每个都具有不同的能力。如果您认为为旧浏览器开发 web 应用很脆弱,那么移动设备就不那么标准了。幸运的是,在我们的探索中,有一些系统可以帮助我们。为了在移动设备上渲染,我们将展示如何用 WALL 抽象标记,自动调整图像大小,并使 CSS 更加流畅。

我们还将介绍设备模拟器,在 Android 驱动的设备上开发 PHP,以及用于 PHP 的 Flash Builder。最后,我们将介绍快速响应(QR)码以及如何生成它们。

移动差异

在处理移动开发时,最大的挑战之一是让网站在呈现时可读。对于桌面 web 开发,我们检查主要的浏览器,如 Chrome、Firefox、Safari、Opera 和 Internet Explorer,还可能检查不同的操作系统(OS),如 WinXP、Windows 7、Linux 和 Mac OS X。迎合浏览器、浏览器版本和 OS 的不同可能组合可能是一件相当麻烦的事情。

对于移动设备,渲染功能就不那么标准了。这使得移动渲染变得更加复杂。例如,几乎所有现代台式计算机都支持数千种颜色和至少 800×600 像素的屏幕分辨率。然而,手机、智能手机、平板电脑、电子阅读器和其他移动设备可能只支持灰度或有限的调色板。物理尺寸也有很大差异。这只是三种能力。设备可能有数百种不同的功能。我们将在本章的后面讨论其中的一些功能。

与桌面 web 开发不同,天真地尝试为每个可能的移动变体手工编程是不可能的,或者至少会花费太多的时间和精力,超出任何人愿意花费的时间和精力。相反,我们将讨论系统来确定正在使用的设备,然后动态呈现内容并流畅地调整 CSS。

检测设备

定制内容的第一步是知道我们在什么设备上渲染。我们将研究几种技术来确定活动设备。

用户代理

任何设备检测系统的核心都是在标准HTTP请求中发送的用户代理头字符串。使用 PHP,我们可以访问$_SERVER['HTTP_USER_AGENT']超级全局服务器变量中的用户代理字符串。用户代理头可以包含关于浏览器、渲染引擎和操作系统的信息。用户代理字符串将类似于下面的字符串,它适用于 Firefox 4:

Mozilla/5.0 (Windows NT 5.1; rv:2.0) Gecko/20100101 Firefox/4.0

从这个字符串可以看出,客户端的操作系统是 Windows,渲染引擎是 Gecko,浏览器版本是 Firefox 4.0。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意的探测装置并非万无一失。尽管用户代理字符串很少出现,但对于两个不同的设备来说,它们可能不是唯一的。同样,正如安全性一章中所讨论的,报头也可能是伪造的。

内置 PHP 支持

PHP 有get_browser函数,它试图获得关于所用浏览器的信息。它通过引用文件browscap.ini中的信息来做到这一点。在这方面,它就像是 WURFL 系统的一个更简单、更有限的版本,我们将在后面介绍。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意该功能依赖于在您的系统上安装browscap.ini文件,并设置该文件在您的php.ini文件中的位置,例如:

browscap = "C:\your\path\to\browscap.ini"

关于get_browser的更多信息可在 http://php.net/manual/en/function.get-browser.php 的获得,更新的browscap.ini文件可在 http://browsers.garykeith.com/downloads.asp获得

如果我们将第一个参数设置为null或者传入实际的用户代理,那么我们将获得关于当前正在使用的客户端的信息。我们还可以传入一个不同的用户代理字符串来了解它的信息。第二个参数是可选的。通过将参数设置为true,我们请求将信息作为数组而不是默认对象返回。参见列表 3-1 和列表 3-2 。

***清单 3-1。*使用 PHP 的 get_browser 函数

`<?php
echo $_SERVER [‘HTTP_USER_AGENT’] . “\n\n”;

var_dump ( get_browser ( null, true ) );

//equivalently,  we could have passed in the user agent string into the first parameter
//var_dump ( get_browser ( $_SERVER [‘HTTP_USER_AGENT’], true ) );
?>`

***清单 3-2。*Chrome 浏览器中的输出

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.65 Safari/534.24 array   'browser_name_regex' => string '§^.*$§' *(length=6)*   'browser_name_pattern' => string '*' *(length=1)*   'browser' => string 'Default Browser' *(length=15)*   'version' => string '0' *(length=1)*   'majorver' => string '0' *(length=1)*   'minorver' => string '0' *(length=1)*   'platform' => string 'unknown' *(length=7)*   'alpha' => string '' *(length=0)*   'beta' => string '' *(length=0)*   'win16' => string '' *(length=0)*   'win32' => string '' *(length=0)*   'win64' => string '' *(length=0)*   'frames' => string '1' *(length=1)*   'iframes' => string '' *(length=0)*   'tables' => string '1' *(length=1)*   'cookies' => string '' *(length=0)*   'backgroundsounds' => string '' *(length=0)*   'cdf' => string '' *(length=0)*   'vbscript' => string '' *(length=0)*   'javaapplets' => string '' *(length=0)*   'javascript' => string '' *(length=0)*   'activexcontrols' => string '' *(length=0)*   'isbanned' => string '' *(length=0)*   'ismobiledevice' => string '' *(length=0)*   'issyndicationreader' => string '' *(length=0)*   'crawler' => string '' *(length=0)*   'cssversion' => string '0' *(length=1)*   'supportscss' => string '' *(length=0)*   'aol' => string '' *(length=0)*   'aolversion' => string '0' *(length=1)*

正如您所看到的,对于这个新的浏览器,get_browser函数获得的信息没有返回任何内容。这是因为我的 WAMP (Windows、Apache、MySQL、PHP)包中包含的browscap.ini文件已经超过一年了。解决方案是下载一个最新版本。如果文件被缓存,我们可能还需要重启 Apache 服务器。更新后,我们得到了一些更有用的信息,如清单 3-3 所示。

***清单 3-3。*在 Chrome 浏览器中输出更新后的browscap.ini

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.65 Safari/534.24 array   'browser_name_regex' => string '§^mozilla/5\.0 \(.*windows nt 6\.1.*wow64.*\) applewebkit/.* \(khtml, like gecko\).*chrome/11\..*safari/.*$§' (length=108)   'browser_name_pattern' => string 'Mozilla/5.0 (*Windows NT 6.1*WOW64*) AppleWebKit/* (KHTML, like Gecko)*Chrome/11.*Safari/*' (length=90)   'parent' => string 'Chrome 11.0' (length=11)   'platform' => string 'Win7' (length=4)   'win32' => string '' (length=0)   'win64' => string '1' (length=1)   'browser' => string 'Chrome' (length=6)   'version' => string '11.0' (length=4)   'majorver' => string '11' (length=2)   'frames' => string '1' (length=1)   'iframes' => string '1' (length=1)   'tables' => string '1' (length=1)   'cookies' => string '1' (length=1)   'javascript' => string '1' (length=1)   'javaapplets' => string '1' (length=1)   'cssversion' => string '1' (length=1)   'minorver' => string '0' (length=1)   'alpha' => string '' (length=0)   'beta' => string '' (length=0)   'win16' => string '' (length=0)   'backgroundsounds' => string '' (length=0)   'vbscript' => string '' (length=0)   'activexcontrols' => string '' (length=0)   'isbanned' => string '' (length=0)   'ismobiledevice' => string '' (length=0)   'issyndicationreader' => string '' (length=0)   'crawler' => string '' (length=0)   'aolversion' => string '0' (length=1)Using Regex

如果您只关心检测几个主要的移动设备,那么您可以使用正则表达式来搜索用户代理字符串。在清单 3-4 中,我们检查用户代理字符串中的几部电话。如果找到匹配,那么我们重定向到一个单独的移动页面,并加载一个备用模板和样式表。正则表达式(regex)中的/i选项使我们的搜索不区分大小写。|表示“或”,因此“iPhone”和“iPod”都匹配,但“iPod”不匹配。类似地,“windows ce”和“windows phone”会匹配,但“windows xp”不会匹配有关正则表达式的更多信息,请参考索引。

***清单 3-4。*使用正则表达式检查特定的移动设备

<?php   if (preg_match ( '/i(Phone|Pad)|Android|Blackberry|Symbian|windows (ce|phone)/i',                  $_SERVER ['HTTP_USER_AGENT'] )) {         //redirect, load different templates, stylesheets           header ( "Location: mobile/index.php" );   } ?>

为了检测更广泛的移动设备,我们需要更多的正则表达式。网站[detectmobilebrowser.com/](http://detectmobilebrowser.com/)越来越受欢迎,因为它可以为几种不同的脚本语言或框架生成我们需要的长正则表达式(15 个,还在增加)。如果我们需要,它还会将客户端重定向到特定的移动页面。清单 3-5 显示了站点生成的示例正则表达式。

***清单 3-5。*detectmobilebrowser.com生成的正则表达式

`<?php
$useragent = $_SERVER[‘HTTP_USER_AGENT’];

if(preg_match(‘/android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|symbian|treo|up.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i’,KaTeX parse error: Undefined control sequence: \- at position 90: …abac|ac(er|oo|s\̲-̲)|ai(ko|rn)|al(…useragent,0,4)))
header(‘Location: http://detectmobilebrowser.com/mobile’);
    ?>`

这种类型的解决方案在某些情况下会起作用。然而,为了获得更准确的结果和识别设备功能,我们需要一个更精细的系统。这个系统就是 WURFL,我们将在接下来讨论它。

检测移动能力

要超越简单的设备检测并了解设备能够做什么,需要使用更复杂的 WURFL 系统。

WURFL

无线通用资源文件(WURFL)是一个 XML 文件,由 Luca Passani 发明,它包含移动设备功能。

简介

目前,WURFL 中列出了 500 多种不同的设备功能。使用 WURFL 的实现已经在包括 Java 和. NET 在内的许多语言和平台中被创建。在 PHP 中,官方 API 被称为新的 PHP WURFL API ,可在[wurfl.sourceforge.net/nphp/](http://wurfl.sourceforge.net/nphp/)获得。

设备功能使用继承堆栈层次结构。如果没有列出最具体型号的功能,则检查更通用的设备。如果该功能仍然没有列出,那么 WURFL 将检查下一个最通用的设备,并重复这个过程,直到达到基本根设备级别。这种分层结构节省了空间并提高了性能。WURFL 还试图通过 ZipArchive 使用 XML 文件的 ZIP 存档版本,ZIP archive 是 PHP >= 5.2.0 中包含的一个包。由于该文件的 ZIP 版本目前小于一兆字节(MB ),而实际的 XML 文件是 16MB,这是另一个性能改进。

了解特定设备的一些有用功能可能是屏幕分辨率、编解码器支持和格式、JavaScript、Java 和 Flash 支持。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意对 XML 文件的贡献大部分是由开发者和最终用户自愿贡献的,并且可能包含错误。此外,新的设备一直在被创造出来。尽管它非常庞大和全面,但我们不应该期望 WURFL 是 100%准确的。如果您很快需要包含一个设备,您可以列出它的功能并将信息修补到主 XML 文件中。

如果准确性是最重要的,声称甚至更好的准确性的专有系统确实存在。

设置

对于本章中的所有例子,我们将把 WURFL 库文件放在一个名为wurfl,的目录中,该目录相对于 webroot 为./wurfl/。在我们的例子中,我们将使用一个公共的配置文件,并通过使用清单 3-6 中的代码每次获得一个WURFLManager对象。

***清单 3-6。*创建一个 WURFLManager 对象:wurflSetup.php

`<?php

error_reporting(E_ALL);
define( “WURFL_DIR”, dirname(FILE) . ‘/wurfl/WURFL/’ );
define( “RESOURCES_DIR”, dirname(FILE) . “/wurfl/examples/resources/” ); require_once WURFL_DIR . ‘Application.php’;

function getWurflManager() {
     c o n f i g f i l e = R E S O U R C E S D I R . ′ w u r f l − c o n f i g . x m l ′ ;      config_file = RESOURCES_DIR . 'wurfl-config.xml';      configfile=RESOURCESDIR.wurflconfig.xml;    wurfl_config = new WURFL_Configuration_XmlConfig( $config_file );

$wurflManagerFactory = new WURFL_WURFLManagerFactory( $wurfl_config );
    return $wurflManagerFactory->create();
}

?>`

带 WURFL 的检测设备

在我们的第一个设备检测示例中,我们将使用新的 WURFL PHP API 打印出设备堆栈。我们将使用 fallback 和 id 属性输出 UA 的设备层次结构。参见清单 3-7 。

***清单 3-7。*输出用户代理的设备栈,从最特定到一般

`<?php

error_reporting(E_ALL);
require_once(‘wurflSetup.php’);

$wurflManager = getWurflManager();

$device = w u r f l M a n a g e r − > g e t D e v i c e F o r H t t p R e q u e s t ( wurflManager->getDeviceForHttpRequest( wurflManager>getDeviceForHttpRequest(_SERVER);

print “

ID Stack is:
”;
while ($device != null)
{
    print d e v i c e − > i d . " < b r / > " ;      i f ( ! device->id . "<br/>";     if (! device>id."<br/>";    if(!device->fallBack || d e v i c e − > f a l l B a c k = = " r o o t " )               b r e a k ;           device->fallBack == "root")     {         break;     }      device>fallBack=="root")            break;        device = w u r f l M a n a g e r − > g e t D e v i c e ( wurflManager->getDevice( wurflManager>getDevice(device->fallBack);
}
print “

”;

?>`

以下是在 Firefox 4 浏览器中浏览台式电脑时脚本的输出:

ID Stack is: firefox_1 firefox generic_web_browser generic_xhtml generic

在 Chrome 浏览器中:

ID Stack is: google_chrome_1 google_chrome generic_web_browser generic_xhtml generic

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意第一次运行清单 3-7 中的脚本可能需要很长时间,因为 WURFL 会构建资源缓存。您可能需要增加您的php.ini max_execution_time指令。

如果我们想使用另一个设备进行模拟,我们可以修改用户代理服务器变量。清单 3-7 的修改版本如清单 3-8 所示。输出如清单 3-9 所示。

***清单 3-8。*通过修改服务器用户代理仿真另一台设备

`<?php

error_reporting(E_ALL);
require_once(‘wurflSetup.php’);

$wurflManager = getWurflManager();

$_SERVER[‘HTTP_USER_AGENT’] =
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML,外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
** like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7";**

$device = $wurflManager->getDeviceForHttpRequest( $_SERVER );

print “

ID Stack is:
”;
while ( $device != null) {
    print d e v i c e − > i d . " < b r / > " ;      i f ( ! device->id . "<br/>";     if ( ! device>id."<br/>";    if(!device->fallBack  || d e v i c e − > f a l l B a c k = = " r o o t " )               b r e a k ;           device->fallBack == "root" )     {         break;     }      device>fallBack=="root")            break;        device = $wurflManager->getDevice( $device->fallBack );
}
print “

”;

?>`

***清单 3-9。*仿真 iPhone 4 用户代理的 WURFL 输出

ID Stack is: apple_iphone_ver4_sub405 apple_iphone_ver4 apple_iphone_ver3_1_3 apple_iphone_ver3_1_2 apple_iphone_ver3_1 apple_iphone_ver3 apple_iphone_ver2_2_1 apple_iphone_ver2_2 apple_iphone_ver2_1 apple_iphone_ver2 apple_iphone_ver1 apple_generic generic_xhtml generic

使用 WURFL 检测和列出设备功能

在清单 3-10 中,我们将展示我们可以检查的可用能力组。我们还将输出displaycss组所有可用的特定功能。输出如清单 3-11 所示。

***清单 3-10。*列出可用的能力组

`<?php

error_reporting(E_ALL);
require_once(‘wurflSetup.php’);

$wurflManager = getWurflManager();

$device = $wurflManager->getDeviceForHttpRequest( $_SERVER );
$capability_groups = $wurflManager->getListOfGroups();
asort( $capability_groups );

foreach ( $capability_groups as $c ) {
    print $c . “
”;
}
?>`

***清单 3-11。**清单 3-10 的输出清单 *

ajax bearer bugs cache chtml_ui css display drm flash_lite html_ui image_format j2me markup mms object_download pdf playback product_info rss security sms sound_format storage streaming transcoding wap_push wml_ui wta xhtml_ui

为了输出所有可用能力的列表,我们可以修改清单 3-10 中的来使用getCapabilitiesNameForGroup方法,如清单 3-12 中的所示。输出的第一部分如清单 3-13 中的所示。

***清单 3-12。*列出我们可以检查的所有能力

`<?php

error_reporting(E_ALL);
require_once(‘wurflSetup.php’);

$wurflManager = getWurflManager();

$device = $wurflManager->getDeviceForHttpRequest( $_SERVER );
$capability_groups = $wurflManager->getListOfGroups();
asort( $capability_groups );

foreach ( $capability_groups as $c ) {
**    print “” . $c . “
”;**
**    var_dump( $wurflManager->getCapabilitiesNameForGroup( $c ) );**
}
?>`

***清单 3-13。*第一部分输出自清单 3-12

`ajax

array
  0 => string ‘ajax_preferred_geoloc_api’ (length=25)
  1 => string ‘ajax_xhr_type’ (length=13)
  2 => string ‘ajax_support_getelementbyid’ (length=27)
  3 => string ‘ajax_support_event_listener’ (length=27)
  4 => string ‘ajax_manipulate_dom’ (length=19)   5 => string ‘ajax_support_javascript’ (length=23)
  6 => string ‘ajax_support_inner_html’ (length=23)
  7 => string ‘ajax_manipulate_css’ (length=19)
  8 => string ‘ajax_support_events’ (length=19)

bearer

array
  0 => string ‘sdio’ (length=4)
  1 => string ‘wifi’ (length=4)
  2 => string ‘has_cellular_radio’ (length=18)
  3 => string ‘max_data_rate’ (length=13)
  4 => string ‘vpn’ (length=3)
…`

我们可以修改清单 3-12 使其在视觉上更加有用,并且只显示某些设备功能,用绿色复选标记作为支持功能的前缀(用 HTML 实体呈现),用红色删除线样式列出不支持的功能。参见清单 3-14 。输出如图图 3-1 所示。

***清单 3-14。*显示颜色编码的设备能力值

`<?php
error_reporting ( E_ALL );
require_once (‘wurflSetup.php’);

$wurflManager = getWurflManager ();

$device = $wurflManager->getDeviceForHttpRequest ( $_SERVER );
$capability_groups = $wurflManager->getListOfGroups ();
asort ( $capability_groups );

foreach ( $capability_groups as $group ) {
        //only output the capabilities of certain groups
        if (in_array ( $group, array (“ajax”, “css”, “image_format” ) )) {
                print “” . $group . “
”;
                print “

  • ”;
                    foreach ( $wurflManager->getCapabilitiesNameForGroup ( $group ) as KaTeX parse error: Expected '}', got 'EOF' at end of input: …               c = $device->getCapability ( n a m e ) ;                          i f ( name );                         if ( name);                        if(c == “false”) {
                                     c = " < l i > < s p a n s t y l e = ′ c o l o r : r e d ;                                                         t e x t − d e c o r a t i o n : l i n e − t h r o u g h ; ′ > " ;                                  c = "<li><span style='color:red;                                                        text-decoration:line- through;'>";                                  c="<li><spanstyle=color:red;                                                       textdecoration:linethrough;>";                                c .= KaTeX parse error: Expected 'EOF', got '}' at position 43: …               }̲ else if (c == “true”) {
                                     KaTeX parse error: Expected 'EOF', got '&' at position 38: …color:green;'> &̲#10003; ";     …c .= KaTeX parse error: Expected 'EOF', got '}' at position 43: …               }̲ else {        …c = “
  • ” . $name . “: ” . $c . “”;
                            }
                            print $c;
                            print “
  • ”;
                    }                 print “
”;
        }
}

?>` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 3-1。*清单 3-14 中的一些输出,显示更容易看到的设备功能

对于我们使用新的 WURFL PHP API 的最终脚本,我们将输出我们的用户代理设备的一些特定功能。见清单 3-15 。

***清单 3-15。*输出 iPhone 4 的选定音频和显示功能

`<?php

error_reporting(E_ALL);
require_once(‘wurflSetup.php’);

$wurflManager = getWurflManager();

$_SERVER[‘HTTP_USER_AGENT’] =
        “Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
(KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7”;

$device = w u r f l M a n a g e r − > g e t D e v i c e F o r H t t p R e q u e s t ( wurflManager->getDeviceForHttpRequest( wurflManager>getDeviceForHttpRequest(_SERVER);

//output fields that interest us

//display information
print “

” . $device->id . “

”;
print “

Display:
”;
print $device->getCapability( ‘resolution_width’ ) . " x "; //width
print $device->getCapability( ‘resolution_height’ ) . " : "; //height
print d e v i c e − > g e t C a p a b i l i t y ( ′ c o l o r s ′ ) . ′ c o l o r s < b r / > ′ ; ∗ ∗ ∗ ∗ p r i n t " d u a l o r i e n t a t i o n : " . device->getCapability( 'colors' ) . ' colors<br/>';** **print "dual orientation: ". device>getCapability(colors).colors<br/>;print"dualorientation:".device->getCapability( ‘dual_orientation’ ) . “

”;

//audio information
print “

Supported Audio Formats:
”;
foreach ( $wurflManager->getCapabilitiesNameForGroup( “sound_format” ) as KaTeX parse error: Expected '}', got 'EOF' at end of input: …me ) {** **    c = $device->getCapability( $name );
**    if ( $c == “true”) {**
**            print $name . “
”;**
**    }**
}
print “

”;
?>`

运行清单 3-15 输出以下信息:


`apple_iphone_ver4_sub405

Display:

320 x 480 : 65536 colors

dual orientation: true Supported Audio Formats:

aac

mp3`


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 如前所述,不保证用户代理识别。测试 Kindle 3 时,使用如下用户代理:

``Mozilla/5.0 (Linux; U; en-US) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+)![images](https://gitee.com/OpenDocCN/vkdoc-php-zh/raw/master/docs/pro-php-prog/img/U002.jpg)  Version/4.0 Kindle/3.0 (screen 600x800; rotate)

丹希拉得到了错误的结果:


toshiba_folio100_ver1 Display: 600 x 1024 : 256 colors dual orientation: true Supported Audio Formats: aac mp3


特创伏尔

WURFL 的 Tera-WURFL 实现于[www.tera-wurfl.com](http://www.tera-wurfl.com)发布。新的 PHP WURFL API 专注于精确的结果。Tera-WURFL 更注重性能。为了实现这一点,使用数据库而不是大的 XML 文件来获取结果。Tera-WURFL 目前支持 MySQL、Microsoft SQL Server 和 MongoDB。它声称比普通的 WURFL 快五到十倍,准确率达 99%,并提供更好的桌面检测。此外,Tera-WURFL 提供了显示正在使用的移动设备的图片的能力。稍后我们将展示如何显示设备图像。

设置

要设置 Tera-WURFL,我们需要执行以下操作:

  1. 创建一个数据库,并修改TeraWurflConfig.php配置文件中的凭证以使用它。
  2. [localhost/Tera-WURFL/admin/](http://localhost/Tera-WURFL/admin/)进入管理页面。如果您收到由于缺少表而导致的错误,不要担心——当我们加载数据时,会创建这些表。
  3. 您可以加载本地 XML 文件或远程 XML 文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意如果你收到一个类似于fatal error maximum function nesting level of '100' reached aborting,的错误信息,那么你需要暂时禁用php.ini文件中的xdebug,或者通过将 php.ini 指令xdebug.max_nesting_level=100设置为一个更高的值,比如 500,来增加你的 xdebug 嵌套级别限制。

Tera-WURFL 检测设备

在我们使用 Tera-WURFL 的第一个例子中,如清单 3-16 所示,我们将输入 iPhone 4 的用户代理字符串,并验证 Tera-WURFL 能够识别它并正确设置。

***清单 3-16。*识别特定用户代理的 Tera-WURFL 代码

`<?php

error_reporting(E_ALL);
require_once(‘Tera-WURFL/TeraWurfl.php’);

$teraWURFL = new TeraWurfl();
$iphone_ua = “Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us)外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7”;

if ( $teraWURFL->getDeviceCapabilitiesFromAgent( KaTeX parse error: Expected '}', got 'EOF' at end of input: …  print "ID: ".teraWURFL->capabilities[‘id’].“
”;
} else {
    print “device not found”;
}
?>`

运行清单 3-16 的输出是:

ID: apple_iphone_ver4_sub405

如果我们没有将 user-agent 作为参数传入,那么就像 WURFL 一样,结果将来自实际使用的客户机。

使用 Tera-WURFL 检测和列出设备功能

在清单 3-17 中,我们将输出 iPhone 设备的显示和音频功能。这是等同于清单 3-15 中的 WURFL 功能的 Tera-WURFL。

***清单 3-17。*使用 Tera-WURFL 确定 iPhone 4 的显示和声音格式功能

`<?php

error_reporting(E_ALL);
require_once(‘Tera-WURFL/TeraWurfl.php’);

$teraWURFL = new TeraWurfl();
$iphone_ua = “Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us)外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7”;

if ( $teraWURFL->getDeviceCapabilitiesFromAgent( KaTeX parse error: Expected '}', got 'EOF' at end of input: …e_ua ) ) {     brand_name = t e r a W U R F L − > g e t D e v i c e C a p a b i l i t y ( " b r a n d n a m e " ) ;      teraWURFL->getDeviceCapability( "brand_name" );      teraWURFL>getDeviceCapability("brandname");    model_name = t e r a W U R F L − > g e t D e v i c e C a p a b i l i t y ( " m o d e l n a m e " ) ;      teraWURFL->getDeviceCapability( "model_name" );      teraWURFL>getDeviceCapability("modelname");    model_extra_info = $teraWURFL->getDeviceCapability( “model_extra_info” );

//output fields that interest us
    print “

” . $brand_name . " " . $model_name . " " . $model_extra_info . “

”;

//display information
    print “

Display:
”;
    print $teraWURFL->getDeviceCapability( ‘resolution_width’ ) . " x "; //width
    print $teraWURFL->getDeviceCapability( ‘resolution_height’ ) . " : "; //height
    print $teraWURFL->getDeviceCapability( ‘colors’ ) . ’ colors
';
    print "dual orientation: " . $teraWURFL->getDeviceCapability( ‘dual_orientation’ );
    print “

”;

//audio information
    print “

Supported Audio Formats:
”;

foreach ( $teraWURFL->capabilities[‘sound_format’] as $name => $value ) {
         if ( $value == “true” ) {
            print $name . “
”;
        }
    }
    print “

”;
} else
{
    print “device not found”;
}
?>`

清单 3-17 的输出是:

苹果 iPhone 4.0

显示:

320 x 480 : 65536 色

双重定位:1

支持的音频格式:

自动幅度控制(Automatic Ampltiude Control 的缩写)

mp3

用 Tera-WURFL 输出设备图像

在 Tera-WURFL 的最后一个例子中,我们将输出一个设备映像。首先,我们需要从[sourceforge.net/projects/wurfl/files/WURFL%20Device%20img/.](http://sourceforge.net/projects/wurfl/files/WURFL%20Device%20img/.)下载设备映像的档案,然后我们需要解压缩文件并将内容放入一个可通过网络访问的文件夹中。我们将在/Tera-WURFL/中创建一个名为device_pix的文件夹。我们将在清单 3-17 中添加我们之前的例子。首先,我们需要包含新的实用程序文件,然后我们可以添加代码来获取适当的设备映像并显示它。参见清单 3-18 。输出如图 3-2 所示。

***清单 3-18。*显示设备图像

`<?php

error_reporting ( E_ALL );
require_once (‘Tera-WURFL/TeraWurfl.php’);
require_once (‘Tera-WURFL/TeraWurflUtils/TeraWurflDeviceImage.php’);

$teraWURFL = new TeraWurfl ();
$iphone_ua = “Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us)?
AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7”;

if ($teraWURFL->getDeviceCapabilitiesFromAgent ( KaTeX parse error: Expected '}', got 'EOF' at end of input: …a )) {         brand_name = t e r a W U R F L − > g e t D e v i c e C a p a b i l i t y ( " b r a n d n a m e " ) ;          teraWURFL->getDeviceCapability ( "brand_name" );          teraWURFL>getDeviceCapability("brandname");        model_name = t e r a W U R F L − > g e t D e v i c e C a p a b i l i t y ( " m o d e l n a m e " ) ;          teraWURFL->getDeviceCapability ( "model_name" );          teraWURFL>getDeviceCapability("modelname");        model_extra_info = $teraWURFL->getDeviceCapability ( “model_extra_info” );

//output fields that interest us
        print “

” . $brand_name . " " . $model_name . " " . $model_extra_info . “

”;

//image
        $image = new TeraWurflDeviceImage ( t e r a W U R F L ) ;          / / l o c a t i o n o n s e r v e r          teraWURFL );         //location on server          teraWURFL);        //locationonserver        image->setBaseURL ( ‘/Tera-WURFL/device_pix/’ );
        //location on filesystem
        $image->setImagesDirectory ( $_SERVER [‘DOCUMENT_ROOT’].
                                    ‘/Tera-WURFL/device_pix/’ );

$image_src = i m a g e − > g e t I m a g e ( ) ;          i f ( image->getImage ();         if ( image>getImage();        if(image_src) {
                print ‘’;
        } else {
                echo “No image available”;
        }

//display information
        print “

Display:
”;
        print $teraWURFL->getDeviceCapability ( ‘resolution_width’ ) . " x "; //width
        print $teraWURFL->getDeviceCapability ( ‘resolution_height’ ) . " : "; //height
        print $teraWURFL->getDeviceCapability ( ‘colors’ ) . ’ colors
';
        print "dual orientation: " . $teraWURFL->getDeviceCapability ( ‘dual_orientation’ );
        print “

”;         //audio information
        print “

Supported Audio Formats:
”;

foreach ( $teraWURFL->capabilities [‘sound_format’] as $name => KaTeX parse error: Expected '}', got 'EOF' at end of input: …           if (value == “true”) {
                        print $name . “
”;
                }
        }
        print “

”;
} else {
        print “device not found”;
}
?>` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2。清单 3-18 的输出,显示设备图像

渲染工具

为了动态调整不同移动设备上的内容,我们可以使用几种工具。其中包括使用无线抽象库(WALL)的抽象标记、自动调整图像大小和 CSS 媒体属性。

除了 WURFL,Luca Passani 还负责创建 WALL。WALL 是一个抽象移动设备中的标记的库。这到底是什么意思?首先,你需要知道与普通桌面浏览器上的内容不同,这些内容是用 HTML 或 XHTML 编写的,在移动设备上有更多差异更大的标记变体。

一些最常见的可用标记方案如下:

  • XHTML MP(移动配置文件)
  • 压缩 HTML
  • 超文本标记语言

所有这些标记语言中可接受的标记的交集非常有限。例如,显示为<br>而不是<br/>的换行符标签可能会被忽略或导致错误,这取决于所使用的标记。

有了 WALL,我们可以将换行符标记为<wall:br/ >。使用 WURFL,我们可以通过找到preferred_markup功能来找到设备的期望标记。有了这些信息,WALL 将为设备呈现适当的标记,如<br><br/>

WALL 最初是为 Java Servlet Pages (JSP)编写的。可在[laacz.lv/dev/Wall/](http://laacz.lv/dev/Wall/)获得的库 WALL4PHP 是为 PHP 编写的。该库还有一个更新版本,由 Tera-WURFL 开发人员维护,可在[github.com/kamermans/WALL4PHP-by-Tera-WURFL](https://github.com/kamermans/WALL4PHP-by-Tera-WURFL)获得。在下面的例子中,我们将使用原始的实现。在[www.tera-wurfl.com/wiki/index.php/WALL4PHP](http://www.tera-wurfl.com/wiki/index.php/WALL4PHP)可以找到将墙壁与 WURFL 集成的详细说明。

带有 WALL 的 PHP 文件可能看起来像清单 3-19 中的。

***清单 3-19。*带墙壁标记的文档

<?php require_once('WALL4PHP/wall_prepend.php'); ?> <wall:document><wall:xmlpidtd /> <wall:head>     <wall:title>WALL is Cool</wall:title> </wall:head> <wall:body>     <wall:h1>A header</wall:h1>         <wall:block>This will be a paragraph in HTML</wall:block>         <wall:menu autonumber="true">        <wall:a href="http://urlA">A</wall:a>        <wall:a href="http://urlB">B</wall:a>     </wall:menu> </wall:body> </wall:document>

根据用户代理的不同,渲染会有所不同。如果设备支持 HTML,标记可以输出,如清单 3-20 所示。

***清单 3-20。*支持 HTML 的设备的渲染标记

`<?xml version="1.0" encoding="UTF-8"?>

` ` WALL is Cool

A header

This will be a paragraph in HTML

  1.       
  2. A
  3.       
  4. B
`
图像大小调整

另一个 WURFL 实用程序是 PHP 图像渲染库,可在[wurfl.sourceforge.net/utilities/dwld/ImageAdapter.zip](http://wurfl.sourceforge.net/utilities/dwld/ImageAdapter.zip)获得。该实用程序与 WURFL 一起工作,获取源图像并生成适当大小的输出图像以供显示。如有必要,图像也将被转换为设备支持的格式。这个工具要求你已经为 PHP 安装了 GD 图形库,可以在 http://php.net/manual/en/book.image.php 的找到。

设置 PHP 图像渲染库的说明位于[wurfl.sourceforge.net/utilities/phpimagerendering.php](http://wurfl.sourceforge.net/utilities/phpimagerendering.php)

基本轮廓如下:

  1. 创建新数据库
  2. 改变 DBConfig.php

一旦建立了数据库,我们只需要提供一个图像和目标用户代理。参见清单 3-21 。

**清单 3-21。**基于 ImageAdapter 库文件example.php的代码,用两个不同的用户代理输出图像

`<?php
error_reporting ( E_ALL ^ E_DEPRECATED);
require_once (‘ImageAdapter/imageAdaptation.php’);

$base_image = ‘ImageAdapter/imgc/eye.jpg’;

$iphone_ua = “Mozilla/4.0 (compatible; MSIE 4.01;
            Windows CE; PPC; 240x320; HP iPAQ h6300)”;
$img = convertImageUA ( $base_image, $iphone_ua );
if ( KaTeX parse error: Expected group as argument to '\"' at end of input: …nt "<img src=\"img">
";
}

t r i d e n t u a = " M o z i l l a / 4.0 ( c o m p a t i b l e ; M S I E 7.0 ; W i n d o w s P h o n e O S 7.0 ; T r i d e n t / 3.1 ; I E M o b i l e / 7.0 ; H T C ; 7 M o z a r t ; O r a n g e ) " ; ‘ ‘ trident_ua = "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; HTC; 7 Mozart; Orange)";` ` tridentua="Mozilla/4.0(compatible;MSIE7.0;WindowsPhoneOS7.0;Trident/3.1;IEMobile/7.0;HTC;7Mozart;Orange)";‘‘img = convertImageUA ( $base_image, $trident_ua );

if ( KaTeX parse error: Expected group as argument to '\"' at end of input: …nt "<img src=\"img">";
}
?>` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 3-3。*top image:iphone ua;底部图像:三叉戟 UA

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意如果清单 3-21 没有运行在与 WURFL 库文件相同的目录下,那么你需要调整文件wurfl_class.phpwurfl_config.phpimageAdapation.php的相对包含路径。将require_once('./wurfl_config.php');改为require_once('/ImageAdapter/wurfl_config.php');

这个工具已经过时了,因为它使用了不推荐使用的ereg_replace,并且代码风格有些松散。但是,它确实可以工作,并展示了使用 WURFL 功能可以获得的强大功能。

响应式 CSS

为了使网页设计更具响应性,我们可以使用流体网格布局,并像上一节一样调整图像大小。我们也可以使用移动特定的样式表。CSS3 最近的一个进步是媒体查询。可以查询的属性有宽度高度器件宽度器件高度取向器件宽高比器件宽高比颜色颜色索引、mon chrome分辨率参见清单 3-22 。

***清单 3-22。*设备属性媒体查询示例

   @media screen and (min-device-width:400px) and (max-device-width:600px){      /* limit device width */    }    @media screen and (orientation:landscape){     /* good for flippable devices like the iPad and kindle */    }

对 CSS 的深入探讨超出了本书的范围,但是在[www.netmagazine.com/tutorials/adaptive-layouts-media-queries](http://www.netmagazine.com/tutorials/adaptive-layouts-media-queries)有一篇很好的文章。所有这些技术都可以改善网站的移动显示。有关 CSS3 规范媒体查询的更多信息,请访问[www.w3.org/TR/css3-mediaqueries](http://www.w3.org/TR/css3-mediaqueries)

仿真器和软件开发工具包

为了帮助有限的预算,负担不起物理电话测试和全面易用,许多模拟器和软件开发工具包(SDK)存在的移动设备。有些只能模拟一个设备,而有些可以一次模拟几个设备。以下是一些链接:

  • Android:t0]
  • 苹果:[developer.apple.com/devcenter/ios/index.action](http://developer.apple.com/devcenter/ios/index.action)
  • 黑莓:[www.blackberry.com/developers/downloads/simulators/](http://www.blackberry.com/developers/downloads/simulators/)
  • Kindle: [www.amazon.com/kdk/](http://www.amazon.com/kdk/)
  • Opera Mini: [www.opera.com/mobile/demo](http://www.opera.com/mobile/demo) /
  • 视窗:[create.msdn.com/en-us/resources/downloads](http://create.msdn.com/en-us/resources/downloads)
在安卓系统上开发

谷歌的 Android 操作系统可以运行 Java 和原生 C 代码。位于[code.google.com/p/android-scripting/](http://code.google.com/p/android-scripting/)的 Android 脚本层(SL4A)项目支持 Android 上的脚本语言。然而,PHP 不是目前官方支持的脚本语言之一。

要在 Android 上开发应用,我们可以使用开源 PHP for Android 项目,可在[www.phpforandroid.net/](http://www.phpforandroid.net/)获得。这个项目通过提供一个 Android 包(APK)文件,为 SL4A 中的 PHP 提供非官方支持。

Adobe Flash Builder for PHP

最近,Zend 宣布它已经与 Adobe 合作,将 PHP 支持引入 Flash Builder 4.5(见图 3-4 )。关于 Flash Builder for PHP 的更多信息可以在 IDE 中的[www.zend.com/en/products/studio/flash-builder-for-php/.](http://www.zend.com/en/products/studio/flash-builder-for-php/.)Flash Builder for PHP integrated Zend Studio 找到。Flex 可以在前端和 PHP 后端一起使用。

IDE 致力于加速开发并提高移动代码的跨平台能力。它甚至可以交叉编译 Flex 代码,以便在 iPhone 和 iPad 等 iOS 设备上原生运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 3-4。*Zend 网站上的 Flash Builder 公告页面

二维码

QR(快速响应)码是一种 2D 条形码。大约 20 年前,日本引进了这种仪器,以帮助跟踪汽车零部件。内置摄像头的现代移动设备导致二维码在全球范围内流行。

QR 码通常代表一个 URL,但可以包含大量文本。我们将展示如何使用三个不同的库轻松生成 QR 码。TCPDF 和 Google Chart API 这两个库在第十章中有更深入的介绍。

我们将在这里提到的第一个用于生成二维码的库 TCPDF 可在[www.tcpdf.org/](http://www.tcpdf.org/)获得。使用 TCPDF,我们可以生成 QR 码作为 PDF 的一部分,但不能直接输出到独立的图像文件。参见清单 3-23 。

***清单 3-23。*使用 TCPDF 在 PDF 中生成二维码

`<?php

error_reporting(E_ALL);
require_once(‘/tcpdf/config/lang/eng.php’);
require_once(‘/tcpdf/tcpdf.php’);

$pdf = new TCPDF();     //create TCPDF object
$pdf->AddPage();        //add a new page
$pdf->write2DBarcode( ‘Hello world qrcode’, ‘QRCODE’ );
//write ‘Hello world qrcode’ as a QR Code
$pdf->Output( ‘qr_code.pdf’, ‘I’ ); //generate and output the PDF
?>` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 3-5。*二维码为字符串“Hello world qrcode”当由任何库生成时,图像应该看起来是相同的。

要将二维码保存到文件中,我们可以使用库 phpqrcode,该库可在[phpqrcode.sourceforge.net/index.php](http://phpqrcode.sourceforge.net/index.php)获得。见清单 3-24 。

***清单 3-24。*用 phpqrcode 生成 QR 码到图像文件或直接到浏览器

`<?php
require_once(‘phpqrcode/qrlib.php’);

QRcode::png( ‘Hello world qrcode’, ‘qrcode.png’ ); //to a file
QRcode::png( ‘Hello world qrcode’ ); //direct to browser
?>`

我们也可以使用位于[code.google.com/p/gchartphp/](http://code.google.com/p/gchartphp/)的 Google Chart API 包装器。参见清单 3-25 。

***清单 3-25。*用 qrcodephp 生成二维码

`<?php
error_reporting(E_ALL);
require_once (‘GChartPhp/gChart.php’);

$qr = new gQRCode();
q r − > s e t Q R C o d e ( ′ H e l l o w o r l d q r c o d e ′ ) ; e c h o " < i m g s r c = " ¨ . qr->setQRCode( 'Hello world qrcode' ); echo "<img src=\"". qr>setQRCode(Helloworldqrcode);echo"<imgsrc="¨.qr->getUrl().“” />";
?>`

总结

在本章中,我们讨论了检测移动设备及其功能。目前没有完美的设备检测系统,但我们拥有的是相当可靠的。也就是说,保持警惕并保持文件的最新状态取决于程序员——无论是使用 browscap、WURFL 还是其他系统。

我们还展示了能够抽象标记、自动调整图像大小和流畅调整内容大小的工具。尽可能使用为我们工作的工具。这项工作可以是了解一个设备的能力,如何转换现有的样式,图像和标记。自动化和协助都是很好的东西,应该在开发的所有领域使用和接受。

移动技术仍在发展,开发方法也在快速调整。要成为一名优秀的移动开发人员并保持下去,您需要跟上最佳实践、最新技术以及新兴的 SDK 和 API。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值