以下代码摘自:
MATTZANDSTRA. 深入PHP:面向对象、模式与实践[M]. 人民邮电出版社, 2011.
第四章:高级特性
第四章 高级特性 摘录
4.1 静态属性和静态方法
<?php
class StaticExample(){
static public $aNum=0;
static public function sayHello(){
print "hello";
}
}
?>
调用上述类中的静态属性和静态方法:
StaticExample::$aNum;
StaticExample::sayHello();
seif 和 ::
从类外部访问静态属性和方法时采用
- ::
从类内部访问静态属性和方法时采用
- self
这里再附带一句话,虽然是我自己编的:
非静态的常量和属性是属于对象的,包括伪变量:$this
静态的常量和属性是属于类的,所以不用实例化对象就能调用
4.2 常量属性
设置常量属性
const
4.3 抽象类
<?php
abstract class ShopProductWriter{
protected $products=array();
public function addProduct(ShopProductWriter $shopProduct){
$this->products[]=$shopProduct;
}
abstract public function write();
}
//继承抽象类,并重写全部抽象方法
class XmlProductWriter() extends ShopProductWriter{
public function write(){
//code...
}
}
?>
抽象类可以定义属性和方法,但是不能实例化。
抽象类还中抽象方法定义方法:
abestrct public/private/protectd function Function_Name();
注意,在括号后面使用;结尾,不是{}
4.4 接口
<?php
interface Chargeable{
public function getPrice();
}
//实现接口
class ShopProduct implements Chargeable{
public function getPrice(){
return ($this->price - $this->discount);
}
}
?>
一个类只可以继承一个类,但可以实现多个接口。
4.5 延迟静态绑定:static 关键字
static又登场了,欢迎!这里的static的作用很强大,而且有有趣。
代码1:
<?php
abstract class DomainObject{
}
class User extends DomainObject{
public static function create(){
return new User();
}
}
class Document extends DomainObject{
public static function create(){
return new Document();
}
}
?>
上面的代码是希望在调用各自静态的方法:create()的时候能返回一个自身实例化的对象。
但是有一个地方很麻烦,就是要在所有的类中编写该create()方法。
代码2:黑暗进化版(亚古兽完全体黑暗进化,丧尸暴龙兽)
<?php
abstract class DomainObject{
public static function create(){
return new self();
}
}
class User extends DomainObject{}
class Document extends DomainObject{}
//当要调用create方法实例化自身类时,可以直接写
$Document=Document::create();
?>
Document::create()会实例化DomainObject,而其是抽象类,不能实例化。所以会报错。
代码3:正确进化版(亚古兽完全体进化,机械暴龙兽)
<?php
abstract class DomainObject{
public static function create(){
return new static();
}
}
class User extends DomainObject{}
class Document extends DomainObject{}
//当要调用create方法实例化自身类时,可以直接写
$Document=Document::create();
?>
这里将原来的self换成了static,就具有静态延迟效果。
上面是将静态延迟使用在了类的实例化中,下面展示一段代码,是将静态延迟使用在方法中:
代码4:
<?php
abstract class DomainObject{
private $group;
public static function getGroup(){
return "DomainObject";
}
public function __construct(){
$this->group=static::getGroup();
}
public static function create(){
return new static();
}
}
//重写DomainObject
class Document extends DomainObject{
public static function getGroup(){
return "Document";
}
}
class User extends DomainObject{
}
echo Document::create();
echo User::create();
//结果输出
//Document
//DomainObject
?>
猜猜看最后的输出结果,什么?我已经写在上面了?我是让你们猜,你们知道答案再去猜怎么了?委屈你们了?
我知道你们一定猜的对,所以我直接来介绍原理。
Document重写了DomainObject中的方法,但是我们没有直接调用getGroup()方法来获取输出。
注意当继承了DomainObject的Document和User调用其父类的方法的顺序:
- create()
- __construct()
- getGroup()
那么分歧出现在哪里呢?getGroup()上。
在DomainObject中使用
$this->group=static::getGroup();
的方式进行group参数的设定,参考
代码3:正确进化版(亚古兽完全体进化,机械暴龙兽)
中的内容我们可以理解,这里的getGroup()方法不仅仅表示getGroup()方法是静态方法,也具有一定的
延时性
当子类中有方法复写该方法时,该方法就调用子类的方法,而不是父类的方法。
而在User中并没有复写该方法,所以直接调用父类的方法。
当然,重点来了,这玩意有什么用呢?答案是我也不知道,总感觉是很牛逼的东西,所以记下来先,反正脑子存储空间足够大。
4.6 错误处理
Exception类的public方法
<?php
class DIY_Exception{
public function hello_bug(){
throw new Exception("hello expaction", 1);
}
}
$new_world=new DIY_Exception();
try{
$new_world->hello_bug();
echo "when exception start,the code will stop and never show this text";
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
?>
- getMessage()
- getCode()
- getFile()
- getLine()
- getPrevious() 获得一个嵌套的异常对象组合
- getTrace() 获得一个多维数组
- getTraceAsString() 上面信息的字符串形式
- __toString() 返回一个描述异常的字符串
其实上述方法只是把Exception中的一些参数进行了返回,而同时,
Exception $e是一个实例化的Exception对象,其具体对象就是在DIY_Expection
中
throw new Exception("hello expection");
的对象。其实我有个猜想,如果说上面介绍的函数都只是获取$e的部分信息,那么为什么不直接将$e打印出来,直接查看呢?
echo "<pre>";
var_dump($e);
Exception类的继承
<?php
class XmlException extends Exception{
private $error;
function __construct(LibXmlError $error){
$msg="错误提示信息";
$this->error=$error;
parent::__construct($msg,$error->code);
}
public function getLibXmlError(){
return $this->error;
}
?>
这里我把Exception的构造函数的代码贴一下:
public Exception::__construct([String $message=''[,int $code=0[,Throwable $previous=NULL]]])
注意点:
- $message,$code,$previous都是可选值
- $code的类型是int,不是出错误的代码
- 如果子类的$code和$message属性已经设置过了,在调用Exception父类的构造函数时可以省略默认参数
所以上面代码也可以这样写:
<?php
class XmlException extends Exception{
private $error;
function __construct(Exception $error){
$this->message="错误提示信息";
$this->error=$error;
$this->code=$error->code;
parent::__construct();
}
public function getLibXmlError(){
return $this->error;
}
}
class FileException extends Exception {}
class ConfException extends Exception {}
?>
<?php
class Conf {
function __construct(){
if(){
throw new XmlException();
}else if(){
throw new FileException();
}else if(){
throw new ConfException();
}
}
}
?>
<?php
class Runner {
public static function init(){
try{
$conf=new Conf();
}catch(XmlException $e){
//code...
}catch(FileException $e){
//code...
}catch(ConfException $e){
//code...
}catch(Exception $e){
//这个是到最后才运行的,保证异常能被处理
}
}
}
?>
4.7 Final类和方法
- final类不能有子类
- final方法不能被覆写
4.8 使用拦截器
- __get($property)
- __set($property,$value)
- __isset($property)
- __unset($property)
__call($method,$arg_array)
4.9 析构方法
<?php
class Person{
private $name;
private $age;
private $id;
function __construct($name,$age){
$this->name=$name;
$this->age=$age;
}
function setId($id){
$this->id=$id;
}
function __destruct(){
if(!empty($this->id)){
echo "数据已经存入数据库";
}
}
}
?>
当外部删除该实例化的类时,__destruct()就会被调用。
<?php
$Person=new Person();
$Person->setId(100);
unset($Person);
//__destruct()函数被调用
?>
4.10 使用__clone()复制对象
对象被clone函数调用时进行调用。
<?php
class Person{
private $name;
private $age;
private $id;
function __construct($name,$age){
$this->name=$name;
$this->age=$age;
}
function setId($id){
$this->id=$id;
}
function __clone(){
$this->id=0;
}
}
$Person=new Person('Name','Age');
$Person->setId(100);
$Person2=clone $Person;
?>
上段代码的运行顺序如下:
- 实例化Person类,Person类中的__construct()被调用。
- 通过Person类的setId方法,将id属性设置为100
- $Person2通过clone函数复制$Person类
- 在$Person2中的__clone被调用,将自身的$id属性设置为0
注意,是$Person2的__clone方法,不是$Person的__clone方法,__clone是在新生成的对象中被调用的,不是在被复制的对象中调用的。
所以,原始$Person的$id属性仍为100,而$Person2的$id属性为0。
<?php
class Account{
public $balace;
function __construct($balace){
$this->balace=$balace;
}
}
class Person{
private $name;
private $age;
private $id;
public $account;
function __construct($name,$age,Account $account){
$this->name=$name;
$this->age=$age;
$this->account=$account;
}
public function setId($id){
$this->id=$id;
}
function __clone(){
$this->id=0;
}
$Person=new Person("Name","Age",new Account(200));
$Person->setId(343);
$Person2=clone $Peron;
$Person->account->balace+=10;
echo $Person2->account->balace;
//结果是210,不是200
}
?>
上面代码运行顺序:
- 实例化Person类——$Person
- 将$Person类的id属性设置为343,这个没有关系
- $Person2浅复制(shallow copy)$Person中的属性,这里的浅复制表示$Person2中的属性与$Person中的属性指向同一个对象
- 在对$Person中的属性进行处理后,会同样作用于$Person2中的属性
为了避免上述的情况发生,即:我们希望在克隆的$Person2中能有一个新的Account属性
<?php
function __clone(){
$this->account=new Account();
}
?>
在__clone()函数中,为复制的$Person2新建一个Account对象。
注意,__clone()函数是在复制的$Person2中进行运行的!
4.11 定义对象的字符串
当把对象传递给print或echo时,会自动调用这个方法,并用方法的返回值来代替默认的输出内容。
注意,在PHP 5.2起,打印一个对象会报错!除非该对象设置有__toString()方法
<?php
class Person{
function getName(){
return "Name";
}
function getAge(){
return "Age";
}
function __toString(){
$desc.=$this->getName();
$desc.=this->getAge();
return desc;
}
}
$Person=new Person();
echo $Person;
?>
4.12 回调、匿名函数和闭包
回调函数
<?php
class Product{
public $name;
public $price;
function __construct($name,$price){
$this->name=$name;
$this->price=$price;
}
}
class ProcessSale{
private $callback;
function registerCallback($callback){
if(!is_callable($callback)){
throw new Exception("function error");
}
$this->callback[]=$callback;
}
function sale($product){
echo $product->name."<br>";
foreach($this->callback as $callback){
call_user_func($callback,$product);
}
}
}
//在外部创建回调函数后传入ProcessSale类中处理
$logger=function($product){
print "logging({$product->name})";
};
$processor=new ProcessSale();
try{
$processor->registerCallback($logger);
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
$processor->sale(new Product("Name","Price"));
echo "<hr>";
$processor->sale(new Product("Name2","Price2"));
?>
或者上面的代码也可以是下面的形式:
<?php
function logging2($product){
echo "logging({$product->name})";
}
try{
$processor->registerCallback("logging2");
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
?>
上面代码运行顺序:
- 设置匿名函数:$logger
- 实例化ProcessSale()类
- 设置回调函数,这里的回调函数就是$logger
- 调用ProcessSale类的sale函数,sale函数中对传入的$product参数遍历$callback数组中的函数
匿名函数
形式1:
<?php
$logger=function($product){
echo "logging({$product->product})";
}
?>
形式2:
<?php
class Mailer{
function doMail($product){
echo "mailing({$product->name})";
}
}
try{
$processor->registerCallback(array(new Mailer(),"doMail"));
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
?>
形式3:
<?php
class Totalize{
static function warnAmount(){
return function($product){
echo "Price:{$product->price}";
}
};
//注意上面的分号!
}
try{
$processor->registerCallback(Totalize::warnAmount());
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
?>
方式4:
<?php
class Totalize{
static function warnAmount($amt){
$count=0;
return function($product) use ($amt,$count){
$count+=$product->price+$amt;
echo $count;
}
}
}
try{
$processor->registerCallback(Totalize::warnAmount(9));
}catch(Exception $e){
echo "<pre>";
var_dump($e);
}
?>
这里与形式3的方式是一样的,只是添加了调用类中方法参数的use选项!