@dungan
2018-09-14T07:07:46.000000Z
字数 8609
阅读 95
PHP
最近在看一些开源库的源码,经常发现有一些比如函数类型申明,参数解构之类的新特性,为了帮助理解和记忆,对这些新特性做一些归纳和总结!
... 这玩意在 JavaScript 叫它剩余参数,我觉得挺形象的,意思就是参数多的数不过来,那就用 ... (省略号)代替呗!
以前给函数定义参数的数量是定的
function test($a, $b)
{
// TODO
}
test(1,2,3,4,5);
像上面这样调用肯定是不行了,但是有了 ... 运算符我们就 可以忽略传入函数的参数数量
function test($a, $b, ...$param)
{
sort($param);
var_dump($param);
}
test(1,2,5,4,3);
/*array(3) {
[0] =>
int(3)
[1] =>
int(4)
[2] =>
int(5)
}*/
可以看到函数参数中的剩余参数,其类型是数组,我们可以使用数组相关的函数处理它!
既然能够省略函数参数数量,那也能展开函数参数
function test($a, $b, $c, $d, $e)
{
return $a + $b + $c + $d + $e;
}
$param = [5, 4, 3];
test(1,2, ...$param); // 15
一个 * 是乘法运算,两个 ** 是幂运算
echo 2*3; //6
echo 2**3; //8
echo 2 ** 3 ** 2;// 512
// 2 ** 3 ** 2 = 2 ** (3**2=9)
格式为 use function 和 use const
namespace Name\Space {
const FOO = 42;
function f() { echo __FUNCTION__."\n"; }
}
namespace {
use Name\Space\FOO;
use Name\Space\f;
echo FOO."\n";
f();
}
输入流本身是一种资源,资源如果不能复用的话就会很耗内存的!另外注意 enctype="multipart/form-data" 的时候 php://input 是无效的,form-data 意味着你有问价要上传!
var_dump 输出对象的时候,会一股脑的输出对象里面『公有的』,『私有的』属性,但现在可以通过__debugInfo 来控制要输出对象的哪些属性和值,比如打印对象 Test 时只想输出它的 $money 属性!
class Test
{
public $money = 100;
public function __debugInfo()
{
return ['money' => $this->money * 100];
}
}
$test = new Test();
var_dump($test);
/*class Test#1 (1) {
public $money =>
int(10000)
}*/
php7.0 是一个大版本升级,引入了几个重量级的新特性,例如 『类型申明』,『三元运算符合并』,『组合比较符』等等!
类型申明是挺有用的,因为你不想战战兢兢的向数据库 int 类型的字段插入数据时还要 intval() 一下!
可用的类型如图
标量类型声明有两种模式: 『强制模式』 和 『严格模式』,默认是强制模式!
类型声明允许函数在调用时要求参数为特定类型。 如果给出的值类型不对,那么将会产生一个错误;类型应该加到参数名前!
默认新情况下,PHP 将会强迫错误类型的值转为函数期望的标量类型,正如你看到的,下面的 3.56 被转成成了 int 类型的 3!
function test(int $num)
{
echo $num;
}
test('3.56'); // 3
可以看到类型申明起作用了,将一个字符类型的数字转换成了它被申明时的
当然你可能觉得应该像正统的oo语言一样,对类型进行严格校验,那么也是可以的!
declare(strict_types=1);
function test(int $num)
{
echo $num;
}
test('3.56');
// Fatal error: Uncaught TypeError: Argument 1 passed to test()....
当然类型也可以是某个类的实例
class C {}
function f(Array $arr) {
echo gettype($arr);
}
f([]); // array
f(''); // Fatal error: Uncaught TypeError ...
function test():int
{
return '123';
}
echo test(); // 123
同样你可以使用严格模式
declare(strict_types=1);
function test():int
{
return '123';
}
echo test(); // Fatal error: Uncaught TypeError...
返回值也可以是某个类的实例
class C {}
function getC(): C {
return new C;
}
var_dump(getC());
关于严格模式
- 要使用严格模式,一个 declare 声明指令必须放在文件的顶部,加了严格模式以后就会对类型申明进行严格判断!
- 启用严格模式 既影响『标量类型申明』,同时也会影响『返回值类型声明』!
- 一个没有启用严格模式的文件内调用了一个在启用严格模式的文件中定义的函数,那么将会遵循调用者的偏好(弱类型),而这个值将会被转换,所以在你 require 文件时这点需要注意!
?? 运算符本身是对三元运算符的简化
如果变量存在且值不为NULL, 它就会返回自身的值,否则返回它的第二个操作数
// demo1
$b = null;
$a = $b ?? 'a';
echo $a; // a
// demo2
$a = $b ?? 'a';
echo $a; // a
// demo3
$b = '';
$a = $b ?? 'a';
echo $a; // 空字符
// demo4
$b = '';
$a = empty($b) ?? 'a';
echo $a; // 1
// demo5
$a = isset($b) ?? 'a';
echo gettype($a); // boolean
『组合比较符』能够让你避免写 if-else 之类的比较逻辑
组合比较符用于比较两个表达式。当b时它分别返回-1、0或1。
// 整数
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// 浮点数
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// 字符串
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Array 类型的常量之前只能用 const 定义,7.0+ 你可以使用通过『define』定义
define('SUM', [1, 2, 3]);
echo array_sum(SUM); // 6
new 一个没有名字的类就是匿名类,7.0 开始支持匿名类, 匿名类很有用,可以创建一次性的简单对象!
// PHP 7 之前的代码
class Logger
{
public function log($msg)
{
echo $msg;
}
}
$util->setLogger(new Logger());
// 使用了 PHP 7+ 后的代码
$util->setLogger(new class {
public function log($msg)
{
echo $msg;
}
});
当然匿名类也可以继承其他类、实现接口,使用 trait!
正如匿名函数一样,可以看到匿名类常作为函数的参数出现,就像回调函数函数一样,我们把这种类也可以叫做『回调类』(我自己给取得名字)
匿名函数我们知道可以在函数中被返回形成闭包,匿名类也可以在函数中返回
class Outer
{
protected $prop1 = 1;
protected $prop2 = 2;
public function test()
{
return new class() extends Outer
{
public $prop3;
public function __construct()
{
$this->prop3 = $this->prop1 + $this->prop2;
}
};
}
}
echo (new Outer)->test()->prop3; //3
匿名类也可以赋值给一个变量
class A
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
$b = new class('anonymous') extends A
{
public function getName()
{
return parent::getName() . ' class';
}
};
echo $b->getName(), PHP_EOL; // anonimous class
7.0 之前为闭包绑定作用域并执行,我们只能通过 IIFE((立即调用的函数表达式)) 这样的淫巧,不过现在有了 『Closure::call()』这一切就变得简单多了!
如果你不理解闭包是什么并且为什么要绑定作用,可以看下我之前写的一遍『 博客』
class Test
{
private $num = 1;
}
$test = new Test();
// PHP 7 之前版本的代码
$closure = function($add)
{
echo $this->num + $add;
};
(Closure::bind($closure, $test, $test))(1); //2
// PHP 7+ 及更高版本的代码
$closure->call($test, 1); //2
7.0 为 unserialize 提供了第一个数组参数,它可以对你要反序列化的对象提供过滤!
allowed_classes 可以看成是反序列化时白名单,可以控制能够反序列化哪些对象
// 不允许反序列化任何对象
$obj = unserialize($string, ['allowed_classes' => false]);
// 只反序列化 Class1 和 Class2
$obj = unserialize($string, ['allowed_classes' => ['Class1', 'Class2']]);
// 默认情况下,如果没有第二个参数,则相当于 'allowed_classes'=true,也就是不管怎样都可以反序列化
$obj = unserialize($string, ['allowed_classes' => true]);
可以使用 use 一次性导入同一命名空间下的类,函数,常量
// PHP 7 之前的代码
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;
use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;
use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;
// PHP 7+ 及更高版本的代码
use some\namespace\{ClassA, ClassB, ClassC as C};
use function some\namespace\{fn_a, fn_b, fn_c};
use const some\namespace\{ConstA, ConstB, ConstC};
生成器有两个新特性被引入
$gen = (function() {
yield 1;
yield 2;
return 3;
})();
foreach ($gen as $val) {
echo $val, PHP_EOL;
}
echo $gen->getReturn(), PHP_EOL;
//1
//2
//3
需要注意的是,要想能够访问生成器中的返回值,必须构造一个立即调用的函数表达式
生成器新增关键字 『yield from』,借助『yield from』可以迭代一个生成器,数组,可迭代对象!
function outer()
{
yield 1;
yield 2;
yield from [3,4];
yield from inner();
}
function inner()
{
yield 5;
yield 6;
}
foreach (outer() as $val)
{
echo $val, PHP_EOL;
}
//1
//2
//3
//4
//5
//6
session_start() 可以接受一个数组参数来对即将开启的回话进行设置,该数组的选项会覆盖 php.ini 文件中设置的会话配置选项。
例如
session_start([
'cache_limiter' => 'private',
'read_and_close' => true,
]);
『preg_replace_callback()』 函数由于针对每个正则表达式都要执行回调函数,可能导致过多的分支代码,所以就有了 『preg_replace_callback_array() 』函数!
preg_replace_callback_array() 函数使用一个关联数组来对每个正则表达式注册回调函数, 正则表达式本身作为关联数组的键, 而对应的回调函数就是关联数组的值!
// PHP 7 之前的代码
$htmlString = preg_replace_callback(
'/(href="?)(\S+)("?)/i',
function (&$matches) {
return $matches[1] . urldecode($matches[2]) . $matches[3];
},
$htmlString
);
$htmlString = preg_replace_callback(
'/(href="?\S+)(%24)(\S+)?"?/i', // %24 = $
function (&$matches) {
return urldecode($matches[1] . '$' . $matches[3]);
},
$htmlString
);
// 使用了 PHP 7+ 后的代码
$htmlString = preg_replace_callback_array(
[
'/(href="?)(\S+)("?)/i' => function (&$matches) {
return $matches[1] . urldecode($matches[2]) . $matches[3];
},
'/(href="?\S+)(%24)(\S+)?"?/i' => function (&$matches) {
return urldecode($matches[1] . '$' . $matches[3]);
}
],
$htmlString
);
list 函数有了三个新特性
- 使用数组索引赋值操作的顺序发生了变化,PHP 5 里,list() 从最右边的参数开始赋值; PHP 7 里,list() 从最左边的参数开始赋值!
- list() 表达式不再可以完全为空!
- 字符串无法被解析!
$info = array('coffee', 'brown', 'caffeine');
list($a[0], $a[1], $a[2]) = $info;
// PHP 7 之前版本的代码从右边赋值
var_dump($a);
//array(3) {
// [2]=>
// string(8) "caffeine"
// [1]=>
// string(5) "brown"
// [0]=>
// string(6) "coffee"
//}
// PHP 7+ 及更高版本的代码从左边赋值
var_dump($a);
//[0]=>
// string(6) "coffee"
//[1]=>
// string(5) "brown"
//[2]=>
// string(8) "caffeine"
//}
函数参数和函数返回值可以通过在类型前加上一个问号使之允许为空。 当启用这个特性时,如果你不想给函数传参,你必须显示的传入一个null,不能啥都不传,否则会报错;
function test(?string $name)
{
var_dump($name);
}
test(null); // null
test(); //ArgumentCountError: Too few arguments to function test()
函数返回值和函数一样,你必须显示的返回一个null,否则会报错
function test2(): ?string
{
return null; // null
//return ; //即使没有值,必须显示的返回null,否则会报错Fatal error: A function with return type must return a value
}
var_dump(test2());
正如上面 null 类型中必须显示返回 null,而 void 不允许你返回 null,只允许你要么使用一个空的 return 语句,要么干脆省去 return 语句
function test() : void
{
//return null; // 会报错 Fatal error: A void function must not return a value ...
return; // 这种可以
}
var_dump(test());
数组解构赋值其实是 list() 函数的变体,通过 [] 来解构源数组
list($n1, $n2) = [1,2];
echo $n1,$n2; // 1 2
[$n1, $n2]=[1, 2];
echo $n1,$n2; // 1 2
注意:键名必须和源数组的键名相同
$data = [
["id" => 1, "name" => 'Tom'],
["id" => 2, "name" => 'Fred'],
];
// list() 函数
foreach ($data as list("id" => $id, "name" => $name))
{
// TODO
}
//数组解构
foreach ($data as ["id" => $id, "name" => $name])
{
// TODO
}
class ConstDemo
{
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
如果一个对象部署了 iterable 对象,就称对象是个可迭代对象,这样的一个对象就能被 foreach 遍历,并且也能被生成器的 『yield from』处理
function iterator(iterable $iter)
{
foreach ($iter as $val) {
//
}
}
一个catch语句块现在可以通过管道字符(|)来实现多个异常的捕获。 这对于需要同时处理来自不同类的不同异常时很有用
try {
// some code
} catch (FirstException | SecondException $e) {
// handle first and second exceptions
}
字符串变量支持通过[]或{}操作字符串下标,负数意味着从字符串结尾处偏移
$a='abc';
echo $a{-1}; //c
echo $a[-1]; //c
什么是callable?
一个可被调用的『正常函数』,『匿名函数』以及『类方法』都认为是 『callable』,意即可被调用;
为什么要将一个 callable 转为闭包?
当然是为了使用闭包的某些的特性,比如闭包就可以访问类的私有属性!
下面这个例子将 Test 类的私有方法 privateFunction 转成闭包后,你会发现它在类外部可以访问了
class Test
{
public function exposeFunction()
{
return Closure::fromCallable([$this, 'privateFunction']);
}
private function privateFunction($param)
{
var_dump($param);
}
}
$privFunc = (new Test)->exposeFunction(); // 私有方法被转成了闭包
$privFunc('some value');
另外还需要注意的是这个 callable 到底该怎么传参的问题,该方法的传参完全可以依照 一些函数如 call_user_func() 或者 usort() 等一些支持回调参数的函数!
回调函数可以是简单函数,还可以是对象的方法,包括静态类方法