6.10 理解PHP面向对象的高级功能
6.10.1 使用Per-Class常量
PHP提供了Per-Class常量设计,可以通过::操作符指定常量所属的类来访问。
Scope Resolution Operator (::) // http://php.net/manual/en/oop4.php
Sometimes it is useful to refer to functions and variables in base classes or to refer to functions in classes that have not yet any instances. The :: operator is being used for this.
class A {
const PI =3.14;
public $varp = 'var1_public';
static $vars = 'var2_static';
function example() {
echo "I am the original function A::example().
\n";
$this->other(); }
function other() {echo "I am the original function A::other().
\n";}
}
class B extends A {
function example() {
echo "I am the redefined function B::example().
\n";
A::example(); }
function other() {echo "I am the redefined function B::other().
\n";}
}
echo A::PI; // 正常
echo A::$vars; // 正常
echo A::$varp; // 本行会报致命错误:Access to undeclared static property: A::$varp
A::example(); // 本行会出错:Non-static method A::example()
$b = new B;
echo $b->varp; // 正常
$b->example(); // 将输出以下内容
// I am the redefined function B::example().
// I am the original function A::example().
// I am the redefined function B::other(). // 注意,此处的调用的是B的other()
上面这个例子把所有双冒号的用法和注意点都囊括在内了。
类中的常量,随时可访问
类中的变量以及方法,如果不是static的,类的外部无法用双冒号访问
子类重载父类变量和操作后,可以通过双冒号访问父类资源,但要注意 $this 指代的变化
6.10.2 实现静态方法
static关键字适用于允许在未初始化类的情况下就可以调用的方法,这种方法等价于Per-Class常量。
注意,在一个静态方法中,不能使用$this指针,因为可能会没有可以引用的对象实例。
6.10.3 检查类的类型和类型提示
instanceof关键字可以检查一个对象是否是特定类的实例,是否是从某个类继承过来的或者是否实现了某个接口。
函数使用类型提示,可以指定必须传入的参数类类型。如果传入的类不是指定的类型,将产生一个错误。
function check_hint(B $someclass){... }
check_hint($a); // 此处传入的是父类A的实例$a,将产生一个致命错误;
而如果要求的是父类A,传入子类B的实例$b,就不会报错
6.10.4 后期静态绑定(没完全看懂)
http://php.net/manual/zh/language.oop5.late-static-bindings.php
自 PHP 5.3.0 起,PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。
准确说,后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类。所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。可用 get_called_class() 函数来得到被调用的方法所在的类名,static:: 则指出了其范围。
该功能从语言内部角度考虑被命名为“后期静态绑定”。“后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用。
在非静态环境下,所调用的类即为该对象实例所属的类。由于 $this-> 会在同一作用范围内尝试调用私有方法,而 static:: 则可能给出不同结果。另一个区别是 static:: 只能用于静态属性。
后期静态绑定的用法:后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。
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
6.10.5 克隆对象(好理解,但具体用法碰到再研究)
在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。
$copy_of_object = clone $object;
当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被自动调用,可用于修改属性的值(如果有必要的话)
void __clone ( void )
6.10.6 使用抽象类
PHP提供了抽象类,这些类不能被实例化,同样类方法只有声明没有实现。包含抽象方法的任何类自身必须是抽象的。
抽象方法和抽象类主要用于复杂的类层次关系中,该层次关系需要确保每一个子类都包含并重载了某些特定的方法,这可以通过接口来实现。
abstract class A {abstract function operationX($param1,...)... }
6.10.7 重载(更正确的词应该是 编译器钩子)
PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法。我们是通过魔术方法(magic methods)来实现的。
属性重载
public void __set (string $name , mixed $value ) //在给不可访问属性赋值时,__set() 会被调用
public mixed __get (string $name ) //读取不可访问属性的值时,__get() 会被调用
public bool __isset (string $name ) //当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
public void __unset (string $name ) //当对不可访问属性调用 unset() 时,__unset() 会被调用
方法重载
public mixed __call ( string $name , array $arguments ) //在对象中调用一个不可访问方法时,__call() 会被调用
public static mixed __callStatic ( string $name , array $arguments )
//用静态方式中调用一个不可访问方法时,__callStatic() 会被调用
来自社区的评论
This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
A word of warning! It may seem obvious, but remember, when deciding whether to use __get, __set, and __call as a way to access the data in your class (as opposed to hard-coding getters and setters), keep in mind that this will prevent any sort of autocomplete, highlighting, or documentation that your ide mite do.
6.10.8 使用__autoload()方法
void __autoload ( string $class )
你可以通过定义这个函数来启用类的自动加载。他不是一个类方法,而是一个单独的函数。
function __autoload($classname) { // 将这个函数放在文档开头,相当于多个include_once语句
$filename = "./". $classname .".php";
include_once($filename);
}
$obj = new myClass(); // 此时__autoload将自动加载:include_once("./myClass.php");
6.10.9 遍历
PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被用于遍历。
class MyClass
{
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';
protected $protected = 'protected var';
private $private = 'private var';
}
$class = new MyClass();
foreach($class as $key => $value) {
print "$key => $value\n"; // 将输出$var1 $var2 $var3
}
6.10.10 将类转换成字符串
如果在类定义中实现了__toString()函数,当尝试打印该类时,可以调用这个函数。
$p = new printable;
echo $p;
6.10.11 使用Reflection API
PHP的面向对象引擎还包括反射API。反射是通过访问已有类和对象来找到类和对象的结构和内容的能力。当使用未知或文档不详的类时,这个功能是非常有用的,例如使用经过编码的PHP脚本
require_once("page.inc");
$class = new ReflectionClass("Page");
echo $class;
这里,使用了Reflection类的__toString()方法来打印这个数据。
6.10.12 对象和引用
默认情况下对象是通过引用传递的,php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。
6.10.13 序列化对象 - 在会话中存放对象
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回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();