PHP

由于PHP 5及之前的版本历史太悠久, 语言层级的错误五花八门, 需要注意辨认网络上的代码使用的PHP版本.
PHP可能是市场占比最高的服务端语言, 但有相当比例的使用者是非专业用户.
PHP的代码经常有很多错误实践, 这主要因为它的代码非常容易编写猴子补丁.
2015年发布的PHP 7被认为是现代PHP时代的开始.
现代PHP受到JavaScript/TypeScript及其他现代编程语言和工具链的强烈启发.
现代PHP比过去快得多, 能够在多项性能基准测试里击败Python和Ruby.
https://www.php.net/manual/zh/migration70.incompatible.php
总的来说, PHP 5到PHP 7的改变让PHP从一团彻底的混沌变得较为正常.
官方的迁移指南暴露出了PHP 5的很多设计是多么不正常, 很高兴这些有问题的行为都在新版本里被改正了.
一个用C++编写的高性能PHP异步事件驱动网络通信引擎.
使用Swoole可以将PHP作为一般意义上的服务器端语言, 而不仅仅是Web服务语言使用.
  • PHP代码与HTML混写, 因此它可以直接作为模板引擎使用, 现代PHP较少依赖此特性.
  • 基于文件系统的路由, 现代PHP较少依赖此特性.
  • 一次性执行, PHP的每个进程只响应一个请求, 代码使用的内存空间会在响应完毕后立即销毁,
    PHP因此非常依赖外部状态, 像Redis这样的服务对PHP很重要.
    • 优点: 这使得PHP代码很难积累内存泄漏问题.
    • 缺点: 每次运行PHP代码, 都需要重新初始化相关组件, 导致PHP代码的效率很差.
  • 缺乏泛型支持.
  • 社区还没有完全走向异步化.
  • 语言本身与Web服务这一应用场景绑定过深.
自PHP 7开始, 大多数错误都会以内部错误类Error的异常抛出.
Error类(PHP内部错误)和Exception类(用户定义的错误)没有继承关系,
不能通过Exception来捕获.
可以用set_execption_handler捕获传播到顶级的异常.
// 抛出异常
throw new Exception('Hello World');
// 将throw作为表达式使用(自PHP 8.0开始可用)
do_something() or throw new Exception('Hello World');
// 抑制错误(set_error_handler设置的错误处理函数仍然会被调用)
@do_something(); // 抑制后表达式返回假值
// 借用抑制错误来单行处理处理错误
@do_something() or die('Failed'); // die函数是exit函数的别名
// 捕获异常
try {
// ...
} catch (ExceptionA | ExceptionB $e) {
// ...
} catch (Exception $e) {
// ...
} finally {
// ...
}
// 声明命名空间, 一个文件里可以定义多个命名空间, 第一个命名空间必须是文件的第一条语句
namespace MyProject;
const MYCONST = 1;
// '\'可以指代当前命名空间
$a = \MYCONST;
// 等价于(此处的namespace相当于类中的self)
$a = namespace\MYCONST;
// 等价于
$a = MyProject\MYCONST;
// 声明分层的命名空间, 用'\'作为分隔符
namespace MyProject\Sub\Level;
// 大括号语法(推荐)
namespace MyProject {
const MYCONST = 1;
}
// 导入命名空间
use MyProject\Sub\Level;
// 以别名形式导入
use MyProject\Sub\Level as NewName;
// 导入命名空间中的常量(也可以导入其他内容)
use const MyProject\Sub\Level\MYCONST;
class SimpleClass extends BaseClass {
// 类常量, 没有访问修饰符时默认为public
const CONSTANT = 'something';
// 属性必须有访问修饰符
public ?string $prop;
// 自PHP 8.1开始支持只读修饰符
public readonly string $readonlyProp;
// 构造函数(是魔术方法的一种)
// 没有访问修饰符时默认为public
function __construct(int $x /* 一般参数 */, protected int $y /* 带修饰符的构造器参数会被提升为属性 */) {
parent::__construct();
//...
$this->prop = 'value';
}
// 析构函数(是魔术方法的一种)
function __destruct(): void {
parent::__destruct();
// ...
}
// 如果方法覆盖了父类的方法, 则方法签名必须兼容, 否则会抛出错误
function method() {
parent::method();
// ...
}
// 静态方法
static function staticMethod(): static {
return new static();
}
}
SimpleClass::CONSTANT;
SimpleClass::staticMethod();
$instance = new SimpleClass();
$instance->method();
// 带有null检查的成员访问
$instance?->method();
// 复制一个对象实例
$instanceClone = clone $instance
用于定义抽象类或抽象方法.
final修饰的方法不能被子类覆盖.
final修饰的类不能被继承.
self总是指向它定义位置所在的类.
被调用的方法属于父类时, self会指向父类而不是子类.
当static在方法中作为一个引用使用时, 可以用static指代该方法被调用时的类本身.
即使被调用的方法属于父类, static仍然会指向调用时的那个子类.
// 创建匿名类的实例
new class {
// ...
}
interface Interface {
// 接口中定义的方法必须是public.
public function method();
// 接口可以定义常量, 并且常量可以直接以`接口名::常量名`的方式访问
const CONTANT = 'interface constant';
}
// 继承接口
interface SubInterface extends Interface {
// ...
}
// 实现接口, 一个类可以同时实现多个接口
class Class implements Interface, SubInterface {
// ...
}
trait是一种基于组合的代码复用机制, trait可以在trait, class, enum内使用.
支持抽象成员和静态成员.
trait SomeTrait {
public $prop = 'value';
public function method() {
// ...
}
}
class A {
use SomeTrait; // 可以用逗号隔开多个trait
}
class B {
use SomeTrait;
}
trait Trait {
public function methodA() {
// ...
}
public function methodB() {
// ...
}
}
class Class {
use Trait {
methodA as private; // 修改访问修饰符
methodB as anotherMethod; // 重命名
}
}
当一个类使用多个trait, 且trait内有同名的方法时, 需要手动解决冲突.
trait A {
public function method() {
// ...
}
}
trait B {
public function method() {
// ...
}
}
// 通过手动选择一个方法来解决冲突
class Class {
use A, B {
B::method insteadof A; // 用B中的method替代A中的method, 最终使用的是B::method.
}
}
// 通过重命名一个方法来解决冲突
class Class {
use A, B {
B::method insteadof A; // 用B中的method替代A中的method, 最终使用的是B::method.
A::method as methodA; // 将A中的method重命名为methodA
}
}
类型名可以用于现代PHP支持的类型声明.
// 开启类型严格模式
declare(strict_types=1);
// 查看变量类型
echo gettype($variable);
// 输出变量
var_dump($variable);
// 强制类型转换
((bool) "");
A | B // union
A & B // intersection
  • void
  • never
  • static
有两个常量, truefalse, 使用时不区分大小写.
出于历史原因, float还有一个别称叫double, 实际上是一样的类型.
有一个特殊值NAN, 该值需要通过 is_nan() 函数判定.
PHP的字符串是可写的, 允许原地修改某个下标的字符.
使用 . 运算符连接字符串.
主要表达方式:
  • 单引号字符串: 不会发生转义和变量解析的字符串, 原生支持用来表示多行字符串.
  • 双引号字符串: 会发生转义和变量解析的字符串.
array的命名有误, 它其实是一个有序的 Map<string | int, mixed>, 因此可以用来表示各种对象.
array的赋值是值拷贝而不是引用复制, 如果需要引用复制, 则需要手动在数组前加上取引用运算符 &.
数组的相等性可以通过 === 判断.
// 通过构造函数创建映射
$array = array(
'foo' => 'bar'
, 'bar' => 'foo'
);
// 通过语法创建映射(推荐)
$array = [
'foo' => 'bar'
, 'bar' => 'foo'
];
// 通过构造函数创建数组
$array = array('foo', 'bar');
// 通过语法创建数组(推荐)
$array = ['foo', 'bar'];
// 在数组尾部追加一个元素, 数组尾部是由最大的int索引决定的
$array[] = 'hello world';
// 解构赋值(自PHP 7.1起可用)
[$a, $b, $c] = [1, 2, 3]
// 等价于
list(, $second) = [1, 2, 3]
// 删除数组元素, 这只会将对应索引位置的元素移除, 数组本身不会用后面的元素向前补充
unset($array[1]);
// 用array_values函数可以重建索引
$array = array_values($array);
// 删除数组
unset($array);
// 遍历数组
foreach ($array as $value) {
// ...
}
// 遍历类实例的可见属性
foreach ($instance as $key => $value) {
// ...
}
从PHP 7.2开始可用.
作为类型声明使用, 代表任何PHP类的实例.
作为类型声明使用, 代表一个可调用的函数.
PHP的一个奇葩设计是它在将函数作为参数时, 参数是函数名字符串而不是函数的引用.
从PHP 8.0开始可用.
作为类型声明使用, 代表"任何类型".
从PHP 7.1开始可用.
作为类型声明使用, 它相当于 array | Traversable 的别名.
// 参数是iterable类型
function foo(iterable $iter) {
// ...
}
// 返回值是iterable类型
function bar(): iterable {
return [];
}
// iterable作为生成器的返回值类型
function foobar(): iterble {
yield 1;
yield 2;
yield 3;
}
自PHP 8.1可用.
枚举除了可以当值使用, 还可以作为类型使用.
PHP的枚举本质上是一个精简版的类, 可以实现接口和添加方法,
但不能有构造函数和析构函数, 不支持继承, 不支持属性.
// 值为int的枚举
enum Color {
case Red;
case Green;
case Blue;
}
Color::cases(); // 返回所有枚举值
Color::Red;
Color::Red->name; // 枚举值的字符串名称
// 值为string的枚举
enum Color: string {
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
}
Color::cases(); // 返回所有枚举值
Color::Red;
Color::Red->value; // '#FF0000'
  • resource: 一种特殊的类型, 代表着一个外部资源引用, 比如文件句柄.
  • NULL
// 用const关键字定义常量(推荐)
const NAME = 'value';
// 用define函数定义常量
define('NAME, 'value');
$a = 1
$b = 2
function sum() {
// 函数默认的作用域是本地作用域, 通过global让它访问到全局作用域的变量
// 推荐使用$GLOBALS替代, 因为global容易不慎污染本地作用域
global $a, $b;
return $a + $b;
}
// 使用$GLOBALS
function sum() {
$a = $GLOBAL['a']
$b = $GLOBAL['b']
return $a + $b
}
声明静态作用域, 该特性不应该在任何现代编程语言里使用.
函数声明具有提升机制, 并且在函数体内可以访问到外部作用域的类和函数.
// 让参数引用传递而不是值传递
function increase(&$value) {
$value++;
}
$var = 0;
increase($var);
// 从函数返回引用
function &foo() {
return ['hello', 'world'];
}
$result = &foo;
// 可选参数(推荐)
function bar(?A $a) {}
// 等价于
function bar(A $a = NULL) {}
// 可变数量参数
function sum(int ...$nums) {}
// 从PHP 8.0开始调用时支持使用命名参数, 结合位置参数使用时, 命名参数必须在位置参数之后
func(pararmName: $value);
// 在PHP里, 函数的引用是通过函数名字符串表示的.
$func = 'bar'; // 将一个函数引用赋值给变量$func
$func(); // 调用函数
// 匿名函数
$foo = function($name): string {}
// 从父作用域继承变量
$foo = function($name) use ($message): string {}
// 箭头函数(自PHP 7.4开始可用), 是匿名函数的简写, 区别在于父作用域的变量会自动继承
$foo = fn($name): string => {}
function gen1() {
yield 1;
yield 2;
yield 3;
}
function gen2() {
// yield另一个可迭代对象的值
yield from gen1();
}
clone
new
instanceof // 类型判断运算符, 右值不仅可以是类, 也可以是接口, 或二者的字符串形式
// 逻辑运算符
|| // 逻辑或(优先级不够低)
&& // 逻辑与(优先级不够低)
and // 逻辑与(优先级最低, 推荐使用)
xor // 逻辑异或(优先级最低, 推荐使用)
or // 逻辑或(优先级最低, 推荐使用)
:: // 范围解析操作符, 可用于访问类的静态成员, 类常量, 以及接在self, parent, static之后.
== // 松散相等, 不同类型之间会触发类型转换(因此禁用)
=== // 严格相等, 要求类型也相同
!= // 松散不等, 不同类型之间会触发类型转换(禁用)
<> // 松散不等, 不同类型之间会触发类型转换(禁用)
!== // 严格不等, ===的反面, 转换后的值不同, 或者类型不同都会返回真.
<=> // 组合比较, 当左值小于右值时, 返回小于0的整数, 当左值等于右值时, 返回0, 当左值大于右值时, 返回大于1的整数
`` // 执行反引号内的shell命令, 设计与Bash相同
?? // null合并运算符
? : // 三元运算符
require和include的功能一样, 都是在运行时加载另一个PHP脚本.
require和include的区别在于require时出错会抛出错误, 导致脚本终止, 而include只产生警告.
require和include分别有require_once和include_once用来确保只导入相同文件一次.
建议使用require_once.
require('module.php');
大部分流程控制语句都有另一种类似Basic语言的形式, 鉴于C类语法是默认选项, 不推荐使用Basic形式.
// if
// 形式1
if ($condition1) {
// ...
} elseif ($condition2) {
// ...
} else {
// ...
}
// 形式2
if ($condition1):
// ...
elseif ($condition2):
// ...
else:
// ...
endif;
// switch
switch ($value) {
case $case1:
// ...
break;
case $case2:
// ...
break;
default:
// ...
}
从PHP 8.0开始可用.
用于模式匹配, 使用的是严格比较.
match表达式内必须包括所有可能的情况, 否则会抛出UnhandledMatchError.
$result = match ($value) {
$condition1 => 1
, $condition2 => 2
, $condition3, $condition4 => 3
, default => 4
};
//while
// 形式1
while ($condition) {
// ...
}
// 形式2
while ($condition):
// ...
endwhile;
// do while
do {
// ...
} while ($condition);
// for
for ($i = 0; $i < 10; $i++) {
// ...
}
// foreach
foreach ($arr as $value) {
// ...
}
现代PHP包管理器的事实标准.
Composer的公共包存储库.
学习曲线陡峭, 在开发者中的声誉比Laravel好.
微框架.
Drupal v8使用Symfony作为其框架.
建立在Symfony之上的轻量级框架, 是目前最受欢迎的PHP框架.
https://lumen.laravel.com/
基于Laravel的微框架.