分类 PHP笔记 下的文章

PHP常用数组函数

入门
先让我们从一些处理数组键名和键值的基础数组函数开始。array_combine() 作为数组函数中的一员,用于通过使用一个数组的值作为其键名,另一个数组的值作为其值来创建一个全新数组:

<?php
$keys = ['sky', 'grass', 'orange'];
$values = ['blue', 'green', 'orange'];

$array = array_combine($keys, $values);

print_r($array);
// Array
// (
//     [sky] => blue
//     [grass] => green
//     [orange] => orange
// )

你应该知道,array_values() 函数会以索引数组形式返回数组中的值,array_keys() 则会返回给定数组的键名, 以及 array_flip() 函数,它的功能则是交换数组中的键值和键名:

<?php

print_r(array_keys($array));// ['sky', 'grass', 'orange']

print_r(array_values($array));// ['blue', 'green', 'orange']

print_r(array_flip($array));
// Array
// (
//     [blue] => sky
//     [green] => grass
//     [orange] => orange
// )

简化代码
list() 函数,确切的说它不是一个函数,而是一种语言结构,可以在单次操作中将数组中的值赋值给一组变量。举个例子,下面给出 list() 函数的基本使用:

<?php
// 定义数组
$array = ['a', 'b', 'c'];

// 不使用 list()
$a = $array[0];
$b = $array[1];
$c = $array[2];

// 使用 list() 函数
list($a, $b, $c) = $array;
这个语言结构结合 preg_split() 或 explode() 这类函数使用效果更佳,如果你无需定义其中的某些值,可以直接跳过一些参数的赋值:

$string = 'hello|wild|world';

list($hello, , $world) = explode('|', $string);
echo $hello, ' ', $world;

另外,list() 还可用于 foreach 遍历,这种用法更能发挥这个语言结构的优势:

$arrays = [[1, 2], [3, 4], [5, 6]];

foreach ($arrays as list($a, $b)) {
    $c = $a + $b;

    echo $c, ', ';
}

译者注:list() 语言结构仅适用于数字索引数组,并默认索引从 0 开始,且无法用于关联数组,查看 文档。
而通过使用 extract() 函数,你可以将关联数组导出到变量(符号表)中。对数组中的各个元素,将会以其键名作为变量名创建,变量的值则为对应元素的值:

<?php
$array = [
    'clothes' => 't-shirt',
    'size' => 'medium',
    'color' => 'blue',
];

extract($array);

echo $clothes, ' ', $size, ' ', $color;

注意在处理用户数据(如请求的数据)时 extract() 函数是一个安全的函数,所以此时最好使用更好的 标志类型 如 EXTR_IF_EXISTS 和 EXTR_PREFIX_ALL。

extract() 函数的逆操作是 compact() 函数,用于通过变量名创建关联数组:

<?php
$clothes = 't-shirt';
$size = 'medium';
$color = 'blue';

$array = compact('clothes', 'size', 'color');
print_r($array);

// Array
// (
//     [clothes] => t-shirt
//     [size] => medium
//     [color] => blue
// )

过滤函数
PHP 提供一个用于过滤数组的超赞的函数,它是 array_filter()。将待处理数组作为函数的第一个参数,第二个参数是一个匿名函数。如果你希望数组中的元素通过验证则在匿名函数返回 true,否则返回 false:

<?php

$numbers = [20, -3, 50, -99, 55];

$positive = array_filter($numbers, function ($number) {
    return $number > 0;
});

print_r($positive);// [0 => 20, 2 => 50, 4 => 55]

函数不仅支持通过值过滤。你还可以使用 ARRAY_FILTER_USE_KEY 或 ARRAY_FILTER_USE_BOTH 作为第三参数指定是否将数组的键值或将键值和键名同时作为回调函数的参数。

你还可以不在 array_filter() 函数中定义回调函数以删除空值:

<?php
$numbers = [-1, 0, 1];

$not_empty = array_filter($numbers);

print_r($not_empty);// [0 => -1, 2 => 1]

你可以使用 array_unique() 函数用于从数组中获取唯一值元素。注意该函数会保留唯一元素在原数组中的键名:

<?php
$array = [1, 1, 1, 1, 2, 2, 2, 3, 4, 5, 5];

$uniques = array_unique($array);

print_r($uniques);
print_r($array);
// Array
// (
//     [0] => 1
//     [4] => 2
//     [7] => 3
//     [8] => 4
//     [9] => 5
// )

array_column() 函数可以从多维数组(multi-dimensional)中获取指定列的值,如从 SQL 数据库中获取答案或者 CSV 文件导入数据。只需要传入数组和指定的列名:

<?php
$array = [
    ['id' => 1, 'title' => 'tree'],
    ['id' => 2, 'title' => 'sun'],
    ['id' => 3, 'title' => 'cloud'],
];

$ids = array_column($array, 'id');

print_r($ids);// [1, 2, 3]

从 PHP 7 开始,array_column 功能更加强大,因为它开始支持 包含对象的数组,所以在处理数组模型时变得更加容易:

<?php
$cinemas = Cinema::find()->all();
$cinema_ids = array_column($cinemas, 'id'); // php7 forever!

数组遍历处理
通过使用 array_map(),你可以对数组中的每个元素执行回调方法。你可以基于给定的数组传入函数名称或匿名函数来获取一个新数组:

<?php
$cities = ['Berlin', 'KYIV', 'Amsterdam', 'Riga'];
$aliases = array_map('strtolower', $cities);

print_r($aliases);// ['berlin', 'kyiv, 'amsterdam', 'riga']

$numbers = [1, -2, 3, -4, 5];
$squares = array_map(function ($number) {
    return $number ** 2;
}, $numbers);

print_r($squares);// [1, 4, 9, 16, 25]

对于这个函数还有个谣言,无法同时将数组的键名和键值传入到回调函数,但是我们现在要来打破它:

<?php
$model = ['id' => 7, 'name' => 'James'];
$res = array_map(function ($key, $value) {
    return $key . ' is ' . $value;
}, array_keys($model), $model);

print_r($res);
// Array
// (
//     [0] => id is 7
//     [1] => name is James
// )

不过这样处理起来实在是丑陋。最好使用 array_walk() 函数来替代。这个函数表现上和 array_map() 类似,但是工作原理完全不同。第一,数组是以引用传值方式传入,所以 array_walk() 不会创建新数组,而是直接修改原数组。所以作为源数组,你可以将数组的值以引用传递方法传入回调函数,数组的键名直接传入就好了:

<?php
$fruits = [
    'banana' => 'yellow',
    'apple' => 'green',
    'orange' => 'orange',
];

array_walk($fruits, function (&$value, $key) {
    $value = $key . ' is ' . $value;
});

print_r($fruits);

数组连接操作
在 PHP 中合并数组的最佳方式是使用 array_merge() 函数。所有的数组选项会合并到一个数组中,具有相同键名的值会被最后一个值所覆盖:

<?php
$array1 = ['a' => 'a', 'b' => 'b', 'c' => 'c'];
$array2 = ['a' => 'A', 'b' => 'B', 'D' => 'D'];
 
$merge = array_merge($array1, $array2);
print_r($merge);
// Array
// (
//     [a] => A
//     [b] => B
//     [c] => c
//     [D] => D
// )

译注:有关合并数组操作还有一个「+」号运算符,它和 array_merge() 函数的功能类似都可以完成合并数组运算,但是结果有所不同,可以查看 PHP 合并数组运算符 + 与 array_merge 函数 了解更多细节。
为了实现从数组中删除不在其他数组中的值(译注:计算差值),使用 array_diff()。还可以通过 array_intersect() 函数获取所有数组都存在的值(译注:获取交集)。接下来的示例演示它们的使用方法:

<?php
$array1 = [1, 2, 3, 4];
$array2 = [3, 4, 5, 6];

$diff = array_diff($array1, $array2);
$intersect = array_intersect($array1, $array2);

print_r($diff); // 差集 [0 => 1, 1 => 2]
print_r($intersect); //交集 [2 => 3, 3 => 4]

数组的数学运算
使用 array_sum() 对数组元素进行求和运算,array_product 对数组元素执行乘积运算,或者使用 array_reduce() 处理自定义运算规则:

<?php

$numbers = [1, 2, 3, 4, 5];

print_r(array_sum($numbers));// 15

print_r(array_product($numbers));// 120

print_r(array_reduce($numbers, function ($carry, $item) {
    return $carry ? $carry / $item : 1;
}));// 0.0083 = 1/2/3/4/5

为了实现统计数组中值的出现次数,可以使用 array_count_values() 函数。它将返回一个新数组,新数组键名为待统计数组的值,新数组的值为待统计数组值的出现次数:

<?php

$things = ['apple', 'apple', 'banana', 'tree', 'tree', 'tree'];
$values = array_count_values($things);

print_r($values);

// Array
// (
//     [apple] => 2
//     [banana] => 1
//     [tree] => 3
// )

生成数组
需要以给定值生成固定长度的数组,可以使用 array_fill() 函数:

<?php
$bind = array_fill(0, 5, '?');
print_r($bind);

根据范围创建数组,如小时或字母,可以使用 range() 函数:

<?php
$letters = range('a', 'z');
print_r($letters); // ['a', 'b', ..., 'z']

$hours = range(0, 23);
print_r($hours); // [0, 1, 2, ..., 23]

为了实现获取数组中的部分元素 - 比如,获取前三个元素 - 使用 array_slice() 函数:

<?php
$numbers = range(1, 10);
$top = array_slice($numbers, 0, 3);

print_r($top);// [1, 2, 3]

排序数组
首先谨记 PHP 中有关排序的函数都是 引用传值 的,排序成功返回 true 排序失败返回 false。排序的基础函数是 sort() 函数,它执行排序后的结果不会保留原索引顺序。排序函数可以归类为以下几类:

a 保持索引关系进行排序
k 依据键名排序
r 对数组进行逆向排序
u 使用用户自定义排序规则排序
你可以从下表看到这些排序函数:

a k r u
a asort arsort uasort
k ksort krsort
r arsort krsort rsort
u uasort usort
数组函数的组合使用
数组处理的艺术是组合使用这些数组函数。这里我们通过 array_filter() 和 array_map() 函数仅需一行代码就可以完成空字符截取和去空值处理:

<?php
$values = ['say', '  bye', '', ' to', ' spaces  ', '    '];
$words = array_filter(array_map('trim', $values));

print_r($words);// ['say', 'bye', 'to', 'spaces']

依据模型数组创建 id 和 title 数据字典,我们可以结合使用 array_combine() 和 array_column() 函数:

<?php
$models = [$model, $model, $model];

$id_to_title = array_combine(
    array_column($models, 'id'),
    array_column($models, 'title')
);

print_r($id_to_title);

译注:提供一个 可运行的版本。
为了实现获取出现频率最高的数组元素,我们可以使用 array_count_values()、arsort() 和 array_slice() 这几个函数:

<?php

$letters = ['a', 'a', 'a', 'a', 'b', 'b', 'c', 'd', 'd', 'd', 'd', 'd'];

$values = array_count_values($letters);
arsort($values);
$top = array_slice($values, 0, 3);

print_r($top);

还可以轻易的通过 array_sum() 和 array_map() 函数仅需数行就能完成计算订单的价格:

<?php
$order = [
    ['product_id' => 1, 'price' => 99, 'count' => 1],
    ['product_id' => 2, 'price' => 50, 'count' => 2],
    ['product_id' => 2, 'price' => 17, 'count' => 3],
];

$sum = array_sum(array_map(function ($product_row) {
    return $product_row['price'] * $product_row['count'];
}, $order));

print_r($sum);// 250

PHP trait 学习笔记

介绍
自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。
众所周知,PHP 中是单继承的,trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
实例
首先我们举个例子来介绍 trait,和类定义相似,但使用关键字 trait 定义。在类中使用 use 组合。

trait T
{
    public function m1()
    {
        return 'm1';
    }

    public function m2()
    {
        return 'm2';
    }
}

class Demo
{
    use T;
    public function test()
    {
        return 'test';
    }
}

$demo = new Demo;
echo $demo->m1(), PHP_EOL;
echo $demo->test(), PHP_EOL;

使用多个 trait

trait T
{
    public function m1()
    {
        return 'm1';
    }

    public function m2()
    {
        return 'm2';
    }
}

trait T2
{
    public function m3()
    {
        return 'm3';
    }
}

class Demo
{
    use T, T2;
    public function test()
    {
        return 'test';
    }
}

$demo = new Demo;
echo $demo->m1(), PHP_EOL;
echo $demo->m3(), PHP_EOL;
echo $demo->test(), PHP_EOL;

多个 trait 冲突解决

如果使用多个 trait,但是出现了方法名相同,这是就出现了冲突,就要手动指定使用哪个 trait 的方法,使用 insteadof 关键字实现。

trait T
{
    public function m1()
    {
        return 'm1';
    }

    public function m2()
    {
        return 'm2';
    }
}

trait T2
{
    public function m1()
    {
        return 'm3';
    }
}

class Demo
{
    use T, T2{
        // 使用 T 的 m1 方法
        T::m1 insteadof T2;
    }
    public function test()
    {
        return 'test';
    }
}

$demo = new Demo;
echo $demo->m1(), PHP_EOL;
echo $demo->test(), PHP_EOL;

在冲突的时候,也可以使用 use 定义方法别名解决冲突,例子如下:

trait T
{
    public function m1()
    {
        return 'm1';
    }

    public function m2()
    {
        return 'm2';
    }
}

trait T2
{
    public function m1()
    {
        return 'm3';
    }
}

class Demo
{
    use T, T2{
        T::m1 insteadof T2;
        T2::m1 as new_m1;
    }
    public function test()
    {
        return 'test';
    }
}

$demo = new Demo;
echo $demo->m1(), PHP_EOL;
echo $demo->new_m1(), PHP_EOL;
echo $demo->test(), PHP_EOL;

改变访问权限
也可以使用 use 关键字来改变方法的访问权限。

trait T
{
    public function m1()
    {
        return 'm1';
    }

    public function m2()
    {
        return 'm2';
    }
}

class Demo
{
    use T{
        m2 as protected;
    }

    public function test()
    {
        return 'test';
    }
}

$demo = new Demo;
echo $demo->m1(), PHP_EOL;
echo $demo->m2(), PHP_EOL;
echo $demo->test(), PHP_EOL;

上面我只是介绍了一些常用的特性,详细可参考 官方手册。

PHP yield 读取大文件

今天来优化下读取大文件,在 PHP 读取大文件的时候,经常会出现内存不足的情况,如果文件过大的话,没法一次读取完,今天采用 yield 来实现大文件的读取。yield生成器是php5.5之后出现的,yield提供了一种更容易的方法来实现简单的迭代对象,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。yield生成器允许你 在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组。

老式读取方式

function readLocalFile($fileName)
{
  $handle = fopen($fileName, 'r');
  $lins = [];
  while (!feof($handle)) {
      $lines[] = fgets($handle);
  }
  fclose($handle);
  return $lines;
}

yield 读取方式
使用 yield 的特性,来读取大文件

function readYieldFile($fileName)
{
  $handle = fopen($fileName, 'r');
  while (!feof($handle)) {
      yield fgets($handle);
  }
  fclose($handle);
}

辅助函数
为了便于测试,我们写一个读取内存的辅助函数

function formatBytes($bytes)
{
  if ($bytes < 1024) {
      return $bytes . "b";
  } else if ($bytes < 1048576) {
      return round($bytes / 1024, 2) . "kb";
  }
  return round($bytes / 1048576, 2) . 'mb';
}

测试
我这里准备了一个 7M 大小的文本文件来做测试。

# 第一种
readLocalFile('./all.txt');
echo formatBytes(memory_get_peak_usage());  // 结果为 7.59mb

# 第二种
$lines = readYieldFile('./all.txt');
foreach ($lines as $row) {}
echo formatBytes(memory_get_peak_usage());  // 结果为 137.79kb

PHP验证邮箱地址是否合法

背景

PHP校验邮箱地址的方法很多, 比较常用的就是自己写正则了, 不过正则多麻烦, 用PHP自带了方法做校验多方便;

filter_var

filter_var是PHP内置的一个变量过滤的方法,提供了很多实用的过滤器,可以用来校验整数、浮点数、邮箱、ULR、MAC地址等等。使用格式如:
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

参数说明:
$variable 待过滤的变量
$filter 应用的筛选器的标识。具体可以查看https://php.net/manual/zh/filter.filters.sanitize.php
$options

一个选项的关联数组,或者按位区分的标示。如果过滤器接受选项,可以通过数组的 "flags" 位去提供这些标示。 对于回调型的过滤器,应该传入 callable。这个回调函数必须接受一个参数,即待过滤的值,并且 返回一个在过滤/净化后的值。
filter_var如果返回false, 说明变量无法通过过滤器, 也就是不合法了。

$email = "ningpanyun@@yeah.net";
var_dump(filter_var($email,  FILTER_VALIDATE_EMAIL));
$email = "asb";
var_dump(filter_var($email,  FILTER_VALIDATE_EMAIL));

$email = "1@a.com";
var_dump(filter_var($email,  FILTER_VALIDATE_EMAIL));
输出:

string(21) "lastchiliarch@163.com"
bool(false)
string(7) "1@a.com"

对于asb这种非法邮箱格式返回了false, 但对于1@a.com则通过了,还是略有瑕疵啊。不过一般的正则也通过会认为1@a.com是一个合法的邮箱, 那有啥办法可以更精准的验证呢?

checkdnsrr

checkdnsrr其实是用来查询指定的主机的DNS记录的,我们可以借用它来验证邮箱是否存在。对于1@abc.com 肯定是MX记录不存在的。checkdnsrr函数的使用可以查看https://php.net/manual/zh/function.checkdnsrr.php

$email = "ningpanyun@@yeah.net";
$arr = explode("@", $email);
$str = array_pop( $arr );
var_dump( checkdnsrr($str, 'MX') );

$email = "1@a.com";
$arr = explode("@", $email);
$str = array_pop( $arr );
var_dump( checkdnsrr($str, 'MX') );

输出:
bool(true)
bool(false)
filter_var+checkdnsrr

我们可以接合filter_var 和checkdnsrr做校验, 对于绝大多数的非法邮箱肯定会在filter_var的时候就挂掉了, 剩下的再用checkdnsrr进一步判断。

$email = "ningpanyun@@yeah.net";
if (filter_var($email) === false) 
{
    echo "invalid email: $email \n";
    continue;
}
if(checkdnsrr(array_pop(explode("@",$email)), "MX") === false) 
{
    echo "invalid email: $email \n";
    continue;
}
输出: invalid email: 1@a.com

但要注意的是, 由于只是检查MX记录, 所以只能判断163.com是存在的, 但不能说明ningpanyun这个用户是存在的。想要更精确的判断邮箱存在, 那只能连接到smtp服务器去验证了。

php魔术方法详解

PHP 将所有以 (两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 为前缀。方术方法包括:(加组表示常用)

__construct(),类的构造函数

__destruct(),类的析构函数

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get(),获得一个类的成员变量时调用

__set(),设置一个类的成员变量时调用

__isset(),当对不可访问属性调用isset()或empty()时调用

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

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__toString(),类被当成字符串时的回应方法

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行unserialize()时,先会调用这个函数

__invoke(),以调用函数的方式调用一个对象时会行此方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__debugInfo(),打印所需调试信息

范例

下面让我们以实例的形式向大家讲解下这几个魔术方法是如何使用的。

1、__construct(),类的构造函数

说明:php中构造方法是对象创建完成后第一个被对象自动调用的方法。在每个类中都有一个构造方法,如果没有显示地声明它,那么类中都会默认存在一个没有参数且内容为空的构造方法。

作用: 通常构造方法被用来执行一些有用的初始化任务,如对成员属性在创建对象时赋予初始值。

格式: void __construct()

注意: 在同一个类中只能声明一个构造方法,原因是,PHP不支持构造函数重载。

<?php
class Demo
{
    private $prop;

    public function __construct($val) 
    {
        $this->prop = $val;
    }

    public function abc()
    {
        echo $this->prop;
    }
}
$demo = new Demo(1); // 会执行 __construct 方法
$demo->abc(); // 输出 1

2、__destruct(),类的析构函数

说明:析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等。

析构方法是PHP5才引进的新内容。析构函数不能带有任何参数

析造方法的声明格式与构造方法 __construct() 比较类似,也是以两个下划线开始的方法 __destruct() ,这种析构方法名称也是固定的

<?php
class Demo
{
    private $prop;

    public function __construct($val) 
    {
        echo "实例化的时候,会首先调用这个方法<br><br>";
        $this->prop = $val;
    }

    public function __destruct()
    {
        echo "在销毁时调用";
    }
}

$demo = new Demo(1); // 会执行 __construct 方法

// 这里会输出“在销毁时调用”, 默认的时候,我们不需要手动去 unset 一个对象实例,当PHP执行完会自动销毁
3、__call()和__callStatic()方法

说明:该方法有两个参数,第一个参数$functionName会自动接收不存在的方法名,第二个 $arguments 则以数组的方式接收不存在方法的多个参数

格式:public function __call(string $functionName, array $arguments)

格式:public static function __callStatic( string $functionName, array $arguments )

作用:为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免。该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去。

<?php
class Demo
{
    public function __call($functionName, $arguments)
    {
        var_dump( $functionName );
        var_dump( $arguments );
    }

    public static function __callStatic($functionName, $arguments)
    {
        var_dump( $functionName );
        var_dump( $arguments );
    }
}

$demo = new Demo();
$demo->test(123); // 会调用 __call() 方法
$demo::abc(123); // 会调用 __callStatic() 方法

4、__get()和__set()方法

说明:在 php 面向对象编程中,类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误。那么为了解决这个问题,我们可以使用魔术方法 __get()

同样给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值

作用:__get 可以在必要的时候获取私有成员属性的值,__set 可以给未定义属性赋值

格式:public function __get($propertyName)

格式:public function __set($propertyName, $propertyValue)

<?php
class Demo
{
    private $prop = 123;

    public function __get($propertyName)
    {
        if( isset($this->$propertyName) )
        {
            return $this->$propertyName;
        }
    }

    public function __set($propertyName, $propertyValue)
    {
        $this->$propertyName = $propertyValue;
    }
}

$demo = new Demo();
echo $demo->prop;// 调用 __get() 返回 123
$demo->test = 123; // 调用 __set() 给 $demo 对象设置一个 test 属性

5、__isset()方法,当对不可访问属性调用isset()或empty()时调用

说明:在看这个方法之前我们看一下isset()函数的应用,isset()是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false。如果变量的值是 NULL, isset() 也是返回 false

那么如果在一个对象外面使用isset()这个函数去测定对象里面的成员是否被设定可不可以用它呢?

分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用isset()函数来测定私有成员属性是否被设定了呢?当然是可以的,但不是一成不变。你只要在类里面加上一个__isset()方法就可以了,当在类外部使用isset()函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的__isset()方法了帮我们完成这样的操作。

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

<?php
class Demo
{
    private $prop = 123;

    public function __isset($propertyName)
    {
        var_dump( $propertyName );

        return isset($this->$propertyName);
    }
}

$demo = new Demo();
var_dump( isset($demo->prop) ); // 这里会调用 __isset() 方法,返回 __isset() 的返回值true

6、__unset()方法,当对不可访问属性调用unset()时被调用

看这个方法之前呢,我们也先来看一下 unset() 函数,unset()这个函数的作用是删除指定的变量且传回true,参数为要删除的变量。

那么如果在一个对象外部去删除对象内部的成员属性用unset()函数可以吗?

这里自然也是分两种情况:

1、 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性。

2、 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。

虽然有以上两种情况,但我想说的是同样如果你在一个对象里面加上__unset()这个方法,就可以在对象的外部去删除对象的私有成员属性了。在对象里面加上了__unset()这个方法之后,在对象外部使用“unset()”函数删除对象内部的私有成员属性时,对象会自动调用__unset()函数来帮我们删除对象内部的私有成员属性。

<?php
class Demo
{
    private $prop = 123;

    public function __unset($propertyName)
    {
        var_dump( $propertyName );
        unset( $this->$propertyName );
    }
}

$demo = new Demo();
unset($demo->prop); // 这里会调用 __unset() 方法,删除私有属性 prop
print_r( $demo ); // 已经没有了 prop 属性

7、__clone()方法,当复制一个对象的时候会被调用

说明:在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本

格式:void __clone( void )

<?php
class Demo
{
    private $prop = 123;
    private $link = 'abc';

    public function __clone()
    {
        $this->prop = 345;
    }
}

$demoa = new Demo();
$demob = clone $demoa;

var_dump( $demoa ); // demoa 的prop为123
var_dump( $demob ); // demob 的prop为345

8、__autoload() 函数,尝试加载未定义的类

说明:这个函数和其它方法有所不同,这个函数不需要放到类里面。是在类外调用

格式:function void __autoload( string $className );

作用:可以通过这个函数,来做自动加载

<?php
function __autoload($className)
{
    var_dump( $className );
    include '这里要加载 $className 这个类的文件';
}

$demoa = new Demo(); // 如果没有Demo这个类,就会调用 __autoload 函数

以上就是PHP中常用的魔术方法了,以下几个不常用。但是最好了解下,当看到别人的代码用到时候,最好知道什么意思

1、__sleep() 和 __wakeup() 方法

格式:public array __sleep ( void )

格式: void __wakeup ( void )

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误

__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。

与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。__wakeup() 常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作

<?php
class Connection
{
    private $link = 111;
    private $connection = 'abc';
    
    public function __sleep()
    {
        echo "当调用serialize函数时,先调用此方法", "<br/>";
        return array('link', 'connection');
    }
    
    public function __wakeup()
    {
        echo "当调用unserialize函数时,先调用此方法", "<br/>";
    }
}

$mylink = new Connection('localhost', 'root', 'root', 'test');
$serialize =  serialize($mylink);// 输出 "当调用serialize函数时,先调用此方法"
unserialize( $serialize ); // 输出 "当调用unserialize函数时,先调用此方法"

2、__toString() 方法

格式:public string __toString ( void )

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

<?php
class TestClass
{
    public $foo;

    public function __construct($foo) 
    {
        $this->foo = $foo;
    }

    public function __toString() 
    {
        return $this->foo;
    }
}

$class = new TestClass('Hello');
echo $class; // Hello

特别说明:在 PHP 5.2.0 之前,__toString() 方法只有在直接使用于 echo 或 print 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。自 PHP 5.2.0 起,如果将一个未定义 __toString() 方法的对象转换为字符串,会产生 E_RECOVERABLE_ERROR 级别的错误

3、__invoke() 方法

格式:public mixed __invoke ([ $... ] )

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。本特性只在PHP5.3.0及以上版本有效。

<?php
class CallableClass 
{
    public function __invoke($x) 
    {
        var_dump($x);
    }
}

$obj = new CallableClass;
$obj(5); // int 5
var_dump(is_callable($obj)); // boolean true

4、__set_state() 方法

格式:public static object __set_state( array $properties )

说明:自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性

<?php
class A
{
    public $var1;
    public $var2;

    public static function __set_state($an_array) 
    {
        $obj = new A;
        $obj->var1 = $an_array['var1'];
        $obj->var2 = $an_array['var2'];
        return $obj;
    }
}

$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
eval('$b = ' . var_export($a, true) . ';');
var_dump( $b ); 
// 此时 $b == A::_set_state(array('var1'=>5, 'var2'=>'foo'))

5、__debugInfo()方法

格式:public array __debugInfo ( void )

说明:这个方法在PHP 5.6.0以后支持, 当在用 var_dump 函数输出类的时候,会调用这方法

<?php
class C 
{
    private $prop;

    public function __construct($val) 
    {
        $this->prop = $val;
    }

    public function __debugInfo() 
    {
        echo "会先调用这个方法";
        return array(1, 2, 3);
    }
}
var_dump(new C(42)); // 会先调用 __debugInfo()

本文参考内容:

https://php.net/manual/zh/language.oop5.magic.php