1.2 OOP简介
开始冒险吧!在理论知识方面,我们会结合代码示例来讲解,这让你更容易看懂代码的实际意义。
1.2.1 声明类
类相当于蓝图,是表明如何创建对象的一组指令。它还不是一个对象,而仅仅是对象的一个描述。在Web应用程序中,用类来表示各种实体。下面是一个可能用于电子商务应用程序中的Courier类:
以上代码表明了如何声明类,可以将Courier类保存在一个名为courier.php的文件中。这个文件的命名方法是需要牢记的一个要点,其重要性在1.3节中会详细阐述,我们会讲解当需要时如何访问对象。
上面的例子表明Courier类有两个属性:$name和$home_country,以及两种方法:__construct()和ship(),在类中声明方法的方式和我们所熟悉的声明函数的方式完全一样,因此一定要牢记这个语法。当写一个函数的时候,可以用同一种方式向方法中传入参数以及返回值。
你可能已经注意到示例代码中还有一个名为$this的变量,这是一个特别的变量,在对象的范围内它一直是可用的,用于指代当前对象。在本章的示例代码中,会用它从对象内部直接访问变量和调用方法,因此,在阅读本章时要留心这一点。
1.2.2 类的构造
construct()函数名字前面有两条相连的下划线。在PHP中,两条下划线表示一个神奇的方法,表示这是一个具有特殊意义或功能的方法。在本章我们将看到很多这样的方法。construct()方法是一种特殊的函数,在实例化一个对象时会调用它,称其为构造函数(constructor)。
PHP 4构造函数
PHP 4中没有什么神奇的方法。对象都有构造函数,并且在类声明中,构造函数的名字与类名相同。虽然在最新版本的PHP中已不再使用这种规范,但在遗留的代码或者与PHP 4兼容的代码中还可以看到这种规范,不过在PHP 5中已不再使用这种规范。
当实例化一个对象时通常会调用构造函数,当释放构造函数进而在代码中使用之前会用它创建和配置对象。构造函数还有一个和它相匹配的神奇方法,称为析构函数(destructor),它是一个名为__destruct()不带参数的方法。当销毁对象时,会调用析构函数,并且运行对象所需的停止或者清除任务。注意,虽然我们不能保证析构函数何时会运行,但当对象因销毁或超出作用域,或者PHP垃圾收集器开始运行等原因而不再使用时,析构函数才会运行。
当我们阅读本章的示例时,将会发现很多这样的示例或者神奇的方法。现在,让我们实例化一个对象,这将极好地解释构造函数是如何运行的。
1.2.3 对象实例化
要实例化或创建一个对象,要使用新的关键字,如同给一个对象取名,需要声明一个类;然后将预期使用的参数传入构造函数中。要实例化一个courier,应该这样做:
var_dump()的输出告诉我们:
这是一个属于Courier类的对象;
它有两种属性;
每个属性的名称和值。
当实例化对象时可以将参数值传入构造函数。在示例中,构造Courier对象时通过构造函数传入参数给$name属性赋值。
1.2.4 自动加载
到目前为止,上述示例表明了如何声明一个类,然后在需要的地方引用那个文件。在一个大型应用程序里,不同的文件需要包含在不同的脚本里,这一切很快会变得复杂而混乱,令人高兴的是,PHP有一种特性可使这一切变得很容易,称为自动加载。当需要一个类声明而不知道在哪里寻能找到类文件时,PHP自动加载会指引我们。
为自动加载定义规则时,用到了另一个神奇的方法:__autoload()。在前面的示例中,引用了一个文件,但作为一种选择,可以用自动加载方法来替代这种方法:
只有当你用显而易见的方法命名而且保存包含类定义的文件时,自动加载才会发挥作用。到目前为止,示例都是很简单的内容;类文件都具有相同的名字,都带有.php扩展的小写文件名,因此自动加载函数处理的都是这种简单的示例。
如果需要,也可以创建一个复杂的自动加载函数。例如,许多现代应用程序都是以MVC(Model-View-Controller,第4章会深入解释)模式构建的,在为类定义时,模型、视图和控制器往往会存放在不同的目录,为了解决这个问题,通常会用类的类型来为类命名,比如UserController,而自动加载功能会用字符串匹配或正规表达式来解释要寻找的类的特征,以及在哪里可以找到这些类。
1.2.5 使用对象
到目前为止,我们已经知道如何声明类,实例化对象,并且谈到了自动加载,但我们还没有进行更多的面向对象编程。接下来我们将使用所创建对象的属性和方法来工作,让我们用一些示例代码来看看究竟应该怎么做:
在这里,使用了对象运算符,这是用一个连字符和大于号组成的符号:->。它将对象与属性、方法或者你想访问的内容连接起来。连接符后的方法带有圆括号,而属性不带圆括号。
1.2.6 使用静态属性和方法
在举例说明了如何使用类,并解释了如何实例化对象之后,接下来要介绍的内容在概念上发生了转变。和初始化对象一样,将类的属性和方法定义为静态的(static)。静态属性或方法在使用时无需事先实例化对象。在任何一种情况下,可以标记一个元素为静态,将静态的关键字放在public之后(或其他可视性修饰符—本章后面会讲到很多这样的情况)。通过双冒号操作符::就可以访问它们。
范围解析操作符
在PHP中,使用双冒号操作符访问静态属性或方法,双冒号操作符在技术上称为范围解析操作符(scope resolution operator)。如果包含::操作符的代码发生问题,你常常会看到一个包含有T_PAAMAYIM_NEKUDOTAYIM内容的错误提示。虽然一眼看上去颇令人恐惧,但这只是一个简单的::引用。“Paamayim Nekudotayim”在希伯来语中的意思是“二点,二次”的意思。
静态属性是仅属于类的变量,而不属于对象。它完全孤立于任何属性,甚至于在类的对象中具有相同名字的属性。
静态方法是不需要访问类的其他部分的方法。因为没有创建用于引用的对象,所以在静态方法里不能引用$this关键字。你经常在库中看到的静态属性,其功能独立于任何对象的属性。它常常用来作为一种命名空间(直到5.3版本PHP才有命名空间;相关内容在下一节中介绍),并且静态属性对于检索对象集合的函数也是非常有用的,可以像这样在Courier类中增加函数:
如果你想用这种方式调用方法,那么必须将该方法标记为静态方法;否则,你将会看到一个错误提示。这是因为设计一个用于调用的方法时,不论使用静态或是动态的方法,都应该像这样先声明。如果没有必要访问$this,并且这个方法是静态的,那么像示例一样声明和调用它。如果不是这样,那么应该首先实例化对象,这是因为这个方法不是静态的方法。
何时使用静态方法是学习重点。一些类库和框架频繁地使用它们;但在没有严格要求使用的地方,其他类库和框架通常会使用动态函数。
1.2.7 对象和命名空间
从PHP 5.3开始,PHP开始提供对命名空间(namespace)的支持。这一新功能有两个主要目的,第一个目的是为了避免给类取像这样的名字:Zend_InfoCard_Xml_Security_Transform_Exception,这样长达47个字符的名字在代码中使用非常不方便(这里没有任何对Zend Framework的不敬,我们只是随机抽取一个具有描述性的名字);第二个目的是提供简单的方法将类和函数从各种类库中分离出来。每个框架都有不同的优势,可以仔细挑选几个最好的框架在应用程序中使用。然而,当两个类在不同的框架中具有相同的名字时,问题就出现了;在声明两个类时不能取相同的名字。
幸运的是,使用命名空间能解决这个问题,可以用很短的名字为类取名,但要加上前缀。命名空间在文件的顶部声明,并适用于所有在该文件中声明的类、方法和常数。我们将重点关注命名空间对类的影响,但我们要牢记的是,这些原则也可用于其他项目。例如,将代码放入一个名为shipping的命名空间里:
因为Courier类嵌套了两个层次的深度,所以用命名空间声明顶部的shop/shipping并将其类定义放进一个文件中。加上这些适当的前缀,你可能想知道这将如何帮助解决长类名的问题;到目前为止,我们似乎只能使用通过命名空间运算符替代下划线的方法!事实上,可以用简写来指代命名空间,甚至包括在一个文件中使用多个命名空间的情况。
下面的示例使用了我们刚才所见列表结构中的一系列类:
因为有shipping,所以可以在嵌套命名空间中的最低一层使用缩写,并且因为有user,所以可以创建昵称或缩写来使用这个类。这对于我们逐步解决多数特定元素的同名问题大有裨益。你可以为这些同名元素各取一个有特色的名字以便区分。
命名空间越来越多地应用于自动加载功能,你很容易想象目录分隔符和命名空间分隔符如何相互代表。相对于PHP而言,命名空间是一个新增加的内容,你一定要在类库和框架中理解它们。现在你知道该如何有效使用命名空间了。