关键词搜索

源码搜索 ×
×

《PHP 7从零基础到项目实战》学习笔记9——类与对象(续)

发布2020-06-13浏览406次

详情内容

3. 魔术方法

PHP中提供了内置的拦截器,也称为“魔术方法”,可以拦截发送到未定义方法和属性的消息。魔术方法通常以两个下划线“__”开始。

3.1 __set()和__get()方法

  1. __set()方法

__set()方法在代码试图要给未定义的属性赋值时调用,或在类外部修改被private修饰的类属性时被调用。它会传递两个参数:属性名和属性值。通过__set()方法也可以实现对private关键词修饰的属性值进行更改。

<?php

class magic
{
    private $_name;
    private $_age = '16';
    function __set($key, $value)
    {
        echo 'execute __set method', "\n";
        $this->$key = $value;
    }
}
$obj = new magic();
echo $obj->_gender = 'male', "\n"; // 访问类中不存在的$_gender属性被__set()方法拦截
$obj->_name = '张三'; // 在类外部修改private修饰的属性$name被拦截
?>
    execute __set method
    male
    execute __set method
    
    • 1
    • 2
    • 3
    1. __get()方法

    当在类外部访问被private或protected修饰的属性或访问一个类中原本不存在的属性时被调用。

    <?php
    
    class magic2
    {
        private $_age = '18';
        protected $_height = '180cm';
        function __get($key)
        {
            echo 'execute __get() method';
            $oldKey = $key;
            if(isset($this->$key)) {
                return $this->$key;
            }
    
            $key = '_'. $key;
            if(isset($this->$key)) {
                return $this->$key;
            }
            return '$this->' . $oldKey. ' not exist';
        }
    }
    
    $obj = new magic2();
    echo $obj->_age, "\n"; // 访问被private修饰的属性
    echo $obj->_height, "\n"; // 访问被protected修饰的属性
    echo $obj->job; // 放不存在的属性
    ?>
    
      17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    execute __get() method18
    execute __get() method180cm
    execute __get() method$this->job not exist
    
    • 1
    • 2
    • 3

    3.2 __isset()和__unset()方法

    1. __isset()方法

    当在类外部对未定义的属性或者非公有属性使用isset()函数时,魔术方法__isset()将会被调用。

    <?php
    
    class magic
    {
        public $product = "smart mobile phone";
        private $_brand;
        private $price = '$3000.00';
        protected $_weight = '300g';
        private $_description = '充电5分钟,续航一星期';
    
        function __isset($key)
        {
        	/**
             * 说明: property_exists(class_name, property_name)用于检测类中是否定义了某个属性
             */
            if(property_exists('magic', $key)) {
                echo 'property ' . $key . ' exists', "\n";
            } else {
                echo 'property ' .$key . ' not exists', "\n";
            }
        }
    }
    
    $obj  = new magic();
    isset($obj->_price); // 被private修饰的属性
    
    isset($obj->introduction); // 不存在的属性
    
    isset($obj->product); // 被 public修饰的属性,不会触发__isset()方法
    
    isset($obj->_weight); // 被protected修饰的属性
    ?>
    
      17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    property _price not exists
    property introduction not exists
    property _weight exists
    
    • 1
    • 2
    • 3
    1. __unset()方法

    对类中未定义的属性或非公有属性进行unset()操作时,将会触发__unset()方法。如果属性存在,unset()操作会销毁这个属性,释放该属性在内存中占用的空间,再次用对象访问该属性时,将会返回NULL。

    <?php
    
    class magic
    {
        public $product = "smart mobile phone";
        private $_brand;
        private $price = '$3000.00';
        protected $_weight = '300g';
        private $_description = '充电5分钟,续航一星期';
    
        function __isset($key)
        {
            /**
             * 说明: property_exists(class_name, property_name)用于检测类中是否定义了某个属性
             */
            if(property_exists('magic', $key)) {
                echo 'property ' . $key . ' exists', "\n";
            } else {
                echo 'property ' . $key . ' not exists', "\n";
            }
        }
    
        function __unset($key)
        {
            if(property_exists('magic', $key)) {
                unset($this->$key);
                echo 'property ' . $key . ' has been unset!', "\n";
            } else {
                echo 'propert ' . $key . ' not exists!', "\n";
            }
        }
    }
    
    $obj  = new magic();
    
    isset($obj->price); // 被private修饰的属性
    
    isset($obj->introduction); // 不存在的属性
    
    isset($obj->product); // 被 public修饰的属性,不会触发__isset()方法
    
    isset($obj->_weight); // 被protected修饰的属性
    
    var_dump($obj);
    
    unset($obj->price);
    
    unset($obj->introduction);
    
    unset($obj->product); // 存在该属性,且被public修饰,不会触发__unset()方法
    
    unset($obj->_weight);
    
    //echo $obj->price, "\n";
    //echo $obj->product, "\n";
    //echo $obj->_weight, "\n";
    var_dump($obj);
    ?>
    
      17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    property price exists
    property introduction not exists
    property _weight exists
    object(magic)#1 (5) {
      ["product"]=>
      string(18) "smart mobile phone"
      ["_brand":"magic":private]=>
      NULL
      ["price":"magic":private]=>
      string(8) "$3000.00"
      ["_weight":protected]=>
      string(4) "300g"
      ["_description":"magic":private]=>
      string(31) "充电5分钟,续航一星期"
    }
    property price has been unset!
    propert introduction not exists!
    property _weight has been unset!
    object(magic)#1 (2) {
      ["_brand":"magic":private]=>
      NULL
      ["_description":"magic":private]=>
      string(31) "充电5分钟,续航一星期"
    }
    
      17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.3 __call()和__toString()方法

    1. __call()方法

    当试图调用类中不存在的方法时会触发__call()方法。__call()方法有两个参数,即方法名参数,参数以索引数组的形式存在。

    <?php
    
    class testCall
    {
        function __call($func, $param)
        {
            echo "$func method not exists!\n";
            var_dump($param);
        }
    }
    
    $test = new testCall();
    $test->login('username', 'password');
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行结果如下:

    login method not exists!
    array(2) {
      [0]=>
      string(8) "username"
      [1]=>
      string(8) "password"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. __toString()方法

    当使用echo或print打印对象时会调用__toString()方法将对象转化为字符串。

    <?php
    
    class testToString
    {
        function __toString()
        {
           return 'when you want to echo or print the object, __toString() will be called';
        }
    }
    
    $test = new testToString();
    print $test;
    echo "\n";
    echo $test;
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    when you want to echo or print the object, __toString() will be called
    when you want to echo or print the object, __toString() will be called
    
    • 1
    • 2

    4. 自动加载

    PHP中提供了两个可用来自动加载文件的函数__autoload()和spl_autoload_register()函数。

    4.1 __autoload()方法

    当在代码中尝试加载未定义的类时,会触发__autoload()函数,语法如下:

    void __autoload(string $class)
    
    • 1

    其中,$class是待加载的类名,该函数没有返回值。
    假设有两个文件,分别是testA.php和testB.php:

    <?php
    class testA
    {
        function classA() {
            echo "A", "\n";
        }
    }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    <?php
    class testB
    {
        function classB() {
            echo "B", "\n";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在同一目录下写一个autoload.php:

    <?php
    /**
     * @param $name
     */
    function __autoload($name) {
        if(file_exists($name . ".php")) {
            require_once $name . '.php';
        } else {
            echo "The path is error!";
        }
    }
    $a = new testA();
    $a->classA();
    $b = new testB();
    $b->classB();
    ?>
    

      执行autoload.php,原本应该是能输出的,但是由于我这phpEnv中php版本为7.4。运行报错:PHP Deprecated: __autoload() is deprecated, use spl_autoload_register() instead

      4.2 spl_autoload_register()函数

      spl_autoload_register()函数可实现自动加载,以及注册给定的函数作为__autoload()的实现。

      spl_autoload_register()函数的语法如下:

      bool spl_autoload_register([callable $autoload_function [, bool $throw = true [, bool $prepend = false]]])
      
      • 1

      说明:

      • $autoload_function:要注册的自动装载函数,如果没有提供任何参数,则自动注册autoload的默认实现函数spl_autoload()。
      • $throw:autoload_function无法成功注册时,spl_autoload_register()是否抛出异常。若$throw为true或未设置值,则抛出异常,否则不抛出。
      • $prepend为true时,spl_autoload_register()会添加函数到队列之首,而不是队列尾部。

      修改autoload.php的代码:

      <?php
      
      spl_autoload_register(function ($class) {
          include $class . '.php';
      });
      
      $a = new testA();
      $a->classA();
      $b = new testB();
      $b->classB();
      ?>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      输出:A B

      5. 抽象类和接口

      抽象类和接口都是不能被实例化的特殊类,可以在抽象类和接口中保留公共的方法,将抽象类和接口作为公共的基类。

      5.1 抽象类

      • 创建一个抽象类可使用关键词abstract
      • 一个抽象类必须至少包含一个抽象方法
      • 抽象类中的方法不能定义为私有的(private),因为抽象类中的方法需要被子类覆盖
      • 同样,抽象类中的方法不能用final修饰,因为其需要被子类继承
      • 抽象类中的抽象方法不包含方法实体
      • 如果一个类中包含了一个抽象方法,那么这个类也必须声明为抽象类。
      • 抽象类中的抽象方法必须被子类实现(除非该抽象类的子类也是抽象类),否则会报错
      • 抽象类中的非抽象方法可以不被子类实现
      • 非抽象方法必须包含实体,抽象方法不能包含实体
        比如定义一个数据库抽象类,有多种数据库,如MySQL、Oracle、MSSQL、SQLite等,虽然每种数据库都有不同的使用方法,但是都有共同的操作部分,比如建立数据库连接、查询数据、关闭数据库连接等。
        定义一个抽象Database类:
      <?php
      /**
       * 数据库抽象基类
       * Class Database
       */
      abstract class Database
      {
          // 建立数据库连接
          abstract function connect($host, $username, $pwd, $db);
          // 查询数据库
          abstract function query($sql);
          abstract function fetch();
          // 关闭数据库连接
          abstract function close();
          function test() {
              echo 'test';
          }
      }
      
        17
      • 18

      定义一个MySQL类,继承自抽象基类Database。

      <?php
      
      class MySQL extends Database
      {
          protected $conn;
          protected $query;
      
          /**
           * 建立数据库连接
           * @param $host 数据库服务器
           * @param $username 用户名
           * @param $pwd 密码
           * @param $db 数据库名
           */
          function connect($host, $username, $pwd, $db)
          {
              $this->conn = new mysqli($host, $username, $pwd, $db);
          }
      
          /**
           * 查询数据库
           * @param $sql
           * @return mixed
           */
          function query($sql)
          {
              return $this->conn->query($sql);
          }
      
          function fetch()
          {
              return $this->query->fetch(); // 获取查询结果集
          }
      
          /**
           * 关闭数据库连接
           */
          function close()
          {
              $this->conn->close();
          }
      }
      
        17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      5.2 接口

      子类只能继承自一个抽象类,却可以继承自多个接口。接口实现了PHP的多重继承。

      • 同样,接口是需要被继承的,所以接口中定义的方法不能为私有方法(被private修饰)或被final修饰
      • 接口中定义的方法必须被子类实现,并且不能包含实体
      <?php
      /**
       * 数据库接口
       * Interface Db
       */
      interface Db
      {
          function connect($host, $username, $pwd, $db);
          function query($sql);
          function fetch();
          function close();
          function test();
      } 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      <?php
      class mysql implements Db{
          
          protected $conn;
          protected $query;
          
          function connect($host, $username, $pwd, $db)
          {
              $this->conn = new mysqli($host, $username, $pwd, $db);
          }
      
          function query($sql)
          {
              return $this->conn->query($sql);
          }
      
          function fetch()
          {
              return $this->query->fetch();
          }
      
          function close()
          {
              $this->conn->close();
          }
      
          function test()
          {
              echo 'test';
          }
      }
      ?>
      
        17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      与抽象类不同的是,一个子类可继承多个接口。再定义一个接口MysqlAdmin:

      <?php
      interface MysqlAdmin
      {
          function import();
          function export();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      使mysql实现它。类继承多个接口,多个接口之间用逗号(“,”)隔开,类要实现其继承的所有接口的方法。

      class mysql implements Db, MysqlAdmin {
      
          protected $conn;
          protected $query;
      
          function import()
          {
              $sql = " load data local infile '/data/import.txt' into table table_name";
              $this->conn->query($sql);
          }
      
          function export()
          {
              $sql = "select * from table_name into outfile 'export.txt'";
              $this->conn->query($sql);
          }
          
          function connect($host, $username, $pwd, $db)
          {
              $this->conn = new mysqli($host, $username, $pwd, $db);
          }
      
          function query($sql)
          {
              return $this->conn->query($sql);
          }
      
          function fetch()
          {
              return $this->query->fetch();
          }
      
          function close()
          {
              $this->conn->close();
          }
      
          function test()
          {
              echo 'test';
          }
      }
      
        17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      此外,接口也可以继承接口。一个接口也可以继承自多个接口。

      6. 类中的关键字

      类中常用到的关键字有:final、clone、instanceof、== 和 ===。

      6.1 final关键字

      子类可以覆写父类中的方法,但有时并不希望父类中的方法被重写,此时可以在父类的方法前加上final控制符,使该方法无法被子类重写。

      <?php
      
      class father
      {
       final function test() {
           echo "test", "\n";
       }
      }
      
      class son extends father {
          function test() {
              echo "new test", "\n";
          }
      }
      // 运行报错: Fatal error: Cannot override final method father::test()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      6.2 clone关键字

      可通过clone关键字克隆一个对象,克隆后的对象相当于在内存中重新开辟了一个空间,克隆得到的对象拥有和原来对象相同的属性和方法,修改克隆得到的对象不会影响原来的对象。

      <?php
      
      class car
      {
          public $brand = '宝马';
          function test() {
              echo "test";
          }
      }
      
      $car = new car();
      $car_clone = clone $car;
      $car_clone->brand = '丰田';
      echo $car->brand; // 宝马
      ?>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      注意:
      如果使用“=”将一个对象赋值给一个变量,这时得到的将是一个对象的引用,通过这个变量改变属性的值将会影响原来的对象。

      <?php
      
      class car
      {
          public $brand = '宝马';
          function test() {
              echo "test";
          }
      }
      
      $car = new car();
      $car_clone = clone $car;
      $car_clone->brand = '丰田';
      echo $car->brand; // 宝马
      
      $car2 = $car;
      $car2->brand = '兰博基尼';
      echo $car->brand, "\n"; // 兰博基尼
      echo $car2->brand; // 兰博基尼
      ?>
      
        17
      • 18
      • 19
      • 20

      可以使用__clone()魔术方法将克隆后的副本初始化,也可理解为当对象被克隆时自动调用这个方法。

      <?php
      class car
      {
          public $brand = '宝马';
          function test() {
              echo "test";
          }
      
          function __clone()
          {
              echo "__clone() has been called\n";
              $this->brand = '玛莎拉蒂'; // 当克隆对象时,克隆后对象得到的将是此处的brand属性值
          }
      }
      
      $car = new car();
      $car_clone = clone $car;
      echo $car->brand, "\n";
      echo $car_clone->brand;
      ?>
      
        17
      • 18
      • 19
      • 20

      输出:

      __clone() has been called
      宝马
      玛莎拉蒂
      
      • 1
      • 2
      • 3

      6.3 instanceof关键字

      instanceof关键字可检测对象属于哪个类,也可用于检测生成实例的类是否继承自某个接口。

      <?php
      class car
      {
          public $brand = '宝马';
          function test() {
              echo "test";
          }
      }
      
      interface Database{
          function test();
      }
      class mysql implements Database{
      
          function test()
          {
              echo 'test';
          }
      }
      
      $car = new car();
      $mysql = new mysql();
      var_dump($car instanceof car); // bool(true)
      var_dump($mysql instanceof Database); // bool(true)
      ?>
      
        17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

      6.4 “==” 和“===”

      可使用“==”和“===”比较两个对象:

      • “==”比较两个对象的内容是否相同,即是否具有相同的属性和方法,相同则返回bool(true),否则返回bool(false)。
      • ”===“比较两个对象是否为同一引用,若是则返回bool(true),否则返回bool(false)。
      <?php
      class car
      {
          public $brand = '宝马';
          function test() {
              echo "test";
          }
      }
      
      [video(video-L83qS77t-1591978591705)(type-edu_course)(url-https://edu.csdn.net/course/blogPlay?goods_id=19446&blog_creator=username666&marketing_id=1256)(image-https://img-bss.csdnimg.cnhttps://cdn.jxasp.com:9143/image/20200603213452470.jpg)(title-图解Python数据结构与算法-实战篇)]
      
      $car = new car();
      $car_clone = clone $car;
      $car2 = $car;
      var_dump($car == $car_clone); // bool(true)
      var_dump($car === $car_clone); // bool(false)
      var_dump($car === $car2); // bool(true)
      ?>
      
        17
      • 18

      往期文章:

      相关技术文章

      点击QQ咨询
      开通会员
      返回顶部
      ×
      微信扫码支付
      微信扫码支付
      确定支付下载
      请使用微信描二维码支付
      ×

      提示信息

      ×

      选择支付方式

      • 微信支付
      • 支付宝付款
      确定支付下载