魔术方法 | 作用 |
---|---|
__construct() | 实例化类时自动调用(构造函数) |
__destruct() | 类对象使用结束时自动调用(析构函数) |
__set() | 在给未定义的属性赋值时自动调用 |
__get() | 调用未定义的属性时自动调用 |
__isset() | 使用 isset() 或 empty() 函数时自动调用 |
__unset() | 使用 unset() 时自动调用 |
__sleep() | 使用 serialize 序列化时自动调用 |
__wakeup() | 使用 unserialize 反序列化时自动调用 |
__call() | 调用一个不存在的方法时自动调用 |
__callStatic() | 调用一个不存在的静态方法时自动调用 |
__toString() | 把对象转换成字符串时自动调用 |
__invoke() | 当尝试把对象当方法调用时自动调用 |
__set_state() | 当使用 var_export() 函数时自动调用,接受一个数组参数 |
__clone() | 当使用 clone 复制一个对象时自动调用(克隆) |
__debugInfo() | 使用 var_dump() 打印对象信息时自动调用 |
注意: 这些魔术方法的参数都不能通过引用传递。
PHP所提供的 重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的。
当调用当前环境下不可访问(未定义或不可见)的类属性或方法时,重载方法会被调用。
● 属性的重载 __get
与__set
● 方法的重载 __call
与 __callStatic
<?php // PHP 重载class Credit{ private $idNum; private $age; // 魔术方法 构造函数 如果我没有显示的定义__construct方法时候,系统在实例化对象的时候会自动初始一个无参的构造函数 public function __construct($idNum,$age){ $this->idNum = $idNum; $this->age = $age; } // 我们也可以显示的声明重载方法 __get __set public function __set($name,$value){ echo "Setting $name to $value<br>"; $this->$name = $value; return $this->$name; } public function __get($name){ return $this->$name; }}$c = new Credit('341226186915868525',88);// 访问未定义的成员属性时,重载方法会被调用$c->name = 'zhang'; // __setecho $c->name; // __get// 访问私有成员属性echo "<br>";$c->idNum = "****************";echo $c->idNum;?>
导出问题:因为魔术方法都是公有的,所以一些私有成员的不可见性就不会生效。
解决这一问题,我们需要用到属性重载处理(属性拦截器)
__set()
当给不可访问的属性赋值的时候会触发
有两个参数第一个参数是成员名第二个参数是成员的值
__get()
当读取不可访问的属性的时候会触发
有一个参数是成员名
// 其中不可访问成员指的是:没有定义的成员,还有因为受到访问控制而访问不到的<?php // PHP 重载class Credit{ private $idNum; private $age; public function __construct($idNum,$age){ $this->idNum = $idNum; $this->age = $age; } // 设置 中转站 public function __set($name,$value){ // 根据属性名称$name 生成对应的属性访问私有接口 // 'set'.'IdNum' = setIdNum $method = 'set'.ucfirst($name); // method_exists() 检测对象中是否存在某个方法 return method_exists($this,$method)?$this->$method($name,$value):null; } private function setIdNum($name,$value){ // property_exists()检测对象中是否存在某个属性 if(property_exists($this,$name)){ return $this->$name = strlen($value)==18?$value:null; } } private function setAge($name,$value){ if(property_exists($this,$name) && intval($value)>0){ return $this->$name = $value; }else{ return $this->$name = null; } } // 访问 中转站 public function __get($name){ $method = 'get'.ucfirst($name); return method_exists($this,$method)?$this->$method($name):null; } private function getIdNum($name){ // 用户修改并查看的属性是否存在,并且属性内容已修改成功,不为空。 if(property_exists($this,$name) && !empty($this->$name)){ return substr_replace($this->$name,"****",-4,4); }else{ return "身份证信息不合法"; } } private function getAge($name){ if(property_exists($this,$name) && !empty($this->$name)){ if($this->$name<18){ return "年龄小于18岁"; }elseif($this->$name<35){ return "年龄小于35岁"; }else{ return "年龄大于35岁"; } }else{ return "年龄不合法"; } }}$c = new Credit('341226186915868525',88);$c->idNum = "341226186915868585";echo $c->idNum."<br>";$c->age = 25;echo $c->age;?>
拦截器典型的作用:
1、给出友好提示
2、执行默认操作
当调用不可访问的方法时,__call
会被自动调用, 还会自动传给__call
两个参数:分别代表被调用的不存在的方法名 和调用时传递的参数__callStatic()
与 __call
类似,当静态调用一个不可访问的方法时,会自动执行!
<?php // 方法拦截器,当从类的外部,访问类中不存在或无权限访问的方法的时候,会自动调用它 class User{ public static function normal(){ return __METHOD__; } /* *@access public *@param string $method 方法名 *@param array $args 调用参数 */ // 方法拦截器 魔术方法 __call(不存在的方法名, 调用时传递的参数数组) public function __call(string $method,array $args){ printf("调用对象方法%s(),参数为:[%s]<br>",$method,implode(",",$args)); } // 静态方法拦截器: __callSatic(方法名称, 参数数组) public static function __callStatic(string $method,array $args){ printf("调用静态方法%s(),参数为:[%s]<br>",$method,implode(",",$args)); } } (new User)->getInfo('刘亦菲','美女','170cm'); User::getName("小爱","咩咩",'皮特朱'); echo User::normal();?>
访问类中不存在的成员方法时,会被魔术方法拦截,把请求重定向到别的类的成员方法来处理
<?php // 被委托的类 class Base { public function write(...$args) //...为展开 { printf('方法名: %s(), 参数 [%s]',__METHOD__, implode(', ', $args)); } public static function fetch(...$args) { printf('方法名: %s(), 参数 [%s]',__METHOD__, implode(', ', $args)); } } // 工作类 class Work { // 事件委托时,重定向到的目标类 private $base; // 将$base初始化 public function __construct(Base $base) { $this->base = $base; } // 方法拦截器,将$this->write()重定向到$this->base->write() public function __call($name, $args) { // 将$this->$name()重定向到$this->base->$name() if (method_exists($this->base, $name)) // return $this->base->$name($name, $args); 这个也可以,但基本上不用 // 用回调的方式来调用: call_user_func_array(函数, 参数数组) // 如果是一个对象的方法,应该使用数组的方式: [对象, '方法名'] return call_user_func_array([$this->base, 'write'], $args); } // 方法拦截器,将self::fetch()重定向到Base::fetch() public static function __callStatic($name, $args) { // 将self::$name()重定向到Base::$name() if (method_exists('Base', $name)) return call_user_func_array(['Base', 'fetch'], $args); } } // 客户端代码 // $base = new Base(); // $work = new Work($base); $work = new Work(new Base); $work->write(11,22,33); echo "<br>"; $work::fetch('皮特朱', '咩咩', '欧阳');
事件委托/方法拦截器 实战: 数据库查询构造器(链式查询)
<?php// 查询类class Query{ // 连接对象 protected $db; // 数据表 protected $table; // 字段列表 protected $field; // 记录数量 protected $limit; // 添加数据 protected $values; // 操作条件 protected $where; // 构造方法: 连接数据库 public function __construct($dsn, $username, $password) { $this->connect($dsn, $username, $password); } // 连接数据库 private function connect($dsn, $username, $password) { $this->db = new PDO($dsn, $username, $password); } // 设置默认的数据表名称 public function table($table) { $this->table = $table; return $this; } // 设置默认的字段名称 public function field($field) { $this->field = $field; return $this; } // 链式方法: 设置查询数量 public function limit($limit) { $this->limit =empty($limit)?"":"LIMIT $limit"; return $this; } // 设置修改或添加的数据 public function values($values) { $this->values = $values; return $this; } // 设置where条件 public function where($where) { $str = ''; if (!empty($where)) { foreach ($where as $key => $value) { if(is_array($where)){ if(count($where)>1){ $str .= $key.'='.$value.' and '; }else{ $str .= $key.'='.$value; } } } } $str = count($where)>1?substr($str,0,-4):$str; $this->where = empty($where)?"":"where $str"; return $this; } // 生成SQL语句 protected function getSql($key) { switch ($key) { case 'select': return sprintf('SELECT %s FROM %s %s %s', $this->field, $this->table, $this->where, $this->limit); break; case 'update': return sprintf('UPDATE %s SET %s = %s %s', $this->table, $this->field, $this->values,$this->where); break; case 'insert': $this->field = $this->field == "*"?"":$this->field; return sprintf('INSERT INTO %s (%s) VALUES (%s)', $this->table, $this->field, $this->values); break; case 'delete': return sprintf('DELETE FROM %s %s', $this->table, $this->where); break; } } // 执行SQL语句 public function select() { return $this->db->query($this->getSql('select'))->fetchAll(PDO::FETCH_ASSOC); } public function insert() { return $this->db->query($this->getSql('insert')); } public function update() { return $this->db->query($this->getSql('update')); } public function delete() { return $this->db->query($this->getSql('delete')); }}// 数据操作类class DB{ // 静态方法委托 public static function __callStatic($name, $args) { // 获取到查询类的对象: new Query() $dsn = 'mysql:host=localhost;dbname=myblog'; $username = 'root'; $password = 'root'; $query = new Query($dsn, $username, $password); // 直接跳到Query类中的具体方法来调用 return call_user_func_array([$query, $name], $args); }}
查询记录
$result = DB::table('blogs')->field("*")->select();echo "<pre>";print_r($result);
添加记录
$result = DB::table('blogs')->field('*')->values("'2','2','php重定向','事件委托/方法拦截器 实战: 数据库查询构造器(链式查询)', '2021-05-10 15:58:26'")->insert(); echo "<pre>";print_r($result);$result = DB::table('blogs')->field("*")->limit(5)->select();echo "<pre>";print_r($result);
修改记录
DB::table('blogs')->field('title')->values('"我的标题被修改了"')->where(["id"=>1])->update();$result = DB::table('blogs')->field("*")->limit(1)->select();echo "<pre>";print_r($result);
删除记录
DB::table('blogs')->where(["user_id"=>"2"])->delete();$result = DB::table('blogs')->field("*")->select();echo "<pre>";print_r($result);
数据库表SQL脚本文件
SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for blogs-- ----------------------------DROP TABLE IF EXISTS `blogs`;CREATE TABLE `blogs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `title` varchar(128) NOT NULL, `content` text, `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), CONSTRAINT `blogs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;-- ------------------------------ Records of blogs-- ----------------------------INSERT INTO `blogs` VALUES ('1', '1', '可用的路由方法', '路由器允许你注册能响应任何', '2020-09-26 16:43:00');INSERT INTO `blogs` VALUES ('3', '1', '基本路由', '构建基本路由只需要一个 URI 与一个 闭包', '2020-09-26 16:42:03');INSERT INTO `blogs` VALUES ('4', '2', 'web前端开发', 'HTML、css、js', '2020-09-30 22:34:46');INSERT INTO `blogs` VALUES ('5', '1', '重定向路由', '如果要定义重定向到另一个 URI 的路由', '2020-09-26 17:09:49');-- ------------------------------ Table structure for users-- ----------------------------DROP TABLE IF EXISTS `users`;CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `account` varchar(20) NOT NULL, `password` varchar(32) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;-- ------------------------------ Records of users-- ----------------------------INSERT INTO `users` VALUES ('1', 'admin', '1234');INSERT INTO `users` VALUES ('2', 'user1', '0000');
在PHP中代码只能单继承,为了实现类代码的复用因此实现了trait,trait类与普通class不同它自身无法实例化,只能通过use在class中引用trait,然后这个类就能使用trait中的方法。
共用的一些属性和方法提取出来做公共trait类,就像是装配汽车的配件,如果你的类中要用到这些配件,就直接用use导入就可以了,相当于把trait中的代码复制到当前类中.
因为trait不是类,所以不能有静态成员,类常量,当然也不可能被实例化。
如果说:继承可以纵向扩展一个类,那么trait就是横向扩展一个类功能
通俗点讲:trait就是为了代码复用而生
例一:创建两个trait类,并使用。(trait类不仅可以定义方法,也可以定义属性)
<?php trait test1{ public $name = "zhang"; public function aa(){ return "test1"; }}trait test2{ public function bb(){ return "test2"; }}class test3{ //引入关键字 use use test1,test2; public function getName($name){ return "My name is $name"; }}$obj = new test3;echo $obj->aa()."<br>";echo $obj->bb()."<br>";echo $obj->getName($obj->name)."<br>";/*test1test2My name is zhang */?>
例二:trait类也可以相互嵌套,一个trait直接使用use引用另一个trait类就行
此处只需要在例一中的test2类中use test1,并去掉test3 类中对test1的引用,代码如下
<?php trait test1{ public $name = "zhang"; public function aa(){ return "test1"; }}trait test2{ use test1; public function bb(){ return "test2"; }}class test3{ use test2; public function getName($name){ return "My name is $name"; }}$obj = new test3;echo $obj->aa()."<br>";echo $obj->bb()."<br>";echo $obj->getName($obj->name)."<br>";/*test1test2My name is zhang */?>
例三、trait类不影响继承父类,但是 如果trait类中存在和父类同名的方法时,返回结果为trait类的方法返回值
(trait类的优先级比父类高)
<?php trait test1{ public $name = "zhang"; public function aa(){ return "test1"; }}class test2{ use test1; public function aa(){ return "test2"; }}class test3 extends test2{ use test1; public function getName($name){ return "My name is $name"; }}$obj = new test3;echo $obj->aa()."<br>";echo $obj->getName($obj->name)."<br>";/*test1My name is zhang */?>
例四:当本类,trait类和父类都哈有同名方法时,优先使用本类的方法
(优先级 : 本类>trait类>父类)
<?php trait test1{ public $name = "zhang"; public function aa(){ return "test1"; }}class test2{ use test1; public function aa(){ return "test2"; }}class test3 extends test2{ use test1; public function aa(){ return "test3"; }}$obj = new test3;echo $obj->aa()."<br>";// test3?>
例五:如果两个trait类中含有同名方法(需要为同名方法起别名)
<?php trait test1{ public function aa(){ return "test1"; }}trait test2{ use test1; public function aa(){ return "test2"; }}class test3{ use test1,test2{ test2::aa insteadof test1; test2::aa as bb; }}$obj = new test3;echo $obj->aa()."<br>";echo $obj->bb()."<br>";?>