PHP入门学习笔记之—— PHP 类与对象 (下)

N、魔术方法 :__construct, __destruct (参看 构造方法和析构方法), __call, __callStatic, __get, __set, __isset, __unset (参看 重载), __sleep, __wakeup,
                          __toString, __set_state 和 __clone 等方法在PHP中被称为“魔术方法”(Magic methods)。
在命名自己的类方法时不能使用这些方法名。
Caution
PHP把所有以__(两个下划线)开头的类方法当成魔术方法。
所以在定义自己的类方法时,不要以 __为前缀。

__sleep 和 __wakeup
serialize() 函数会检查是否存在一个魔术方法 __sleep.如果存在,__sleep()方法会先被调用, 然后才执行序列化操作。这个功能可以用于清理对象,并返回一个包含对象中所有变量名称的数组。如果该方法不返回任何内容,则NULL被序列化,导致 一个E_NOTICE错误。
__sleep方法常用于提交未提交的数据,或类似的操作。同时,如果你有一些很大的对象, 不需要保存,这个功能就很好用。
与之相反,unserialize()会检查是否存在一个__wakeup方法。如果存在,则会先调用__wakeup方法,预先准备对象数据。
__wakeup经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
Example #1 Sleep 和 wakeup
<?php
class Connection {
    protected $link;
    private $server, $username, $password, $db;
   
    public function __construct($server, $username, $password, $db)
    {
        $this->server = $server;
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
        $this->connect();
    }
   
    private function connect()
    {
        $this->link = mysql_connect($this->server, $this->username, $this->password);
        mysql_select_db($this->db, $this->link);
    }
   
    public function __sleep()
    {
        return array('server', 'username', 'password', 'db');
    }
   
    public function __wakeup()
    {
        $this->connect();
    }
}
?>

 __toString 方法可以让一个类决定它如何转换成一个字符串。
Example #2 简单示例
<?php
// Declare a simple class
class TestClass
{
    public $foo;

    public function __construct($foo) {
        $this->foo = $foo;
    }

    public function __toString() {
        return $this->foo;
    }
}

$class = new TestClass('Hello');
echo $class;
?>以上例程会输出:
Hello

在PHP 5.2.0之前,__toString方法只有结合使用echo() 或 print()时 才能生效。PHP 5.2.0之后,则可以在任何字符串环境生效(例如通过printf(),使用%s修饰符),但 不能用于非字符串环境(如使用%d修饰符)。从PHP 5.2.0,如果将一个未定义__toString方法的对象 转换为字符串,会报出一个E_RECOVERABLE_ERROR错误。
当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。

Example #3 Using __invoke
<?php
class CallableClass {
    function __invoke($x) {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
以上例程会输出:
int(5)
bool(true)

__set_state
当调用var_export()时,这个静态 方法会被调用(自PHP 5.1.0起有效)。
本方法的唯一参数是一个数组,其中包含按array('property' => value, ...)格式排列的类属性。
Example #4 Using __set_state (PHP 5.1.0及更高版本支持)
<?php

class A
{
    public $var1;
    public $var2;

    public static function __set_state($an_array) // As of PHP 5.1.0
    {
        $obj = new A;
        $obj->var1 = $an_array['var1'];
        $obj->var2 = $an_array['var2'];
        return $obj;
    }
}

$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';

eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array(
                                            //    'var1' => 5,
                                            //    'var2' => 'foo',
                                            // ));
var_dump($b);

?>以上例程会输出:
object(A)#2 (2) {
  ["var1"]=>
  int(5)
  ["var2"]=>
  string(3) "foo"
}

     O、Final关键字:如果父类中的方法被声明为final,则子类无法覆盖该方法; 如果一个类被声明为final,则不能被继承。
                   
Example #1 Final 方法示例
<?php
class BaseClass {
   public function test() {
       echo "BaseClass::test() called ";
   }
  
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called ";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called ";
   }
}
// 产生 Fatal error: Cannot override final method BaseClass::moreTesting()
?>
Example #2 Final 类示例
<?php
final class BaseClass {
   public function test() {
       echo "BaseClass::test() called ";
   }
  
   // 这里无论你是否将方法声明为final,都没有关系
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called ";
   }
}

class ChildClass extends BaseClass {
}
// 产生 Fatal error: Class ChildClass may not inherit from final class (BaseClass)
?>

     P、对象复制:在多数情况下,我们并不需要完全复制一个对象来获得其中属性。
但有一个情况下确实需要:如果你有一个 GTK窗口对象,该对象持有窗口  相关的资源。
你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同, 但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况: 如果对象A中保存着对象B的引用,当你复制对象A时,
你想其中使用的对象不再是对象B而是B的一个副本,那么 你必须得到对象A的一个副本。
对象复制可以通过clone关键字来完成(如果对象中存在__clone()方法,会先被调用)。
对象中的 __clone()方法不能直接调用。
 $copy_of_object = clone $object;
当对象被复制后,PHP5会对对象的所有属性执行一个“浅复制”(shallow copy)。所有的属性中的引用 仍然不变,指向原来的变量。
如果定义了__clone()方法,则新创建的对象(复制生成的对象)中的__clone()方法会被调用, 可用于修改属性的值(如果有必要的话)。

     Example #1 复制一个对象
<?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: ");
print_r($obj);

print("Cloned Object: ");
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
        )

)

     Q、对象比较 :当使用对比操作符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,
那么这两个对象变量相等。 而如果使用全等操作符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。

Example #1 PHP 5的对象比较
<?php
function bool2str($bool)
{
    if ($bool === false) {
        return 'FALSE';
    } else {
        return 'TRUE';
    }
}

function compareObjects(&$o1, &$o2)
{
    echo 'o1 == o2 : ' . bool2str($o1 == $o2) . " ";
    echo 'o1 != o2 : ' . bool2str($o1 != $o2) . " ";
    echo 'o1 === o2 : ' . bool2str($o1 === $o2) . " ";
    echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . " ";
}

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 ";
compareObjects($o, $p);

echo " Two references to the same instance ";
compareObjects($o, $q);

echo " Instances of two different classes ";
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


     R、类型约束:函数的参数可以指定只能为对象(在函数原型里面指定类的名字),php 5.1 之后也可以指定只能为数组。
                         注意,即使使用了类型约束,如果使用NULL作为参数的默认值,那么在调用函数的时候依然可以使用NULL作为实参。

          Example #1 类型约束示例
<?php
//如下面的类
class MyClass
{
    /**
     * 测试函数
     * 第一个参数必须为类OtherClass的一个对象
     */
    public function test(OtherClass $otherclass) {
        echo $otherclass->var;
    }


    /**
     * 另一个测试函数
     * 第一个参数必须为数组
     */
    public function test_array(array $input_array) {
        print_r($input_array);
    }
}

//另外一个类
class OtherClass {
    public $var = 'Hello World';
}
?>函数调用的参数与定义的参数类型不一致时,会抛出一个致命错误。
<?php
// 两个类的对象
$myclass = new MyClass;
$otherclass = new OtherClass;

// 致命错误:参数1必须是类OtherClass的一个对象
$myclass->test('hello');

// 致命错误:参数1必须为类OtherClass的一个实例
$foo = new stdClass;
$myclass->test($foo);

// 致命错误:参数1不能为null
$myclass->test(null);

// 正确:输出 Hello World
$myclass->test($otherclass);

// 致命错误:参数1必须为数组
$myclass->test_array('a string');

// 正确:输出 数组
$myclass->test_array(array('a', 'b', 'c'));
?>类型约束不只是用在类的成员函数里,也能使用在函数里。
<?php
// 如下面的类
class MyClass {
    public $var = 'Hello World';
}

/**
 * 测试函数
 * 第一个参数必须是类MyClass的一个对象
 */
function MyFunction (MyClass $foo) {
    echo $foo->var;
}

// 正确
$myclass = new MyClass;
MyFunction($myclass);
?>类型约束允许NULL值:
<?php

/* 接受NULL值 */
function test(stdClass $obj = NULL) {

}

test(NULL);
test(new stdClass);

?>

     S、后期静态绑定:用于在继承范围内引用静态调用的类。
  该功能从语言内部角度考虑被命名为”后期静态绑定“。
”后期绑定“的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。
也可以称之为”静态绑定“,因为它可以用于(但不限于)静态方法的调用。
          使用self:: 或者 __CLASS__对当前类的静态引用,取决于定义当前方法所在的类:
Example #1 self:: 用法

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
?>以上例程会输出:
A
后期静态绑定试图通过引入一个关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用test()时引用的类是B而不是A。最终决定不引入新的关键字,而是使用已经预留的static关键字。
Example #2 static:: 简单用法
<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // 后期静态绑定从这里开始
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
?>以上例程会输出:
B
Note:
static:: 在处理静态方法时与 $this 是不同的。 $this-> 会遵循继承规则,但是 static:: 不会。

Example #3 static:: 用于非静态引用
<?php
class TestChild extends TestParent {
    public function __construct() {
        static::who();
    }

    public function test() {
        $o = new TestParent();
    }

    public static function who() {
        echo __CLASS__." ";
    }
}

class TestParent {
    public function __construct() {
        static::who();
    }

    public static function who() {
        echo __CLASS__." ";
    }
}
$o = new TestChild;
$o->test();

?>以上例程会输出:
TestChild
TestParent

Note:
后期静态绑定的处理方式解决了以往完全没有办法解决的静态调用。另外一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
Example #4 转发和非转发调用
<?php
class A {
    public static function foo() {
        static::who();
    }

    public static function who() {
        echo __CLASS__." ";
    }
}

class B extends A {
    public static function test() {
        A::foo();
        parent::foo();
        self::foo();
    }

    public static function who() {
        echo __CLASS__." ";
    }
}
class C extends B {
    public static function who() {
        echo __CLASS__." ";
    }
}

C::test();
?>以上例程会输出:
A
C
C


特殊情况在PHP中有很多方式来触发一个方法的调用,例如回调函数或者魔术方法。因为后期静态绑定取决于运行时的信息,因此在特殊情况下可能会得到意想不到的结果。
Example #5 在魔术方法中使用后期静态绑定
<?php
class A {

   protected static function who() {
        echo __CLASS__." ";
   }

   public function __get($var) {
       return static::who();
   }
}

class B extends A {

   protected static function who() {
        echo __CLASS__." ";
   }
}

$b = new B;
$b->foo;
?>以上例程会输出:
B

     T、对象和引用:在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。
php的引用是别名,就是两个不同的变量名字指向相同的内容。
在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。
当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,
只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

Example #1 引用和对象
<?php
class A {
    public $foo = 1;

$a = new A;
$b = $a;     // $a ,$b都是同一个标识符的拷贝
             // ($a) = ($b) = <id>   
$b->foo = 2;
echo $a->foo." ";


$c = new A;
$d = &$c;    // $c ,$d是引用
             // ($c,$d) = <id>

$d->foo = 2;
echo $c->foo." ";


$e = new A;

function foo($obj) {
    // ($obj) = ($e) = <id>
    $obj->foo = 2;
}

foo($e);
echo $e->foo." ";

?>以上例程会输出:
2
2
2
  
     U、对象序列化:序列化对象 - 在会话中存放对象
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。
unserialize()函数能够重新把字符串变回php原来的值。
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。
如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。
 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,
 可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。

?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.3.0已经废弃,而且在php6.0.0就不再支持,所以不要依赖这个函数。
在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。 不然有可能出现在解序列化对象的时候,没有找到该对象的类的定义,从而把没有方法的类__PHP_Incomplete_Class_Name作为该对象的类,导致返回一个没有用的对象。
所以在上面的例子中,当运行session_register("a"),把变量$a放在会话里之后,需要在每个页面都包含文件classa.inc,而不是只有文件page1.php和page2.php。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值