首页 -> Zend PHP 5 认证指南 第二版 -> 第六章:PHP的面向对象技术 Object Oriented Programming in PHP 6.2 成员方法与属性 Class Methods and Properties
第六章:PHP的面向对象技术 Object Oriented Programming in PHP 6.2 成员方法与属性 Class Methods and Properties
2010-04-25 12:20:22
作者:Siemen

正如我们之前提到的,类可以包含方法与变量(属性)。定义一个方法就像定义一个函数一样:

class myClass {
  function myFunction() {
    echo "You called myClass::myFunction";
  }
}

使用->指令操作符从一个类的外部调用其内部的方法:

$obj = new myClass();
$obj->myFunction();

理所当然,对象$obj是唯一能够访问类内部代码的途径——可是,如果我们想在类内部调用自己的方法应该如何做呢?可以这样:

class myClass {
  function myFunction() {
    echo  "调用了myClass::myFunction";
  }
 
  function callFunction() {
    // ???
  }
}

很明显,callMyFunction()需要一种可以在类内部调用myFunction()的途径。根据这类需求,PHP中定义了一个特殊的变量$this;这个变量在对象内部,永远指向本类:

class myClass {
  function myFunction($data) {
    echo "The value is $data";
  }
 
  function callMyFunction($data) {
    // 调用myFunction()
    $this->myFunction($data);
  }
}
 
$obj = new myClass();
$obj->callMyClass(123);

结果就是“The value is 123”。

6.2.1 构造器

在PHP5的类中构造器与构析器都是一致的。构造器与构析器是特殊的方法,他们分别在对象被创建时调用与对象被销毁时调用。构造器对于初始化对象的属性非常有用,或者是执行一些需要在对象创建时的操作,比如,连接数据库或者打开一个远程文件。

构造器的概念,在PHP4中是被定义成与类同名的函数就被当作是构造函数在对象创建时去执行。这样做有些不足的地方——比如,如果需要改变类名,就不得不同时修改构造方法。

PHP5规避的了这个问题,取而代之的是魔法方法__construct()对任意类名的类都是有效的构造器。这种提供了一致的构造器的标准机制,更加便于识别:

class foo {
  function __construct() {
    echo __METHOD__;
  }
 
  function foo()
  {
    // 这是PHP4中的构造器写法
  }
}
 
new foo();

本例的结果是输出foo::__construct(__METHOD__这个常量在编译时被替换成这句话所在的类的方法)。需要提醒的是,如果__construct()方法没有找的,PHP会继续查找PHP4风格的构造器来执行。

6.2.2 构析器

除了__construct()方法,还有__destruct()方法。它相当于是__construct()的反向:在对象被销毁之前被调用,这个方法经常被用来清理对象——比如从远程资源中断开连接,或者删除临时文件:

class foo {
  function __construct() {
    echo __METHOD__ . PHP_EOL;
  }
 
  function __destruct() {
    echo __METHOD__;
  }
}
 
new foo();

代码执行的结果:

foo::construct
foo::destruct

构析方法只在所有没有任何变量的引用指向这个对象时才会被调用,这可能会与你预料的构析方法的调用时机有点差别。事实上unset()一个指向对象的引用变量,或者重写这个变量赋予其他类型的值时对象肯能还是不会被销毁,因为有可能其他变量也指向这个对象。例如,在下面的脚本中,构析器不会在unset()后被调用,因为$b仍然指向对象:

$a = new foo();
$b = $a;
unset($a);

即使对象仍然有一个或者跟多的变量指向它,但是脚本结束运行之前__destruct()方法也会被调用——因此,可以肯定的是,构析器一定会被执行。可是,没有办法去指定两个对象销毁的先后顺序。这是因为当一个对象一个或者更多的方法需要依赖另外一个对象情况下就会出现问题——比如,如果一个类包含了数据库连接,而另外一个类需要使用数据库连接来向数据库填充数据,对象都被删除时就别指望使用构析方法来写入数据:第一个类的实例提供了数据库连接,而它一定比第二个对象被销毁的更早些,所以当第二个对象操作写入数据时,第一个对象已经被删除了,操作也就没办法完成了。

6.2.3 作用域

PHP5中加入了对象的方法与属性的作用域的概念(通常被称为PPP),允许指定类内部的每一个组件的可见性。

共有四个等级的作用域:

public 该资源内部外部都可以自由使用
protected 该资源只能在内部或者继承类中使用
private 该资源只能在本类内部使用
final 该资源内外部都可以自由使用,但是不可以被继承重写

作者提示:作用域final只应用在类和方法上,类被声明为final则不可被继承。(Siemen注:方法被final则不能被重写)

最典型的是,会希望所有的API方法与属性都是public,原因是它们需要在外部被使用,当希望只在内部使用的就用API去调用被定义为protected或者private。构造器与构析器——与其他所有的魔法方法一样(见下文)——会默认被定义为public;有时也会把构造器定义为private——比如使用单间或者工厂设计模式的时候。

class foo {
  public $foo = 'bar';
  protected $baz = 'bat';
  private $qux = 'bingo';
 
  function __construct() {
    var_dump(get_object_vars($this));
  }
}
 
class bar extends foo {
  function __construct() {
    var_dump(get_object_vars($this));
  }
}
 
class baz {
  function __construct() {
    $foo = new foo();
 
    var_dump(get_object_vars($foo));
  }
}
 
new foo();
new bar();
new baz();

上例中创建了三个类foo类,bar类继承了foo类可以使用foo类中所有的public与protected属性与方法最后baz类,创建了一个foo的实例,仅能使用foo类中的public属性与方法。

输出结果:

// 输出foo
array(3) {
  ["foo"]=>
  string(3) "bar"
  ["baz"]=>
  string(3) "bat"
  ["qux"]=>
  string(5) "bingo"
}
 
// 输出foo的继承类bar
array(2) {
  ["foo"]=>
  string(3) "bar"
  ["baz"]=>
  string(3) "bat"
}
 
// 输出独立的baz
array(1) {
  ["foo"]=>
  string(3) "bar"
}

6.2.4 定义与访问属性

属性的定义在PHP中使用一个PPP操作符跟随它们的名字来完成:

class foo {
  public $bar;
  protected $baz;
  private $bas;
  public $var1 = "Test"; // 字符串
  public $vat2 = 1.23; // 数值
  public $var3 = array(1, 2, 3);
}

提醒一下,类的属性可以在定义的时候与普通变量一样进行初始化。不管怎样,也仅局限于赋初值(不能是一个表达式)。你无法做到初始化变量的时候结果来自一个函数的返回值——如果在一个类的方法中这是允许的(通常是构造器中)。