PHP基础教程十三之反射、对象序列化

x33g5p2x  于2022-03-06 转载在 其他  
字(8.8k)|赞(0)|评价(0)|浏览(468)

本节讲解的内容

  • 对象的克隆
  • 对象的遍历
  • 对象的序列化和反序列化
  • 内置标准类的使用
  • traits的使用
  • 类和对象的相关函数
  • PHP反射机制

前言

PHP的面向对象是一个重要的知识点,它的思想贯穿着我们开发的整个流程。在面向对象中还有一些知识点是需要我们去了解的,对象克隆的特点以及对象的遍历,对象的序列化和反序列化,如果你想写一个PHP的框架,那么你对PHP的反射也是要掌握的。

对象的克隆

当我们创建一个对象后,就会在内存中分配一个空间,对象名指向这个空间,前面我们讲过对象的赋值,当一个对象把里面的数据修改了,另一个对象的数据也会跟着变化,因为赋值是相当于把对象标识符赋值了一份,而克隆并不是这样的。

<?php

class Person{
    public $name;
    public $age;
    public $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }

    public function getName(){
        return $this -> name;
    }

    public function getAge(){
        return $this -> age;
    }

    public function getSex(){
        return $this -> sex;
    }
}

$a = new Person('宋江','45','男');
echo '<pre>';

$b = clone $a;
$b -> name = '武松';
var_dump($a);
var_dump($b);

结果

对象克隆的语法:

$新对象 = clone $就对象;

从上面的结果中可以看出来,一个对象的数据变化,并不会影响到另外一个对象的数据。对象克隆会生成一个全新的对象。可以理解如下:

在PHP的魔术方法中有一个魔术方法和PHP的克隆有关的__clone(),在复制完成时,如果在类里面定义了魔术方法__clone()方法,则新创建也就是复制生成的对象会调用这个__clone()方法,在这个方法中如果有需要可以对属性做一些变动。

如果不想一个对象进行克隆可以在内部把魔术方法__clone()方法定义成私有的,这时如果在进行克隆,会提示

Call to private Peoson::__clone() from context ''

对象的遍历

在PHP中对象也是可以遍历的,对象的遍历可以理解成把某个对象的属性和值,遍历显示出来。遍历使用到foreach这个循环体。基本语法:

foreach(对象名 as $key => $val){
    //$key是属性名,$val是属性值
}
<?php

    class Person{
    public $name;
    public $age;
    private $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }
}

$person = new Person('孙悟空',256,'男');

foreach ($person as $key => $value) {
    echo $key . ' = ' . $value . '<br>';
}
.......结果........
name = 孙悟空
age = 256

对象的遍历只能把属性的修饰符是public的遍历出来,protected和private不能再类外访问,所以在类外进行对象的遍历,用这两个修饰符修饰的属性是取不出来的,就像上面代码的sex属性一样。

对象的序列化和反序列化

在PHP中当程序执行完一个文件,就会自动的释放内存,我们在文件中创建的对象,变量等,都会消失,如果我们在一个文件创建了一个对象,在另外一个对象中想要使用这个对象,就可以使用到对象的序列化(serialize())和反序列化(unserialize())。

对象的序列化和反序列化可以理解成把对象转换成字符串保存在一个文件中,在用到对象时把文件进行反序列化得到对象,对象是不能直接保存在文件中的。

示意图:

a.php文件,创建一个对象并保存:

<?php

    class Person{
    public $name;
    public $age;
    private $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }
}

$person = new Person('孙悟空',256,'男');
//使用file_put_contents()函数把将一个字符串写入文件 
file_put_contents('D:person.txt', serialize($person));

上面的代码file_put_contents()函数是把一个字符串写入到一个文件中。
上面的代码执行完可以看到在D盘有一个person.txt文件,里面是一个转换成字符串的对象。

b.php文件,使用到a.php文件中的对象person

<?php

    class Person{
        public $name;
        public $age;
        private $sex;
        public function __construct($name,$age,$sex){
            $this -> name = $name;
            $this -> age = $age;
            $this -> sex = $sex;
        }
    }
    //通过file_get_contents这个方法把一个文件读取到字符串。
    $str = file_get_contents('D:person.txt');
    //进行反序列化。
    $person = unserialize($str);
    echo '<pre>';
    var_dump($person);
    ......结果......
    object(Person)#1 (3) {
      ["name"]=>
      string(9) "孙悟空"
      ["age"]=>
      int(256)
      ["sex":"Person":private]=>
      string(3) "男"
    }

在b.php中通过file_get_contents()这个方法把一个文件读取到字符串,然后通过unserialize()反序列化,但是这样反序列化得到的对象不是person对象,所以在文件中把person类的声明粘贴复制过来,自动转换成person对象。

对象的序列化和反序列化可以让多个文件共享一个对象。

PHP内置标准类

有时我们希望把一些数据,以对象的属性的方式存储,同时我们又不想定义一个类,可以考虑使用PHP内置标准类 stdClass,这是用系统提供的一个虚拟的类,并不需要我们定义就可以直接使用。

<?php

    $person = new StdClass();
    $person -> name = '孙悟空';
    $person -> age = 524;
    $person -> sex = '男';
    echo '<pre>';
    var_dump($person);
    ......结果......
    object(stdClass)#1 (3) {
      ["name"]=>
      string(9) "孙悟空"
      ["age"]=>
      int(524)
      ["sex"]=>
      string(3) "男"
    }

在上面代码中我们可以看出来,并没有定义stdClass这个类就能使用。

Traits的使用

Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用方法集。可以理解为在traits中定义一段代码块,可以在不同的类中使用。

图解:

traits使用语法:

trait 自定义名称{
    //额外定义的代码
}
在类中使用格式
use 自定义的名称;

代码:

<?php
    //使用trait,自定义名称
    trait my_code{
        public function minus($num1,$num2){
            return $num1 - $num2;
        }
    }

    class A{
        public function plus($num1,$num2){
            return $num1 + $num2;
        }
    }

    class B extends A{
        //使用trait定义的代码块
        use my_code;
    }

    class C extends A{
        use my_code;
    }

    class D extends A{

    }

    $b = new B();
    $c = new C();
    $d = new D();
    echo $b -> plus(1,2);
    echo '<br>';
    echo $b -> minus(2,1);
    echo '<br>';

    echo $d -> plus(1,2);
    echo '<br>';
    echo $d -> minus(2,1);
    ......结果......
    3
    1
    3
    Fatal error: Call to undefined method D::minus() in D:\mywamp\Apache24\htdocs\zendstudio\yunsuanfu\staits.php on line 36

在上面代码中类D没有使用trait代码,所以在使用minus方法时,出现错误。

当我们在trait中写的函数和父类的函数一样时,以trait代码为准,即trait代码的优先级高于继承的类。

类与对象相关函数

在PHP中系统提供了一系列的函数来判断类和对象的。从帮助文档中可以看到好多函数:

在这里我们只对里面的几个函数进行了解。

<?php

    class Person{
        public $name;
        public $age;
        private $sex;
        public function __construct($name,$age,$sex){
            $this -> name = $name;
            $this -> age = $age;
            $this -> sex = $sex;
        }

        public function showInfo(){
            echo '名字是:' . $this -> name . ',年龄是:' . $this -> age . ',性别是:' . $this -> sex . '<br>'; 
        }
    }

    //判断是否创建了对象,没有创建返回true,创建返回false。
    if(class_exists('Person')){
        $person = new Person('唐僧',25,'男');
        //返回对象的类名
        echo '类名是: ' . get_class($person) . '<br>';
        //判断方法是否存在。
        if(method_exists($person, 'showInfo')){
            $person -> showInfo();
        }else{
            echo '该方法不存在';
        }
        //判断属性是否存在
        if(property_exists($person,'name')){
            echo $person -> name;
        }else{
            echo '属性不存在';
        }
    }else{
        echo '类不存在';
    }
    ......结果......
    类名是: Person
    名字是:唐僧,年龄是:25,性别是:男
    唐僧

使用类和对象函数,可以保证我们代码的完整性,对出错信息进行及时的捕获输出。

PHP反射机制

在很多编程语言中都有反射这种概念,反射简单理解就是通过类,获取里面的属性,方法,甚至注释也可以,不管属性和方法的访问修饰符是什么类型都可以获取到。

在PHP 5中具有完整的反射API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。而我们在开发中一般是使用不到反射的,但是在某些情况下使用反射可以更好的处理问题,比如我们需要我们写框架底层,扩展功能,管理大量的未知类。

定义一个类,通过反射创建对象和调用里面的方法:

<?php

    class Dog{

        public $name;
        protected $age;
        private $food;

        public function __construct($name, $age, $food){

            $this->name = $name;
            $this->age = $age;
            $this->food = $food;
        }

        public function cry($sound){

            echo '<br> ' . $this->name . ' 叫声是.' . $sound;
        }

    }

    //使用反射完成对象的创建和方法调用
    //1. 创建一个反射对象
    $reflect_obj = new ReflectionClass('Dog');

    //2. 通过反射对象创建Dog对象实例
    $dog = $reflect_obj->newInstance('大黄狗', 4, '排骨');
    echo '<pre>';
    var_dump($dog);

    //3. 调用方法-使用代理方式.
    $reflect_method_cry = $reflect_obj->getMethod('cry');
    echo '<pre>';
    var_dump($reflect_method_cry);
    //4. 代理调用cry
    $reflect_method_cry->invoke($dog, '汪汪');

结果:

在上面代码中,我们通过new创建了一个反射的对象,在反射对象里面通过newInstance()方法得到类的对象。获取里面的方法可以使用反射对象的getMethod()方法,返回来的是一个方法对象ReflectionMethod类,通过里面的invoke()方法执行该方法。这里只是基本的介绍,可以查找帮助文档了解更多的反射对象和方法。

反射实现TP控制器调度

需求:

有一个类IndexAction,其中的方法和访问控制修饰符是不确定的,
1. 如果index 方法是public,可以执行 _before_index.
2. 如果存在_before_index 方法,并且是public的,执行该方法
3. 执行test方法
4. 再判断有没有_after_index方法,并且是public的,执行该方法

代码:

<?php

    class IndexAction{
        public function index(){
            echo 'index<br>';
        }

        public function _before_index(){
            echo '_before_index方法执行 <br>';
        }

        public function test($data){
            echo 'data : '  . $data . '<br>';
        }

        public  function _after_index(){
            echo '_after_index方法执行<br>';
        }
    }

    if(class_exists('IndexAction')){
        //创建对象
        $reflectionClass = new ReflectionClass('IndexAction');
        //判断index是否存在
        if($reflectionClass -> hasMethod('index')){

            //获取index方法对象
            $reflec_index_method = $reflectionClass -> getMethod('index');
            //判断修饰符是否是public
            if($reflec_index_method -> isPublic()){
                //判断是否有_before_index方法
                if($reflectionClass -> hasMethod('_before_index')){
                    $reflec_before_method = $reflectionClass -> getMethod('_before_index');
                    //判断是否是public
                    if($reflec_before_method -> isPublic()){
                        $reflec_before_method -> invoke($reflectionClass -> newInstance());
                        //调用test()方法
                        $reflectionClass -> getMethod('test') -> invoke($reflectionClass -> newInstance(),'这是test的数据');
                        //判断是否有_after_index方法
                        if($reflectionClass -> hasMethod('_after_index')){
                            $reflec_after_method = $reflectionClass -> getMethod('_after_index');
                            //判断是否是public
                            if($reflec_after_method -> isPublic()){
                                //执行_after_index方法
                                $reflec_after_method -> invoke($reflectionClass -> newInstance());

                            }else{
                                echo '_after_index不是public修饰的';
                            }

                        }else{
                            echo '没有_after_index方法';
                        }

                    }else{
                        echo '_before_index修饰符不是public';
                    }

                }else{
                    echo '没有_before_index方法';
                }

            }else{
                echo 'index方法不是public修饰';
            }

        }else{
            echo 'index方法不存在';
        }

    }else{
        echo '类名不存在';
    }
    ......结果.......
    _before_index方法执行 
    data : 这是test的数据
    _after_index方法执行

在上面的代码中可以看到我们不停地在判断类中有没有某个方法,是不是public修饰,然后执行,我们可以利用封装的思想,把一些共性的特征抽取出来写成一个函数。从而对我们的代码进行优化。

优化的代码:

<?php

    class IndexAction{
        public function index(){
            echo 'index<br>';
        }

        public function _before_index(){
            echo '_before_index方法执行 <br>';
        }

        public function test($data){
            echo 'data : '  . $data . '<br>';
        }

        public  function _after_index(){
            echo '_after_index方法执行<br>';
        }
    }

    if(class_exists('IndexAction')){
        $reflectionClass = new ReflectionClass('IndexAction');
        //创建IndexAction对象。
        $indexAction = $reflectionClass -> newInstance();

        execute($reflectionClass,$indexAction,'index');
        execute($reflectionClass,$indexAction,'_after_index');
        execute($reflectionClass,$indexAction,'test','test使用的数据');
        execute($reflectionClass,$indexAction,'_after_index');
    }else{
        echo '没有IndexAction方法';
    }

    //封装的函数
    /**
     * [execute description]对反射的封装。
     * @param  [type]  $reflect_obj [description]反射对象
     * @param  [type]  $worker      [description]类对象
     * @param  [type]  $name        [description]方法的名字
     * @param  [type]  $canshu      [description]方法的参数
     * @return boolean              [description]
     */
    function execute($reflect_obj,$indexAction,$name,$param = ''){
        if($reflect_obj-> hasMethod($name)){
            $method = $reflect_obj->getMethod($name);
        if($method->isPublic()){
            $method->invoke($indexAction,$param); 
        }else{
            echo $name . '不是public';
        }
        }else{
            echo $name . '方法不存在';
        }
    }
    ......结果.....
    index
    _after_index方法执行
    data : test使用的数据
    _after_index方法执行

可以看到进行功能的封装,可以简化我们的代码,同时代码看起来更加的清晰。

总结

PHP的面向对象的内容到这里算是讲完了,在开发中利用面向对象的思想进行开发是一定要掌握的技能。同时也要对面向对象进行深度的了解。

相关文章