PHP 核心技术 --面向对象
继承和多态
类的组合与继承
- 假设我们有两个类,一个 person,另外一个是 family;在 family 类中我们创建 person 类中的对象,并且我们把这个对象视为 family 类的一个属性,并调用它的方法处理问题,那么这种复用方式也称为组合。
- 类与类之间还有一种父与子的关系,子类可以继承父类的属性和方法,我们称之为继承。在继承里,子类拥有父类的属性和方法,同时子类也有自己的属性和方法。
<?php
class person{
public $name = 'Tom';
public $gender;
static $money = 10000;
public function __construct(){
echo '这里是父类',PHP_EOL;
}
public function say(){
echo $this->name,"\t is ",$this->gender,"\r\n";
}
}
class family extends person{
public $name;
public $gender;
public $age;
static $money = 1000;
public function __construct(){
parent::__construct();
echo '这里是子类',PHP_EOL;
}
public function say(){
parent::say();
echo $this->name,"\t is \t",$this->gender,",and is \t ",$this->age,PHP_EOL;
}
public function cry(){
echo parent::$money,PHP_EOL;
echo '% >_< %',PHP_EOL;
echo self::$money,PHP_EOL;
echo "(*^_^*)";
}
}
$poor = new family();
$poor->name = 'Lee';
$poor->gender = 'female';
$poor->age = 25;
$poor->say();
$poor->cry();
返回结果
这里是父类
这里是子类
Lee is female
Lee is female,and is 25
10000
% >_< %
1000
(*^_^*)%
- 组合和继承都是提高代码可重用行的手段。在设计对象模型时,可以按照语义识别类之间的组合关系和继承关系。
例如:
- 继承是一种“是,像”的关系,组合则是一种“需要”的关系 【父亲和儿子应该是继承关系,父亲和家庭应该是组合关系 】
- 组合偏重与和局部的关系,继承偏重与父与子的关系。
- 从方法复用的角度考虑,如果两个类具有很多相同的代码和方法,我们就可以从这两个类中抽象出一个父类,提供公共方法,然后两个类作为子类,提供个性方法,这时继承更好。
- 组合的限制很少,组合之间的类可以关系很小(体现为复用代码),设置没有关系。
编程中
- 继承和组合的取舍往往都不是这么直接明了,很难说出二者是“像”的关系还是需要的关系。甚至说把它拿到现实世界中建模,更加无法决定是继承还是组合的关系了。这时,它该如何办,有什么标准?这个标准就是:低耦合
低耦合
- 耦合是一个软件结构内不同模块之间互联程度的度量,也就是不同模块之间的依赖关系。
- 低耦合是指模块和模块之间,尽可能地使模块间独立存在;模块与模块之间的接口尽量少而简单。现代的面向对象的思想不强调为真实世界建模,变得更加理性化一些,把目标放在解耦上。
解耦
- 目的是为了解除模块与模块之间的依赖。
- 继承和组合二者在语义上难以区分,但是我们更倾向于使用组合。
- 继承存在的问题:
- 继承破坏封装性;(鸟类为父类,而鸭子和鸵鸟作为子类,它们却拥有飞翔的方法)
- 继承是紧耦合的,使得子类和父类捆绑在一起。组合仅是通过唯一接口和外部进行通信,耦合度低于继承。
- 继承扩展复杂,随着继承层数的增加和子类的增加,将涉及大量方法重写。使用组合,可以根据类型约束,实现动态组合,减少代码。
- 不恰当的使用继承可能违反现实世界中的逻辑;
组合
<?php
class car{
public function addoil(){
echo "Add oil \r\n";
}
}
class bmw extends car{
}
class benz{
public $car;
public function __construct(){
$this->car = new car();
}
public function addoil(){
$this->car->addoil();
}
}
$bmw = new bmw();
$bmw->addoil();
$benz = new benz();
$benz->addoil();
- 在创建组合对象时,组合需要一一创建局部对象,这一点程度上增加了一些代码,而继承不需要这一步,继承拥有父类的方法,可以直接使用
如何使用继承:
- 精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不多于三层;
- 对于不是专门用于被继承的类,禁止其被继承,也就是使用 final 修饰符。使用 final 修饰符即可防止重要方法被非法覆写,又能给编辑器寻找优化的机会;
- 优先考了用组合关系提高代码的可重用性;
- 子类是一直特殊的类型,而不只是父类的一个角色;
- 子类扩展,而不是覆盖或者使父类的功能失效;
- 底层代码多用组合,顶层/业务层代码多用继承。底层用组合可以提供效率,避免对象臃肿。顶层代码用继承可以提高灵活性,让业务使用更方便。
既要组合的灵活,又要继承的简洁
- 多重继承,一个类可以同时继承多个父类,组合两个父类的功能;缺点:多重继承过于灵活,并且会带来“零星问题”,故为其使用带来了不少困难,模型变得复杂起来。
- traits php5.4引入的新的语法结构,可以方便我们实现对象的扩展,是除extend,implements外的另外一种扩展对象的方式,traits 即可以使单继承模式的语言获得多重继承的灵活,又可以避免多重继承带来的种种问题
traits的用法
- 通过在类中使用use关键字声明要组合的Trait名称,而具体某个Trait的声明使用trait关键词,Trait不能直接实例化
<?php trait Drive { public $carName = 'BMW'; public function driving() { echo "driving {$this->carName}\n"; } } class Person { public function age() { echo "i am 18 years old\n"; } } class Student extends Person { use Drive; public function study() { echo "Learn to drive \n"; } } $student = new Student(); $student->study(); $student->age(); $student->driving();
Learn to drive i am 18 years old driving BMW
- Student类通过继承Person,有了age方法,通过组合Drive,有了driving方法和属性carName。
如果Trait、基类和本类中都存在某个同名的属性或者方法,最终会保留哪一个呢
<?php
trait Drive {
public function hello() {
echo "hello 周伯通\n";
}
public function driving() {
echo "周伯通不会开车\n";
}
}
class Person {
public function hello() {
echo "hello 大家好\n";
}
public function driving() {
echo "大家都会开车\n";
}
}
class Student extends Person {
use Drive;//trait 的方法覆盖了基类Person中的方法,所以Person中的hello 和driving被覆盖
public function hello() {
echo "hello 新学员\n";//当方法或属性同名时,当前类中的方法会覆盖 trait的 方法,所以此处hello会覆盖trait中的hello
}
}
$student = new Student();
$student->hello();
$student->driving();
hello 新学员
周伯通不会开车
- 当方法或属性同名时,当前类中的方法会覆盖 trait的 方法,而 trait 的方法又覆盖了基类中的方法。
如果要组合多个Trait,通过逗号分隔 Trait名称:
use Trait1, Trait2;
<?php
trait Hello {
public function sayHello() {
echo "Hello 我是周伯通\n";
}
}
trait World {
use Hello;
public function sayWorld() {
echo "hello world\n";
}
abstract public function getWorld();
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
public static function doSomething() {
echo "Doing something\n";
}
}
class HelloWorld {
use World;
public function getWorld() {
return 'do you get World ?';
}
}
$Obj = new HelloWorld();
$Obj->sayHello();
$Obj->sayWorld();
echo $Obj->getWorld() . "\n";
HelloWorld::doSomething();
$Obj->inc();
$Obj->inc();
Hello 我是周伯通
hello world
do you get World ?
Doing something
1
2
语言中得多态
- 多态准确的含义是:同一类的对象收到相同消息时,会得到不同的结果。而这个消息是不可预测的。多态:顾名思义,就是多种状态,也就是多种结果
- 多态性是一种通过多种状态或阶段描述相同对象的编程方式。它真正的意义在于:实际开发中,只要关心一个接口或基类的编程,而不必关心一个对象所属于的具体类
案例
- 通过判断传入的对象所属的类不同来调用其同名方法来实现'多态'
<?php class employee{ protected function working(){ echo '本方法需要重载才能运行'; } } class teacher extends employee{ public function working(){ echo '教书'; } } class coder extends employee{ public function working(){ echo '敲代码'; } } function doprint($obj){ //get_class 获取当前对象调用的类名 if(get_class($obj)=='employee'){ echo 'Error'; }else{ $obj->working(); } } doprint(new teacher()); doprint(new coder()); doprint(new employee());
- 通过接口实现多态
<?php interface employee{ public function working(); } class teacher implements employee{ public function working(){ echo '教书'; } } class coder implements employee{ public function working(){ echo '敲代码'; } } function doprint(employee $i){ $i->working(); } $a=new teacher; $b=new coder; doprint($a); doprint($b);
- 在这段代码中,doprint函数的参数为一个接口类型的变量,符合'同一类型,不同结果'这一条件,具备多态的一般特征。
总结
- 多态指同一类对象在运行时的具体化。
- php语言是弱类型的,实现多态更简单,更灵活。
- 类型转换不是多态。
- php中父类和子类看作'继父'和'继子'关系,他们存在继承关系,但不存在血缘关系,因此子类无法向上转型为父类,从而失去多态最典型的特征。
- 多态的本质就是 if -- else -- ,只不过实现的层次不同。
面向接口编程
面向接口编程并不是一种新的编程范式,在三大范式中并没有面向接口。这里是侠义的接口,即 interface 关键字。广义的接口可以是任何一个对外提供服务的出口,比如提供数据传输的USB接口,淘宝网对其他网站开发的支付宝接口。
接口的作用
定义
- 接口定义一套规范,描述一个‘物’的功能,要求如果现实中的“物”想成为可用,就必须实现这些基本的功能。通俗的来说:对于实现我的所有类,看起来都应该像我这个样子。
- 采用一个特定接口的所有代码都必须知道对于那个接口会调用什么方法;接口常用来作为类和类之间的一个'协议'。接口是抽象类的实体,接口中所有方法都是抽象的,没有一个程序实体;接口除了可以包含方法外,还能包含常量。
案例
- 我们用接口描述发动机,要求接口必须又“run”功能,至于如何实现(摩托车或者汽车),应该是什么样(前驱还是后驱),都不是接口所要关心得,因为接口为抽象而生。
- 接口作为质检总局,要判断这辆车是否合格,按照“接口”得定义规则一条条验证,这辆车不能“run”,那么就为废品,不能通过验收;但是,如果,如果汽车实现了接口中原没有不存在得方法music,并不认为有什么错误,因为它不在我们得检查范围。
- 接口就是一种契约,在程序中,接口的方法必须被全部实现,否则不能“通过验收”。
- 我们首先定义了一个机动车的接口,里面含有所有类必须的方法也就是发动机的功能。
- 然后,我们定义了一个实现这个接口并新增了飞翔方法的飞机类,还有一个实现接口并没有实现飞翔的机动车类,最后,我们定义了一个机器检测类对所有定义的类进行检测(用类型约束指定要测试的是mobile这个接口),检测定义的类是否有飞翔方法,没有则报错。
案例总结
- 这种构念实际上是错误的,不符合接口的语义。但是在php里,只关心是否实现这个方法,而并不关心接口语义是否正确。
- 按理说,接口应该是起一个强制规范和契约的作用,但是这里对接口的约束并没有起效,也打破了契约,对监测站这个类(machine)的行为失去控制。正常来说,如果你打破了我们之间的契约,行为变得无法控制,这是非法的。这符合逻辑,也符合现实世界,在才真正起到接口作为规范的作用了。
- 接口不仅规范了接口的实现者,还规范接口的执行者,不允许调用接口中本不存在的方法。当然这并不是说一个类如果实现了接口,就只能实现接口中才有的方法,而是说,如果针对的是接口,而不是具体的类,则只能按照接口的约定办事。这样的语法规定对接口的使用是有利的,让程序更健壮。从这个角度来说,为了保证接口的语义,通常一个接口的实现类仅实现该接口所具有的方法,做到专一,当然也不是一场不变的。
案例的思考
- 从上面的例子可以看出,php 里接口作为规范和契约的作用打了折扣。根据这个例子,我们很自然的可以想到接口的使用场合,比如数据库操作,缓存实现等。而不用关心我们所面对的数据库是MySql 还是 Oracle ,只关心面向Database接口进行具体业务逻辑相关的代码。
- 在这里Database 和 employee 一样,针对这个接口实现就好了。缓存功能也是一样,我们不关心缓存时内存缓存还是文件缓存,或者是数据库缓存,只关注它是否实现了cache接口,只要它实现了 cache 接口,就实现了写入缓存和读取缓存中的数据以及清楚缓存这几个关键的功能点。
php接口的思考
简介
- 由于 php 是弱类型,且强调灵活。并不推荐大规模的使用接口。而仅在部分"内核"代码中使用接口,因为php中的接口已经失去很多接口应该具有的语义。从语义上考虑,可以更多的使用抽象类。
- php5 对面向对象的特性做了许多加强,其中有一个 SPL (标准php库) 的尝试。
- SPL 中实现一些接口,其中最主要的就是 Iterator 迭代器接口,通过这个接口,就能使对象能够用于foreach结构,从而在使用形式上比较统一。
- 接口都是对多重继承的一种变相的实现,在继承的时候,我们也可以使用traits来实现。
总结
- 接口作为一种规范和契约存在。作为规范,接口应性该保证可用性;作为契约,接口应该保证可控性。
- 接口只是一个声明,一旦使用 interface 关键字,就必须实现它。可以由自己来实现(外部接口),也可以由系统实现(内部接口)。接口本身什么都不做,但是它可以告诉我们它能做什么。
- php 中的接口存在两个不足,一是没有契约限制,二是确实足够多的内部接口.
- 接口的各种应用很灵活,设计模式中也有很大一部分是围绕接口展开的。
补充
抽象类
- 抽象类是指在 class 前加了 abstract 关键字且存在抽象方法(在类方法function关键字前加了abstract关键字)的类。
- 抽象类不能被直接实例化。抽象类中只定义(或部分实现)子类需要的方法。子类可以通过继承抽象类并通过实现抽象类中的所有抽象方法,使抽象类具体化。
- 如果子类需要实例化,前提是它实现了抽象类中的所有抽象方法。如果子类没有全部实现抽象类中的所有抽象方法,那么该子类也是一个抽象类,必须在class前面加上abstract关键字,并且不能被实例
抽象类和接口的区别
相同点
- 两者都是抽象类,都不能实例化。
- interface实现类及abstract class的子类都必须要实现已经声明的抽象方法。
不同点
- interface需要实现,要用implements,而abstract class需要继承,要用extends。
- 一个类可以实现多个interface,但一个类只能继承一个abstract class。
- interface强调特定功能的实现,而abstract class强调所属关系。
- 尽管interface实现类及abstract class的子类都必须要实现相应的抽象方法,但实现的形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方法体),实现类必须要实现。而abstract class的子类可以有选择地实现。这个选择有两点含义:a) abstract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在 abstract class中必须定义方法体;b) abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
- abstract class是interface与class的中介。abstract class在interface及class中起到了承上启下的作用。一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖。另外,它还可以定义自己的实例变量,以供子类通过继承来使用。
- 接口中的抽象方法前不用也不能加abstract关键字,默认隐式就是抽象方法,也不能加final关键字来防止抽象方法的继承。而抽象类中抽象方法前则必须加上abstract表示显示声明为抽象方法。
- 接口中的抽象方法默认是public的,也只能是public的,不能用private,protected修饰符修饰。而抽象类中的抽象方法则可以用public,protected来修饰,但不能用private。
interface的应用场合
- 类与类之间需要特定的接口进行协调,而不在乎其如何实现。
- 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
- 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
- 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。
abstract class 的应用场合
- 在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它
- 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖
- 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
- 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。
<?php interface mobile{ public function run();//驱动方法 } class plain implements mobile{ public function run(){ echo '我是飞机'; } public function fly(){ echo '飞翔'; } } class car implements mobile{ public function working(){ echo '我是汽车'; } } class machine{ function demo(mobile $a){ //mobile接口没有这个方法 //寻找可以飞翔的机器/类 $a->fly(); } } $obj=new machine; $obj->demo(new plain()); //运行成功 $obj->demo(new car()); //运行失败 abstract class A { /** 抽象类中可以定义变量 */ protected $value1 = 0; private $value2 = 1; public $value3 = 2; /** 也可以定义非抽象方法 */ public function my_print() { echo "hello,world/n"; } /** * 大多数情况下,抽象类至少含有一个抽象方法。抽象方法用abstract关键字声明,其中不能有具体内容。 * 可以像声明普通类方法那样声明抽象方法,但是要以分号而不是方法体结束。也就是说抽象方法在抽象类中不能被实现,也就是没有函数体“{some codes}”。 */ abstract protected function abstract_func1(); abstract protected function abstract_func2(); } abstract class B extends A { public function abstract_func1() { echo "implement the abstract_func1 in class A/n"; } /** 这么写在zend studio 8中会报错*/ //abstract protected function abstract_func2(); } class C extends B { public function abstract_func2() { echo "implement the abstract_func2 in class A/n"; } } Class B extends A{};
反射
- 面向对象编程中对象被赋予了自省的能力,而自省的过程就是反射
- 反射,直观理解就是根据到达地找到出发地和来源。比方说,我给你一个光秃秃的对象,我可以仅仅通过这个对象就能知道它所属的类,拥有的哪些方法。
- 反射指在 PHP 运行状态中,拓展分享 PHP 程序,导出和提出关于类,方法,属性,参数等详细信息,包括注释。这种动态获取信息已经动态调用对象方法的功能称为反射。
如何使用反射Api
案例
<?php
class person{
public $name;
public $gender;
public function say(){
echo $this->name,"\tis",$this->gender,"\r\n";
}
public function __set($name, $value)
{
echo "Setting $name to $value \r\n";
$this->name=$value;
}
public function __get($name)
{
if (!isset($this->name)) {
echo "未设置";
$this->$name="正在设置默认值";
}
return $this->name;
}
}
$student=new person();
$student->name ='Tom';
$student->gender="male";
$student->age=24;
- 现在,要获取这个 student 对象的方法和属性
//获取对象属性列表 $reflect=new ReflectionObject($student); $props =$reflect->getProperties(); foreach ($props as $prop) { print $prop -> getName() ."\n"; } //获取对象方法列表 $m=$reflect->getMethods(); foreach ($m as $prop) { print $prop->getName()."\n"; }
- 也可以不用反射Api ,使用class函数,返回对象属性的关联数组及相关信息
//返回对象属性的关联数组 var_dump(get_object_vars($student)); //类属性 var_dump(get_class_vars($student)); //返回由类的方法名组成的数组 var_dump(get_class_methods(get_class($student)));
- 假如这个对象是从其他页面传过来的,获取它属于哪个类
//获取对象属性列表所属的类 echo get_class($student);
- 反射Api的功能显然更强大,甚至能还原这个类的原型,包括方法的访问权限
<?php //反射获取类的原型 $obj =new ReflectionClass('person'); $className= $obj->getName(); $Methods = $Properties = array(); foreach ($obj->getProperties() as $v) { $Properties[$v->getName()]=$v; } foreach ($obj->getMethods() as $v) { $Methods[$v->getName()]=$v; } echo "class {$className}\n{\n"; //ksort 按照键名对关联数组进行升序排序 is_array($Properties) && ksort($Properties); foreach ($Properties as $k => $v) { echo "\t"; echo $v->isPublic()?'public':'',$v->isPrivate()?'private':'', $v->isProtected()?'protected':'', $v->isStatic()?'statoc':''; echo "\t{$k}\n"; } echo "\n"; if(is_array($Methods)) ksort($Methods); foreach ($Methods as $k => $v) { echo "\tfunction {$k}(){}\n"; } echo "}\n";
-
输出
class oersin{ public gender public name function __get(){} function __set(){} function say(){} }
PHP 手册中关于反射的 API 更有几十个,可以说,反射完整地描述了一个类或者对象的原型。反射不用于用于类和对象,还可以用于函数,扩展模块,异常等。
反射的作用
反射可以用于文档生成,因此可以用它对文件里的类进行扫描,逐个生成描述文档。【探究类的内部结构】
使用反射做hook实现插件功能/动态代理
<?php
class mysql{
function connect($db){
echo "连接到数据库${db[0]}\r\n";
}
}
class sqlproxy{
private $target;
function __construct($tar)
{
$this->target[]=new $tar();
}
function __call($name, $args)
{
foreach ($this->target as $obj) {
$r=new ReflectionClass($obj);
if ($method= $r->getMethod($name)) {
if ($method->isPublic() && !$method->isAbstract()) {
echo "方法前拦截记录LOG\r\n";
$method->invoke($obj,$args);
echo "方法后拦截\r\n";
}
}
}
}
}
$obj=new sqlproxy('mysql');
$obj->connect('member');
- 这里,真正的操作类是mysql类,但是sqlproxy 类实现了根据动态传入参数,代替实际的类运行,并且在方法运行前后进行拦截,并且动态的改变类中的方法和属性,这就是简单的动态代理。
总结
- 平时开发中,真正用到反射的地方并不多:一个是对对象进行调式,另一个是获取类的信息,在MVC 和插件开发中,使用反射很常见,但是反射的消耗很大,在可以找到代替方案的情况下,就不要滥用。
- PHP 有Token 函数,可以通过这个机制实现一些反射功能。从简单灵活的角度讲,使用已反射API 是可取的。
- 很多时候,善于反射能保持代码的优雅和简洁,但反射也会破坏类的封装性,因为反射可以使原本不应该暴露的方法或属性被强制暴露出来,这既是优点也是缺点。
命名空间和自动加载
命名空间
- 简介:是一种封装事务的方法
- 案例:用来给目录下的相关角色分组,在平时一个目录下不能存在相同的文件名,也不能在目录外来访问这个目录里的文件,命名空间的引用就是为了解决此类问题
- 主要解决的问题:用户编写的代码和php内部的类/函数/常量/第三方类/常量之间的名字相冲突;为很长的标识符名称(类名),创建别名,提高代码的可读性
自动加载
目的
- 为了能使用一个类,需要先加载这个了类所在的文件。
说明
- bool spl_autoload_register ([ callback $autoload_function ] ) 注册 __autoload() 函数
- 将函数注册到 SPL __autoload 函数栈中。如果该栈中的函数尚未激活,则激活它们。
- 如果在你的程序中已经实现了 autoload 函数,它必须显式注册到 autoload 栈中。因为 spl_autoload_register() 函数会将Z end Engine 中的__autoload函数取代为 spl_autoload() 或 spl_autoload_call()。
诞生
- php5.1.2 之前,只有 include/require 加载;
- php5.1.2 之后,提供了自动加载的函数 spl_autoload_register() 函数
参数
- autoload_function 欲注册的自动装载函数。没有提供,则自动使用 autoload 的默认实现函数 spl_autoload();
- throw 设置了 autoload_function 无法成功注册时,spl_autoload_register() 是否抛出异常;
- prepend 如果是 true,spl_autoload_register() 会添加函数到队列之首,而非队列之尾。(当一个项目里有多个自动加载器时,这个参数就会很有用)
注意
如果使用了命名空间,那么由于在使用命名空间的时候,class类名中带了命名空间限制符,在解析并加载时需要注意并特殊处理。
- 正是有了命名空间和自动加载,才让php发展出了composer包机制,从而可以在任意的引入第三方类库,促使php走向更大规模的企业协作开发。
__autoload
- 这是一个自动加载函数,在PHP5中,当我们实例化一个未定义的类时,就会触发此函数。
- 运行index.php后正常输出hello world。在index.php中,由于没有包含printit.class.php,在实例化printit时,自动调用__autoload函数,参数$class的值即为类名printit,此时printit.class.php就被引进来了。
spl_autoload_register()
- 将autoload 换成 loadprint 函数。但是 loadprint 不会像autoload 自动触发,这时spl_autoload_register() 就起作用了,它告诉PHP碰到没有定义的类就执行 loadprint()。
spl_autoload_register() 调用静态方法
备注
SPL是Standard PHP Library(标准PHP库)的缩写。它是PHP5引入的一个扩展库,其主要功能包括autoload机制的实现及包括各种Iterator接口或类。SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。
如果同时用spl_autoload_register注册了一个类的方法和__autoload函数,那么,会根据注册的先后,如果在第一个注册的方法或函数里加载了类文件,就不会再执行第二个被注册的类的方法或函数。反之就会执行第二个被注册的类的方法或函数。
异常和错误处理
php 中错误级别
php错误就是会使脚本运行不正常得情况
- deprecated 是最低级得错误,表示'不推荐,不建议'。如再 php5 中使用ereg系列得正则匹配函数就会报此类错误。在种错误一般由于使用不推荐得,过时的函数或语法造成的。虽然不影响PHP 的正常流程,但一般情况下建议修正。
- notice 这种错误一般告诉你语法中存在不当的地方。如使用变量但是未定义就会报此错。最常见的,数组索引是字符时没有加引号,php 就视为一个常量,先查找常量,找不到再视为变量。虽然 PHP 是脚本语言,语法要求不严,但是仍然建议对变量进行初始化。这种错误不影响 PHP 正常流程。
- warning 是级别计较高的错误,在语法中出现很不恰当的情况时才会出现此错误,比如函数参数不匹配,这种级别的错误会导致得不到预期结果,故需要修改代码。
- fetal error 是更高级别的错误,是致命错误,直接导致 PHP 流程结束,后面的代码不再执行。
prase error 最高级别错误,是语法解析错误。
PHP 的错误处理机制
PHP 有一套错误处理机制,可以使用 set_error_handle 接管 PHP 错误处理,也可以使用 trigger_error函数主动抛出一个错误。
set_error_hadler(error_function,error_types) 函数
说明
- 设置用户自定义的错误处理函数。函数用于创建运行期间的用户的自己的错误处理方法。它需要先创建一个错误处理函数,然后设置错误级别。
参数
- error_function:规定发生错误时运行的函数。必需
- error_types:规定在哪个错误报告级别会显示用户定义的错误。可选
案例
如果在脚本执行前发生错误,由于在那时自定义程序还没有注册,因此就不会用到这个自定义错误处理程序,这先实现一个自定义的错误处理函数。
function customError($error,$errstr,$errfile,$errline){
echo "错误代码:</b>[{$error}]${errstr}\r\n";
echo "错误所在的代码行:{$errline}文件{$errfile}\r\n";
echo "PHP版本",PHP_VERSION,"(",PHP_OS,")\r\n";
}
//E_ALL 所有的错误 E_STRICT
set_error_handler("customError",E_ALL | E_STRICT);
$a=array('o'=>2,3,6,7);
echo $a[o];
- 在这个函数里,可以对错误的详情进行格式化输出,也可以做任何想要做的事情,比如判断当前环境和权限给出不同的错误提示。也可以使用error_log 函数将错误记入 log 文件,可以细化处理,针对 $error 的不同进行对应的处理。
- 自定义的错误处理函数一定要有这四个输入变量 $error $errstr $errfile $errline
- error 是一组常量,代表错误的等级,同时也有一组整数和其对应,但一般使用字符串表示,这样语义更好一点。比如E_WARNING,其二进制掩码为4,表示警告信息。
- 接下来,就是将这个函数作为回调函数传递给set_error_handler。这样就能接管php 原生的错误处理函数了,要注意的是,这种托管方式并不能托管所有种类的错误,如E_ERROR,E_PARSE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING,已经E_STRICT中的部分。这些错误将会以原始的方式显示或不显示。
案例二
在 PHP 异常中,异常处理机制是有限的,无法自动抛出异常,必须手动进行,并且内置异常有限。PHP 把许多异常看作错误,这样可以把这些“异常”像错误一样用set_error_handle 接管,进而主动抛出异常。
function customError($errno,$errstr,$errfile,$errline){
//自定义错误处理时,手动抛出异常
throw new Exception($level.'|'.${errstr});
}
set_error_handler("customError",E_ALL|E_STRICT);
try{
$a=5/0;
}
catch(Exception $e)
{
echo "错误信息:",$e->getMessage();
}
-
这样就能捕获到异常和非致命得到错误。
-
存在的问题:必须依靠程序员自己来掌握自己来掌握对异常的处理,对于异常高发区,敏感区,如果处理的不好,就会导致前面所提到的业务数据不一致的问题。
-
优点:可以获得程序运行时的上下文信息,以进行针对性的补救。
fetal error 这样的错误虽然捕获不到,也无法在发生此错误后恢复流程处理,但是还是可以使用一些特殊方法对这种错误进行处理的。这需要用到一个函数--register_shutdown_function,此函数会在PhP 程序终止或者 die 时触发一个函数,给 PHP 来一个短暂的”回光返照“。
PHP7 对异常机制的改进
- PHP 早期只有错误机制,在 PHP5 引入异常机制后,导致错误机制和异常机制并行,并且异常机制并不完善。
- 实际上从 PHP5 开始, PHP 就开始了对 PHP 异常机制进行完善。比如,从PHP5.5 开始,PHP的异常引入了 finally 语法,可以像 Java 一样在 finally 语句块中进行最终的异常处理。
- PHP7 实现了一个全局的 Throwable 接口,原来 Exception 和部分 Error 都实现了这个接口(interface),以接口的方式定义了异常的继承结构。
- PHP7 改变了大多数错误的报告方式。不同于传统 (PHP5) 的错误报告机制,使得大多数错误会作为 Error 异常抛出。
- 这种 Error 异常可以像 Exception 异常一样被第一个匹配的 try/catch 块捕获。如果没有匹配的catch 块,则调用异常处理函数(事先通过set_exception_handler()注册)进行处理。如果尚未注册异常处理函数,则按传统方式处理:被报告为一个致命错误(Fatal Error )。也就是说,PHP7 中更多的 Error 变为可捕获的 Exception返回给开发者,如果不进行捕获则为 Error,如果捕获就变为一个可在程序内处理的 Exception。PHP7 被处理更加方便,让开发者对程序的掌握能力更强。因为在默认情况下,Error 会直接导致程序中断,而PHP7 则提供捕获并且处理的能力,让程序继续执行下去,为程序员提供更灵活的选择。
案例
- 函数不存在的情况,之前时需要使用function_exist来判断,如果不判断就直接调用,以错误的形式给出。
- 尝试捕获函数不存在的异常,PHP 将函数不存在作为错误抛出了,并没有作为异常捕获。
try{ hell(); }catch(Exception $e){ echo $e->getMessage(); echo '捕获到了嘛?'; }finally{ echo 'finally..'; }
- 使用Throwable捕获函数不存在的异常,可以捕获undefined function 这种错误并将其转化为异常处理了。
try{ hello(); }catch(Throwable $e){ echo $e->getMessage(),PHP_EOL; echo '捕获到了嘛?',PHP_EOL; }finally{ echo 'finally..'; }
- 尝试捕获除零异常
try{ 5/0; }catch(Throwable $e){ echo $e->getMessage(),PHP_EOL; echo '捕获到了嘛?',PHP_EOL; }finally{ echo 'finally..'; }
- 只有当对0进行取模的时候才会抛出 DivisionByZeroError 错误,我们把Throwable 换成 DivisionByZeroError 也是一样的结果。事实上,对应于除0这种操作,PHP 并不会抛出异常,而是返回一个 INF 值,并且同时出发一条 E_WARNING。
可以看出,php7 的异常体系已经比较接近java了,但是还不够完善,只有部分错误实现了Throwable接口。
本作品采用《CC 协议》,转载必须注明作者和本文链接
好文
信息量有点大
看完一遍还要继续洗脚 回过头才发现基础重中之重