基本概念
PHP對待物件的方式與引用和句柄相同,即每個變數都持有物件的引用,而不是整個物件的拷貝。
當建立新物件時,該物件總是被賦值,除非該物件定義了建構函式並且在出錯時拋出了一個異常。類別應在被實例化之前定義。
建立物件時,如果該類別屬於一個名字空間,則必須使用其完整名稱。
在類別定義內部,可以用new self
和new parent
建立物件。
<code><?php $instance = new stdClass(); $assigned = $instance; $reference = & $instance; $instance->var = '$assigned will have this value.'; $instance = null; var_dump($instance); var_dump($reference); var_dump($assigned);</code>
這段程式碼的輸出如下,這是為什麼呢?
<code>null null object(stdClass)[1] public 'var' => string '$assigned will have this value.' (length=31)</code>
PHP 5.3引進了兩個新方法來建立一個物件的實例,可以使用下面的方法建立實例。
<code><?php class Test { static public function getNew() { return new static; } } class Child extends Test {} $obj1 = new Test(); $obj2 = new $obj1; var_dump($obj1 !== $obj2); // true $obj3 = Test::getNew(); var_dump($obj3 instanceof Test); // true $obj4 = Child::getNew(); var_dump($obj4 instanceof Child); // true var_dump($obj1 == $obj2); // true</code>
PHP不支援多重繼承,被繼承的方法和屬性可以透過同樣的名字重新宣告被覆寫,注意參數必須保持一致,當然建構子除外。但是如果父類別定義方法時使用了final
,則該方法不可被覆寫。可以透過parent::
來存取被覆蓋的方法和屬性,parent::
只能存取父類別中的常數const
,不能存取變數。
<code><?php class A { private $name = 'A'; const conname = 'A'; public function getName() { return $this->name; } } class B extends A { private $name = 'B'; const conname = 'B'; public function getName() { return $this->name; } public function getParent() { return parent::conname; } } class C extends B { private $name = 'C'; const conname = 'C'; public function getName() { return $this->name; } public function getParent() { return parent::conname; } } $a = new A; var_dump($a->getName()); // A $b = new B; var_dump($b->getName()); // B var_dump($b->getParent()); // A $c = new C; var_dump($c->getName()); // C var_dump($c->getParent()); // B</code>
自PHP 5.5起,關鍵字class
也可用於類別名稱的解析。使用ClassName::class
你可以取得一個字串,包含了類別ClassName
的完全限定名稱。
<code><?php namespace NS { class ClassName {} echo ClassName::class; // NS\ClassName }</code>
屬性
屬性,也就是類別的變數成員。屬性中的變數可以初始化,但是初始化的值必須是常數。這裡的常數是指 PHP 腳本在編譯階段時就可以得到其值,而不依賴執行時的資訊才能求值。
在類別的成員方法裡,存取非靜態屬性使用$this->property
,存取靜態屬性使用self::$property
。靜態屬性聲明時使用static
關鍵字。
類常數
在定義常數時不需要$
符號和存取控製關鍵字。
介面(interface)中也可以定義常數。
自動載入類別
寫物件導向的應用程式時,通常對每個類別的定義履歷表一個PHP來源檔案。當某個檔案需要呼叫這些類別時,需要在檔案開頭寫一個長長的包含檔案清單。其實,並不需要這樣,可以定義一個__autoload()
函數,它會在試圖使用尚未被定義的類別時自動呼叫。
手冊Tip說,spl_autoload_register()
提供了一種更靈活的方式來實現類別的自動加載,這個後面再看。
自動載入不可用於PHP的CLI互動模式,也就是命令列模式。
用戶輸入中可能存在危險字符,起碼要在__autoload()
時驗證下輸入。
可以透過下面的方式自動載入類別。
<code><?php function __autoload($class_name) { require_once $class_name.'.php'; } $obj1 = new MyClass1(); $obj2 = new MyClass2();</code>
對於異常處理,後面再看。
建構子和析構函式
PHP 5允許開發者在一個類別中定義一個方法作為建構函數,建構函數也不支援重載。
如果子類別中定義了建構函數,則不會隱式呼叫父類別的建構函數,否則會如同一個普通類別方法那樣從父類別繼承(前提是未被定義為private
)。要執行父類別的建構函數,需要在子類別建構子中呼叫parent::__construct()
。
與其它方法不同,當__construct()
與父類__construct()
具有不同參數時,可以覆蓋。
自PHP 5.3.3起,在命名空間中,與類別名稱同名的方法不再作為建構子。
析構函數會在某個物件的所有參考都被刪除或物件被顯示銷毀時執行。析構函數即使在使用exit()
終止腳本執行時也會被呼叫。
試圖在析構函數中拋出異常,將會導致致命錯誤。
門禁控制
類別屬性必須定義為公有、受保護、私有之一,且不能省略關鍵字。如果類別中方法沒有設定存取控制的關鍵字,則該方法預設為公有。
同一個類別的對象,即使不是同一個實例,也可以互相存取對方的私有與保護成員。範例程式如下。
<code><?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) { $other->foo = 'hello'; var_dump($other->foo); $other->bar(); } } $test = new Test('test'); $test->baz(new Test('other'));</code>
對象繼承
如果一個類別擴展了另一個,則父類別必須在子類別前被聲明。
範圍解析操作符
範圍解析運算符,簡單來說就是一對冒號,可以用來存取靜態成員、類別常數,也可以用來呼叫父類別中的屬性和方法。
當在類別定義之外引用這些項目時,要使用類別名稱。
static
使用static
關鍵字可以用來定義靜態方法和屬性,也可用於定義靜態變數以及後期靜態綁定。聲明類別屬性或方法為靜態,就可以不實例化類別而直接存取。
靜態屬性不能透過一個類別已實例化的物件來訪問,但靜態方法可以。
如果沒有指定存取控制,屬性和方法預設為公有。
用靜態方法呼叫一個非靜態方法會導致一個E_STRICT
等級的錯誤。
抽象类
PHP 5支持抽象类和抽象方法。类中如果有一个抽象方法,那这个类必须被声明为抽象的。
抽象类不能被实例化。抽象方法只是声明了其调用方式(参数),不能定义其具体的功能实现。继承抽象类时,子类必须定义父类中的所有抽象方法,且这些方法的访问控制必须和父类一样活更宽松。
方法的调用方式必须匹配。但是,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。这也试用与PHP 5.4起的构造函数。可以在子类中定义父类签名中不存在的可选参数。
<code><?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'); echo $class->prefixName('Pacwoman');</code>
对象接口
听说过接口,一直没用过。使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容,也就是说接口中定义的所有方法都是空的。接口中定义的所有方法都必须是公有的,这是接口的特性。
接口也可以继承多个接口,用逗号分隔,使用extends
操作符。类中必须实现接口中定义的所有方法,否则会报错。要实现一个接口,使用implements
操作符。类可以实现多个接口,用逗号分隔。实现多个接口时,接口中的方法不能有重名。类要实现接口,必须使用和接口中所定义的方法完全一致的方式。
接口中也可定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口覆盖。
traits
从PHP 5.4.0开始,可以使用traits
实现代码复用。Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合。
优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误,为解决冲突,需使用insteadof
操作符来指明使用冲突方法中的哪一个,这种方法仅允许排除掉其它方法。as
操作符可以将其中一个冲突的方法以另一个名称(别名)来引入。
<code><?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; B::bigTalk as talk; } } $t = new Talker(); $t->smallTalk(); // b $t->bigTalk(); // A $t->talk(); // B</code>
使用as
操作符还可以用来调整方法的访问控制,或者给方法一个改变了访问控制的别名,原版方法的访问控制规则没有改变。
<code><?php trait HelloWorld { public function sayHello() { echo 'Hello World.'; } } class MyClass1 { use HelloWorld { sayHello as protected; } } class MyClass2 { use HelloWorld { sayHello as private myPrivateHello; } }</code>
就像类能够使用trait
那样,多个trait
能够组合为一个trait
。
为了对使用的类施加强制要求,trait 支持抽象方法的使用。
<code><?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; } } $c = new MyHelloWorld; $c->setWorld('world'); $c->sayHelloWorld();</code>
如果trait
定义了一个属性,那类将不能定义同样名称的属性,否则会产生错误。
重载
PHP提供的重载是指动态地创建类属性和方法,与其它绝大多数面向对象语言不同。通过魔术方法来实现。当使用不可访问的属性或方法时,重载方法会被调用。所有的重载方法都必须被声明为public
。
使用__get()
,__set()
,__isset()
,__unset()
进行属性重载,示例如下。
<code><?php class PropertyTest { private $data = array(); public $declared = 1; private $hidden = 2; public function __set($name, $value) { echo "Setting $name to $value. " . '<br>'; $this->data[$name] = $value; } public function __get($name) { echo "Getting $name. <br>"; if(array_key_exists($name, $this->data)) { return $this->data[$name]; } return null; } public function __isset($name) { echo "Is $name set? <br>"; return isset($this->data[$name]); } public function __unset($name) { echo "Unsetting $name. <br>"; unset($this->data[$name]); } } $obj = new PropertyTest; $obj->a = 1; var_dump($obj->a); var_dump(isset($obj->a)); unset($obj->a); var_dump(isset($obj->a)); var_dump($obj->declared); var_dump($obj->hidden);</code>
输出结果如下:
<code>Setting a to 1. Getting a. int 1 Is a set? boolean true Unsetting a. Is a set? boolean false int 1 Getting hidden. null</code>
在对象中调用一个不可访问方法时,__call()
会被调用。用静态方式中调用一个不可访问方法时,__callStatic()
会被调用。参数为调用方法的名称和一个枚举数组,注意区分大小写。
使用__call()
和__callStatic()
对方法重载,示例如下。
<code><?php class MethodTest { public function __call($name, $arguments) { echo "Calling object method $name " . implode(', ', $arguments) . '<br>'; } public static function __callStatic($name, $arguments) { echo "Calling static method $name " . implode(', ', $arguments) . '<br>'; } } $obj = new MethodTest; $obj->runTest('in object context'); MethodTest::runTest('in static context');</code>
遍历对象
对象可以用过单元列表来遍历,例如用foreach
语句。默认所有可见属性都将被用于遍历。
<code><?php class MyClass { public $var1 = 'value 1'; public $var2 = 'value 2'; public $var3 = 'value 3'; private $var4 = 'value 4'; protected $var5 = 'value 5'; } $obj = new MyClass; foreach($obj as $key => $value) { echo "$key => $value <br>"; }</code>
示例程序2实现了Iterator接口的对象遍历,示例程序3通过实现IteratorAggregate来遍历对象。
魔术方法
PHP 将所有以__
(两个下划线)开头的类方法保留为魔术方法。定义类方法时,除魔术方法外,建议不要以__
为前缀。
前面遇到过的魔术方法有:__construct()
,__destruct()
,__call()
,__callStatic()
,__get()
,__set()
,__isset()
,__unset()
。后面将会介绍:__sleep()
,__wakeup()
,__toString()
,__invoke()
,__set_state()
,__clone()
和__debugInfo()
。
__sleep
和__wakeup
不清楚具体做什么用的,示例程序中给出了个数据库连接的例子。
__toString
方法用于一个类被当成字符串时应怎样回应。此方法必须返回一个字符串,且不能再方法中抛出异常。如果将一个未定义__toString()
方法的对象转换为字符串,将产生错误。
当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被调用。
当调用var_export()
导出类时,__set_state()
会被调用。
当调用var_dump()
时,__debugInfo
会被调用。PHP 5.6新加入,没合适的环境无法测试。
final
果父类中的方法被声明为final
,则子类无法覆盖该方法。如果一个类被声明为final
,则不能被继承。属性不能被定义为final
,只有类和方法才能被定义为final
。
对象复制
多数情况,我们不需要完全复制一个对象,但有时确实需要。对象复制可以通过clone
关键字来完成。这种复制是通过调用对象的__clone()
方法实现的,但是对象中的__clone()
方法不能被直接调用。
对象比较
比较运算符==
为真的条件是:两个对象的属性和属性值都相等,而且两个对象是同一个类的实例。
继承与统一个基类的两个子类的对象不会相等==
。
<code><?php class Base {} class A extends Base {} class B extends Base {} $a = new A; $b = new B; var_dump($a == $b); // false</code>
全等运算符===
为真的条件是:两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
类型约束
类型约束是指函数的参数可以指定必须为对象、接口、数组或者callable
类型。但是类型约束不能用于标量类型如int
或string
,traits
也不允许。类型约束允许NULL
值。
后期静态绑定
后期静态绑定,用于在继承范围内引用静态调用的类。
转发调用,指的是通过以下几种方式进行的静态调用:self::
,parent::
,static::
以及forward_static_call()
。
后期静态绑定的工作原理是,存储了上一个非转发调用的类名。
当进行静态方法调用时,该类名即为明确指定的那个;当进行非静态方法调用时,即为该对象所属的类。
使用self::
或者__CLASS__
对当前类的静态引用,取决于定义当前方法所在的类。
<code><?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 B::who(); // B</code>
用static::
关键字表示运行时最初调用的类,后期静态绑定就是这样使用。如下面程序所示,也就是说调用test()
时引用的类是B
而不是A
。
<code><?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 B::who(); // B</code>
示例2给出的是非静态环境下使用static::
。
后期静态绑定的解析,会一直到取得一个完全解析了的静态调用为止。另外,如果静态调用使用parent::
或self::
将转发调用信息。
<code><?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(); // ACC</code>
那么问题来了,结果为什么是这样的呢?
对象和引用
默认情况下,对象时通过引用传递的。但这种说法不完全正确,其实两个对象变量不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
对象序列化
所有PHP里面的值,都可以使用函数serialize()
来返回一个包含字节流的字符串来表示。unserialize()
函数能够重新把字符串变为原来的值。
序列化一个对象,将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()
一个对象,这个对象的类必须已经定义过。在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。
(全文完)
以上就介绍了类与对象 - PHP手册笔记,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。