💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[php4变化日志](https://www.php.net/ChangeLog-4.php)(主要是修复bug方面) [php5变化日志](http://php.net/ChangeLog-5.php) (主要是修复bug方面) [php7变化日志](http://php.net/ChangeLog-7.php)(主要是修复bug方面) [更新日志](https://www.php.net/manual/zh/doc.changelog.php)(绑定的扩展的函数更新内容) [迁移日志(包含新增或废弃的特性和函数等)](https://www.php.net/manual/zh/appendices.php) [TOC] 主要新特性一览,详情查看迁移日志 # **php5.4新特性** ## **增加短数组支持如:[1, 2, 3, 4]** ## **新增Trait** # **php5.5新特性** ## **新增yield关键字简化生成器** **新增密码加密及验证** ``` //之前 //模拟input提交的密码 $user_input = '12+#æ345'; //crypt()函数不能正确处理加号。请首先对密码使用urlencode,以确保登录过程可以处理任何字符 $pass = urlencode($user_input)); $pass_crypt = crypt($pass); if ($pass_crypt == crypt($pass, $pass_crypt)) { echo "成功!有效的密码"; } else { echo "无效的密码"; } //当校验密码时,应该使用一个不容易被时间攻击的字符串比较函数来比较crypt()的输出与之前已知的哈希。出于这个目的,PHP5.6开始提供了hash_equals() $hashed_password = crypt('mypassword'); // 自动生成盐值 /* 你应当使用 crypt() 得到的完整结果作为盐值进行密码校验,以此来避免使用不同散列算法导致的问题。(如上所述,基于标准 DES 算法的密码散列使用 2 字符盐值,但是基于 MD5 算法的散列使用 12 个字符盐值。)*/ if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) { echo "Password verified!"; } //新特性 /** * 我们想要使用默认算法散列密码 * 当前是 BCRYPT,并会产生 60 个字符的结果。 * * 请注意,随时间推移,默认算法可能会有变化, * 所以需要储存的空间能够超过 60 字(255字不错) * 强烈建议不要自己为这个函数生成盐值(salt)。只要不设置,它会自动创建安全的盐值 * password_hash 参数2有三个加密算法:PASSWORD_DEFAULT(默认),PASSWORD_BCRYPT ,PASSWORD_ARGON2I(php7.2加入) */ $hash=password_hash("rasmuslerdorf", PASSWORD_DEFAULT);//$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a if (password_verify('rasmuslerdorf', $hash)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; } print_r(password_get_info ($hash)); //Array ( [algo] => 1 [algoName] => bcrypt [options] => Array ( [cost] => 10 ) ) ``` ![](https://img.kancloud.cn/3c/2d/3c2d9a4794eb4a29661e8fa507b0c4ab_1384x376.png) password_hash方法我们无需关注salt,只需要将password_hash返回的hash保存在数据库就ok ``` $_POST['pwd']== 'rasmuslerdorf'; $password =$_POST['pwd']; //数据库没有就使用password_hash,数据库有则查查出数据库的hash if(!empty($res=findPwdToMysql($_POST['user'], $_POST['pwd']))){ $hash=$res; }else{ $hash=password_hash($password);//$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS savePwdTosql($hash); } // 当硬件性能得到改善时,cost 参数可以再修改(默认10) $options = array('cost' => 11); // 根据明文密码验证储存的散列 if (password_verify($password, $hash)) { // 检测是否有更新的可用散列算法 // 或者 cost 发生变化 if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) { // 如果是这样,则创建新散列,替换旧散列 $newHash = password_hash($password, PASSWORD_DEFAULT, $options); } // 使用户登录setcookie() } ``` **访问单个元素和字符** ``` //之前 $arr=[123]; $str='abc'; $arr[0]; $str[0]; //新特性: [123][0]; 'abc'[0]; ``` foreach控制结构现在支持通过[list()](https://www.php.net/manual/zh/function.list.php)构造将嵌套数组分离到单独的变量 ``` //之前 $arr1=[1,2]; $arr2=[3,4]; list ($a, $b)=$arr1; list ($a, $b)=$arr2; //新特性: $array = [ [1, 2], [3, 4], ]; foreach ($array as list($a, $b)) { echo "A: $a; B: $b\n"; } 输出: A: 1; B: 2 A: 3; B: 4 ``` ## **异常处理新增finally块,`try{}catch{}finally{}`** finally块中的代码总是在try和catch块之后执行,不管是否抛出了异常,也不管正常的执行是否继续 # **php5.6 新特性** ## **可变数量的参数** 可变参数由 ... 语法实现;在 PHP 5.5 及更早版本中,使用函数 func_num_args(),func_get_arg(),和 func_get_args() ``` //5.6以前实现可变参数 function sum() { $acc = 0; foreach (func_get_args() as $n) { $acc += $n; } return $acc; } echo sum(1, 2, 3, 4); //新特性 function f($req, $opt = null, ...$params) { // $params 是一个包含了剩余参数的数组 printf('$req的值: %d; $opt的值: %d; $params的个数: %d;', $req, $opt, count($params)); echo gettype($params)."<br>"; } f(1);//$req的值: 1; $opt的值: 0; $params的个数: 0;array f(1, 2);//$req的值: 1; $opt的值: 2; $params的个数: 0;array f(1, 2, 3);//$req的值: 1; $opt的值: 2; $params的个数: 1;array f(1, 2, 3, 4);//$req的值: 1; $opt的值: 2; $params的个数: 2;array f(1, 2, 3, 4, 5);//$req的值: 1; $opt的值: 2; $params的个数: 3;array ``` 在调用函数的时候,使用...运算符, 将**数组**和**可遍历**对象展开为函数参数。 在其他编程语言,比如 Ruby中,这被称为连接运算符 ``` function add($a, $b, $c) { return $a + $b + $c; } //add(1,...[2, 3]) $operators = [2, 3]; echo add(1, ...$operators);//6 与add(1,2,3)效果一样 ``` ## **使用表达式定义常量** ``` //之前只能使用静态值定义常量 const ONE = 1; //现在可以用表达式定义常量 const TWO = ONE * 2; ``` ## **新增幂运算** 加入右连接运算符`**`来进行幂运算。 同时还支持简写的`**=`运算符,表示进行幂运算并赋值 ``` printf("2的3次方等于 %d\n", 2 ** 3);//2的3次方等于 8 ``` ## **新增__debugInfo()魔术方法 当使用vardump打印类对象时触发** ## **扩展了use 以前可以导入命名空间的类现在还可以导入函数及常量** ``` namespace Name\Space { const FOO = 42; function f() { echo __FUNCTION__."\n"; } } namespace { use const Name\Space\FOO; use function Name\Space\f; echo FOO."\n";//42 f();//Name\Space\f } ``` # **php7新特性** php7有三种方式创建空对象 ``` $obj1 = new \stdClass; // Instantiate stdClass object $obj2 = new class{}; // Instantiate anonymous class $obj3 = (object)[]; // Cast empty array to object var_dump($obj1); // object(stdClass)#1 (0) {} var_dump($obj2); // object(class@anonymous)#2 (0) {} var_dump($obj3); // object(stdClass)#3 (0) {} ``` $obj1和$obj3类型一样但是不相等 ## **1.在use语句增加了group支持** ~~~ use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo }; ~~~ php7.2末尾可以留,而不会报错 ``` use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo, }; ``` ## **2.类型约束与标量类型声明(标量string、int、float和 bool)** 现在可在参数前加上参数类型,支持:具体类名接口名(php5.0),self(php5.0),array(php5.1),callable(php5.4),string(php7.0),int(php7.0),float(php7.0),bool(php7.0),iterable(php7.1),object(php7.2) >[info]特别注意 Closure类型约束的是匿名函数,匿名函数本身就是个Closure对象 >[danger]php有两种模式强制模式(默认)和严格模式 >声明严格模式`declare(strict_types=1);`后,如果形参声明为int那么参数是其他类型比如string时就会抛出致命错误,强制模式下的话会将string转换为int而不会报错 >由上可知php5有可以约束array 类和接口 可调用函数 self 类型,我们可以简单的认为php5类型约束是严格模式如`function foo(array $arr) {} foo(null)`会报错提示给的参数不是指定的类型,但是参数为null时还是会报这个错,解决办法是将默认参数给个null`function foo(array $arr = null) {}`这样fool(null)就不会报错了 ``` class A{ public $name=null; public function demo(self $a){ var_dump($a); } public function __construct(){ $this->demo($this); //$this->demo(new stdClass()); // 致命错误 A::demo() must be an instance of A } } class B implements Iterator{//Iterator继承Traversable public function current (){} //返回当前产生的值 public function key (){} //返回当前产生的键 public function next (){} // 生成器继续执行 public function rewind (){} //重置迭代器 public function valid (){} //检查迭代器是否被关闭 } new A(); //字符串 function demo(string $param){ var_dump(is_string($param)); } demo(1);//true demo(2.2); //true //整数 function demo1(int $param){ echo $param; var_dump(is_integer($param)); } demo1('1.1');//1 true demo1(1.1); //1 true //浮点数 function demo2(float $param){ echo $param; var_dump(is_float($param)); } demo2('1.2');//1.2 true demo2(1); //1 true //布尔值 function demo3(bool $param){ var_dump($param); var_dump(is_bool($param)); } demo3('a');// true true demo3(3.3);// true true //数组 function demo4(array $param){ var_dump($param); var_dump(is_array($param)); } demo4(['a']);//['a'] true //demo4('a');//demo4() must be of the type array //函数 function demo5(callable $param){ $param(); var_dump(is_callable($param,true)); } demo5(function(){echo 111;});// 111 true //对象 function demo6(object $param){ var_dump(is_object($param)); } demo6(new A());//true //迭代对象 function demo7(iterable $param){ var_dump($param instanceof Traversable); //var_dump(is_iterable($param)); } demo7(new B());//true demo7([1,2]);//false php7.2以前致命错误demo6() must be an instance of iterable //具体类名或接口名 function demo9(Exception $param){ print_r($param); } //demo9(new Exception('哈哈')); //demo9(new A());//致命错误 demo3() must be an instance of Exception, ``` ## **3.返回值类型声明** 可用的类型与参数声明中可用的类型相同。即支持:具体类名接口名(php5.0),self(php5.0),array(php5.1),callable(php5.4),string(php7.0),int(php7.0),float(php7.0),bool(php7.0),iterable(php7.1),void(php7.1),object(php7.2) >[danger]注意指定类型后不能返回null,php7.1在类型前加?可解决此问题 ``` function demo($param):int{ return $param; } demo(null);//7.0致命错误 function demo1($param):?int{ return $param; } demo1(null);//php7.1类型前加上?就能接受及返回null function demo(?int $param):?int{ return $param; } demo(null);//php7.1类型前加上?就能接受及返回null ``` ## **4.三元运算新增null合并运算符??** 当第一个表达式的值不存在或者为null时返回第二个表达式的值,否则返回第一个表达式的值,详情参看其他--三元运算章节 ``` $username = $_GET['user'] ?? 'nobody'; echo $username;//nobody ``` ## **5.太空船操作符<=>(组合比较符)** 太空船操作符用于比较两个表达式。当$a小于、等于或大于$b时它分别返回-1、0或1。 比较的原则是沿用 PHP 的常规比较规则进行的。 ``` // 整数 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 ``` ## **6.define支持定义数组** 自从PHP5.6后,使用const数组也能被定义为常量,define在PHP7中被实现 ## **7.支持[匿名类](https://www.php.net/manual/zh/language.oop5.anonymous.php)** 现在支持通过new class来实例化一个匿名类,这可以用来替代一些“用后即焚”的完整类定义 ``` interface Logger { public function log(string $msg); } class Application { private $logger; public function getLogger(): Logger { return $this->logger; } public function setLogger(Logger $logger) { $this->logger = $logger; } } $app = new Application; $app->setLogger(new class implements Logger { public function log(string $msg) { echo $msg; } }); var_dump($app->getLogger());//object(class@anonymous)#2 (0) {} ``` ## [**8.Unicode字符编码表**](https://www.cnblogs.com/chris-oil/p/8677309.html) 中日韩统一表意文字(CJK Unified Ideographs),外加一些特殊的字符;用 [ \u2E80-\uFE4F] ``` echo '\u{2E80}';//\u{2E80} 必须双引号 echo "\u{2E80}";//⺀ ``` ## **9.增强之前的[assert()](https://www.php.net/manual/zh/function.assert.php)的方法** 简单但却最精确的定义一个*表达式*的方式就是“任何有值的东西” 早期assert是一个函数,php7是一个语法结构(在php语言中是用来判断一个表达式是否成立。返回true or false;) 老版本的API出于兼容目的将继续被维护,assert()现在是一个语言结构, 断言在PHP 5,这必须是一个字符串或者一个布尔计算测试。在PHP 7中,这也可以是**返回值的任何表达式**,该值将被执行,其结果用于指示断言是否成功或失败 **php7新增的的ini配置如下:** **zend.assertions** 1:生成并执行代码(开发模式) 0:生成代码,但在运行时绕过它 -1:不生成代码(生产模式) >[info]**zend.assertions=1** ## **assert.exception** //1:当断言失败时抛出,方法是抛出作为异常提供的对象,或者在没有提供异常时抛出一个新的AssertionError对象 //0:使用或生成一个Throwable,如前所述,但只生成一个基于该对象的警告,而不是抛出它(兼容PHP 5行为) >[info]**assert.exception=0** ``` assert(true == false); echo 'Hi!'; zend.assertions=0时输出: Hi! zend.assertions=1并且assert.exception=0时输出: Warning: assert(): assert(true == false) failed in - on line 2 Hi! zend.assertions=1并且assert.exception=1时输出: Fatal error: Uncaught AssertionError: assert(true == false) in -:2 Stack trace: #0 -(2): assert(false, 'assert(true == ...') #1 {main} thrown in - on line 2 ``` ## **10.为unserialize()提供过滤** ~~~ //将所有对象分为__PHP_Incomplete_Class对象 $data = unserialize($foo, ["allowed_classes" => false]); //将所有对象分为__PHP_Incomplete_Class 对象 除了ClassName1和ClassName2 $data = unserialize($foo, ["allowed_classes" => ["ClassName1", "ClassName2"]); //默认行为,和 unserialize($foo)相同 $data = unserialize($foo, ["allowed_classes" => true]); ~~~ ## **11.增加Closure::call支持**  Closure::call将一个闭包函数动态绑定到一个新的对象实例并调用执行该函数, ~~~ class Value { protected $value; public function __construct($value) { $this->value = $value; } public function getValue() { return $this->value; } } $three = new Value(3); $four = new Value(4); $closure = function ($delta) { var_dump($this->getValue() + $delta); }; $closure->call($three, 4); $closure->call($four, 4); // outputs int(7),int(8) ~~~ ~~~ class Test { public $name = "lixuan"; } //PHP7和PHP5.6都可以 $getNameFunc = function () { return $this->name; }; $name = $getNameFunc->bindTo(new Test, 'Test'); echo $name(); //PHP7可以,PHP5.6报错 $getX = function () { return $this->name; }; echo $getX->call(new Test); ~~~ ## **12.新增整数除法函数** intdiv 接收两个参数作为被除数和除数,返回他们相除结果的整数部分。 ~~~ // 7/2=3余1 值保留整数部分3 var_dump(intdiv(7, 2));//int(3) ~~~ ## **13.新增加的 IntlChar 类** 旨在暴露出更多的 ICU 功能。这个类自身定义了许多静态方法用于操作多字符集的 unicode 字符。Intl是Pecl扩展,使用前需要编译进PHP中,也可apt-get/yum/port install php5-intl ~~~ printf('%x', IntlChar::CODEPOINT_MAX); echo IntlChar::charName('@'); var_dump(IntlChar::ispunct('!')); ~~~ 以上例程会输出:  10ffff  COMMERCIAL AT  bool(true) CSPRNG ## **14.新增两个跨平台函数:** 可用于生成salt、密钥或初始化向量 兼容windows,linux或者其他大多数平台,如果不兼容抛出异常 **random_bytes** — 生成加密安全的伪随机字节 ``` //随机字节的长度设置为5 $bytes = random_bytes(5); print_r($bytes); //转换为十六进制值 $hexadecimal = bin2hex($bytes); var_dump($hexadecimal);//类似8c7c0481d6的字符串 ``` 使用下面的函数来创建随机的令牌,并且还从令牌中创建了一个salt。我在我的应用程序中使用它来防止CSRF攻击 ``` function RandomToken($length = 32){ if(!isset($length) || intval($length) <= 8 ){ $length = 32; } if (function_exists('random_bytes')) { return bin2hex(random_bytes($length)); } if (function_exists('mcrypt_create_iv')) { return bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)); } if (function_exists('openssl_random_pseudo_bytes')) { return bin2hex(openssl_random_pseudo_bytes($length)); } } function Salt(){ return substr(strtr(base64_encode(hex2bin(RandomToken(32))), '+', '.'), 0, 44); } echo (RandomToken()); echo '<br>'; echo Salt(); /* 调试上面的几个函数 */ function RandomTokenDebug($length = 32){ if(!isset($length) || intval($length) <= 8 ){ $length = 32; } $randoms = array(); if (function_exists('random_bytes')) { $randoms['random_bytes'] = bin2hex(random_bytes($length)); } if (function_exists('mcrypt_create_iv')) { $randoms['mcrypt_create_iv'] = bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)); } if (function_exists('openssl_random_pseudo_bytes')) { $randoms['openssl_random_pseudo_bytes'] = bin2hex(openssl_random_pseudo_bytes($length)); } return $randoms; } echo '<br>'; print_r (RandomTokenDebug()); ``` **random_int**(int`$min`,int`$max`) :int —生成加密安全的伪随机整数 生成适合在非常重要的公正的结果情况下使用的加密随机整数,例如在扑克游戏中洗牌时 ``` var_dump(random_int(100, 999));//int(248) var_dump(random_int(-1000, 0));//int(-898) ``` ## **15.新增preg_replace_callback_array()函数,比preg_replace_callback()函数简洁** 在 PHP 7 之前,当使用 preg_replace_callback() 函数的时候, 由于针对每个正则表达式都要执行回调函数,可能导致过多的分支代码。 而使用新加的 preg_replace_callback_array() 函数, 可以使得代码更加简洁。 现在,可以使用一个关联数组来对每个正则表达式注册回调函数, 正则表达式本身作为关联数组的键, 而对应的回调函数就是关联数组的值。 ## **16.session_start()可以接受一个array作为参数** 现在,session\_start()函数可以接收一个数组作为参数,可以覆盖php.ini中session的配置项。  比如,把cache\_limiter设置为私有的,同时在阅读完session后立即关闭 ~~~ session_start(['cache_limiter' => 'private', 'read_and_close' => true, ]); ~~~ ## **17.生成器中引入其他生成器** 在生成器中可以引入另一个或几个生成器,只需要写yield from functionName1 ~~~ function generator1() { yield 1; yield 2; yield from generator2(); yield from generator3(); } function generator2() { yield 3; yield 4; } function generator3() { yield 5; yield 6; } foreach (generator1() as $val) { echo $val, " "; } ~~~ ## **18.生成器可以返回表达式** 生成器(读取超大文件很有用,节约内存) 它允许在生成器函数中通过使用 return 语法来返回一个表达式 (但是不允许返回引用值), 可以通过调用 Generator::getReturn() 方法来获取生成器的返回值, 但是这个方法只能在生成器完成产生工作以后调用一次 ``` $gen = (function() { yield 1; yield 2; return 3; })(); foreach ($gen as $val) { echo $val, PHP_EOL;// 1 2 } //首先执行函数 该函数遇到yield停止并将该yied的值发送给foreach echo $gen->getReturn(), PHP_EOL;//3 结果: 1 2 3 ``` php全局保留字可以声明使用 ~~~ class View { public function include(View $view) { //... //include关键字可以当普通字符串关键字一样被使[其他](%E5%85%B6%E4%BB%96.md)用 } } ~~~ 允许在克隆表达式上访问对象成员,例如:*(clone $foo)->bar()*。 # **php7.1新特性** ## **参数和返回类型支持null,前提是在类型前加?** ``` function test1(?string $name) { var_dump($name); } //在默认非严格模式下,标量string、int、float和 bool 之间会强制转化,与php7相比php7.1可接受null test1('dash');//'dash' test1(null);//NULL test1(30);//'30' test1(30.1);//'30.1' test1(true);//'1' test1([1,2]);//致命错误 test1(new stdClass);//致命错误 function test1($name):?string { return $name; } echo gettype(test1('dash'));//string echo gettype(test1(null));//NULL echo gettype(test1(30));//string echo gettype(test1(30.1));//string echo gettype(test1(true));//string test1([1,2]);//致命错误 test1(new stdClass);//致命错误 ``` ## **php7.1返回类型新增void** ``` function test(): void { return 'hello'; } test();//致命错误 function test1(): void { return null; } test1();//致命错误 function test2(): void { return ''; } test2();//致命错误 function test3(): void { return; } test3();//正常 function test4(): void { //无返回值 } test4();//正常 ``` ## **短数组语法(*\[\]*)现在作为list()语法的一个备选项,可以用于将数组的值赋给一些变量(包括在*foreach*中)** ``` $data = [ [1, 'Tom'], [2, 'Fred'], ]; //php之前支持格式 list($id1, $name1) = $data[0]; foreach ($data as list($id, $name)) { // logic here with $id and $name } //php7新增的短数组[]格式 [$id1, $name1] = $data[0]; foreach ($data as [$id, $name]) { // logic here with $id and $name } ``` PHP 5 里,list() 从最右边的参数开始赋值; PHP 7 里,list() 从最左边的参数开始赋值 ``` $info = array('coffee', 'brown', 'caffeine'); list($a[0], $a[1], $a[2]) = $info; print_r($a); php5输出:[2=>'caffeine',1=>'brown',0=>'coffee'] php7输出:[0=>'coffee',1=>'brown',2=>'caffeine'] ``` 在 PHP 7.1.0 之前的版本,list() 仅能用于数字索引的数组,并假定数字索引从 0 开始 从 PHP 7.1.0 开始,list() 可以包含显式的键,可赋值到任意表达式。 可以混合使用数字和字符串键。但是不能混合有键和无键不能混用 ``` $data = [ ["id" => 1, "name" => 'Tom'], ["id" => 2, "name" => 'Fred'], ]; foreach ($data as ["id" => $id, "name" => $name]) { echo "id: $id, name: $name\n";//id: 1, name: Tom id: 2, name: Fred } echo PHP_EOL; list(1 => $second, 3 => $fourth) = [1, 2, 3, 4]; echo $second.$fourth.PHP_EOL;//2 4 ``` ## **字符串偏移量可以为负,表示为一个从字符串结尾开始的偏移量** ``` var_dump("abcdef"[-2]);//e //b在abcdef首次出现的位置 从结尾倒数第5个偏移开始查找 echo strpos('abcdef','b', -5);//1 a b c d e f 0 1 2 3 4 5 -6 -5 -4 -3 -2 -1 ``` ## **字符串负偏移在简单的变量解析语法中也支持能了** ``` $string = 'bar'; echo "The last character of '$string' is '$string[-1]'.\n";//The last character of 'bar' is 'r'. ``` ## **对服务器推送的支持现在已经被加入到 CURL 扩展(v7.46)中** ``` $transfers = 1; $callback = function($parent_ch, $pushed_ch, array $headers) use (&$transfers) { $transfers++; // 增量,以跟踪并发请求的数量 return CURL_PUSH_OK; }; $mh = curl_multi_init(); curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); curl_multi_setopt($mh, CURLMOPT_PUSHFUNCTION, $callback); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://localhost:8080/index.html"); curl_setopt($ch, CURLOPT_HTTP_VERSION, 3); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 调式/局部填充 //curl_setopt($ch, CURLOPT_VERBOSE, 1); // 是否会输出curl调试信息 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // self-signed cert curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // self-signed cert curl_multi_add_handle($mh, $ch); $active = null; do { $status = curl_multi_exec($mh, $active); do { $info = curl_multi_info_read($mh); if (false !== $info && $info['msg'] == CURLMSG_DONE) { $handle = $info['handle']; if ($handle !== null) { $transfers--; // 减量 剩余请求 $out = curl_multi_getcontent($info['handle']); // 响应体 curl_multi_remove_handle($mh, $handle); curl_close($handle); } } } while ($info); } while ($transfers); curl_multi_close($mh); ``` ## **异步信号处理** 一个新的名为 pcntl_async_signals() 的方法现在被引入, 用于启用无需 ticks (这会带来很多额外的开销)的异步信号处理。 ``` pcntl_async_signals(true); // 打开异步信号 pcntl_signal(SIGHUP, function($sig) { echo "SIGHUP\n"; }); posix_kill(posix_getpid(), SIGHUP); 以上例程会输出: SIGHUP ``` ## **新增Closure::fromCallable() 将callables转为闭包** Closure新增了一个静态方法,用于将callable快速地 转为一个Closure 对象。 ``` class Test { public function exposeFunction() { return Closure::fromCallable([$this, 'privateFunction']); } private function privateFunction($param) { var_dump($param); } } //((new Test)->exposeFunction())('some value'); $privFunc = (new Test)->exposeFunction(); $privFunc('some value');//some value ``` # **php7.2新特性** ## **新的对象类型** 这种新的对象类型, object, 引进了可用于逆变(contravariant)参数输入和协变(covariant)返回任何对象类型 ~~~ function test(object $obj) : object { return new SplQueue(); } test(new StdClass()); ~~~ ## **既可以通过 php.ini 也可以运行时通过 dl(string $library): bool载入一个 PHP 扩展** 在 安全模式(php5.4废除),总是无法使用 dl() 不推荐,存在安全风险 如果加载模块的功能是无效或者禁用的(既可以通过设置关闭 enable_dl(仅对 Apache 模块版本的 PHP 有效) 设置 ~~~ // 加载一个扩展的例子,基于操作系统 if (!extension_loaded('sqlite')) {//检查一个扩展是否已经加载 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { dl('php_sqlite.dll'); } else { dl('sqlite.so'); } } // 或者,使用常量 PHP_SHLIB_SUFFIX if (!extension_loaded('sqlite')) { $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; dl($prefix . 'sqlite.' . PHP_SHLIB_SUFFIX); } ~~~ ## **允许重写抽象方法** 当一个抽象类继承于另外一个抽象类的时候,继承后的抽象类可以重写被继承的抽象类的抽象方法 ~~~ abstract class A { abstract function test(string $s); } abstract class B extends A { // 覆盖 - still maintaining contravariance for parameters and covariance for return abstract function test($s) : int; } ~~~ ## **重写方法和接口实现的参数类型现在可以省略** ~~~ interface A { public function Test(array $input); } class B implements A { public function Test($input){} // 省略了$input的类型 } ~~~ # **php7.3新特性** ## **更灵活的`Heredoc`和`Nowdoc`语法** ``` //heredoc可以解析变量,nowdoc则不能解析变量 $name='dash'; //原heredoc $str=<<<STR 这是文本第一行 这是文本第二行.{$name}over! STR; echo $str;//这是文本第一行 这是文本第二行.dashover! //原nowdoc $str = <<<'EOD' Example of string spanning multiple lines using nowdoc syntax.{$name}over! EOD; echo $str;//Example of string spanning multiple lines using nowdoc syntax.{$name}over! //可以使用缩进,使用缩进时doc内容的每行都会跳过相应的缩进 (下例两行的缩进都被剥除了) $str=<<<STR 这是文本第一行 | 这是文本第二行 STR; | STR; echo $str;// 这是文本第一行 | 这是文本第二行 STR; | 两行的缩进都被剥除了 //结束标记不再需要独立一行 $str=<<<STR 这是文本第一行 这是文本第二行 STR;echo $str;//这是文本第一行 这是文本第二行 //在某些情况下不用紧跟分号了 $data = ["元素", <<<STR 这是文本第一行 这是文本第二行 STR, 42,]; print_r($data);//Array ( [0] => 元素 [1] => 这是文本第一行 这是文本第二行 [2] => 42 ) $str=<<<STR 这是文本第一行 这是文本第二行 STR echo $str;//这中情况就会报错 ``` ## **数组析构与list结构支持引用赋值** $v = [10, 20]; [$a, &$b] = $v;//短语法等同list($a, &$b)=$v; $b += 10; var_dump($v, $a, $b);//array(2) { [0]=> int(10) [1]=> &int(30) } int(10) int(30) ## ** instanceof 运算符支持字面量语法** instanceof 的第一个运算数支持字面量,非对象型字面量检测的结果为 false var_dump("literal" instanceof stdClass);//false var_dump(42 instanceof stdClass);//false var_dump(new stdClass() instanceof stdClass);//true ## **支持调用时参数的末尾跟随逗号,但是定义是不行的** function methodName($p1, $p2) { var_dump($p1, $p2); } methodName(10, 20, );//10 20 ## **BC 数学函数** bcscale() 函数支持获取当前任意BC(精度的数学扩展)函数所使用的 scale。 bcscale(3);//设置所有bc数学函数的默认小数点保留位数 var_dump(bcscale());//3 ## **废弃大小写不敏感的常量** 将 TRUE 作为第三个参数传递给 define() 会导致一个废弃警告 ## **命名捕获支持** mb_ereg_ *系列函数现在支持命名捕获。类似于mb_ereg()的匹配函数现在将使用其组号和名称返回命名捕获,类似于PCRE: ``` mb_ereg('(?<word>\w+)', '国', $matches); print_r($matches);//Array ( [0] => [0 => "国", 1 => "国", "word" => "国"]; mb_ereg('(?<word>\w+)', '国<=>家', $matches); print_r($matches);//Array ( [0] => [0 => "国", 1 => "国", "word" => "国"]; mb_ereg('(?<word>\w+)', '国家', $matches); print_r($matches);//Array ( [0] => 国家 [1] => 国家 [word] => 国家 ) ``` 此外,mb_ereg_replace()现在支持 \k<>和\k''表示法来引用替换字符串中的命名捕获: ``` //mb_ereg_replace ($pattern , $replacement , $string中匹配) //在string中匹配pattern,匹配成功则替换成replacement $str=mb_ereg_replace('\s*(?<word>\w+)\s*', "_\k<word>_\k'word'_", ' foo '); echo $str;// => "_foo_foo_" ``` # **php7.4新特性** ## **预加载** `PHP`预加载可以极大的提高性能 * 优点:在`PHP 7.4`以前,如果你使用了框架来开发,每次请求文件就必须加载和重新编译。预加载在框架启动时在内存中加载文件,而且在后续请求中永久有效。 * 缺点:性能的提升会在其他方面花费很大的代价,每次预加载的文件发生改变时,框架需要重新启动。 ## **类属性现在支持类型声明** 除了`void`和`callable`外,所有的类型都支持 因此,我们可以放心使用`bool`,`int`,`float`,`string`,`array`,`object`,`iterable`,`self`,`parent` ``` class User {     public int $id;     public string $name; public ?Foo $foo; } ``` ## **短闭包函数** ``` array_map (function($arr1_value, $arr2_value, $arrN_value){ //return $arr1_value.$arr2_value.$arrN_value; }, $arr1, $arr2, $arrN ); //原有特性 function cube($n){ return ($n * $n * $n); } $a = [1, 2, 3, 4, 5]; $b = array_map('cube', $a); print_r($b); //新特性 $a = [1, 2, 3, 4, 5]; $res = array_map(fn($n) => $n * $n * $n, $a); print_r($res); $a = [1, 2]; $b = [3, 4, 5]; $res = array_map(fn($av,$bv) => $av*$bv, $a,$b); print_r($res); $a = [1, 2]; $b = [3, 4, 5]; $res = array_map(fn(?int $av,?int $bv): int => $av*$bv, $a,$b); print_r($res); //如果你想通过引用返回一个值,应该使用以下语法 fn&($x) => $x //总结: //它们以fn关键字开始 //它们只能有一个表达式,即return语句 //不允许return关键字 //参数和返回类型可以是限定类型 ``` ## **Null 合并运算符** ~~~ $data['date'] ??= new DateTime(); //相当于之前的 $data['date'] = $data['date'] ?? new DateTime(); 或: if (!isset($data['date'])) { $data['date'] = new DateTime(); } ~~~ ## **数组扩展运算符(展开运算符)** 现在你可以在数组中使用展开运算符: **注意**:只对数字索引有效 ~~~ $arrayA = [1, 2, 3]; $arrayB = [4, 5]; $result = [0, ...$arrayA, ...$arrayB, 6 ,7]; // [0, 1, 2, 3, 4, 5, 6, 7] ~~~ ## **自定义对象序列化** 添加了两个新的魔术方法:`__serialize` 和`__unserialize` ## **数字分隔符** 允许使用下划线更直观的分隔数值 ~~~ $unformattedNumber = 107925284.88; //新特性 $formattedNumber = 107_925_284.88; ~~~ ## **支持反射引用** `PHP 7.4`将会新增`ReflectionReference`类 ## **改进的类型差异(协变返回和逆变参数)** [协变和逆变](https://link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E5%258D%258F%25E5%258F%2598%25E4%25B8%258E%25E9%2580%2586%25E5%258F%2598) [百度百科的解释](https://link.zhihu.com/?target=https%3A//baike.baidu.com/item/%25E5%258D%258F%25E5%258F%2598) 协变与逆变(Covariance and contravariance )是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。 * Invariant (不变): 包好了所有需求类型 * Covariant (协变):类型从通用到具体 * Contravariant (逆变): 类型从具体到通用目前,PHP 主要具有`Invariant`的参数类型,并且大多数是`Invariant`的返回类型,这就意味着当我是 T 参数类型或者返回类型时,子类也必须是 T 的参数类型或者返回类型。但是往往会需要处理一些特殊情况,比如具体的返回类型,或者通用的输入类型。而[RFC](https://link.zhihu.com/?target=https%3A//wiki.php.net/rfc/covariant-returns-and-contravariant-parameters)的这个提案就提议,PHP7.4 添加协变返回和逆变参数,以下是提案给出来的例子:协变返回: ``` class Parent {} class Child extends Parent {} class A { public function covariantReturnTypes(): Parent { /* … */ } } class B extends A { public function covariantReturnTypes(): Child { /* … */ } } ``` ## **箭头函数** 匿名函数和箭头函数都是[](https://www.php.net/manual/zh/class.closure.php)[Closure](https://www.php.net/manual/zh/class.closure.php)类的实现 箭头函数的基本语法为`fn (argument_list) => expr` ``` $y = 1; $fn1 = fn($x) => $x + $y; // 相当于 using $y by value: $fn2 = function ($x) use ($y) {     return $x + $y; }; var_export($fn1(3)); //箭头函数自动捕捉变量的值,即使在嵌套的情况下 $z = 1; $fn = fn($x) => fn($y) => $x * $y + $z; var_export($fn(5)(10));// 输出 51 //合法的箭头函数例子 fn(array $x) => $x; static fn(): int => $x; fn($x = 42) => $x; fn(&$x) => $x; fn&($x) => $x; fn($x, ...$rest) => $rest; //示例 #4 来自外部范围的值不能在箭头函数内修改 $x = 1; $fn = fn() => $x++; // 不会影响 x 的值 $fn(); var_export($x); // 输出 1 ``` # **php8** ## **参数最后可尾随`,`** ``` function demo($a,$b,){} ``` ## 不推荐在可选参数之后传递强制参数,但null除外 如果带有默认值的参数后面跟着一个必要的参数,那么默认值就会无效。这在 PHP 8.0.0 中已被废弃,通常可以通过删除默认值,不影响现有功能: ``` //不推荐 function demo($a=[],$b){} demo(1);//$a为可选参数,他后面跟的是一个必要参数$b,k可选参数$a的默认值则在8.0之前是无效的 //可行替代方案 function demo($a, $b){} //null时除外 function demo($a=null,$b){} //官方推荐 function demo(?array $a){} ``` ## **新增注解功能** https://www.php.net/manual/zh/language.attributes.overview.php ## **在构造函数中声明类的属性** ``` class Point { protected int $x; protected int $y; public function __construct(int $x, int $y = 0) { $this->x = $x; $this->y = $y; } //8.0构造器提升类属性 public function __construct(protected int $x, protected int $y = 0) { } } // 两个参数都传入 $p1 = new Point(4, 5); // 仅传入必填的参数。 $y 会默认取值 0。 $p2 = new Point(4); // 使用命名参数(PHP 8.0 起): $p3 = new Point(y: 5, x: 4); ``` ## **新增联合类型** [mixed](https://www.php.net/manual/zh/language.types.declarations.php#language.types.declarations.mixed)等同于[联合类型](https://www.php.net/manual/zh/language.types.declarations.php#language.types.declarations.union)object|resource|array|string|int|float|bool|null。PHP 8.0.0 起可用。 ## **新增`match`表达式** `match`表达式跟`switch`语句相似,但是有以下关键区别: * 它会像三元表达式一样求值 * `match`比较分支值,使用了严格比较 (`===`), 而 switch 语句使用了松散比较。 * `match`表达式会返回一个值。 * `match`的分支不会像`switch`语句一样, 落空时执行下个 case。 * `match`表达式必须彻底列举所有情况。如果主体表达式不能被任意分支条件处理, 会抛出**UnhandledMatchError** ``` $food = 'cake'; $return_value = match ($food) { 'apple' => 'This food is an apple', 'bar' => 'This food is a bar', 'cake' => 'This food is a cake', }; var_dump($return_value);//This food is a cake //逐个检测匹配分支。一开始不会执行代码。 只有在所有之前的条件不匹配主体表达式时,才会执行剩下的条件表达式。 只会执行返回的表达式所对应的匹配条件表达式 $result = match ($x) { foo() => ..., $this->bar() => ..., // 如果 foo() === $x,不会执行 $this->bar() $this->baz => beep(), // 只有 $x === $this->baz 时才会执行 beep() // 等等 }; //`match`表达式分支可以通过逗号分隔,包含多个表达式。 这是一个逻辑 OR,当多个分支表达式右侧相同时,就可以用这种缩写 $result = match ($x) { // 匹配分支: $a, $b, $c => 5, // 等同于以下三个分支: $a => 5, $b => 5, $c => 5, }; //`default`模式是个特殊的条件。 当之前的条件都不匹配时,会匹配到该模式 //多个 default 模式将会触发**`E_FATAL_ERROR`**错误 $expressionResult = match ($condition) { 1, 2 => foo(), 3, 4 => bar(), default => baz(), }; //**针对整数范围,使用宽泛的表达式匹配分支** $age = 23; $result = match (true) { $age >= 65 => 'senior', $age >= 25 => 'adult', $age >= 18 => 'young adult', default => 'kid', }; var_dump($result);//young adult //**针对字符串内容,使用宽泛的表达式匹配分支** $text = 'Bienvenue chez nous'; $result = match (true) { str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en', str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr', // ... }; var_dump($result);//fr //表达式存在未处理的示例 $condition = 5; try { match ($condition) { 1, 2 => foo(), 3, 4 => bar(), }; } catch (\UnhandledMatchError $e) { var_dump($e); } ``` ## **新增Nullsafe 运算符(`?->`)** nullsafe 操作符和->原来的属性、方法访问是一致的: 对象引用解析(dereference)为**`null`**时不抛出异常,而是返回**`null`**。 并且如果是链式调用中的一部分,剩余链条会直接跳过.此操作的结果,类似于在每次访问前使用 is_null() 函数判断方法和属性是否存在,但更加简洁 ``` // 自 PHP 8.0.0 起可用 $result = $repository?->getUser(5)?->name; // 上边那行代码等价于以下代码 if (is_null($repository)) { $result = null; } else { $user = $repository->getUser(5); if (is_null($user)) { $result = null; } else { $result = $user->name; } } ``` ## 新增[WeakMap](https://www.php.net/manual/zh/class.weakmap.php)类 ## 新增**ValueError**类 ## 任意数量的函数参数都可以用一个可变参数替换(只要类型兼容) ``` class A { public function method(int $many, string $parameters, $here) {} } class B extends A { public function method(...$everything) {} } ``` ## static("后期静态绑定"中) 可以作为返回类型 ``` class Test {      public function create(): static {           return new static();      } } ``` ## 可以通过`$object::class`获取类名,返回的结果和`get_class($object)`一致 ## 可作为表达式使用`throw` ``` $fn = fn() => throw new Exception('Exception in arrow function'); $user = $session->user ?? throw new Exception('Must have user'); ``` ## 在父类上声明的私有方法不再对子类的方法强制执行任何继承规则(最终私有构造函数除外)。 以下示例说明了哪些限制已被删除: ``` class ParentClass { private function method1() {} private function method2() {} private static function method3() {} // Throws a warning, as "final" no longer has an effect: private final function method4() {} } class ChildClass extends ParentClass { // All of the following are now allowed, even though the modifiers aren't // the same as for the private methods in the parent class. public abstract function method1() {} public static function method2() {} public function method3() {} public function method4() {} } ``` ## 其他 * [`new`](https://www.php.net/manual/zh/language.oop5.basic.php#language.oop5.basic.new)、[`instanceof`](https://www.php.net/manual/zh/language.operators.type.php)可用于任何表达式, 用法为`new (expression)(...$args)`和`$obj instanceof (expression)`。 * 添加对一些变量语法一致性的修复,例如现在能够编写`Foo::BAR::$baz`。 * 添加[Stringable](https://www.php.net/manual/zh/class.stringable.php)interface, 当一个类定义[\_\_toString()](https://www.php.net/manual/zh/language.oop5.magic.php#object.tostring)方法后会自动实现该接口。 * Trait 可以定义私有抽象方法(abstract private method)。 类必须实现 trait 定义的该方法。 * 新增 [get\_resource\_id()](https://www.php.net/manual/zh/function.get-resource-id.php) ## 不向后兼容的变更 | Comparison | Before | After | | --- | --- | --- | | `0 == "0"` | **`true`** | **`true`** | | `0 == "0.0"` | **`true`** | **`true`** | | `0 == "foo"` | **`true`** | **`false`** | | `0 == ""` | **`true`** | **`false`** | | `42 == " 42"` | **`true`** | **`true`** | | `42 == "42foo"` | **`true`** | **`false`** | `(real)`和`(unset)`转换已被移除。 # **php8.1**