PHP的类和对象

自PHP 5以来,引入了对象模型,新特性包括访问控制抽象类final类与方法,附加的魔术方法,接口,对象复制和类型约束,PHP对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝,可与C++进行类比。

class:

每个类都以关键字class开头,后面跟着类名,再后面跟着一对花括号,里面包含类的属性与方法的定义,类名可以是任何非PHP保留字的合法标签,一个合法类名以字母或下划线开头,后跟着若干字母,数字或下划线,以正则表达式表示为:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*。一个类可包含有属于自己的常量,变量以及函数。

实例#1:简单类定义

<?php
class SimpleClass
{
    //声明变量
    public $var = 'a default value';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}
?>

当一个方法再类定义内部被调用时,有一个可用的伪变量$this,$this是一个到主叫对象的引用,通常是该方法所从属的对象,但若是从第二个对象静态调用时也可能是另一个对象。

实例#2:伪变量$this实例:

<?php
error_reporting(0); //屏蔽STRICT警告
class A
{
    function foo()
    {
        if (isset($this)) {
            echo '$this is defined (';
            echo get_class($this);
            echo ")<br>";
        } else {
            echo "\$this is not defined.<br>";
        }
    }
}


class B
{
    function bar()
    {
        // Note: the next line will issue a warning if E_STRICT is enabled.
        A::foo();
    }
}


$a = new A();
$a->foo();


// Note: the next line will issue a warning if E_STRICT is enabled.
A::foo();
$b = new B();
$b->bar();


// Note: the next line will issue a warning if E_STRICT is enabled.
B::bar();
?>

输出:


new:

1.使用new关键字来创建一个类的实例,当创建新对象时该对象总是被赋值,除非该对象定义了构造函数且在出错时抛出了一个异常,类应该在实例化之前定义

2.若new之后跟着的是一个包含有类名的字符串,则该类的一个实例被创建,若该类属于一个名字空间,则必须使用其完整名称。

实例#3:创建一个实例

<?php
class SimpleClass
{
    //声明变量
    public $var = 'a default value';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}

class SimpleClassName
{
    //声明变量
    public $var = 'a default value2';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}

$instance = new SimpleClass();
$instance->displayVar();
echo '<br>';
// 也可以这样做:
$className = 'SimpleClassName';
$instance = new $className(); // Foo()
$instance->displayVar();
?>

输出:


在类定义内部,可使用new self和new parent创建新对象,当把一个对象已创建的实例赋给一个新变量时,新变量会访问同一个实例,就和该对象赋值一样。此行为和给函数传递入实例时一样,可克隆给一个已创建的对象建立一个新实例。

实例#4:对象赋值

<?php
class SimpleClass
{
    //声明变量
    public $var = 'a default value';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}

class SimpleClassName
{
    //声明变量
    public $var = 'a default value2';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}

$instance = new SimpleClass();
$assigned = $instance;
$reference = & $instance;
$instance->var = '$assigned will have this value';
$instance = null; // $instance and $reference become null
var_dump($instance);
echo '<br>';
var_dump($reference);
echo '<br>';
var_dump($assigned);
?>

输出:


PHP 5.3.0引入两个新方法来创建一个对象的实例:

实例#5 创建新对象:

<?php
class Test
{
    static public function getNew()
    {
        return new static;
    }
}

class Child extends Test
{}

$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);
echo '<br>';
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);
echo '<br>';
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);
?>

输出:


extends:

extends关键字用于继承另一个类的方法和属性,PHP不支持多重继承,一个类只能继承一个基类。被继承的方法和属性可通过用同样的名字重新声明被覆盖,但若父类定义方法时使用了final,则该方法不可被覆盖,可通过parent::来访问被覆盖的方法和属性。

当覆盖方法时,参数必须保持一致否则PHP将发出E_STRICT级别的错误信息,当构造函数例外,构造函数可在被覆盖时使用不同的参数。


实例#6 简单的类继承:

<?php
class SimpleClass
{
    //声明变量
    public $var = 'a default value';
    //声明方法
    public function displayVar(){
        echo $this->var;
    }
}

class ExtendClass extends SimpleClass
{
    // Redefine the parent method
    function displayVar()
    {
        echo "Extending class\n";
		echo '<br>';
        parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

输出:


::class:

从PHP 5.5开始,关键词class也可用于类名解析,使用ClassName:class可获取包含类ClassName的完全限定名称的一个字符串,这对使用命名空间的类尤其有用。

实例#7 类名解析:

<?php
namespace NS {
    class ClassName {
    }
    
    echo ClassName::class;
}
?>

输出:



PHP对象赋值与变量的赋值不同点:

1. 给变量声明一个别名与变量指向的是同一个内存区,它们的内容地址相同,内存实际只有一个数据备份。而若变量赋给另一个新变量,则会将变量指向的数据再拷贝一份给新变量,存在两个备份。(与C类比)


以下是C语言的一个实例test.c,使用GCC编译器,也就是AT&T汇编语法:

#include<stdio.h>

int main(void)
{
    int var = 123;
    int var_alias = &var;
    int var_new = var;

    printf("var = %p\n var_alias = %p\n var_new = %p\n", &var, var_alias, &var_new);
    return 0;
}

gcc -S test.c

cat test.s,其中一段可验证其原理:

        movl    $123, -20(%rbp)  //值123保存到rbp-20 -> var = 123
        leaq    -20(%rbp), %rax  //将rbp-20的地址保存到rax ->  取地址:&var
        movl    %eax, -12(%rbp)   //将rax中保存的地址保存到地址rbp-12 -> 将取得的var地址赋var_alias:var_alias = &var
        movl    -20(%rbp), %eax   // 将rbp-20中的值保存到eax -> 拷贝一个var到临时寄存器eax,拷贝操作
        movl    %eax, -16(%rbp)   //将eax保存到rbp-16 -> 为var_new分配栈rbp-16,将eax中的值var拷贝到var_new,var_new = var
        leaq    -16(%rbp), %rcx   //将rbp-16的地址保存到rcx -> 取地址var_new到临时寄存器rcx,给printf使用:&var_new
        movl    -12(%rbp), %edx   // 将rbp-12的值保存到edx -> 保存地址var_alias到临时寄存器edx,给printf使用
        leaq    -20(%rbp), %rax    // 将rbp-20的地址保存到rax -> &var
        movq    %rax, %rsi //将rax的值保存到rsi
        movl    $.LC0, %edi
        movl    $0, %eax

2.给对象声明一个别名变量以及对象变量赋给一个新对象变量,都只存在一个对象实例备份。对象别名变量与对象变量所保存的是对象实例的句柄,赋给新变量时也会拷贝一个实例句柄,因此也会存在两个实例句柄备份,但是句柄之间彼此不会产生影响,所以若是把对象变量或对象别名变量指向别的对象实例句柄,也不会影响到原来的对象实例。


实例#8 对象的赋值引用:

<?php
// Assignment of an object
Class Object{
   public $foo="bar";
};

$objectVar = new Object();
$reference =& $objectVar;
$assignment = $objectVar;

$objectVar->foo = "qux";
print_r( $objectVar );
echo '<br>';
print_r( $reference );
echo '<br>';
print_r( $assignment );
?>

 输出:


属性:

类的变量成员叫做属性,或字段,特征,在这里统称为属性,属性声明是由关键字public,protected或private开头,后跟一个普通的变量声明来组成,属性中的变量可初始化,但初始化值,必须是常数,这里的常数是指PHP脚本在编译阶段时就可得到其值,而不依赖于运行时的信息才能求值。

为了向后兼容php 4, php 5声明属性依然可直接使用关键字var来替代public,protected或private,但已不再需要var,在php 5.0到5.1.3,var被认为是废弃的,且会抛出E_STRICT警告,但是5.1.3之后就不再认为是废弃,也不会抛出警告。

若直接使用var声明属性,而没有用public , protected或private之一,PHP 5会将其视为public。

1.在类成员方法里面,可用->对象运算符:$this->property(property为属性名)来访问非静态属性。

2.静态属性用::来访问,比如:self::$property

当一个方法在类定义内部被调用时,由一个可用的伪变量$this,它是一个到主叫对象的引用,通常是该方法所从属的对象,但若是从第二个对象静态调用时也可能是另一个对象。

#1 属性声明:

<?php
class SimpleClass
{
   // 错误的属性声明
   public $var1 = 'hello ' . 'world';
   public $var2 = <<<EOD
hello world
EOD;
   public $var3 = 1+2;
   public $var4 = self::myStaticMethod();
   public $var5 = $myVar;

   // 正确的属性声明
   public $var6 = myConstant;
   public $var7 = array(true, false);

   //在 PHP 5.3.0 及之后,下面的声明也正确
   public $var8 = <<<'EOD'
hello world
EOD;
}
?>

#2:使用nowdoc初始化属性

<?php
class foo {
   // 自 5.3.0 起
   public $bar = <<<'EOT'
bar
EOT;
}
?>

类常量:

可以把在类中始终保持不变的值定义为常量,在定义和使用常量的时候不需要使用$符号。常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。接口中也可以定义常量,自PHP 5.3.0起,可用一个变量来动态调用类,但该变量的值不能为关键字,如self,parent或static。

实例 #1:定义和使用一个类常量

<?php
class MyClass
{
    const constant = 'constant value';

    function showConstant() {
        echo  self::constant . "<br>";
    }
}

echo MyClass::constant . "<br>";

$classname = "MyClass";
echo $classname::constant . "<br>"; // 自 5.3.0 起

$class = new MyClass();
$class->showConstant();

echo $class::constant."<br>"; // 自 PHP 5.3.0 起
?>

输出:


实例#2 静态数据:

<?php
class foo {
    // 自 PHP 5.3.0 起
    const bar = <<<'EOT'
bar
EOT;
}
?>

与heredoc不同的是,nowdoc可用在任何静态数据中。

Nowdoc支持是在PHP 5.3.0新增的。

在基类中声明常量并在子类中覆盖它,后面可用静态函数get_called_class访问子类的常量值。

<?php
abstract class dbObject
{    
    const TABLE_NAME='undefined';
    
    public static function GetAll()
    {
        $c = get_called_class();
        return "SELECT * FROM `".$c::TABLE_NAME."`";
    }    
}

class dbPerson extends dbObject
{
    const TABLE_NAME='persons';
}

class dbAdmin extends dbPerson
{
    const TABLE_NAME='admins';
}

echo dbPerson::GetAll()."<br>";//output: "SELECT * FROM `persons`"
echo dbAdmin::GetAll()."<br>";//output: "SELECT * FROM `admins`"

?>

输出:


类的自动加载:

在编写面向对象程序OOP时,大多是为每个类新建一个PHP文件,这会使得每个脚本的开头,都需要包含一个长长的列表,每个类都有一个文件。

而在PHP5中,已不再需要如此,spl_autoload_register()函数可以注册任意数量的自动加载器,当使用尚未被定义的类和接口时自动去加载,通过注册自动加载脚本,脚本引擎在PHP出错失败前有了最后一个机会加载所需的类。

注意事项:

1.尽管__autoload()函数也能自动加载类和接口,但更建议使用spl_autoload_register()函数,spl_autoload_register()提供了一种更为灵活的方式来实现类的自动加载,同一个应用中,可支持任意数量的加载器,比如第三方库中的,因此,不再建议使用_autoload()函数,在以后的版本中它可能被弃用。

2.在PHP 5.3之前,__autoload函数抛出的异常不能被catch语句块捕获并会导致一个致命错误Fatal Error,自PHP 5.3起,能够thrown自定义的异常Exception,随后自定义异常类即可使用,__autoload函数可以递归的自动加载自定义异常类。

3.自动加载不可用于PHP CLI交互模式

4.若类名比如被用于call_user_func(),则它可能包含一些危险的字符,比如.../。建议在这样的函数中不要使用用户的输入,起码需要在__autoload()时验证下输入。

实例 #1 自动加载:

本例分别从MyClass1.php和MyClass2.php文件中加载MyClass1和MyClass2类:

MyClass1.php:

<?php
class MyClass1
{
    const constant = 'constant value1';

    function showConstant() {
        echo  self::constant . "<br>";
    }
	
}
echo MyClass1::constant . "<br>";
?>

MyClass2.php:

<?php
class MyClass2
{
    const constant = 'constant value2';

    function showConstant() {
        echo  self::constant . "<br>";
    }
	
}
echo MyClass2::constant . "<br>";
?>

test.php:

<?php
spl_autoload_register(function ($class_name) {
    require_once $class_name . '.php';
});

$obj  = new MyClass1();
$obj2 = new MyClass2();
?>

输出:


实例 #2 加载接口:

接口ITest加载:

<?php

spl_autoload_register(function ($name) {
    var_dump($name);
});

class Foo implements ITest {
}

/*
string(5) "ITest"

Fatal error: Interface 'ITest' not found in ...
*/
?>

输出:


实例#3 自动加载在PHP 5.3.0+中的异常处理:

本例抛出一个异常并在try/catch语句块中演示。

<?php
spl_autoload_register(function ($name) {
    echo "Want to load $name.<br>";
    throw new Exception("Unable to load $name.");
});

try {
    $obj = new NonLoadableClass();
} catch (Exception $e) {
    echo $e->getMessage(), "\n";
}
?>

输出:


实例 #4 自动加载PHP 5.3.0+中的异常处理,没有自定义异常机制:

本例将一个异常抛给不存在的自定义异常处理函数。

<?php
spl_autoload_register(function ($name) {
    echo "Want to load $name.<br>";
    throw new MissingException("Unable to load $name.");
});

try {
    $obj = new NonLoadableClass();
} catch (Exception $e) {
    echo $e->getMessage(), "\n";
}
?>

输出:


还有一种方法不需要通过异常去判断一个类是否能够被自动加载:

<?php
//Define autoloader 
function __autoload($className) { 
      if (file_exists($className . '.php')) { 
          require_once $className . '.php'; 
          return true; 
      } 
      return false; 
} 

function canClassBeAutloaded($className) { 
      return class_exists($className); 
} 
?>
构造函数和析构函数:
void __construct ([ mixed $args [, $... ]] )

PHP 5允许开发者在一个类中定义一个方法作为构造函数,具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

若子类中定义了构造函数则不会隐式调用其父类构造函数,要执行父类构造函数,需要在字类的构造函数中调用parent::__construct(),若子类没有定义构造函数则会如同一个普通的类方法一样从父类继承,是在构造函数没有被定义为private前提下。

#1 使用新标准的构造函数:

<?php
class BaseClass {
   function __construct() {
       print "In BaseClass constructor<br>";
   }
}

class SubClass extends BaseClass {
   function __construct() {
       parent::__construct();
       print "In SubClass constructor<br>";
   }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();
?>

输出:


为了实现向后兼容,若PHP 5在类中找不到__construct()函数并且也没有从父类继承一个的话,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为__construct()的方法却被用于其他用途时。

与其他方法不同,当__construct()被与父类__construct()具有不同参数的方法覆盖时,PHP不会产生一个E_STRICT错误信息。

自PHP 5.5.3起,在命名空间中,与类名同名的方法不再作为构造函数,这一改变不影响不在命名空间中的类。

<?php
namespace Foo;
class Bar {
    public function Bar() {
        // treated as constructor in PHP 5.3.0-5.3.2
        // treated as regular method as of PHP 5.3.3
    }
}
?>

析构函数:

PHP 5引入析构函数概念,这类似于其他面向对象的语言,如C++,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

#3 析构函数:

<?php
class MyDestructableClass {
   function __construct() {
       print "In constructor<br>";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "Destroying " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();
?>

输出:


与构造函数一样,父类的析构函数不会被引擎暗中调用,要执行父类析构函数,必须在子类的析构函数中显式调用parent::__destruct(),此外也和构造函数一样,子类若自己没有定义析构函数则会继承父类的。

析构函数即使在使用exit()终止脚本运行时也会被调用,在析构函数中调用exit()将会终止其余关闭操作的运行。

析构函数在脚本关闭时调用,此时所有的HTTP头信息已经发出,脚本关闭的工作目录有可能和SAPI(如apache)中时不同。

试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。

访问控制(可见性):

对属性或方法的访问控制,是通过在前面添加关键字public,protected或private来实现的,被定义为公有的类成员可在任何地方被访问,被定义为受保护的类成员可被其自身及其子类和父类访问,被定义为私有的成员只可被其定义所在的类访问。

属性的访问控制:类属性必须定义为公有,受保护,私有之一,若用var定义,则被视为公有。

实例#1:属性声明

<?php
/**
 * Define MyClass
 */
class MyClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj = new MyClass();
echo $obj->public; // 这行能被正常执行
echo $obj->protected; // 这行会产生一个致命错误
echo $obj->private; // 这行也会产生一个致命错误
$obj->printHello(); // 输出 Public、Protected 和 Private


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // 可以对 public 和 protected 进行重定义,但 private 而不能
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj2 = new MyClass2();
echo $obj2->public; // 这行能被正常执行
echo $obj2->private; // 未定义 private
echo $obj2->protected; // 这行会产生一个致命错误
$obj2->printHello(); // 输出 Public、Protected2 和 Undefined

?>

为了兼容,在php 4中使用var关键字对变量进行定义的方法在PHP 5中仍然有效,只是作为public关键字的一个别名,在php 5.1.3之前的版本,该语法会产生一个E_STRICT警告。

方法的访问控制:类中的方法可被定义为公有,私有或受保护啊,若没设置这些关键字,则该方法认为公有。

实例 #2:方法声明

<?php
/**
 * Define MyClass
 */
class MyClass
{
    // 声明一个公有的构造函数
    public function __construct() { }

    // 声明一个公有的方法
    public function MyPublic() { }

    // 声明一个受保护的方法
    protected function MyProtected() { }

    // 声明一个私有的方法
    private function MyPrivate() { }

    // 此方法为公有
    function Foo()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate();
    }
}

$myclass = new MyClass;
$myclass->MyPublic(); // 这行能被正常执行
$myclass->MyProtected(); // 这行会产生一个致命错误
$myclass->MyPrivate(); // 这行会产生一个致命错误
$myclass->Foo(); // 公有,受保护,私有都可以执行


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // 此方法为公有
    function Foo2()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate(); // 这行会产生一个致命错误
    }
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 这行能被正常执行
$myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行

class Bar 
{
    public function test() {
        $this->testPrivate();
        $this->testPublic();
    }

    public function testPublic() {
        echo "Bar::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Bar::testPrivate\n";
    }
}

class Foo extends Bar 
{
    public function testPublic() {
        echo "Foo::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Foo::testPrivate\n";
    }
}

$myFoo = new foo();
$myFoo->test(); // Bar::testPrivate 
                // Foo::testPublic
?>

其他对象的访问控制:

同一个类的对象即使不是同一个实例也可以互相访问对方的私有或受保护成员,这是由于在这些对象的内部具体实现的细节都是已知的。

实例 #3:访问同一个对象类型的私有成员

<?php
class Test
{
    private $foo;

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

    private function bar()
    {
        echo 'Accessed the private method.';
    }

    public function baz(Test $other)
    {
        // We can change the private property:
        $other->foo = 'hello<br>';
        var_dump($other->foo);

        // We can also call the private method:
        $other->bar();
    }
}

$test = new Test('test');

$test->baz(new Test('other'));
?>

输出:


对象继承:

PHP对象模型也使用了继承,继承会影响到类与类,对象与对象之间的关系。比如,当扩展一个类,子类就会继承父类所有公有的和受保护的方法,除非子类覆盖父类的方法,被继承的方法都会保留其原有功能。继承对于功能的设计和抽象是非常有用的,而且对于类似的对象增加新功能就无需重新再写这些公用的功能。

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

<?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'
echo '<br>';
$foo->printPHP();       // Output: 'PHP is great' 
echo '<br>';
$bar->printItem('baz'); // Output: 'Bar: baz'
echo '<br>';
$bar->printPHP();       // Output: 'PHP is great'

?>

输出:


范围解析操作符::

可用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。当在类定义之外引用到这些项目时,要使用类名。自PHP 5.3.0起,可通过变量来引用类,该变量的值不能时关键字,如self,parent和static。

实例 #1:在类的外部使用::操作符

<?php
class MyClass {
    const CONST_VALUE = 'A constant value<br>';
}

$classname = 'MyClass';
echo $classname::CONST_VALUE; // 自 PHP 5.3.0 起

echo MyClass::CONST_VALUE;
?>

输出:


self,parent和static这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。

实例 #2:在类定义内部使用:

<?php
class MyClass {
    const CONST_VALUE = 'A constant value';
}

class OtherClass extends MyClass
{
    public static $my_static = 'static var';

    public static function doubleColon() {
        echo parent::CONST_VALUE . "<br>";
        echo self::$my_static . "<br>";
    }
}

$classname = 'OtherClass';
echo $classname::doubleColon(); // 自 PHP 5.3.0 起
OtherClass::doubleColon();
?>

输出:


当一个子类覆盖其父类中的方法时,PHP不会调用父类中已被覆盖的方法,是否调用父类的方法取决于子类,该机制也作用于构造函数和析构函数,重载以及魔术方法。

实例#3: 调用父类的方法

<?php
class MyClass
{
    protected function myFunc() {
        echo "MyClass::myFunc()<br>";
    }
}

class OtherClass extends MyClass
{
    // 覆盖了父类的定义
    public function myFunc()
    {
        // 但还是可以调用父类中被覆盖的方法
        parent::myFunc();
        echo "OtherClass::myFunc()\n";
    }
}

$class = new OtherClass();
$class->myFunc();
?>

输出:


Static静态关键字:

static用于定义静态方法和静态变量以及后期静态绑定。声明类属性或方法为静态,就可以不实例化而直接访问,静态属性不能通过一个类已实例化的对象来访问,但静态方法可以。为了兼容php 4.0,若没有指定访问控制,属性和方法默认为公有,由于静态方法不需要通过对象即可调用,所以伪变量$this在静态方法中不可用。

静态属性不可由对象通过->操作符来访问,用静态方式调用一个非静态方法会导致一个E_STRICE级别的错误。就像其他所有的PHP静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式,所以可以把静态属性初始化为整数或数字,但不能初始化为另一个变量或函数返回值,也不能指向一个对象。

自PHP 5.3开始,可用一个变量来动态调用类,但该变量的值不能为关键字self,parent或static。

<?php
class Foo
{
    public static $my_static = 'foo';

    public function staticValue() {
        return self::$my_static;
    }
}

class Bar extends Foo
{
    public function fooStatic() {
        return parent::$my_static;
    }
}


print Foo::$my_static . "\n";

$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n";      // Undefined "Property" my_static 

print $foo::$my_static . "\n";
$classname = 'Foo';
print $classname::$my_static . "\n"; // As of PHP 5.3.0

print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";
?>
   </programlisting>
  </example>

  <example>
   <title>静态方法示例</title>
    <programlisting role="php">
<![CDATA[
<?php
class Foo {
    public static function aStaticMethod() {
        // ...
    }
}

Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod(); // 自 PHP 5.3.0 起
?>
抽象类:

php 5支持抽象类和抽象方法,在类名前面加abstract关键字,定义为抽象的类不能被实例化,任何一个类,若它里面至少有一个方法是被声明为抽象的,则这个类就必须被声明为抽象的,被定义为抽象的方法只能声明其调用方式,也就是参数,而不能定义其具体功能实现。

继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样,或更为宽松,比如某个抽象方法被声明为受保护的,则子类中实现的方法就应该声明为受保护的或为公有的,而不能定义为私有的,此外方法的调用方式必须匹配,即类型和参数数量必须一致,比如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突,这也适用于PHP 5.4开始的构造函数,在PHP 5.4之前的构造函数声明可以不一样。

实例 #1: 抽象类

<?php
abstract class AbstractClass
{
 // 强制要求子类定义这些方法
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);

    // 普通方法(非抽象方法)
    public function printOut() {
        print $this->getValue() . "<br>";
    }
}

class ConcreteClass1 extends AbstractClass
{
    protected function getValue() {
        return "ConcreteClass1";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass1";
    }
}

class ConcreteClass2 extends AbstractClass
{
    public function getValue() {
        return "ConcreteClass2";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass2";
    }
}

$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') ."<br>";

$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') ."<br>";
?>

输出:


实例#2:抽象类

<?php
abstract class AbstractClass
{
    // 我们的抽象方法仅需要定义需要的参数
    abstract protected function prefixName($name);

}

class ConcreteClass extends AbstractClass
{

    // 我们的子类可以定义父类签名中不存在的可选参数
    public function prefixName($name, $separator = ".") {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }
        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "<br>";
echo $class->prefixName("Pacwoman"), "<br>";
?>

输出:


对象接口:

使用接口interface,可指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容,接口是通过interface关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的,接口中定义的所有方法都必须是公有的,这是接口的特性。

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

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

实例#1 接口

<?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;
    }
}
?>

实例 #2:可扩充的接口

<?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)
    {
    }
}
?>

实例#3:继承多个接口

<?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()
    {
    }
}
?>

实例 #4:使用接口常量

<?php
interface a
{
    const b = 'Interface constant';
}

// 输出接口常量
echo a::b;

// 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。
class b implements a
{
    const b = 'Class constant';
}
?>

接口加上类型约束,提供了一种很好的方式来确保某个对象包含由某些方法,如instanceof操作符和类型约束。

Trait:

自PHP 5.4.0开始,PHP实现了一种代码复用的方法,成为trait。

Trait是为类似PHP的单继承语言而准备的一种代码复用机制,Trait为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用method。Trait和Class组合的语义定义了一种减少复杂性的方式,避免传统多继承和Mixin类相关典型问题。

Trait和Class相似,但仅仅旨在细粒度和一致的方式来组合功能,无法通过trait自身来实例化,它为传统继承增加了水平特性的组合;也就是说,应用的几个Class之间不需要继承。实例详解

实例# 1: Trait

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

优先级:从基类继承的成员会被trait插入的成员所覆盖,优先顺序是来自当前类的成员覆盖trait的方法,而trait则覆盖了被继承的方法。

实例#2 优先顺序:从基类继承的成员被插入的SayWorld Trait中的MyHelloWord方法所覆盖,其行为MyHelloWorld类中定义的方法一致,优先顺序是当前类中的方法会覆盖trait方法,而trait方法又覆盖了基类中的方法。

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

输出:


实例#3 另一个优先级顺序的例子

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

输出:


多个trait:通过都好分隔,在use声明列出多个trait,可都插入到一个类中。

实例#4 多个trait的用法

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

输出:


冲突的解决:若两个trait都插入了一个同名的方法,若没有明确解决冲突将会产生一个致命错误,为了解决多个trait在同一个类中的命名冲突,需要使用insteadof操作符来明确指定使用冲突方法中的哪一个。以上方式仅允许排除掉其他方法,as操作符可为某个方法引入别名,注意,as操作符不会对方法进行重命名,也不会影响其方法。

实例#5 冲突的解决

在本例中类Talker使用了trait A和B,由于A和B有冲突的方法,其定义了使用trait B中的smallTalk以及trait A中的bigTalk。

Aliased_Talker使用了as操作符来定义了talk来作为B的bigTalk的别名。

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>
在PHP 7.0之前,在类里定义和trait同名的属性,哪怕是完全兼容的也会抛出E_STRICT,完全兼容的意思是具有相同的访问可见性,初始默认值。

修改方法的访问控制:

使用as语法还可以用来调整方法的访问控制。

实例#6 修改方法的访问控制

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// 修改 sayHello 的访问控制
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

从trait来组成trait:正如class能够使用trait一样,其他trait也能够使用trait,在trait定义时通过使用一个或多个trait,能够组合其他trait中的部分或全部成员。

实例#7:从trait来组成trait

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

输出:


Trait的抽象成员:为了对使用的类施加强制要求,trait支持抽象方法的使用。

实例#8: 表示通过抽象方法来进行强制要求

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}


class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}


$o = new MyHelloWorld();
$o->setWorld(' world');
$o->sayHelloWorld();
?>

输出:


Trait静态成员:traits可被静态成员静态方法定义。

#实例9 静态变量

<?php
trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo "$c<br>";
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

输出:


实例#10 静态方法

<?php
trait StaticExample {
    public static function doSomething() {
        echo 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();
?>

输出:


属性:trait还可定义属性

实例#11 属性定义

 

<?php
trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
echo $example->x;
?>

输出:


trait定义了一个属性后,类就不能定义同样名称的属性,否则会产生fatal error,有种情况例外:属性时兼容的,同样的访问可见性,初始默认值,在php 7.0之前,属性是兼容的,则会有E_STRICT的提醒。

实例 #12  解决冲突

<?php
trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // PHP 7.0.0 后没问题,之前版本是 E_STRICT 提醒
    public $different = true; // 致命错误
}
?>
匿名类:

PHP 7开始支持匿名类,匿名类很有用,可创建一次性的简单对象。类似如下:

<?php

// PHP 7 之前的代码
class Logger
{
    public function log($msg)
    {
        echo $msg;
    }
}

$util->setLogger(new Logger());

// 使用了 PHP 7+ 后的代码
$util->setLogger(new class {
    public function log($msg)
    {
        echo $msg;
    }
});

可传递参数到匿名类的构造器,也可extend扩展其他类,实现接口implement interface,以及像其他普通的类一样使用trait:

<?php

class SomeClass {}
interface SomeInterface {}
trait SomeTrait {}

var_dump(new class(10) extends SomeClass implements SomeInterface {
    private $num;

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

    use SomeTrait;
});
输出(跑这个实例需要切换到PHP 7.0):


匿名类被嵌套进普通Class后,不能访问这个外部类的private,protected方法或属性,为了访问外部类protected属性或方法,匿名类可extend此外部类,为了使用外部类的private属性,必须通过构造器传进来:

<?php

class Outer
{
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1()
    {
        return 3;
    }

    public function func2()
    {
        return new class($this->prop) extends Outer {
            private $prop3;

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

            public function func3()
            {
                return $this->prop2 + $this->prop3 + $this->func1();
            }
        };
    }
}

echo (new Outer)->func2()->func3();

输出:


声明的同一个匿名类,所创建的对象都是这个类的实例。

<?php
function anonymous_class()
{
    return new class {};
}

if (get_class(anonymous_class()) === get_class(anonymous_class())) {
    echo 'same class';
} else {
    echo 'different class';
}

输出:


注意,匿名类的名称是通过引擎赋予的,若下所示,由于实现的细节,不应该去依赖这个类名。

<?php
echo get_class(new class {});

输出:


重载:PHP的重载是指动态地创建类属性和方法,通过魔术方法来实现的,这些魔术方法的参数不能通过引用传递,当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用,后面会使用 不可访问属性 和 不可访问方法 来称呼这些未定义或不可见的类属性或方法。所有的重载方法都必须被声明为public。

PHP重载与其他面向对象语言不同,传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

5.3.0版本增加魔术方法__callStatic(),可见性未设置为public或未声明为static的时候会产生一个警告

5.1.0版本增加__isset()和__unset()两个魔术方法

属性重载:

public void __set(string $name, mixed $value)

public mixed __get(string $name)

public bool __isset(string $name)

public void __unset(string $name)

在给不可访问属性赋值时,__set()会被调用

读取不可访问属性的值时,__get()会被调用

当对不可访问属性调用isset()或empty()时,__isset()会被调用

当对不可访问属性调用unset()时,__unset()会被调用

参数$name是指要操作的变量名称,__set()方法的$value参数指定了$name变量的值。

属性重载只能在对象种进行,在静态方法中,这些魔术方法将不会被调用,所以这些方法都不能被声明为static,从php 5.3.0开始,将这些魔术方法定义为static会产生一个警告。

因为PHP处理赋值运算的方式,__set()返回值将会忽略,类似的,在下面这样的链式赋值中,__get()不会被调用:

$a = $obj->b = 8;

在除isset()外的其他语言结构中无法使用重载的属性,这意味着当对一个重载属性使用empty()时,重载魔术方法将不会被调用,为避开此限制,必须将重载属性赋值到本地变量再使用empty()。

实例#1 使用__get(),__set(),__isset()和__unset()进行属性重载

<?php
class PropertyTest {
     /**  被重载的数据保存在此  */
    private $data = array();

 
     /**  重载不能被用在已经定义的属性  */
    public $declared = 1;

     /**  只有从类外部访问这个属性时,重载才会发生 */
    private $hidden = 2;

    public function __set($name, $value) 
    {
        echo "Setting '$name' to '$value'\n";
        $this->data[$name] = $value;
    }

    public function __get($name) 
    {
        echo "Getting '$name'\n";
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        $trace = debug_backtrace();
        trigger_error(
            'Undefined property via __get(): ' . $name .
            ' in ' . $trace[0]['file'] .
            ' on line ' . $trace[0]['line'],
            E_USER_NOTICE);
        return null;
    }

    /**  PHP 5.1.0之后版本 */
    public function __isset($name) 
    {
        echo "Is '$name' set?\n";
        return isset($this->data[$name]);
    }

    /**  PHP 5.1.0之后版本 */
    public function __unset($name) 
    {
        echo "Unsetting '$name'\n";
        unset($this->data[$name]);
    }

    /**  非魔术方法  */
    public function getHidden() 
    {
        return $this->hidden;
    }
}


echo "<pre>\n";

$obj = new PropertyTest;

$obj->a = 1;
echo $obj->a . "\n\n";

var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
echo "\n";

echo $obj->declared . "\n\n";

echo "Let's experiment with the private property named 'hidden':\n";
echo "Privates are visible inside the class, so __get() not used...\n";
echo $obj->getHidden() . "\n";
echo "Privates not visible outside of class, so __get() is used...\n";
echo $obj->hidden . "\n";
?>

输出:


方法重载:

public mixed __call(string $name, array $arguments)

public static mixed __callStatic(string $name, array $arguments)

在对象调用一个不可访问方法时,__call()会被调用。

在静态上下文中调用一个不可访问方法时,__callStatic()会被调用。

$name 参数时要调用的方法,$arguments参数是一个枚举数组,包含着要传递给方法$name的参数

实例#2 使用__call()和__callStatic()对方法重载

<?php
class MethodTest 
{
    public function __call($name, $arguments) 
    {
        // 注意: $name 的值区分大小写
        echo "Calling object method '$name' "
             . implode(', ', $arguments). "<br>";
    }

    /**  PHP 5.3.0之后版本  */
    public static function __callStatic($name, $arguments) 
    {
        // 注意: $name 的值区分大小写
        echo "Calling static method '$name' "
             . implode(', ', $arguments). "<br>";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');

MethodTest::runTest('in static context');  // PHP 5.3.0之后版本
?>

输出:


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

实例#1 简单的对象遍历

<?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:<br>";
       foreach($this as $key => $value) {
           print "$key => $value<br>";
       }
    }
}

$class = new MyClass();

foreach($class as $key => $value) {
    print "$key => $value<br>";
}
echo "<br>";


$class->iterateVisible();

?>

输出:


类外只能访问类的公有成员。可看到foreach()遍历了所有其能够访问到的可见属性,更进一步可实现Iterator接口,可让对象自行决定如何遍历以及每次遍历时哪些值可用。

实例 #2 实现Iterator接口的对象遍历

<?php
class MyIterator implements Iterator
{
    private $var = array();

    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }

    public function rewind() {
        echo "rewinding<br>";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var<br>";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var<br>";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var<br>";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}<br>";
        return $var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
    print "$a: $b<br>";
}
?>

输出:


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

实例 #3: 通过实现IteratorAggregate来遍历对象

<?php
class MyIterator implements Iterator
{
    private $var = array();

    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }

    public function rewind() {
        echo "rewinding<br>";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var<br>";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var<br>";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var<br>";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}<br>";
        return $var;
    }
}

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]<br><br>";
}
?>

输出:



魔术方法:

__construct(),__destruct(),__call(),__callStatic(),__get(),__set(),__isset(),__unset(),__wakeup(),__toString(),__invoke(),__set_state(),__clone()和__debugInfo()等方法在PHP中被成为魔术方法Magic methods,在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。

注意:PHP将所有以__两个下划线开头的类方法保留为魔术方法,所以在定义类方法时,除了上述魔术方法,建议不要以__为前缀。

__sleep()和__wakeup()

public array __sleep(void)

void __wakeup(void)

serialize()函数会检查类中是否存在一个魔术方法__sleep(),若存在,则该方法会先被调用,然后才执行序列化操作,此功能可用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组,若该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。

__sleep()不能返回父类的私有成员的名字,这样做会产生一个E_NOTICE级别的错误,可用Serializable接口来替代。

__sleep()常用于提交未提交的数据,或类似的清理操作,同时若有一个很大的对象,但不需要全部保存,这个功能就很好用。

与之相反,unserialize()会检查是否存在一个__wakeup()方法,若存在,则会先调用__wakeup方法,预先准备对象需要的资源,__wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其他初始化操作。

实例#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():

public string __toString(void)

该方法用于一个类被当成字符串时应怎样回应,例如echo $obj;应该显示些什么,此方法必须返回一个字符串,否则将发出一条E_RECOVERABLE_ERROR级别的致命错误。

不能再__toString()方法中抛出异常,这么做会导致致命错误。

实例#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;
?>

输出:


需要指出的是在php 5.2.0之前,__toString()方法只有在直接使用echo或print时才能生效,php 5.2.0之后,则可在任何字符串环境生效,例如通过printf()使用%s修饰符,但不能用于非字符串环境,如使用%d修饰符,自php 5.2.0开始,若将一个未定义__toString()方法的转换为字符串,会产生E_RECOVERABLE_ERROR级别错误。

__invoke()

mixed __invoke([$...])

当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用。

本特性只在php 5.3.0及以上版本有效。

实例 #3 使用__invoke()

<?php
class CallableClass 
{
    function __invoke($x) {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
echo '<br>';
var_dump(is_callable($obj));
?>

输出:


__set_state()

static object __set_state(array $properties)

从php 5.1.0开始当调用var_export()导出类时,此静态方法会被调用。本方法的唯一参数是一个数组,其中包含按array('property' ==>value, ...)格式排列的类属性。

实例#4 使用__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);

?>

输出:


__debufInfo():

array __debufInfo(void)

该方法从php 5.6.0开始使用,当对象被dump显示属性时,该方法由var_dump()调用。若在对象内没有定义该函数,则所有的公有,受保护和私有属性都会显示出来。

实例#5 使用__debugInfo()

<?php
class C {
    private $prop;

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

    public function __debugInfo() {
        return [
            'propSquared' => $this->prop ** 2,
        ];
    }
}

var_dump(new C(42));
?>

输出:


Final 关键字:

PHP 5新增一个final关键字,若父类中的方法被声明为final,则子类无法覆盖该方法,若一个类被声明为final,则不能被继承。

实例 #1 Final

<?php
class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called\n";
   }
}
// Results in Fatal error: Cannot override final method BaseClass::moreTesting()
?>

输出:


实例#2 

<?php
final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   // 这里无论你是否将方法声明为final,都没有关系
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

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

输出:


注意:属性不能被定义为final,只有类和方法才能被定义为final。

对象复制:大多数情况下,不需要完全复制一个对象来获得其属性,但以下两种情况下确实需要:

1.若有一个GTK窗口对象,该对象持有窗口相关的资源,可能会想复制一个新的窗口,保持所有属性与原来窗口相同,但必须是一个新的对象,因为若不是新的对象,则一个窗口中的改变就会影响到另一个窗口

2.若对象A保存着对象B的引用,当复制对象A时,若想要其中使用的对象不再是对象B而是B的一个副本,则必须得到对象A的一个副本。

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

$copy_of_object = clone $object;

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

void __clone(void)

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

实例 #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:<br>");
print_r($obj);
echo '<br>';
print("Cloned Object:<br>");
print_r($obj2);

?>

输出:


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

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

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

以下实例可理解以上原则:

#实例1 PHP5 对象比较(切换到5.5版本中运行 7.0版本会报警告信息)

<?php
function bool2str($bool)
{
    if ($bool === false) {
        return 'FALSE';
    } else {
        return 'TRUE';
    }
}

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

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

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

echo "<br>Instances of two different classes<br>";
compareObjects($o, $r);
?>

输出:


注意:php 扩展中可自行定义对象比较的原则。

类型约束:php5 中可使用类型约束,函数的参数可指定必须为对象(在函数原型里面指定类的名字),接口,数组(从php 5.1开始)或callable(PHP 5.4开始),不过若使用NULL作为参数的默认值,则在调用函数的时候依然可使用NULL作为实参。

若一个接口或类指定了类型约束,则其所有的子类或实现都应如此,类型约束不能用于标量类型如int或string,traits也不允许。

实例#1 类型约束

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


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

    /**
     * 第一个参数必须为递归类型
     */
    public function test_interface(Traversable $iterator) {
        echo get_class($iterator);
    }
    
    /**
     * 第一个参数必须为回调类型
     */
    public function test_callable(callable $callback, $data) {
        call_user_func($callback, $data);
    }
}

// OtherClass 类定义
class OtherClass {
    public $var = 'Hello World';
}
?>

函数调用的参数与定义时的参数不一致时会抛出一个可捕获的致命错误:

<?php
// 两个类的对象
$myclass = new MyClass;
$otherclass = new OtherClass;

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

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

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

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

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

// 正确:输出数组
$myclass->test_array(array('a', 'b', 'c'));

// 正确:输出 ArrayObject
$myclass->test_interface(new ArrayObject(array()));

// 正确:输出 int(1)
$myclass->test_callable('var_dump', 1);
?>

类型约束不只是用在类的成员函数里,也能使用在函数里:

<?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);

?>

后期静态绑定: 自PHP5.3.0开始,PHP增加了后期静态绑定的功能,用于在继承范围内引用静态调用的类。

准确说,后期静态绑定工作原理是存储了在上一个"非转发调用"non-forwarding call的类名。当进行静态方法调用时,该类名即为明确指定的那个,通常在::运算符左侧部分;当进行非静态方法调用时,即为该对象所属的类。所谓的"转发调用"forwarding call指的是通过以下几种方式进行的静态调用:self::,parent::,static::以及forward_static_call()。可用get_called_class()函数来得到被调用的方法所在的类名,static::则指出了其范围。

该功能从语言内部角度考虑被命名为后期静态绑定,后期绑定的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的,也可称之为静态绑定,因为它可以用于但不限于静态方法的调用。

self::的限制:

使用self::或__CLASS__对当前类的静态引用,取决于定义当前方法所在的类:

实例#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();
?>

输出:


后期静态绑定的用法:

后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够在上述例子test()时引用的类是B,而不是A,最终决定不引入新的关键字,而是使用已经预留的static关键字。

实例#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();
?>

输出:


注意:在非静态环境下,所调用的类即为该对象实例所属的类,由于$this->会在同一个作用范围内尝试调用私有方法,而static::则可能给出不同结果,另一个区别是static::只能用于静态属性。

实例 #3 非静态环境下使用static::

<?php
class A {
    private function foo() {
        echo "success!<br>";
    }
    public function test() {
        $this->foo();
        static::foo();
    }
}

class B extends A {
   /* foo() will be copied to B, hence its scope will still be A and
    * the call be successful */
}

class C extends A {
    private function foo() {
        /* original method is replaced; the scope of the new one is C */
    }
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //fails
?>

输出:


注意:后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止,另一方面,若静态调用使用parent::或者self::将转发调用信息。

实例#4 转发和非转发调用

<?php
class A {
    public static function foo() {
        static::who();
    }

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

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

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

C::test();
?>

输出:


对象和引用:

在PHP 5的对象编程经常提到的一个关键点是 默认情况下对象是通过引用传递的。但其实这不是完全正确的,下面可知:

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

实例#1 引用和对象

<?php
class A {
    public $foo = 1;
}  

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


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

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


$e = new A;

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

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

?>

输出:


对象序列化:

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

所有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()在php 5.4.0之后被移除了。

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

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

除了以上建议,可以在对象上使用__sleep()和__wakeup()方法对序列化/反序列化事件挂载钩子,使用__sleep()也能够让仅仅序列化对象的某些属性。

最后:

OOP的变更日志:

PHP 5 OOP模型的变更将被记录在此,功能的描述与其他相关注意事项记录在了OOP 5文档之中。


参考网址it.sunzoon.com类与对象部分。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值