博客列表 >【PHP 面向对象】面向对象(OOP)编程之魔术方法实现重载知识点归纳总结(三)

【PHP 面向对象】面向对象(OOP)编程之魔术方法实现重载知识点归纳总结(三)

 一纸荒凉* Armani
 一纸荒凉* Armani原创
2021年05月10日 19:40:051297浏览

PHP 通过魔术方法实现重载

魔术方法作用
__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() 打印对象信息时自动调用

注意: 这些魔术方法的参数都不能通过引用传递。

  • 在给不可访问属性赋值时,__set() 会被调用。

  • 读取不可访问属性的值时,__get() 会被调用。

  • 当对不可访问属性调用 isset()empty() 时,__isset() 会被调用。

  • 当对不可访问属性调用 unset() 时,__unset() 会被调用。

属性重载(属性拦截器)

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');

trait: 类功能横向扩展

在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>";?>
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议