PHP学习笔记9——类与对象(续)

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. __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; // 放不存在的属性
?>
execute __get() method18
execute __get() method180cm
execute __get() method$this->job not exist

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修饰的属性
?>
property _price not exists
property introduction not exists
property _weight exists
  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);
?>
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分钟,续航一星期"
}

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

运行结果如下:

login method not exists!
array(2) {
  [0]=>
  string(8) "username"
  [1]=>
  string(8) "password"
}
  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;
?>
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

4. 自动加载

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

4.1 __autoload()方法

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

void __autoload(string $class)

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

<?php
class testA
{
    function classA() {
        echo "A", "\n";
    }
}
?>
<?php
class testB
{
    function classB() {
        echo "B", "\n";
    }
}

在同一目录下写一个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]]])

说明:

  • $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();
?>

输出: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';
    }
}

定义一个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();
    }
}

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();
} 
<?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';
    }
}
?>

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

<?php
interface MysqlAdmin
{
    function import();
    function export();
}

使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';
    }
}

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

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()

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; // 宝马
?>

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

<?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; // 兰博基尼
?>

可以使用__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;
?>

输出:

__clone() has been called
宝马
玛莎拉蒂

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)
?>

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.cn/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)
?>

猜你喜欢

转载自blog.csdn.net/username666/article/details/106698965