Skip to content

5. 对象

5.1. 任何变量均可成为具有属性的对象(exemple_14)


<?php

// 任何变量在构造时都可能具有属性
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// 显示对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// 修改了该对象
$obj1->attr2+=100;
// 显示对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// 将对象1的值赋给对象2
$obj2 = $obj1;
// 修改对象2
$obj2->attr2 = 0;
// 显示这两个对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// 将对象1的引用赋值给对象3
$obj3 = &$obj1;
// 修改对象3
$obj3->attr2 = 10;
// 显示两个对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// 对象是否为字典?
print count($obj1)."\n";
while (list($attribut, $valeur) = each($obj1))
  print "obj1[$attribut]=$valeur\n";
// 结束
exit;
?>

结果

objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
objet1=[un,10]
objet3=[un,10]
1
obj1[attr1]=un
obj1[attr2]=10

注释

  • 第 4 行:$obj->attr 表示变量 $obj 的属性 attr。 如果该属性不存在,则会创建它,从而将变量 $obj 转换为一个具有属性的对象。
  • 第 13 行:当 $obj1 是一个对象时,表达式 $obj2=$obj1 属于按引用复制对象。 因此,$obj2 和 $obj1 都是指向同一个对象的引用。该对象本身可以通过任一引用进行修改。
  • 第 20 行:表达式 $obj3=&$obj1 将变量 $obj1 的引用(地址)赋值给 $obj3。 如果 $obj1 是一个对象(如本例所示),则上述表达式等同于 $obj3=$obj1
  • 第 28-29 行:表明对象可以像字典一样进行遍历。字典的键是属性的名称,字典的值则是这些属性的值。
  • 第27行:函数count可以应用于一个对象,但并不会像预期的那样返回属性数量。因此,对象与字典有相似之处,但并非字典。

5.2. 未声明属性的 Personne 类 (exemple_15)


<?php

class Personne {

  // 类的属性
  // 未声明——可动态创建
  
  // 方法
  function identité() {
    // 初步判断,使用了不存在的属性
    return "[$this->prénom,$this->nom,$this->âge]";
  }
}

// 测试
// 属性为公共属性,可动态创建
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// 调用方法
print "personne=" . $p->identité() . "\n";
// 结束
exit;
?>

结果

personne=[Paul,Langevin,48]

注释

  • 第 3-13 行:定义了一个 Personne。类是用于创建对象的模板。类是由属性及称为方法的函数组成的集合。属性并非必须声明。
  • 第9-12行:方法identité显示了类中三个未声明属性的值。关键字$this指代应用该方法的对象。
  • 第 17 行:创建一个类型为 Personne 的 $p 对象。 关键字 new 用于创建新对象。该操作返回对所创建对象的引用(即一个地址)。有多种写法:new Personne()new Personnenew personne。类名不区分大小写。
  • 第 18-20 行:在 $p 对象中创建了 identité 方法所需的三个属性。
  • 第 22 行:对 $p 对象应用了 Personne 类的 identité 方法。 在方法 identité 的代码(第 9-11 行)中,$this 引用的对象与 $p 相同。

5.3. 具有声明属性的 Personne 类(exemple_16)


<?php

class Personne {

  // 类属性
  var $prénom;
  var $nom;
  var $âge;

  // 方法
  function identité() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }
}
// 测试
// 属性是公有的
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// 调用方法
print "personne=" . $p->identité() . "\n";
// 结束
exit;
?>

结果

personne=[Paul,Langevin,48]

注释

  • 第 6-8 行:类的属性使用 var 关键字显式声明。

5.4. 带有构造函数的 Personne 类 (exemple_17)

前面的示例展示了类似 Personne 这种在 PHP 4 中常见的特殊类。不建议效仿这些示例。 现在,我们将介绍一个符合 PHP 5 最佳实践的 Personne 类:


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;

// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }

  public function getNom() {
    return $this->nom;
  }

  public function getAge() {
    return $this->âge;
  }

  public function setPrénom($prénom) {
    $this->prénom = $prénom;
  }

  public function setNom($nom) {
    $this->nom = $nom;
  }

  public function setAge($age) {
    $this->âge = $age;
  }

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

// 方法toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }

}

// 测试
// 创建一个人员对象
$p = new Personne("Paul", "Langevin", 48);
// 该人员的身份
print "personne=$p\n";
// 更改年龄
$p->setAge(14);
// 该人的身份
print "personne=$p\n";
// 结束
exit;
?>

结果

personne=[Paul,Langevin,48]
personne=[Paul,Langevin,14]

注释

  • 第3-48行:类Personne
  • 第 6-8 行:类的私有(private)属性。这些属性仅在类内部可见。其他可用的关键字包括:
  • public:将属性设为公共属性,可在类外部访问
  • protected:将属性设为受保护属性,可在类内部及其派生类中访问。
  • 由于属性是私有的,因此无法从类外部访问它们。因此无法编写以下代码:
$p=new Personne() ;
$p->nom="Landru" ;

此处位于 Personne 类外部。由于 nom 属性为私有,因此第 2 行代码是错误的。要初始化 $p 对象的私有字段,有两种方法:

  • (续)
    • 使用第11至33行中的公共方法setget(这些方法的名称可以任意设定)。此时可以编写如下代码:
$p=new Personne() ;
$p->setNom("Landru") ;
  • (续)
    • 使用第 36-41 行中的构造函数。此时应写为:
$p=new Personne("Michel","Landru",44) ;

上述代码会自动调用 Personne 类中名为 __construct 的方法。

  • 第54行:该行将对象$p以字符串形式输出。为此,使用了Personne类中名为__toString的方法。

5.5. 在构造函数中包含有效性检查的 Personne 类 (exemple_18)

类的构造函数是验证对象初始化值是否正确的理想位置。但对象也可以通过其方法 set 或等效方法进行初始化。为避免在两个不同位置重复相同的验证,应将这些验证放入方法 set 中。 如果对象的初始化值被发现不正确,将抛出异常。以下是一个示例:


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;
  
// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }

  public function getNom() {
    return $this->nom;
  }

  public function getAge() {
    return $this->âge;
  }

  public function setPrénom($prénom) {
    // 名字字段不能为空
    if (!preg_match("/^\s*(.+?)\s*$/", $prénom, $champs)) {
      throw new Exception("Le prénom doit être non vide");
    } else {
      $this->prénom = $champs[1];
    }
  }

  public function setNom($nom) {
    // 姓氏不能为空
    if (!preg_match("/^\s*(.+?)\s*$/", $nom, $champs)) {
      throw new Exception("Le nom doit être non vide");
    } else {
      $this->nom = $champs[1];
    }
  }

  public function setAge($âge) {
    // 年龄必须有效
    if (!preg_match("/^\s*(\d+)\s*$/", $âge, $champs)) {
      throw new Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->âge = $champs[1];
    }
  }

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

  // 方法 toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }
}

// 测试
// 创建 Personne 对象
$p = new Personne("Paul", "Langevin", 48);
// 此人的身份
print "personne=$p\n";
// 创建错误的Personne对象
try {
  $p = new Personne("xx", "yy", "zz");
} catch (Exception $e) {
  print $e->getMessage();
}
// 结束
exit;
?>

结果

personne=[Paul,Langevin,48]
L'âge doit être un entier positif ou nul

注释

  • 第23-30:初始化属性prénom并验证初始化值
  • 第 25 行:使用正则表达式。名字是由一个或多个字符组成的字符串,前后可能带有空格。
  • 第 26 行:如果姓氏为空,则抛出异常;否则,名字将被存储(第 28 行)
  • 第 66 行:使用 Personne 类的构造函数
  • 第 68 行:使用类 Personne 的方法 __toString
  • 第70-74行:处理Personne类构造函数可能抛出的异常。

5.6. 添加一个充当第二个构造函数的方法(exemple_19)

在 PHP 5 中,无法拥有多个具有不同参数的构造函数,从而无法以多种方式构建对象。因此,可以使用充当构造函数的方法:


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;
  
// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }

….

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

  // 方法
  public function initWithPersonne($p) {
    // 使用 $p 对象初始化当前对象
    $this->__construct($p->prénom, $p->nom, $p->âge);
  }

  // 方法toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }

}

// 测试
// 创建一个 Personne 对象
$p = new Personne("Paul", "Langevin", 48);
// 该人的身份
print "personne=$p\n";
// 创建第二个人员
$p2 = new Personne("Laure", "Adeline", 67);
// 使用第二个人的值初始化第一个
$p->initWithPersonne($p2);
// 验证
print "personne=$p\n";
// 结束
exit;
?>

结果

personne=[Paul,Langevin,48]
personne=[Laure,Adeline,67]

注释

  • 第 26-29 行:方法 initWithPersonne 用于将另一个对象 Personne 的属性值赋给当前对象。此处它调用了构造函数 __construct,但这并非强制要求。

5.7. Personne 对象数组(exemple_20)

以下示例说明可以创建对象数组。


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;
  
// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }

...

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

  // 方法
  public function initWithPersonne($p) {
….
  }

  // 方法toString
  function __toString() {
    ...
  }

}

// 测试
// 创建一个 Personne 对象数组
$groupe = array(new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70));

// 这些人的身份
for ($i = 0; $i < count($groupe); $i++)
  print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>

结果

groupe[0]=[Paul,Langevin,48]
groupe[1]=[Sylvie,Lefur,70]

注释

  • 第39行:创建一个包含2人的数组
  • 第42行:遍历数组
  • 第43行:$groupe[$i]是Personne类型的对象。使用__toString方法来显示它。

5.8. 创建从 Personne 类(exemple_21)派生的类


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;
  
// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }
...

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

  // 方法
  public function initWithPersonne($p) {
    // 使用 $p 对象初始化当前对象
    $this->__construct($p->prénom, $p->nom, $p->âge);
  }

  // 方法toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }

}

// 一个从 Person 派生的类
class Enseignant extends Personne {

  // 属性
  private $discipline;   // 教授的学科

  // getter 和 setter

  public function getDiscipline() {
    return $this->discipline;
  }

  public function setDiscipline($discipline) {
    $this->discipline = $discipline;
  }

  // 构造函数

  public function __construct($prénom, $nom, $âge, $discipline) {
    // 父类属性
    parent::__construct($prénom, $nom, $âge);
    // 其他属性
    $this->setDiscipline($discipline);
  }

  // 重载父类的 __toString 函数
  public function __toString() {
    return "[" . parent::__toString() . ",$this->discipline]";
  }

}

// 测试
// 创建“Person”对象及其派生对象的数组
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));

// 这些人员的身份
for ($i = 0; $i < count($groupe); $i++)
  print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>

结果

groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]

注释

  • 第 3-35 行:类 Personne
  • 第 38 行:类 Enseignant 继承自类 Personne.。派生类 Enseignant 继承了其父类的属性和方法。
  • 第 41 行:类 Enseignant 添加了一个属于它自己的新属性 discipline
  • 第 55 行:类 Enseignant 的构造函数接收 4 个参数
    • 其中 3 个用于初始化其父类(名字、姓氏、年龄)
    • 1 个用于自身初始化(学科)
  • 第 57 行:派生类通过关键字 parent:: 访问其父类的方法和构造函数。此处将参数(名字、姓氏、年龄)传递给父类的构造函数。
  • 第 64 行:派生类的 __toString 方法调用了父类的 __toString 方法。
  • 第 71 行:一个包含 Enseignant 对象和 Personne 对象的数组。
  • 第 74 行:遍历数组中的元素
  • 第 75 行:将调用每个元素 $groupe[$i] 的方法 __toString。 类 Personne 有一个方法 __toString。 类 Enseignant 有两个方法:其父类的和它自己的。人们可能会问哪个会被调用。运行结果表明,被调用的是类 Enseignant 的方法。 情况总是如此:当在对象上调用方法时,系统会按以下顺序进行查找:首先在对象本身中查找,如果该对象有父类,则在其父类中查找,然后在父类的父类中查找,以此类推……一旦找到该方法,搜索即告结束。

5.9. 创建一个从 Personne 类(exemple_22)派生的第二个类

以下示例创建了一个从类 Personne 派生的类 Etudiant


<?php

class Personne {

// 类的属性
  private $prénom;
  private $nom;
  private $âge;

// 获取器和设置器
  public function getPrénom() {
    return $this->prénom;
  }
...

// 构造函数
  function __construct($prénom, $nom, $âge) {
    // 通过 set 方法
    $this->setPrénom($prénom);
    $this->setNom($nom);
    $this->setAge($âge);
  }

  // 方法
  public function initWithPersonne($p) {
    // 使用 $p 对象初始化当前对象
    ...
  }

  // 方法toString
  function __toString() {
    return "[$this->prénom,$this->nom,$this->âge]";
  }

}

// 从 Person 派生的类
class Enseignant extends Personne {

  // 属性
  private $discipline;   // 教授学科

  // getter 和 setter
...

  // 构造函数

  public function __construct($prénom, $nom, $âge, $discipline) {
    // 父类属性
    parent::__construct($prénom, $nom, $âge);
    // 其他属性
    $this->setDiscipline($discipline);
  }

  // 重载父类的 _identité__toString 函数
  public function __toString() {
    return "[" . parent::__toString() . ",$this->discipline]";
  }

}

// Personne类的派生类
class Etudiant extends Personne {

  // 属性
  private $formation;   // 教授学科

  // getter 和 setter

  public function getFormation() {
    return $this->formation;
  }

  public function setFormation($formation) {
    $this->formation = $formation;
  }

  // 构造函数

  public function __construct($prénom, $nom, $âge, $formation) {
    // 父类属性
    parent::__construct($prénom, $nom, $âge);
    // 其他属性
    $this->setFormation($formation);
  }

  // 重载父类的 __toString 函数
  public function __toString() {
    return "[" . parent::__toString() . ",$this->formation]";
  }

}

// 测试
// 创建人员对象及其派生对象数组
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));

// 这些人员的身份
for ($i = 0; $i < count($groupe); $i++)
  print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>

结果

groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
groupe[2]=[[Steve,Boer,23],iup2 qualité]

5.10. 接口 (exemple_23)

接口是一种定义方法原型的结构。实现接口的类会定义该接口中所有方法的代码。


<?php

// 一个接口
interface IExemple {
  function ajouter($i, $j);

  function soustraire($i, $j);
}

// 接口的实现 1
class Classe1 implements IExemple {

  public function ajouter($a, $b) {
    return $a + $b + 10;
  }

  public function soustraire($a, $b) {
    return $a - $b + 20;
  }

}

// 接口的第二个实现
class Classe2 implements IExemple {

  public function ajouter($a, $b) {
    return $a + $b + 100;
  }

  public function soustraire($a, $b) {
    return $a - $b + 200;
  }

}

// 函数
function calculer($a, $b, IExemple $interface) {
  print $interface->ajouter($a, $b)."\n";
  print $interface->soustraire($a, $b)."\n";
}

// ------------------- main
// 创建 2 个类型为 Classe1 和 Classe2 的对象
$c1 = new Classe1();
$c2 = new Classe2();
// 调用函数calculer
calculer(4, 3, $c1);
calculer(14, 13, $c2);
?>

结果

17
21
127
201

注释

  • 第 4-8 行:接口 IExemple 定义了两个方法:函数 ajouter(第 5 行)和函数 soustraire(第 7 行)。 该接口并未定义这些方法的代码。具体实现将由实现该接口的类来完成。
  • 第 11 行:类 Classe1 实现了接口 IExemple。 因此,它为方法 ajouter(第 13 行)和 soustraire(第 17 行)定义了代码。
  • 第 24-34 行:类 Classe2 亦是如此
  • 第37行:方法calculer接受三个参数: 两个整数 $a 和 $b,以及一个实现 IExemple 接口的对象 $interface。 我们本可以写成 function calculer($a,$b,$interface),而不指定参数 $interface 的类型 IExemple。 通过指定类型,可以使解释器 PHP 验证实际参数是否确实是一个实现了接口 IExemple 的对象。
  • 第 38-39 行:调用对象 $interface 的方法 ajoutersoustraire。 由于该对象实现了 IExemple 接口,因此我们知道它拥有这两个方法。
  • 第 44-45 行:创建两个类型不同但都实现了 IExemple 接口的对象。
  • 第 47 行:将类型为 Classe1 且实现 IExemple 接口的对象 $c1 传递给函数 calculer
  • 第 48 行:调用函数 calculer,类型为 Classe2 的对象 $c2 实现了接口 IExemple