官方中文手册:PHP: PHP 手册 - Manual
使用了 PHP 的 web 页面将被和通常的 HTML 页面一样处理,可以用通常建立 HTML 页面的方法来建立和编辑它们。
基本语法
PHP 脚本可以放在文档中的任何位置。
PHP 脚本以 结束:
PHP 文件的默认文件扩展名是 “.php”。
PHP 文件通常包含 HTML 标签和一些 PHP 脚本代码。
下面,我们提供了一个简单的 PHP 文件实例,它可以向浏览器输出文本 “Hello World!”:
1 | <!DOCTYPE html> |
变量基础
$符号开头为变量。变量默认总是传值赋值。
1 |
|
在字符串中表示$需要转义。
1 | system("c='ls'&&\$c"); |
在PHP中,函数名(自定义和内置的函数)、方法名、类名、关键字不区分大小写;但变量名区分大小写。
可变变量
可以用字符串表示变量名,太灵活了,例如:
1 | $a = 'hello'; |
输出:
1 | hello world |
解释:$a
表示a变量
$$a
表示$hello
也就是hello变量
字符串中的${$a}
相当于$hello
字符串中的$$a
只会解析一次。
PHP 是一门弱类型语言
数据类型
- 字符串(在双引号或单引号)
- 整型(不同十六进制:0x,八进制:0)
- 浮点型(可以用科学计数法)
- 布尔型(true/false)
- 数组:array( [idx] => value, [idx] => value, … );
- 对象
- NULL(NULL 值表示变量没有值。NULL 是数据类型为 NULL 的值。)
使用var_dump();可以输出变量的数据类型和值。
PHP 变量作用域
四种作用域:
- local
- global
- static
- parameter
函数外部定义的变量为全局作用域,要在一个函数中访问一个全局变量需要使用global关键字(这一点参考Python)。原理:
PHP 将所有全局变量存储在一个名为 $GLOBALS[index] 的数组中。 index 保存变量的名称。这个数组可以在函数内部访问,也可以直接用来更新全局变量。
函数内部声明的变量是局部变量。
对于局部变量你可以用static关键字让局部数据不会被删除(参考C++):
1 |
|
会输出什么?结果是 0 1 2
注释:该变量仍然是函数的局部变量。
参数作用域:本质是将值传递给函数的局部变量。
来自 PHP 之外的变量
从一个简单的 POST HTML 表单访问数据
1 |
|
只提交GET参数时:输出一次GET参数值。
同时提交GET和POST参数时:输出两次POST参数的值(GET方式传递的相同名称的参数被忽略了)
HTTP Cookies
PHP 透明地支持RFC 6265定义中的 HTTP cookies。
设定cookies:
1 |
|
注意在浏览器中一个 cookie 会替换掉上一个同名的 cookie,除非路径或者域不同。
引用
引用赋值:改动新的变量将影响到原始变量(引用不是指针,参考C++的reference
)。
例子:
1 |
|
此时将会输出三个foo,第二次ref1输出将为空,因为没有它没有被定义。而最后两次echo将会输出foo,因为他们依然指向$var。
再来个例子:
1 | $baz = "Imbaz!"; |
这里会输出什么?我们来捋清楚:
- 定义了global变量Imbaz!
- 调用了foo并传入了一个参数$bar(作为一个引用传入)
- 进入foo函数作用域,此时$var作为全局里$bar的一个引用,$var就是$bar本身。
- 此时$var被重新赋值,变成了$baz的引用。
- $var被赋值为
Imnotbaz!
,因此全局变量$baz将被赋值为它。 - 函数返回。
- 输出$bar。
- 输出$baz。
因此$bar未得到任何改变,它依然是NULL。而$baz已经变为了Imnotbaz!
常量
常量在定义后,默认是全局变量,可以在整个运行的脚本的任何地方使用。
即便常量定义在函数外也可以正常使用常量。整个脚本都可以使用。
define()函数语法如下:
1 | bool define ( string $name , mixed $value [, bool $case_insensitive = false ] ) |
name:必选参数,常量名称,即标志符。
value:必选参数,常量的值。
case_insensitive :可选参数,如果设置为 TRUE,该常量则大小写不敏感。默认是大小写敏感的。
字符串变量
在 PHP 中,只有一个字符串运算符。
**并置运算符 (.) **用于把两个字符串值连接起来。
strlen() 函数
strlen() 函数返回字符串的长度(字符数)。例如:
1 |
|
输出3。
运算符
运算符优先级
没什么好讲的,参考C++
结合方向 | 运算符 | 附加信息 |
---|---|---|
不适用 | clone new | clone 和 new |
右 | ** | 算术运算符 |
不适用 | + - ++ – ~ (int) (float) (string) (array) (object) (bool) @ | 算术 (一元 + 和 -), 递增/递减,按位,类型转换 和 错误控制 |
左 | instanceof | 类型 |
不适用 | ! | 逻辑运算符 |
左 | * / % | 算术运算符 |
左 | + - . | 算数 (二元 + 和 -), array 和 tring (. PHP 8.0.0 前可用) |
左 | << >> | 位运算符 |
左 | . | string(PHP 8.0.0 起可用) |
无 | < <= > >= | 比较运算符 |
无 | == != === !== <> <=> | 比较运算符 |
左 | & | 位运算符 和 引用 |
左 | ^ | 位运算符 |
左 | | | 位运算符 |
左 | && | 逻辑运算符 |
左 | || | 逻辑运算符 |
右 | ?? | null 合并运算符 |
无关联 | ? : | 三元运算符 (PHP 8.0.0 之前左联) |
右 | = += -= *= **= /= .= %= &= |= ^= <<= >>= ??= | 赋值运算符 |
不适用 | yield from | yield from |
不适用 | yield | yield |
不适用 | ||
左 | and | 逻辑运算符 |
左 | xor | 逻辑运算符 |
左 | or | 逻辑运算符 |
1 | $a = true ? 0 : true ? 1 : 2; // (true ? 0 : true) ? 1 : 2 = 2 (PHP 8.0.0 前可用) |
输出:2
赋值
没什么好讲的,参考C++
1 | $b = "Hello "; |
new 运算符自动返回一个引用,因此对 new 的结果进行引用赋值是错误的。
1 | class C {} |
位运算符
没什么好讲的,参考C++
比较运算符
没什么好说的
错误控制运算符
PHP 支持一个错误控制运算符:@。当将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误诊断都被抑制。如果用 set_error_handler() 设定了自定义的错误处理函数,即使诊断信息被抑制,也仍然会被调用。。
执行运算符
PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回。使用反引号运算符“`”的效果与函数 shell_exec() 相同。
1 | eval("system(`echo ls /`);"); |
关闭了 shell_exec() 时反引号运算符无效。反引号不能在双引号字符串中使用。
流程控制
if
else
elseif / else if
替代语法
PHP: 流程控制的替代语法 - Manual
PHP 提供了一些流程控制的替代语法,包括 if,while,for,foreach 和 switch。替代语法的基本形式是把左花括号({)换成冒号(:),把右花括号(})分别换成 endif;,endwhile;,endfor;,endforeach; 以及 endswitch;。
1 | if ($a == 5): |
while
do while
for
foreach
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
1 | foreach (iterable_expression as $value) |
注意 foreach 不会修改类似current()
和key()
函数所使用的数组内部指针。
break
break 结束执行当前的 for、foreach、while、do-while、switch 结构。
break 接受一个数字的可选参数,决定跳出几重循环。 默认值是 1,仅仅跳出最近一层嵌套结构。
1 | $i = 0; |
输出:
1 | At 1 |
continue
continue 在循环结构用用来跳过本次循环中剩余的代码并在条件求值为真时开始执行下一次循环。
match(After PHP 8.0.0)
和switch
语句类似。但是它是严格比较===
。
require / include
require 和 include 几乎完全一样,但 require 在出错时产生 E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。
被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path 指定的目录寻找。如果在 include_path 下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条 E_WARNING ;这一点和 require 不同,后者会发出一个 E_ERROR 。
注意如果文件无法访问, include 和 require 在分别发出最后的 E_WARNING 或 E_ERROR 之前,都会发出额外一条 E_WARNING。
如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path 都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。
有关 PHP 怎样处理包含文件和包含路径的更多信息参见 include_path 部分的文档。
当一个文件被包含时,其中所包含的代码继承了 include 所在行的变量范围。从该处开始,调用文件在该行处可用的任何变量在被调用的文件中也都可用。不过所有在包含文件中定义的函数和类都具有全局作用域。
require_once
require_once 表达式和 require 表达式完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。
返回值:
laravel 源码中 require_once 返回值 | Laravel | Laravel China 社区
看手册要仔细,require,include,require_once,include_once 都是语法结构,所以文档跟一般的 php 函数不同,根据 php 手册,require_once 的详细说明指向了 require,而 require 的详细说明指向了 include,在 include 的说明中,明确指出了 include 是有返回值的。参见:PHP: include - Manual
php源码分析 require_once 绕过不能重复包含文件的限制-安全客 - 安全资讯平台
include_once
include_once 语句在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含,且 include_once 会返回 true。 顾名思义,require_once,文件仅仅包含(require)一次。
include_once 可以用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。
函数
所有函数和类都有全局作用域。即使它在一个函数之内。
PHP不支持函数重载。
自定义函数
函数无需在调用之前被定义,除非函数是有条件被定义时。
当一个函数是有条件被定义时,必须在调用函数之前
定义。
有条件的函数:
1 |
|
函数中的函数:
1 |
|
函数的参数
从 PHP 8.0.0 开始,函数参数列表可以包含一个尾部的逗号,这个逗号将被忽略。这在参数列表较长或包含较长的变量名的情况下特别有用,这样可以方便地垂直列出参数(参考Python的字典)。
传引用
参考C++
参数默认值
定义时赋值。但是传递null
的话不会分配默认值,也就是说null
也是一种值。
使用对象作为默认值(自 PHP 8.1.0 起):
1 |
|
你应该从后往前定义默认参数。因为这样你在调用的时候就不会让编译器迷惑到底如何给参数赋值。但是这个问题在命名参数
下得到改善(PHP 8.0.0 开始,参考Python)。
可变数量的参数列表
PHP 在用户自定义函数中支持可变数量的参数列表。由 … 语法实现。
旧版本的 PHP:不需要特殊的语法来声明一个函数是可变的;但是访问函数的参数必须使用 func_num_args(), func_get_arg() 和 func_get_args() 函数。
返回值
如果省略了return
,则返回值为 null。
返回一个数组以得到多个返回值。
1 |
|
从函数返回一个引用,必须在函数声明和指派返回值给一个变量时都使用引用运算符 &:
1 |
|
可变函数
和可变变量一样,太灵活了。
1 |
|
当调用静态方法时,函数调用要比静态属性优先:
1 |
|
内部(内置)函数
PHP 有很多标准的函数和结构。还有一些函数需要和特定地 PHP 扩展模块一起编译,否则在使用它们的时候就会得到一个致命的“未定义函数”错误。
输出语句print & echo
区别:
echo - 可以输出一个或多个字符串
print - 只允许输出一个字符串,返回值总为 1
echo:
1 |
|
输出:
1 | Hello world! |
print:
print可以用括号也可以不用。
使用上目前感觉没啥区别。
匿名函数
也叫闭包函数(closures)。匿名函数目前是通过 Closure 类来实现的。
闭包函数也可以作为变量的值来使用。PHP 会自动把此种表达式转换成内置类 Closure 的对象实例。
1 |
|
闭包可以从父作用域中继承变量。 任何此类变量都应该用 **use**
** 语言结构传递进去。 PHP 7.1 起,不能传入此类变量: **superglobals、 $this 或者和参数重名。 返回类型声明必须放在 use 子句的后面 。
1 | $message = 'hello'; |
输出:
1 | 1: NULL |
箭头函数
箭头函数是 PHP 7.4 的新语法,是一种更简洁的 匿名函数
写法。二者都是 Closure
类的实现。箭头函数
支持与匿名函数
相同的功能,只是其父作用域的变量总是自动的。当表达式中使用的变量是在父作用域中定义的,它将被隐式地按值捕获。
1 |
|
箭头函数会自动绑定上下文变量,这相当于对箭头函数内部使用的每一个变量 $x 执行了一个 use($x)。这意味着不可能修改外部作用域的任何值,若要实现对值的修改,可以使用**匿名函数**
来替代。
类与对象
当前对象指向$this。
**PHP 对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。参见 **对象和引用。
以静态方式去调用一个非静态方法,将会抛出一个 Error。 在 PHP 8.0.0 之前版本中,将会产生一个废弃通知,同时 $this 将会被声明为未定义。
1 | class A |
输出:
1 | $this is defined (A) |
属性
也就是字段,开头至少有一个修饰符。
成员方法需用$this->property
访问非静态属性。静态属性就用self::$property
来访问。
非静态属性表现为成员,跟可变变量一样(前面加个$的话就是$var的字符串值声明的变量了),静态属性就表现为一个变量,只是前方多了一个命名空间::
。
1 | class User |
输出:
1 | NULL int(1234) string(2) "nu" int(100) int(200) object(User)#1 (3) { ["id"]=> int(1234) ["name"]=> string(2) "nu" [""]=> int(100) } |
类常量
默认为public
。接口也可以表示常量。使用const
关键字。常量不需要**$**
符号!
1 | class User |
输出什么?
第八行定义了一个字段$a
为100,事实上$a为NULL,因此为user定义了一个变量名为""
的变量值为int(100)。第九行打印字段$b
,事实上$b也未定义,为空,因此将会打印第八行定义的变量的值:int(100)。而第10行与第11行将会打印常量b。
注意,类常量只为每个类分配一次,而不是为每个类的实例分配。
类常量可以通过子类重新定义。PHP 8.1.0 起,如果类常量定义为 final,则不能被子类重新定义。
类的自动加载
构造函数与析构函数
1 | __construct(mixed ...$values = ""): void |
注意: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
构造器属性提升
PHP 8.0.0 起,构造器的参数也可以相应提升为类的属性。 构造器的参数赋值给类属性的行为很普遍,否则无法操作。 而构造器提升的功能则为这种场景提供了便利。 因此上面的例子可以用以下方式重写:
示例 #3 使用构造器属性提升
1 |
|
当构造器参数带访问控制(visibility modifier)时,PHP 会同时把它当作对象属性和构造器参数, 并赋值到属性。 构造器可以是空的,或者包含其他语句。 参数值赋值到相应属性后执行正文中额外的代码语句。
我寻思这有点像C++的初始化列表。
访问控制
public(公有),protected(受保护)或 private(私有)。
对象继承
方法,属性和常量的 可见性 可以放宽,例如 protected 方法可以标记为 public, 但不能增加限制,例如标记 public 属性为 private。
1 |
|
范围解析操作符(::)
暂时没什么好说的
静态关键字static
暂时没什么好说的
抽象类
没什么好说的:
1 | abstract class AbstractClass |
接口
暂时没什么好说的
1 | // 声明一个'Template'接口 |
Trait
匿名类
用于创建一次性的简单对象。参考Java
1 | sampleFunction(new class { |
重载
然而 php 并不直接支持重载,你要是像Java或者其他语言那样直接定义多个同名函数将会报错,说不能重复定义。
那么咋办,PHP所提供的重载(overloading)是指动态地创建类属性和方法。是通过魔术方法(magic methods)来实现的。以此来间接支持重载。
注意:
这些魔术方法的参数都不能通过引用传递。
PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。
PHP5 和 PHP4 的 overload __call 另外一个区别是,PHP5 中用户调用的方法名传给 __call 方法时,是有大小写区分的,而 PHP4 中用户调用的方法名传给 __call 方法时,方法名会全部转化为小写。
方法重载
1 | public __call(string $name, array $arguments): mixed |
在对象中调用一个不可访问方法时,__call() 会被调用。
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
例子:
1 | class MethodTest |
输出:
1 | 调用了非静态方法:'runTest' 参数列表:参数1, 2 |
字段重载
属性重载只能在对象中进行。
1 | public __set(string $name, mixed $value): void |
在给不可访问(protected 或 private)或不存在的属性赋值时,__set() 会被调用。
读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。
当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,__isset() 会被调用。
当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。
遍历对象
默认情况下。所有可见字段都将被用于遍历。
1 | class MyClass |
输出:
1 | var1 => 1 var2 => 2 var3 => 3 |
如果在成员方法里遍历呢?
1 | class MyClass |
输出:
1 | var1 => 1 var2 => 2 var3 => 3 |
可以看到,protected和private的字段都被遍历出来了。
魔术方法
Final关键字
对象序列化
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。
1 |
|
命名空间
注意: 命名空间名称大小写不敏感。
名为 PHP 的命名空间,以及以这些名字开头的命名空间 (例如 PHP\Classes)被保留用作语言内核使用, 而不应该在用户空间的代码中使用。
虽然任意合法的 PHP 代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和 trait)、接口、函数和常量。
声明命名空间
1 |
|
与 PHP 其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
命名空间声明必须在第一行!
1 | <html> |
你还可以声明分层次的命名空间:
1 |
|
在一个文件中声明多个命名空间,简单的组合语法:
1 |
|
大括号语法:
1 |
|
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来。
使用命名空间
枚举
Enum,参考其他编程语言,基本都支持Enum,但是像go这种就没有直接提供枚举。
Enum 类似 class,它和 class、interface、trait 共享同样的命名空间。也能用同样的方式自动加载。一个 Enum 定义了一种新的类型,它有固定、数量有限、可能的合法值。
定义一个枚举类型:
1 |
|
你可以这样用:
1 |
|
本质上每个条目是该名称对象的单例。具体来说:
1 |
|
所有的 case 有个只读的属性 name。 它大小写敏感,是 case 自身的名称。
1 |
|
默认情况下枚举条目实现形式不是标量。 它们是纯粹的对象实例。但是php也内置支持标量形式(更易序列化)
定义标量形式的枚举:
1 |
|
由于有标量的条目回退(Backed)到一个更简单值,又叫回退条目(Backed Case)。
回退枚举仅能回退到 int 或 string 里的一种类型, 且同时仅支持使用一种类型(就是说,不能联合 int|string)。同时也不能自动生成标量,都得你自己明确定义**唯一的**
标量值。
你还可以引用常量到条目,相当于创建了一个别名。
回退条目有个额外的只读属性value
, 它是定义时指定的值
。
枚举方法
枚举可以包含方法,也能实现interface。同时你还可以编写静态方法。
1 |
|
枚举常量
枚举常量可以引用枚举条目:
1 |
|
enum VS class
枚举值清单
无论是纯粹枚举还是回退枚举,都实现了一个叫 UnitEnum 的内部接口。 UnitEnum 包含了一个静态方法: cases()。 按照声明中的顺序,cases() 返回了打包的 array,包含全部定义的条目。
1 |
|
为 Enum 手动定义 cases() 方法会导致 fatal 错误。
序列化
枚举的序列化不同于对象。
PHP: 序列化 - Manual
错误
异常
纤程
PHP8的新功能(PHP 8 >= 8.1.0)
生成器
感觉和Python中的有点类似
PHP: 生成器总览 - Manual
注解
PHP8的新功能