类与对象(续)
3. 魔术方法
PHP中提供了内置的拦截器,也称为“魔术方法”,可以拦截发送到未定义方法和属性的消息。魔术方法通常以两个下划线“__”开始。
3.1 __set()和__get()方法
- __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
- __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()方法
- __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
- __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()方法
- __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"
}
- __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()函数。
扫描二维码关注公众号,回复: 11579022 查看本文章
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)
?>