PHP的魔术方法
__construct方法:PHP中没有构造方法的重载
__destruct方法:析构方法允许在销毁一个对象之前执行一些特定操作,例如关闭文件、释放结果集等
当堆内存段中的对象失去访问它的引用时,就不能被访问了,也就成为垃圾对象了。通常对象的引用被赋予其他的值或者是在页面运行结束时,对象都会失去引用
访问属性:使用下面的方法之后,访问不存在的属性时不会报错
__set方法:void __set(string name, mixed value) 第一个参数需要传入在为私有属性设置值时的属性名,第二个参数则需要传入为属性设置的值。这个方法不需要主动调用,可以在方法前面也加上private修饰,防止用户直接调用。这个方法是在用户值为私有属性设置值时自动调用的
__get方法:这两个方法是用来完成对所有私有属性都能获取和赋值的操作
__isset方法:用来检查私有属性是否存在的方法
__unset方法:用来删除对象中私有属性的方法
<?php class Account { private $user = 1; private $pwd = 2; public function __set($name, $value) { echo "Setting $name to $value "; echo "<br>"; $this -> $name = $value; } public function __get($name) { if (!isset($this->$name)) { echo "未设置"; echo "<br>"; $this -> $name = "正在设置默认值"; } return $this->$name; } } $a = new Account(); echo $a -> user; echo "<br>"; $a -> name = 5; echo $a -> name; echo "<br>"; echo $a -> big; ?> //1 Setting name to 5 5 未设置 Setting big to 正在设置默认值 正在设置默认值
访问方法:使用下面的方法之后,访问不存在的方法不会报错
__call方法
mixed __call(string $name, array $arguments) //当调用一个不可访问的方法(未定义,或者不可见)时,__call()会被调用。其中$name参数是要调用的方法名称 //$arguments参数是一个数组,包含着要传递给方法的参数 //使方法的动态创建成为可能,在MVC等框架设计中是很有用的语法。假设一个控制器调用了不存在的方法,那么只要定义了__call魔术方法,就能友好地处理这种情况
<?php class TestClass { function printHello(){ echo "Hello<br>"; } function __call($functionName, $args) { // TODO: Implement __call() method. echo "你所调用的函数: ".$functionName."(参数: "; print_r($args); echo ")不存在<br> "; } } $obj = new TestClass(); $obj->myFun("one", 2, "three"); $obj->otherFun(8, 9); $obj->printHello(); ?>
__callStatic方法
__toString方法:是在直接输出对象引用时自动调用的方法
如果类定义了__toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的__toString方法,格式化输出这个对象所包含的数据
仍然可以用print_r和var_dump函数输出一个对象
直接echo一个对象就会报语法错误,而如果这个对象实现__toString方法后就可以直接输出==>echo本来可以打印一个对象,而且也实现了这个接口,但是PHP对其做了个限制,只有实现__toString后才允许使用
<?php //声明一个测试类,在类中声明一个成员属性和一个__toString()方法 class TestClass { private $foo; function __construct($foo) { $this->foo = $foo; } public function __toString() { // TODO: Implement __toString() method. return $this->foo; } } $obj = new TestClass('Hello'); echo $obj; ?>
对象串行化
串行化 serialize()函数 参数为对象的引用名,返回值为一个对象被串行化后的字符串
反串行化 unserialize()函数 把对象串行化后转换的二进制字符串再转换为对象 参数为serialize()函数的返回值,返回值是重新组织好的对象
类的组合与继承
组合是指在一个类中创建另一个类的对象,并把后者作为前者的一个属性,并调用它的方法处理问题,这种复用方式叫“组合”
::操作符用于调用父类的方法,还用来作为类常量和静态方法的调用
低耦合指模块与模块之间,尽可能地使模块间独立存在;模块与模块之间的接口尽量少而简单
继承:
子类重写父类的方法:可以使用parent::方法名来获取父类的代码
常见关键字:
final
static 类名::静态属性名 类名::静态成员方法名
在类中声明的成员方法中,也可以使用self来访问其他静态成员,因为静态成员是属于类的,所以不能通过$this访问
self::静态成员属性名
self::静态成员方法名
如果在类的外部访问静态成员,可以使用类名或者对象名来引用
单态设计模式
<?php /** 声明一个类Db,用于演示单态模式的使用 */ class DB { //声明一个私有的、静态的成员属性$obj private static $obj = null; //构造方法,使用private封装后则只能在类的内部使用new去创建对象 private function __construct() { //完成数据库连接操作 echo "连接数据库成功<br>"; } //只有通过这个方法才能返回本类的对象 static function getInstance(){ if(is_null(self::$obj)) { self::$obj = new self(); } return self::$obj; } //执行SQL语句完成对数据库的操作 function query($sql) { echo $sql; } } $db = DB::getInstance(); $db->query("select * from user"); ?>
const关键字:
在PHP中定义常量是通过调用define()函数来完成的,但要将类中的成员属性定义为常量,则只能使用const关键字。将类中的成员属性使用const关键字标识为常量,其访问的方式和静态成员一样,都是通过类名或者在成员方法中使用self关键字访问,也不能用对象来访问
使用const声明的常量名称前不使用$
instanceof关键字
克隆对象:克隆以后,原本和副本两个对象完全独立、互不干扰
<?php class Person { private $name; private $sex; private $age; function __construct($name = "", $sex = "",$age=1) { $this->name = $name; $this->sex = $sex; $this->age = $age; } function say(){ echo "我的名字: ".$this->name.", 性别: ".$this->sex.", 年龄: ".$this->age."<br>"; } } $p1 = new Person("张三", "男", 20); $p2 = clone $p1; $p1 -> say(); $p2 -> say(); ?> <?php //如果需要对克隆后的副本对象在克隆时重新为成员属性赋初值,则可以在类中声明一个魔术方法“__clone()” class Person2 { private $name; private $sex; private $age; function __construct($name = "", $sex = "",$age=1) { $this->name = $name; $this->sex = $sex; $this->age = $age; } //声明此方法则在对象克隆时自动调用,用来为新对象重新赋值 function __clone(){ $this->name = "我是". $this->name. "的副本"; $this->age = 10; } function say(){ echo "我的名字: ".$this->name.", 性别: ".$this->sex.", 年龄: ".$this->age."<br>"; } } $p1 = new Person("张三", "男", 20); $p2 = clone $p1; $p1 -> say(); $p2 -> say(); ?>
抽象类与接口
abstract function fun1(); abstract function fun2(); //只要在声明类时有一个方法是抽象方法,那么这个类就是抽象类,抽象类也要使用abstract关键字修饰。在抽象类中可以有不是抽象的成员方法和成员属性,但访问权限不能使用private关键字修饰为私有的 抽象类中可以定义非抽象方法 //接口中不能声明非抽象方法,不能在接口中声明变量,只能使用const关键字声明为常量的成员属性,而且接口中的所有成员都必须有public的访问权限
匿名类
<?php $person = new class { function say() { echo "匿名类!"; } }; $person->say(); ?> <?php $person1 = new class ('哈哈'){ public $name; function __construct($name) { $this->name = $name; } function say() { echo "匿名类: {$this->name}"; } }; $person1->say(); ?> //匿名类! 匿名类: 哈哈
<?php class SomeClass {} //声明一个类SomeClass作为父类 interface SomeInterface {} //声明一个接口SomeInterface用匿名类实现 trait SomeTrait {} //声明一个trait,导入到匿名类中 //使用var_dump()直接输出匿名类的对象 //声明一个匿名类,通过构造函数为成员属性$num赋初值 //声明匿名类时继承SomeClass类 //声明匿名类时实现接口SomeInterface var_dump(new class(100) extends SomeClass implements SomeInterface { private $num; public function __construct($num) { $this->num = $num; } use SomeTrait; }); ?> <?php //匿名类还可以在一个类的内部方法中声明,当匿名类被嵌套进普通类后,不能访问这个外部类的 //private、protected方法或者属性。为了访问外部类protected属性或方法,匿名类可以继承此外部类 //为了使用外部类的private属性,必须经过构造器传进来 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(); } }; } } //访问内部匿名类实例中的func3() echo (new Outer) -> func2() ->func3(); ?>
多态:同一类的对象收到相同消息时,会得到不同的结果,而这个消息是不可预测的。多态,就是多种状态,多种结果
多态是一种通过多种状态或阶段描述相同对象的编程方式。它的真正意义在于:实际开发中,只要关心一个接口或基类的编程,而不必关心一个对象所属于的具体类
//通过判断对象的类属性实现多态
<?php class employee { protected function working() { echo '本方法需重载才能运行'; } } class teacher extends employee { public function working() { echo '教书'; } } class coder extends employee { public function working() { echo '敲代码'; echo "<br>"; } } function dprint($obj) { if (get_class($obj) == 'employee') { echo 'Error'; } else { $obj->working(); } } dprint(new teacher()); dprint(new coder()); dprint(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 dprint(employee $i) { $i -> working(); } $a = new teacher; $b = new coder(); dprint($a); dprint($b);
PHP中父类和子类存在继承关系,但不存在血缘关系,子类无法向上转型为父类。
面向接口编程
接口本身什么也不做,系统在内部实现了接口的行为,所以只要实现了这个接口,就可以使用接口提供的方法,这就是接口“即插即用”思想
//Traits和接口很像,不同的是Traits可以导入包含代码的接口,Traits和接口都是对“多重继承”的一种变相实现 <?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(); ?>
<?php trait DemoTrait { public $property1 = true; static $property2 = 1; function method1() { //codes } abstract public function method2(); } /** trait的基本使用:Trait不能通过它自身来实例化对象,必须将其混入类中使用。相当于将Trait中的成员复制到类中,在应用类时就像使用自己的 * 成员一样 */ trait Demo1_trait { function method1() { } function method2() { } } class Demo1_class { use Demo1_trait; } $obj = new Demo1_class(); $obj->method1(); $obj->method2(); ?>
<?php trait Demo1_trait { function func() { echo "第一个Trait中的func方法"; } } trait Demo2_trait { function func() { //两个同名方法有冲突 echo "第二个Trait中的func方法"; } } class Demo_class { use Demo1_trait, Demo2_trait { Demo1_trait::func insteadof Demo2_trait; //Demo2_trait中声明的在这里声明使用Demo1_trait的func替换 } } $obj = new Demo_class(); $obj->func(); ?>
为了对使用的类施加强制要求,Trait支持抽象方法的使用。如果在Trait中声明需要实现的抽象方法,这样就使得使用它的类必须先实现它
注意:
- Trait会覆盖调用类继承的父类方法
- 从基类继承的成员被Trait插入的新成员所覆盖。来自当前类的成员覆盖了Trait的方法,而Trait则覆盖了被继承的方法
- Trait不能像类一样使用new实例化对象
- 单个Trait可由多个Trait组成
- 在单个类中,用use引入Trait,可以引入多个
- Trait支持修饰词,例如final、static、abstract
- 可以使用insteadof及as操作符解决Trait之间的冲突
- 使用as语法还可以用来调整方法的访问控制
反射
面向对象编程中对象被赋予了自省的能力,而这个自省的过程就是反射
动态获取信息以及动态调用对象方法的功能称为反射API
<?php class person { public $name; public $gender; public function say() { echo $this->name, " is", $this->gender, " "; } public function __set($name, $value) { echo "Setting $name to $value "; $this -> $name = $value; } public function __get($name) { if (!isset($this -> $name)) { echo '未设置'; $this -> $name="正在设置默认值"; } return $this -> $name; } } $student = new person(); echo $student -> name; $student -> name = "tom"; $student -> gender = "male"; $student -> age = 24; $student -> say(); echo $student -> name; print "<br>"; //获取对象属性列表 $reflect = new ReflectionObject($student); $props = $reflect->getProperties(); foreach ($props as $prop) { print $prop -> getName()." "; print "<br>"; // print "<br>"; } print "<br>"; //获取对象方法列表 $m = $reflect->getMethods(); foreach ($m as $prop) { print $prop->getName()." "; print "<br>"; } //也可以使用class函数,返回对象属性的关联数组以及更多的信息 //返回对象属性的关联数组 var_dump(get_object_vars($student)); //类属性 var_dump(get_class_vars(get_class($student))); //返回由类的方法名组成的数组 var_dump(get_class_methods(get_class($student))); //获取对象属性列表所属的类 echo get_class($student); ------------------------------ Setting age to 24 tom ismale tom name gender age say __set __get array(3) { ["name"]=> string(3) "tom" ["gender"]=> string(4) "male" ["age"]=> int(24) } array(2) { ["name"]=> NULL ["gender"]=> NULL } array(3) { [0]=> string(3) "say" [1]=> string(5) "__set" [2]=> string(5) "__get" } person
//反射获取类的原型 $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}<br>{<br>"; is_array($Properties)&&ksort($Properties); foreach($Properties as $k => $v) { echo " "; echo $v -> isPublic()? 'public' : '', $v -> isPublic()? 'private':'', $v -> isProtected()? 'protected':'', $v->isStatic()? 'static' :' '; echo "{$k}<br>"; } echo " "; if(is_array($Methods)) ksort($Methods); foreach($Methods as $k => $v) { echo "function{$k}(){}<br>"; } echo "}<br>";
异常和错误处理
<?php class emailException extends exception { } class pwdException extends exception { function __toString() { return "<div class = "error">Exception{$this->getCode()}: {$this->getMessage()} in File:{$this->getFile()} on line:{$this->getLine()} </div>"; //改写抛出异常结果 } } //异常分发 function reg($reginfo=null){ if (empty($reginfo) || isSet($reginfo)) { throw new Exception("参数非法"); } if (empty($reginfo['email'])){ throw new emailException("邮件为空"); } if ($reginfo['pwd'] != $reginfo['repwd']) { throw new pwdException("两次密码不一致"); } echo "注册成功"; } //对异常进行分拣并做处理 try { reg(array('email'=>'waitfox@qq.com', 'pwd'=>123456, 'repwd'=>12345678)); } catch(emailException $ee) { echo $ee -> getMessage(); } catch(pwdException $ep) { echo $ep; echo PHP_EOL, '特殊处理'; } catch(Exception $e) { echo $e->getTraceAsString(); echo PHP_EOL, '其他情况,统一处理'; }
<?php try{ //可能出错的代码段 if (文件上传不成功) throw(上传异常); if (插入数据库不成功) throw(数据库操作异常); } catch(异常){ 必须的补救措施,如删除文件、删除数据库插入记录 } ?> <?php 上传{ if(文件上传不成功) throw(上传异常); if(插入数据库不成功) throw(数据库操作异常); } //其他代码... try{ 上传; 其他; } catch(上传异常) { 必须的补救措施,如删除文件,删除数据库插入记录 } catch(其他异常){ 记录log } ?>
错误处理机制
PHP里有一套错误处理机制,可以使用set_error_handler接管PHP错误处理,也可以使用trigger_error函数主动抛出一个错误
set_error_handler函数设置用户自定义的错误处理函数,函数用于创建运行期间的用户自己的错误处理方法。它需要先创建一个错误处理函数,然后设置错误级别
PHP程序的错误发生一般归属于:语法错误、运行时错误、逻辑错误
trigger_error()函数和die()函数
//set_error_handler()函数设置用户自定义的错误处理函数。函数用于创建运行期间的用户自己的错误处理方法。它需要先创建一个错误处理函数,然后设置错误级别 set_error_handler(error_function, error_types) error_function:规定发生错误时运行的函数 error_types:规定在哪个错误报告级别会显示用户定义的错误,默认为“E_ALL”
命名空间
解决重名问题。命名空间将代码划分出不同的区域,每个区域的常量、函数和类的名字互不影响
在命名空间里,define的作用是全局的,const则作用于当前空间
//独立的命名空间使用namespace关键字声明 <?php namespace MyProject; ?> //namespace需要写在PHP脚本的顶部,必须是第一个PHP指令(declare除外) <?php namespace MyProject1; const TEST = 'this is a const'; function demo(){ echo "this is a function"; } class User { function fun() { echo "this is User's fun()"; } } echo TEST; demo(); namespace MyProject2; const TEST2 = "this is MyProject2 const"; echo TEST2; //调用MyProject1空间中的demo函数 MyProject1demo(); //使用MyProject1空间中的类实例化对象 $user = new MyProject1User(); $user->fun(); ?>
命名空间的子空间和公共空间