首页 > PHP教程 > PHP类与对象的使用

PHP对象的使用

对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。

类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类

对象继承

PHP 的对象模型使用了继承。继承将会影响到类与类,对象与对象之间的关系。

比如,当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。

除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口。

继承示例 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class foo
{
    public function printItem($string
    {
        echo 'Foo: ' . $string . PHP_EOL;
    }    
    public function printPHP()
    {
        echo 'PHP is great.' . PHP_EOL;
    }
class bar extends foo
{
    public function printItem($string)
    {
        echo 'Bar: ' . $string . PHP_EOL;
    }
$foo = new foo();
$bar = new bar();
$foo->printItem('baz'); // Output: 'Foo: baz'
$foo->printPHP();       // Output: 'PHP is great' 
$bar->printItem('baz'); // Output: 'Bar: baz'
$bar->printPHP();       // Output: 'PHP is great' 
?>

 

对象接口

使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。

接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。

接口中定义的所有方法都必须是公有,这是接口的特性。

 

实现(implements)

要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。

实现多个接口时,接口中的方法不能有重名。

接口也可以继承,通过使用 extends 操作符。

类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。

接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。

 接口示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
// 声明一个'iTemplate'接口
interface iTemplate
{
    public function setVariable($name, $var);
    public function getHtml($template);
}
// 实现接口
// 下面的写法是正确的
class Template implements iTemplate
{
    private $vars = array();  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
       
        return $template;
    }
// 下面的写法是错误的,会报错,因为没有实现 getHtml():
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (iTemplate::getHtml)
class BadTemplate implements iTemplate
{
    private $vars = array();  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
}
?>

可扩充的接口 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
interface a
{
    public function foo();
interface b extends a
{
    public function baz(Baz $baz);
// 正确写法
class c implements b
{
    public function foo()
    {
    }
    public function baz(Baz $baz)
    {
    }
// 错误写法会导致一个致命错误
class d implements b
{
    public function foo()
    {
   
    public function baz(Foo $foo)
    {
    }
}
?>

继承多个接口 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
interface a
{
    public function foo();
interface b
{
    public function bar();
interface c extends a, b
{
    public function baz();
class d implements c
{
    public function foo()
    {
   
    public function bar()
    {
   
    public function baz()
    {
    }
}
?>

使用接口常量 

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
interface a
{
    const b = 'Interface constant';
// 输出接口常量
echo a::b; 
// 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。
class b implements a
{
    const b = 'Class constant';
}
?>

接口加上类型约束,提供了一种很好的方式来确保某个对象包含有某些方法。

 

遍历对象 

PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被用于遍历。

简单的对象遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class MyClass
{
    public $var1 = 'value 1';
    public $var2 = 'value 2';
    public $var3 = 'value 3'
    protected $protected = 'protected var';
    private   $private   = 'private var'
    function iterateVisible() {
       echo "MyClass::iterateVisible:\n";
       foreach($this as $key => $value) {
           print "$key => $value\n";
       }
    }
$class = new MyClass(); 
foreach($class as $key => $value) {
    print "$key => $value\n";
}
echo "\n"
$class->iterateVisible(); 
?>

以上例程会输出: 

var1 => value 1

var2 => value 2

var3 => value 3 

MyClass::iterateVisible:

var1 => value 1

var2 => value 2

var3 => value 3

protected => protected var

private => private var

如上所示,foreach 遍历了所有其能够访问的可见属性。

更进一步,可以实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。

实现 Iterator 接口的对象遍历 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
class MyIterator implements Iterator
{
    private $var = array(); 
    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
   
    public function rewind() {
        echo "rewinding\n";
        reset($this->var);
   
    public function current() {
        $var = current($this->var);
        echo "current: $var\n";
        return $var;
   
    public function key() {
        $var = key($this->var);
        echo "key: $var\n";
        return $var;
   
    public function next() {
        $var = next($this->var);
        echo "next: $var\n";
        return $var;
   
    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}\n";
        return $var;
    }
$values = array(1,2,3);
$it = new MyIterator($values); 
foreach ($it as $a => $b) {
    print "$a: $b\n";
}
?>

以上例程会输出: 

rewinding

current: 1

valid: 1

current: 1

key: 0

0: 1

next: 2

current: 2

valid: 1

current: 2

key: 1

1: 2

next: 3

current: 3

valid: 1

current: 3

key: 2

2: 3

next:

current:

valid:

可以用 IteratorAggregate 接口以替代实现所有的 Iterator 方法。IteratorAggregate 只需要实现一个方法 IteratorAggregate::getIterator(),其应返回一个实现了 Iterator 的类的实例。

通过实现 IteratorAggregate 来遍历对象 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class MyCollection implements IteratorAggregate
{
    private $items = array();
    private $count = 0; 
    // Required definition of interface IteratorAggregate
    public function getIterator() {
        return new MyIterator($this->items);
   
    public function add($value) {
        $this->items[$this->count++] = $value;
    }
$coll = new MyCollection();
$coll->add('value 1');
$coll->add('value 2');
$coll->add('value 3'); 
foreach ($coll as $key => $val) {
    echo "key/value: [$key -> $val]\n\n";
}
?>

以上例程会输出: 

rewinding

current: value 1

valid: 1

current: value 1

key: 0

key/value: [0 -> value 1] 

next: value 2

current: value 2

valid: 1

current: value 2

key: 1

key/value: [1 -> value 2] 

next: value 3

current: value 3

valid: 1

current: value 3

key: 2

key/value: [2 -> value 3] 

next:

current:

valid:

 

对象复制 

在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。 

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。 

$copy_of_object = clone $object;

当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。 

void __clone ( void )

当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。 

复制一个对象 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
class SubObject
{
    static $instances = 0;
    public $instance
    public function __construct() {
        $this->instance = ++self::$instances;
   
    public function __clone() {
        $this->instance = ++self::$instances;
    }
class MyCloneable
{
    public $object1;
    public $object2
    function __clone()
    {      
        // 强制复制一份this->object, 否则仍然指向同一个对象
        $this->object1 = clone $this->object1;
    }
$obj = new MyCloneable(); 
$obj->object1 = new SubObject();
$obj->object2 = new SubObject(); 
$obj2 = clone $obj;
print("Original Object:\n");
print_r($obj); 
print("Cloned Object:\n");
print_r($obj2); 
?>

以上例程会输出: 

Original Object:

MyCloneable Object

(

    [object1] => SubObject Object

        (

            [instance] => 1

        ) 

    [object2] => SubObject Object

        (

            [instance] => 2

        ) 

)

Cloned Object:

MyCloneable Object

(

    [object1] => SubObject Object

        (

            [instance] => 3

        ) 

    [object2] => SubObject Object

        (

            [instance] => 2

        ) 

)

 

对象比较 

PHP 5 中的对象比较要比 PHP 4 中复杂,所期望的结果更符合一个面向对象语言。

当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。 

而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。 

通过下面的示例可以理解以上原则。 

PHP 5 的对象比较 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
function bool2str($bool)
{
    if ($bool === false) {
        return 'FALSE';
    } else {
        return 'TRUE';
    }
function compareObjects(&$o1, &$o2)
{
    echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
    echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
    echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
    echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
class Flag
{
    public $flag
    function Flag($flag = true) {
        $this->flag = $flag;
    }
class OtherFlag
{
    public $flag
    function OtherFlag($flag = true) {
        $this->flag = $flag;
    }
$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag(); 
echo "Two instances of the same class\n";
compareObjects($o, $p); 
echo "\nTwo references to the same instance\n";
compareObjects($o, $q); 
echo "\nInstances of two different classes\n";
compareObjects($o, $r);
?>

以上例程会输出: 

Two instances of the same class

o1 == o2 : TRUE

o1 != o2 : FALSE

o1 === o2 : FALSE

o1 !== o2 : TRUE 

Two references to the same instance

o1 == o2 : TRUE

o1 != o2 : FALSE

o1 === o2 : TRUE

o1 !== o2 : FALSE 

Instances of two different classes

o1 == o2 : FALSE

o1 != o2 : TRUE

o1 === o2 : FALSE

o1 !== o2 : TRUE

 

对象和引用 

在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。 

php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。 

引用和对象 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class A {
    public $foo = 1;
}   
$a = new A;
$b = $a;     // $a ,$b都是同一个标识符的拷贝
             // ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n"
$c = new A;
$d = &$c;    // $c ,$d是引用
             // ($c,$d) = <id> 
$d->foo = 2;
echo $c->foo."\n"
$e = new A; 
function foo($obj) {
    // ($obj) = ($e) = <id>
    $obj->foo = 2;
foo($e);
echo $e->foo."\n";
?>

以上例程会输出: 

2

2

 

序列化对象 - 在会话中存放对象 

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。 

为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
// classa.inc:  
  class A {
      public $one = 1;    
      public function show_one() {
          echo $this->one;
      }
  }  
// page1.php:
  include("classa.inc");  
  $a = new A;
  $s = serialize($a);
  // 把变量$s保存起来以便文件page2.php能够读到
  file_put_contents('store', $s); 
// page2.php:  
  // 要正确了解序列化,必须包含下面一个文件
  include("classa.inc"); 
  $s = file_get_contents('store');
  $a = unserialize($s); 
  // 现在可以使用对象$a里面的函数 show_one()
  $a->show_one();
?>

当一个应用程序使用函数session_register()来保存对象到会话中时,在每个页面结束的时候这些对象都会自动序列化,而在每个页面开始的时候又自动解序列化。 所以一旦对象被保存在会话中,整个应用程序的页面都能使用这些对象。但是,session_register()在php5.4.0之后被移除了。 

在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。 不然有可能出现在解序列化对象的时候,没有找到该对象的类的定义,从而把没有方法的类__PHP_Incomplete_Class_Name作为该对象的类,导致返回一个没有用的对象。

所以在上面的例子中,当运行session_register("a"),把变量$a放在会话里之后,需要在每个页面都包含文件classa.inc,而不是只有文件page1.php和page2.php。

PHP函数、对象定义与使用

关闭
感谢您的支持,我会继续努力!
扫码打赏,建议金额1-10元


提醒:打赏金额将直接进入对方账号,无法退款,请您谨慎操作。