PHP 学习笔记

简介

  • HTML 脚本语言,面向对象、解释型
  • 适合 Web 开发并可以嵌入 HTML 的多用途脚本语言,动态生成的网页
  • 前身是 Personal HomePage,现在一般理解为 PHP Hypertext Preprosessor,php 超文本预处理器,底层使用 C 语言

静态网站特点:

  • 每个网页都是一个独立的文件
  • 容易被搜索引擎检索
  • 没有数据库支持
  • 交互性差
  • 一般网页后缀是 .htm,.html,.xml

动态网站特点:

  • 交互性:根据用户要求和选择动态地改变和响应
  • 自动更新:无需手动更新 HTML 文档,便会自动生成新页面
  • 因时因人而异:不同时间、不同用户访问同一网址会出现不同页面
  • 一般网页后缀是 .asp,.jsp,.php,.perl,.cgi 还会有标志性的符号

搭建环境

一般 PHP 开发都会搭建 LAMP 环境,也就是 Linux+Apache+Mysql+PHP, 按照网上教程搭建即可。

基础

语法初识

代码标识

ASP 标记:<% php代码 %>

短标记:<? php代码 ?>

脚本标记:<script language="php"> php 代码</script>

标准标记(最常用):<?php php 代码 ?>

注释

// 表示后面所有内容都是注释

#:与 // 一样

/* */ :块注释

分隔符

PHP 中,代码是以行为单位,系统需要通过判断行的结束,该结束通常都是一个符号,分号 ;

1
echo "hello world";

变量

PHP 是一种动态网站开发的脚本语言,动态语言特点是交互性,会有数据的传递,实现的方式就是通过变量。

定义方式:使用 $

1
$var1;

定义,同时赋值

1
$var2 = 1;

访问变量

1
echo $var1, $var2;

修改变量

1
$var2 = 2;

删除变量(从内存种移除)

1
unset($var2);
  • 变量名必须以 $ 开头
  • 变量由字母、数字和下划线构成,不能以数字开头;

预定义变量(超级全局变量)

指的是提前定义的变量,系统定义的变量,存储许多需要用到的数据(预定义变量都是数组),

1
2
3
4
5
6
7
8
9
$_GET; // 获取所有表单以 get 方式提交的数据
$_POST; // POST 提交的数据都会保存在此
$_REQUEST; // GET 和 POST 提交的都会保存
$GLOBALS; // PHP 中所有的全局变量
$_SERVER; // 服务器信息
$_SESSION; // session 会话数据
$_COOKIE; // cookie 会话数据
$_ENV; // 环境信息
$_FILES; // 用户上传的文件信息

可变变量

如果一个变量保存的值刚好是另外一个变量的名字,那么可以直接通过访问一个变量得到另外一个变量的值:在变量前再多加一个 $ 符号;

1
2
3
4
$a = 'b';
$b = 'bb';
echo $a, '<br/>'; //b
echo $$a; // bb

变量传值

也就是将一个变量赋值给另外一个变量:

  • 值传递:将变量保存的值复制一分,将新的值给另外一个变量保存;(赋值后两个变量没有关系)
  • 引用传递:将变量保存的值所在的内存地址,拷贝到另外一个变量上,此时两个变量只想的内存地址是同一份,改变数据之后,另外一个变量的数据也会改变

在内存种,通常有以下几个分区:

  • 栈区:程序可以操作的内存部分(不存数据,运行程序代码),少但是快;
  • 代码段:存储程序的内存部分(不执行)
  • 数据段:存储普通数据(全局区和静态区)
  • 堆区:存储负载数据(例如声明的局部变量从栈逃逸到堆)
1
2
$b = $a; // 值传递
$b = &$a; // 引用传递

常量

常量是一种程序运行中不可改变的变量,一旦定义,通常数据不可改变。

在 PHP 中常量有两种定义方式(5.3 之后才有两种)

1
2
3
4
5
6
// 第一种,使用 define函数定义,''中的是常量名
define('a', 100);
// 第二种,使用 const 关键字定义
const b = 100;
// 使用时,直接通过常量名称即可,不需要加上 $
echo a, '<br/>', b;
  1. 常量定义不需要使用 $ 符号,一旦使用系统就会认为是变量;
  2. 常量的名字由字母、数字和下划线组成,不能以数字开头;
  3. 常量的名字通常是以大写字母为主(与变量以示区别);
  4. 常量命名的规则比变量要松散,可以使用一些特殊字符,但是该方式只能使用 define 定义;
  5. 定义常量通常不区分大小写,但是可以区分,可以参照 define 函数的第三个参数;
  6. 常量一旦定义就不能重新定义或取消定义

defineconst 定义的常量是有区别的:在于访问权限区别;

常量的使用方式,跟变量一样,如果名称特殊,无法直接使用,可以通过访问常量的函数 constant('-_-')

  • const 用于类成员变量的定义,define 不可以用于类成员变量的定义(但是可以用于在类中的函数中),可用于全局常量(函数内部定义也是全局)
  • const 可在类中使用,define 不能
1
2
3
4
5
6
// 获取所有常量
get_defined_constants();
// 返回不同类型常量
get_defined_constants(true);
// 返回具体的常量
get_defined_constants(true)["user"];

系统常量

系统帮助用户定义的常量,用户可以直接使用,例如:

1
2
3
PHP_VERSION; // PHP 版本号
PHP_INT_SIZE; // 整形大小
PHP_INT_MAX; // 整形能表示的最大值

PHP 种还有一些特殊的常量,他们由双下划线开始+常量名+双下划线结束,这种常量通常称之为系统魔术常量:魔术常量的值通常会跟着环境变化,但是用户改变不了(例如代码位置)。

1
2
3
4
5
6
7
8
__DIR__; // 当前被执行的脚本所在电脑的绝对路径
__FILE__; // 当前被执行的脚本所在电脑的绝对路径(带自己名)
__LINE__; // 当前所属行数
__NAMESPACE__; // 当前所属的命名空间
__CLASS__; // 当前所属的类
__METHOD__; // 当前所属的方法
__FUCNTION__; // 返回当前函数名
__TRAIT__; // 获取当前 trait 的名字

数据类型

在 PHP 中指的是存储的数据本身的类型,而不是变量的类型。PHP 是一种弱类型语言,变量本身没有数据类型。(也就是一个变量前面赋值是整形,后面可以换成字符串或者数组赋值给这个变量)

八种数据类型

简单(基本)数据类型,4 个小类:

  • 整型:int/integer,系统分配 4 个字节存储,表示整数类型(有符号)
  • 浮点型:float/double,系统分配 8 个字节存储,表示小数或者整型存不下的整数
  • 字符串型:string,根据实际长度分配,表示字符串(引号)
  • 布尔类型:bool/boolean,表示布尔类型,只有两个值:truefalse

复合数据类型,2 个小类:

  • 对象类型:object,存放对象(面相对象)
  • 数组类型:array,存储多个数据(一次性)

特殊数据类型,2 个小类:

  • 资源类型:resource,存放资源数据(PHP 外部数据,如数据库、文件)
  • 空类型:NULL,只有一个值就是 NULL(不能运算)

类型转换

在很多条件下,需要指定的数据类型,需要外部数据(当前 PHP 取得的数据),转换成目标数据类型。

在 PHP 种有两种类型转换方式:

  1. 自动转换:系统根据需求自己判定,自己转换(用的较多,效率偏低)
  2. 强制(手动)转换:认为根据需要的目标类型转换
    1. 强制转换规则:在变量之前增加一个括号 (),然后在里面写上对应类型:int/integer,其中 NULL 类型用到 unset()

较多的是转布尔类型(判断)和转数值类型(算数运算)。

其他类型转布尔类型:在 PHP 种比较少类型转换成 false。

image-20240623162944743

其他类型转数值的说明:

  1. 布尔 true 为 1,false 为 0;
  2. 字符串转数值有自己的规则;
    1. 以字母开头的字符串,永远为 0;
    2. 以数字开头的字符串,取到碰到字符串为止(不会同时包含两个小数点)
1
2
3
4
5
6
7
8
$a = 'bac1.1.1.1';
$b = '1.1.1.1abc';

// 自动转换 8.0 开始不支持,会出现报错 Fatal error: Uncaught TypeError: Unsupported operand types: string + string
// echo $a + $b;

// 强制转换 0, 1.1
echo 'a: ', (float)$a, '<br/>', 'b: ', (float)$b;

类型判断

通过一组类型判断函数,来判断变量,最终返回这个变量所保存数据的数据类型:是一组以 is_ 开头后面跟类型名字的函数:

1
2
3
4
5
6
echo is_string($a); // 返回 1,代表 true

// bool(true)
$c1 = true;
$c2 = false;
var_dump(is_bool($c1));

还有一组函数可以用来获取以及设定数据(变量)的类型:

  • Gettype(变量名):获取类型,得到的是该类型对应的字符串
  • Settype(变量名):设定数据类型;与强制转换不同
    • 强制转换(类型)变量名:是堆数据值赋值的内容进行处理(不会处理实际存储的内容)
    • settype 回直接改变数据本身
1
2
3
4
echo (float)$b, '<br/>'; // 1.1
echo gettype($b); // string
var_dump(settype($b, 'int')); // bool(true)
echo gettype($b), '<br/>', $b; // integer,1

整数类型

保存整数数值(有范围限制),4 个字节存储。PHP 种默认有符号类型,也就是区分正负数。

PHP 种提供了四种整型的定义方式:十进制定义、二进制定义、八进制定义和十六进制定义。

1
2
3
4
$a1 = 110;   // 十进制
$a2 = 0b110; // 二进制
$a3 = 0110; // 八进制
$a4 = 0x110; // 十六进制

PHP 种提供了很多的函数进行转换,函数命名也很好理解:

1
2
3
decbin(); // 十进制转二进制
decoct(); // 十进制转八进制
dechex(); // 十进制转十六进制

浮点类型

8 个字节,小数类型以及超过整形所能存储范围的整数(不保证精度),精度范围大概在 15 个有效数字左右。

1
2
3
$f1 = 1.23;
$f2 = 1.23e4; // 表示 10 的 4 次方
$f3 = PHP_INT_MAX + 1;

布尔类型

两种,true 和 false

1
2
3
$t1 = true;
$t2 = false;
$t3 = FALSE;

在进行某些数据判断的时候,需要特别注意类型转换。

1
2
empty(); // 判断数据的值是否为空,不是 NULL,
isset(); // 判断数据存储的变量本身是否存在

运算符

operator,将数据进行运算的特殊符号,PHP 种有十种。

赋值运算符

=,将右边的结果(可以是变量、数据、常量和其他运算出来的结果),保存到内存中,然后将内存地址赋值给左侧的变量(常量)。

也可以使用连贯运算符

1
2
// 表示变量 a 和变量 b 都是 10
$a = $b = 10;

算数运算符

基本算数操作:

  • +:也可以数组 + 数组,就是合并两个数组(相同的 key 时,后者不会覆盖前者)
  • -
  • *
  • /
  • %

比较运算符

比较两个数据的大小,或者两个内容是否相同。

  • >
  • <
  • >=
  • <=
  • ==
  • !=
  • ===:全等于,大小以及数据的类型都相同
  • !===:不全等于,只有大小或者类型不同
1
2
3
$a = 123;
$b = '123';
var_dump($a == $b); // bool(true)

逻辑运算符

针对不同的结果进行匹配

  • &&:与
  • ||:或
  • ! :非

逻辑与和逻辑或又称之为短路运算:如果一个表达式结果满足条件了,那么就不会运行逻辑运算符后面的表达式。

连接运算符

是 PHP 中将多个字符串拼接的一种符号。

  • . :将两个字符串连接在一起
  • .=:复合运算,将左边的内容与右边的内容连接起来,然后重新赋值给左边变量
1
2
3
$a = 123;
$a += 10;
var_dump($a); // int(133)

错误抑制符

在 PHP 中有一些错误可以提前预知,但是这些错误可能无法避免,但是又希望报错给用户看,可以使用错误抑制符处理。

@:在可能出错的表达式前面使用 @ 符号即可。

1
2
3
$a = 10;
$b = 0;
@($a % $b);

一般使用的不多

三目运算符(三元运算符)

有三个表达式参与的运算(用于简单的分支)

1
2
// 表达式 1 ? 表达式 2 :表达式 3
$a = $a > 10 ? 1 : 2;

代表表达式1 如果为 true,运算符结果为表达式 2 的结果,否则为表达式 3 的结果。

三目运算符可以进行复合三目运算,三目运算种的表达式 2 和 3 都是可以是另外一个三目运算。

1
2
// 也可以省略写成这样,表示如果 $a 为 true,则 $b = $a,否则为 2
$b = $a ?: 2;

空合并运算符

用于简化处理可能为 null 的变量或数组元素的情况。

作用是判断一个变量是否未定义或者为 null,如果是,则返回指定的默认值;否则返回该变量的值

1
2
$name = $username ?? "mitaka";
echo $name;

组合比较符

1
$c = $a <=> $b;
  • 如果 $a > $b,则 $c 的值为 1
  • 如果 $a == $b,则 $c 的值为 0
  • 如果 $a < $b,则 $c 的值为 -1

自操作运算符

自操作:自己操作自己的运算符

  • ++
  • --

PHP 种自操作符是可以放在变量前或者后:前置操作和后置操作。

1
2
$a++;
++$a;

区别是如果还有别的参与,效果会不一样

1
2
$b = $a++; // $a 是 2,%b 是 1
$b = ++$a; // $a 是 2,$b 是 2

后置自操作:先把自己所保留的值留下来,然后改变别人,自己给别人的值是原来的值;

前置自操作:先把自己改变,然后把改变后的值给别人;

计算机码

包含:原码、反码和补码

原码:数据本身从十进制转换成二进制得到的结果。正数:左边符号位为 0,负数:左边符号位为 1

反码:针对负数,符号位不便,其他位取反

补码:针对负数,反码+1

位运算符

使用计算机最小的单位(位 bit) 进行运算。

  • &
  • |
  • ~
  • ^
  • >>
  • <<

运算优先级

https://php.golaravel.com/language.operators.precedence.html

image-20240623221213270

流程控制

代码执行的方向

控制分类

顺序结构:代码从上往下,顺序执行

分支结构:给定一个条件,同时有多种可执行代码(块),然后会根据条件执行某一段代码

循环结构:在某个条件控制范围内,指定的代码(块)可以重复执行

顺序结构

最基本结构,所有代码默认都是从上往下依次执行

分支结构

PHP 种,分支结构主要有两种,if 分支和 switch 分支

if 分支

给定一个条件,同时为该条件设置多种情况,然后通过条件判断来实现具体的执行段

基本语法:if 分支 PHP 也提供多种方式来实现

  • if
  • if ... else
  • if ... elseif ... else

switch 分支

有一组情形存在,通过同一条件,通常有多个值,但是每一个值都会有对应不同的代码要执行。

switch 判断方式:是将条件放到分支结构内部判断。

1
2
3
4
5
6
7
8
9
10
11
switch (条件表达式) {  // 一般这个表达式是一个具体的值
// 所有判断条件:逐个进行
case1: // 当条件表达式的结果与值 1 相等
要执行的代码;
break; // switch 中,如果条件匹配成功,那么系统就不会再次匹配,会自动顺序执行向下的所有代码,因此需要使用 break 表示结束。
case2
要执行的代码;
break;
default: // 一般回将 default 放在末尾
break;
}

循环结构

代码段在一定的控制下,可以多次执行。

  • for 循环:通过条件、起始和终止判断执行
  • while 循环:通过判断条件终止
  • do-while 循环:跟 while 差不多
  • foreach 循环:专门针对数组
1
2
3
for(条件表达式 1;条件表达式 2;条件表达式 3){
// 跟 golang 差不多
}
1
2
3
for (;;) {
// 无条件,死循环
}

while 没有初始化条件,只需要判断结束条件

1
2
3
while(条件表达式){
// 循环体
}

do-while 很像 while,相比而言,是先执行循环体,再判断结束条件

1
2
3
do{
// 循环体
} while (条件表达式)

循环控制

在循环体内部堆循环体本身进行控制。

  • 中断控制:重新开始循环,循环体种还有其他内容,也再执行
    • continue 层级,默认是 1
  • 终止循环:循环直接结束
    • break 层级,默认是 1
1
2
3
// 使用数字代表层级
continue 2;
break 3;

流程控制替代语法

分支和循环结构的替代语法,主要是让 PHP 脚本语言更加美观

1
2
3
for():
// 循环体
endfor;

一般的替代语法,是左大括号使用冒号替代,右大括号使用 end+ 对应的起始标记替代;

文件包含

在当前文件内引用其他 PHP 文件、HTML 文件或文本文件等,一般用于包含公共方法、公共页面等,例如 header footer sider 等网页通用部分。

与文件包含相关的函数

1
2
3
4
require         找不到被包含的文件时会产生致命错误,并停止脚本运行。
include 找不到被包含的文件时只会产生警告,脚本将继续运行。
include_onceinclude类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。
require_oncerequire类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含
1
2
3
4
5
6
7
echo "<?php phpinfo();?>" > info.txt

# lfi.php
<?php include($_GET['file']); ?>

#
http://ip/?file=info.txt

原理:

  1. 在文件加载 include 或者 require 的时候,系统回自动的将被包含文件种的代码相当于嵌入到当前文件中
  2. 加载位置:在哪加载,对应的文件中的代码嵌入的位置就是对应的 include 位置;
  3. 在 PHP 种被包含的文件是单独进行编译的

PHP 代码的执行流程:

  1. 读取代码(PHP 程序)
  2. 编译:将 PHP 代码转换成字节码(生成 opcode)
  3. zend engine 解析 opcode,按照字节码进行逻辑运算
  4. 将字节码转换成对应的 HTML 代码

PHP 文件在编译的过程中如果出现了语法错误,那么会失败(不会执行);但是如果被包含文件有错误的时候,系统会在执行到包含 include 这条语句的时候才会报错。

include 和 require 区别

  • include :系统会碰到一次,执行一次,如果进行多次加载,会多次执行
  • include_once:系统碰到多次,只加载一次
  • require :本质都是包含文件,唯一区别在于包含找不到的文件时候,报错的形式不一样
    • include 错误级别较轻,不会影响执行
    • require 如果出错,代码不再执行

文件加载路径

文件在加载的时候需要指定文件路径才能保证 PHP 正确的找到对应的文件。

文件的加载路径包含两大类:

  1. 绝对路径:从磁盘的根目录开始;从网站根目录开始,/ 开始
  2. 相对路径:从当前文件所在目录开始的路径,. 或者 ./,或者 ../

文件嵌套包含

一个文件包含了另外一个文件,同时被包含的文件又包含了另外一个文件。

嵌套包含的时候很容易出现相对路径出错的文件:相对路径会因为文件的包含而改变(./../

嵌套如果出现循环嵌套也会出现报错,因此需要注意使用嵌套的过程和逻辑。

函数

function,是一种语法结构,将实现某一个功能的代码块(多行代码)封装到一个结构种,从而实现代码的重复利用,以及逻辑分块。

基本概念

定义语法:

1
2
3
4
function 函数名(参数){
// 函数体
// 返回值: return 结果
}

函数的使用:通过访问函数的名字()。如果函数定义过程中有参数,那么在调用的时候也需要传入参数。

函数定义后不会执行,一定需要调用时才会执行。

函数调用特点:只要系统在内存中能够找到对应的函数,就可以执行(函数的调用可以在函数定义之前)

原因是:编译和执行是分开的(先编译后执行)

函数命名规范

由字母、数字和下划线组成,不能以数字开头。

一般遵循一下规则:

  1. 驼峰法:showParent()
  2. 下划线法:show_parent()

函数名字:在一个脚本周期种,不允许出现同名函数。

参数

形参:形式参数,不具有实际意义的参数,是在函数定义时使用的参数

实参:实际参数,具有实际意义的参数,是在函数调用时使用的参数

形参是实参的载体:实参在调用时是需要传入到参数内部参与计算,那么需要在函数内部找到实际数据所在的位置才能找到数据本身:需要实际调用的时候,将数据以实参的形式传入函数内部,函数内部使用形参来执行。

  1. 在 PHP 种允许实参多余形参:函数外部调用时可以传多个参数,在内部可以不用
  2. 在 PHP 种理论上形参个数没有限制
  3. 实参不能少于形参个数

默认值

default value,指的是形参的默认值,在函数定义的时候,就给形参进行一个初始化赋值:如果实际调用传入的参数(实参)没有提供,那么形参就会使用定义的时的值来进入参数内部参与运算。

1
2
3
4
function add($num1 = 0, $num2 = 1)
{

}
  1. 默认值如果存在,可以不用传入。
  2. 默认值定义是放在最后面,可以多个,不能左边形参有默认值,但是右边没有
  3. 函数外部定义的变量名与内部定义的名字定义相同,不会冲突

引用传递

实参在调用时,会将值赋值给形参,实际上是值传递:将实参的结果取出来,赋值给形参。

如果希望在函数内部拿到外部数据,并且函数内部修改影响外部数据,那么需要在定义时指定使用引用传递(而不是值传递)。

1
2
3
4
5
6
7
8
9
10
function change($num1, &$num2)
{

}

$b = 10;
// 调用
change($a,$b);
// 这样调用会报错,因为需要传入变量,只有变量才能传递引用
change(5,10);

函数体

函数体里面基本所有的代码都可以实现

函数返回值

通过关键字 return 将函数内实现的结果,返回给函数外部。外部可以通过赋值将返回值赋值给其他的变量。

PHP 种所有的函数都有返回值,如果没有明确的 return,会默认返回 NULL。

函数的返回值可以是任意数据类型。

return 除了返回函数的结果,还表示函数运行结束。在文件中,也可以表示文件执行结束。

作用域

变量(常量)能够被访问的区域。

  1. 变量可以在普通代码种定义
  2. 变量可以在函数内部定义

PHP 种作用域严格来说分为三种:

  1. 全局变量:用户普通定义的变量(可以立即为函数外部),只允许在全局使用,理论上函数内部不可使用。
    1. 脚本周期:直到脚本运行结束
  2. 局部变量:函数内部定义的变量,只能在当前函数内部使用
    1. 函数周期:到函数结束
    2. 可以在函数内部声明全局变量,使用关键字 global $g 来定义
  3. 超全局变量:系统定义的变量(预定义变量:$_SERVER),也可以使用在函数外部变量定义,例如 $global,然后在函数内部使用 $GLOBAL['global'] 的方式来访问

静态变量

是在函数内部定义的变量,使用 static 关键字修饰,用来实现跨函数(同一个函数)共享数据的变量:函数运行结束所有局部变量都会清空,如果重新运行一下函数,所有的局部变量又会重新初始化。静态变量就是函数重复执行时,该变量的值可以保留下来

1
2
3
4
5
6
7
8
9
function f1()
{
static $num1;
$num1++;
echo $num1;
}

f1(); // 1
f1(); // 2

系统在进行编译时,会针对 static 变量特殊处理,函数调用时,会跳过 static 关键字这一行。

  1. 为了统计当前函数被调用的次数
  2. 为了统筹多次调用得到不同结果,例如递归、全局变量

可变函数

当前有一个变量所保存到值,刚好是一个函数的名字,那么就可以使用变量+() 来充当函数名使用。

1
2
3
4
5
6
7
$f1 = 'f2';
function f2()
{
echo 'hello world';
}

$f1(); // hello world

当需要用户在外部定义一个自定义函数,但是需要传入系统函数内部使用时,会用到可变函数。

匿名函数

没有名字的函数。

1
2
3
4
5
6
7
8
// 定义
$f1 = function ()
{
echo 'hello';
};

// 执行
$f1();

闭包

闭包包含两个内容:要指定的代码快和为自由变量提供绑定的计算环境。

说白了,就是函数内部有一些局部变量(函数外的变量),在函数执行之后没有被释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
function display()
{
$name = __FUNCTION__;

// use 是将外部变量保留给内部使用
$innerfunction = function () use ($name) {
echo $name;
};

$innerfunction();
}

display();

闭包函数保留了其特点:变量在执行时就已经被赋值,在调用时即可表现对应的值。

伪类型

实际上在 PHP 种不存在的类型,通过伪类型可以达到类似泛型的作用

  • Mixed:混合的,是多种 PHP 中的数据类型
  • Number:数字,整形和浮点型

系统函数

有输出的函数

1
2
3
4
echo print('hello'); // 先输出 hello,然后输出 1
print('hello');
$a = 'hello';
print_r($a);

有关时间的函数

1
2
3
echo date('Y年 m月 d日 H:i:s');
echo time();
echo microtime();

有关数学的函数

1
2
3
4
5
6
7
8
max();
min();
rand();
ceil();
floor();
pow();
abs();
sqrt();

有关函数的函数

1
2
3
4
function_exists(); // 判断指定的函数名字是否在内存种
func_get_arg(); // 在自定义函数种获取指定数值对应的参数
func_get_args(); // 在函数种获取所有参数,数组
func_num_args(); // 获取当前自定义函数的参数

错误处理

在对某些代码进行执行的时候,发现有错误,就会通过错误处理的形式告知。

错误分类

  • 语法错误:PHP 语法规范不合格,代码在编译时就会不通过
  • 运行时错误:代码编译通过,但是在执行过程中会出现一些条件不满足导致的错误
  • 逻辑错误:编写不规范,会出现一些逻辑性错误

错误代号

所有看到的错误代码在 PHP 中都被定义成系统常量。

https://learnku.com/php/wikis/27207

image-20240623232459298

错误触发

程序运行时触发:系统自动根据错误发生后,对比对应的错误信息,输出给用户:主要针对代码的语法错误和运行时错误。

人为触发:直到某些逻辑可能会出错,从而使用对应判断代码来触发响应的错误。

错误显示设置

PHP 种有两种方式来设置当前脚本的错误处理

  1. PHP 的配置文件:全局配置,php.ini 文件
  2. 可以在运行的 PHP 脚本中去设置(比配置文件设置的优先级高)

错误日志设置

在生产环境中一般不显示错误(避免显示在网站上,暴露更多信息),此时可以配置保存到文件种,需要在 PHP 配置文件种或者代码种设置。

自定义错误处理

最简单的错误处理:trigger_errors() 函数,但是该函数不会阻止系统报错。

用户自定义错误处理函数,然后将该函数增加操作系统错误处理的句柄种,然后系统会在碰到错误之后,使用用户定义的错误函数。

  1. set_error_handler()https://www.php.net/manual/zh/function.set-error-handler
  2. 自定义错误处理函数

数据类型

字符串

定义语法:

  1. 单引号字符串:包含的变量不会解析
  2. 双引号字符串:包含的变量会解析(可以使用{}来解决字符串分隔)(这两个一般都不能超过一行)
  3. newdoc 字符串
  4. heredic 字符串
1
2
3
4
5
6
7
8
9
10
// 结构化定义
$str1 = 'hello';
$str2 = "hello";
$str3 = <<<EOD
hello
EOD;

$str4 = <<<'EOD'
hello
EOD;

转义

一些特定的方式定义的字母,系统会特定处理:通常这种方式都是使用反斜杠+字母(单词)的特性。

https://www.php.net/manual/zh/regexp.reference.escape.php

长度

strlen() 得到字符串长度,以字节为单位。

1
2
var_dump(strlen("abc"));  // 3
var_dump(strlen("你好 123")); // 10

中文在 utf8 字符集下占 3 个字节

多字节字符串扩展模块:mbstring 扩展(mb: Mutli Bytes),针对一些关于字符统计,strlen 只是针对标准交换吗 ASCII,mb_string 会针对不同的字符集。

一些其他函数

https://www.php.net/manual/zh/ref.strings.php

切割、组合等转换

  • implode — 用字符串连接数组元素
  • explode — 使用一个字符串分割另一个字符串
  • str_split — 将字符串转换为数组

去除

  • trim — 去除字符串首尾处的空白字符(或者其他字符)
  • ltrim — 删除字符串开头的空白字符(或其他字符)
  • rtrim — 删除字符串末端的空白字符(或者其他字符)

截取

  • substr — 返回字符串的子串
  • strstr — 查找字符串的首次出现

大小转换

  • strtolower — 将字符串转化为小写
  • strtoupper — 将字符串转化为大写
  • ucfirst — 将字符串的首字母转换为大写

查找

  • strpos — 查找字符串首次出现的位置
  • strrpos — 计算指定字符串在目标字符串中最后一次出现的位置

转换函数

  • str_replace — 子字符串替换

格式化函数

  • print — 输出字符串
  • printf — 输出格式化字符串
  • sprintf — 返回格式化字符串

其他

  • str_repeat — 重复一个字符串
  • str_shuffle — 随机打乱一个字符串

数组

array,将一组数据存储到一个容器中,用变量指向该容器,然后可以通过变量一次性得到容器中所有数据。

定义

PHP 中有多种定义数据的方式:

  1. 使用 array 关键字:最常用
  2. 使用中括号包裹数据
  3. 隐形定义数组:给变量增加一个中括号,系统自动变成数组
    1. 如果不提供下标,自动从 0 开始,0 被占用,则往后沿用
1
2
3
4
$arr1 = array(1, 2, 3);
$arr2 = [1, 2, 3, 4];
$arr3[] = 4; // array(1) { [0]=> int(4) }
$arr4[1] = 1;

特点

  1. 可以整数下标或者字符串下标
    1. 如果数组下标都为整数:索引数组
    2. 如果下标都为字符串:关联数组
  2. 不同下标可以混合存在:混合数组
  3. 数组元素的顺序以放入顺序为准,跟下标无关
  4. 数字下标的自增长特性:从 0 开始自动增长,如果被占用,则自动加一
  5. 特殊值下标的自动转换
    1. 布尔值
1
2
3
4
5
$arr1[false] = false;
$arr1[true] = true;
$arr1[NULL] = NULL;
var_dump($arr1);
// array(3) { [0]=> bool(false) [1]=> bool(true) [""]=> NULL }
  1. PHP 中数组元素没有类型限制
  2. PHP 中数组元素没有长度限制

PHP 中的数据是很大的数据,所以存储位置是堆区,为当前数组分配一块连续的内存。

多维数组

数据里面的元素又是数组

二维数组

数组所有元素都是一维数组。

多维数组

第二维的数组元素中可以继续是数组,在 PHP 中没有维度限制。

不建议使用超过三维以上的数组,会增加访问的复杂度,降低访问效率。

异形数组(不规则数组)

数组中的元素不规则,有普通基本变量也有数组。

在实际开发中,并不常用,尽让让数组规律。

数组便利

普通数组数据的访问都是通过数组元素的下标来实现访问,如果说数组中所有的数据都需要依次输出出来,就需要我们使用到一些简化的规则来实现自动获取下标以及输出数组的元素。

使用 foreach 遍历语法

1
2
3
4
5
6
7
8
9
$arr1 = array(1, 2, 3);
foreach ($arr1 as $key => $value) {
var_dump($key, $value);
var_dump('<hr/>');
}
foreach ($arr1 as $value) {
var_dump($value);
var_dump('<hr/>');
}

原理:本质是数组的内部有一颗指针,默认是指向数组元素的第一个元素, foreach 就是利用指针去获取数据,同时移动指针。

  1. foreach 会重置指针:让指针指向第一个元素
  2. 进入 foreach 循环:通过指针获得当前第一个元素,然后将下标取出放到对应的下标变量 $key 中,将值取出来放到对应的值变量 $value
  3. 进入循环内部(循环体),开始执行;
  4. 重复 2 和 3 直到 在 2 的时候遇到指针无法取到内容(也就是数组遍历结束)

For 循环便利数组

基于已知边界条件(起始和结束)然后有条件的变化(规律),因此:for 循环便利数组有对应条件。

  1. 获取数组长度:count(数组) 得到数组元素长度
  2. 要求数组元素的下标是规律的数字
1
2
3
4
$arr1 = array(1, 2, 3);
for ($i = 0; $i < count($arr1); $i++) {
var_dump($arr1[$i]);
}

while

配合 eachlist 遍历数组

while 是在外部定义边界条件,如果要实现可以和 for 循环差不多;

each 函数:能够从一个数组中获取当前数组指针所指向的元素的下标和值,拿到之后将数组指针下移,同时将拿到的元素下标和值以一个四个元素的数组返回。

1
2
$arr1 = array(1, 2, 3);
print_r(each($arr1));

需要注意的是,each() 函数在 PHP 7.2 版本中已被弃用,并在 PHP 7.4 版本中被移除。

list 函数是一种结构,不算一种函数,是 list 提供一堆变量去从一个数组中取得元素值,然后依次存放到对应的变量当中。

list 必须从索引数组中去获取数据,而且必须从 0 开始。

1
2
3
4
5
$array = array(1, 2, 3, "a" => "apple", "b" => "banana", "c" => "cherry");
list($first) = $array;
var_dump($first); // 1
list($first, $second) = $array;
var_dump($first, $second); // 1,2

listeach 配合特别好:each 一定有两个元素就是 0 和 1 下标元素。

1
2
3
4
5
$array = array(1, 2, 3, "a" => "apple", "b" => "banana", "c" => "cherry");
while (each($array)) {
list($key, $value) = each($array);
echo $key . ": " . $value . "<br>";
}

相关函数

https://www.php.net/manual/zh/ref.array.php

排序函数

  • sort — 对数组升序排序
  • rsort — 对数组降序排序
  • asort — 对数组进行升序排序并保持索引关系
  • arsort — 对数组进行降向排序并保持索引关系
  • krsort — 对数组按照键名逆向排序
  • ksort — 对数组根据键名升序排序
  • shuffle — 打乱数组

指针函数

  • reset — 将数组的内部指针指向第一个单元
  • end — 将数组的内部指针指向最后一个单元
  • next — 将数组中的内部指针向前移动一位
  • prev — 将数组的内部指针倒回一位
  • current — 返回数组中的当前值
  • key — 从关联数组中取得键名

nextprev 会移动指针,有可能导致指针移动到最前或者最后(离开数组),导致数组不能使用,通过 nextprev 不能回到正确的指针位置,只能通过 end 或者 reset 进行指针重置。

其他函数

  • count — 统计数组、Countable 对象中所有元素的数量
  • array_push — 将一个或多个单元压入数组的末尾(入栈)
  • array_pop — 弹出数组最后一个单元(出栈)
  • array_shift — 将数组开头的单元移出数组
  • array_unshift — 在数组开头插入一个或多个单元

PHP 模拟数据结构中的队列和栈。

  • array_reverse — 返回单元顺序相反的数组
  • in_array — 检查数组中是否存在某个值
  • array_keys — 返回数组中部分的或所有的键名
  • array_values — 返回数组中所有的值

时间日期

https://www.php.net/manual/zh/ref.datetime.php

函数

  • time — 返回当前的 Unix 时间戳
  • microtime — 返回当前 Unix 时间戳和微秒数
  • date — 格式化 Unix 时间戳
  • strtotime — 将任何英文文本日期时间描述解析为 Unix 时间戳
  • mktime — 取得一个日期的 Unix 时间戳
  • date_create — 创建一个日期时间对象
  • date_format — 别名 DateTime::format,创建一个日期时间对象
  • date_diff — 别名 DateTime::diff,计算两个日期之差
  • strftime — 根据区域设置格式化本地时间/日期
  • gmdate — 格式化 GMT/UTC 日期/时间
  • date_default_timezone_get — 取得脚本中所有日期/时间函数所使用的默认时区
  • date_default_timezone_set — 设置脚本中所有日期/时间函数使用的默认时区(只能在脚本开始时设置默认时区,不能在运行时动态设置)
  • timezone_identifiers_list — 别名 DateTimeZone::listIdentifiers,返回所有可用时区标识符的数组

DateTime 对象

https://www.php.net/manual/zh/class.datetime

1
2
3
4
5
6
7
8
9
10
11
// 创建一个对象
$datetime = new DateTime('now');
// 增加、减少时间间隔
$datetime->modify('+1 day');
$datetime->modify('-1 hour');

// 或者
$interval = new DateInterval('P1D');
$datetime->add($interval);

var_dump($datetime->format('Y-m-d H:i:s'));

面相对象

对象:在面相对象的程序设计(OOP)中,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。

面向对象:

  • 面相对象是一种编程思想和方法,它将程序中的数据操作数据的方法封装在一起,形成对象,并通过对对象之间的交互和消息传递来完成程序的功能
  • 面相对象编程(OOP)强调数据的封装、继承、多态和动态绑定等特性,使得程序具有更好的可扩展性、可维护性和可重用性

对象的主要三个特性:

  • 对象的行为:对象可以执行的操作
  • 对象的形态:对对象不同的行为是如何响应的
  • 对象的表示:对象的表示就相当于身份证,具体区分在相同的行为与状态下有什么不同(在面向对象编程中,对象的表示通常通过类来实现)

面相对象编程的主要特性:

  • 封装:指将对象的属性和方法封装在一起,使得外部无法直接访问和修改对象的内部状态。通过访问控制修饰符(publicprivateprotected)来限制属性和方法的访问权限,从而实现封装;
  • 继承:指可以创建一个新的类,该类继承(extends)了父类的属性和方法,并且可以添加自己的属性和方法。通过继承,可以避免重复编写相似的代码,并且可以实现代码的重用;
  • 多态:指可以使用一个父类类型的变量来引用不同子类类型的对象,从而实现对不同对象的统一操作。多态可以使得代码更加灵活,具有更好的可扩展性和可维护性。在 PHP 中,多态可以通过实现接口 (interface)和使用抽象类 (abstract class)来实现;

类(class)

  • 定义了一件事物的抽象特点
  • 类的定义包含了数据的形式以及对数据的操作

类的定义和调用

定义:

1
2
3
4
5
6
7
8
9
class Animal
{
public $name = "小猫";

public function eat()
{
echo "在吃饭.";
}
}

调用:

使用 new 实例化对象

1
2
3
4
$cat = new Animal();
echo $cat->name;
$cat->eat();
// 小猫在吃饭.
1
2
// 也可以
echo (new Animal())->name;

方法和属性

类的方法也就是类里面的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal
{
public $name = "小猫";

public function eat()
{
echo "在吃饭.";
}

// 函数如果前面不带 public,默认就是 public
function say()
{
echo "在说话.";
}
}

表示自身的对象:$this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal
{
public $name = "小猫";

public function eat()
{
// 一般会使用这种方式来调用
echo $this->name . "在吃饭.";
}

public function say()
{
echo $this->name . "在说话.";
}
}

$cat = new Animal();
$cat->eat();

还可以这样

1
2
3
$dog = new Animal();
$dog->name = "小狗";
$dog->say(); // 小狗在说话.

访问控制

用于设置类中的成员或者方法可访问的区域。

  • public:公共的类成员可以在任何地方被访问(类里面,外部调用时,子类继承父类时);
  • protected:受保护的类成员则可以被其自身以及其子类和父类访问(自身用,继承可用,类外面不能用);
  • private:私有的类成员则只能被其定义所在的类访问(自己的,继承不可用,类外部也不可用);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal
{
private $name = "小猫";
// 或者
//protected $name = "小猫";

public function eat()
{
echo $this->name . "在吃饭.";
}

public function say()
{
echo $this->name . "在说话.";
}

public function setName($name)
{
$this->name = $name;
}
}

$cat = new Animal();
$cat->setName("小花");
$cat->say();

构造函数和析构函数

__construct

  • 构造函数是一种特殊的方法,在创建一个新对象时,他会被自动调用;
  • 它可以用来初始化对象的属性或执行其他必要的操作;
  • 没有返回值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal
{
private $name;
private $brith;
private $age;

public function __construct($name, $brith)
{
$this->name = $name;
$this->brith = $brith;

$days = (time() - strtotime($this->brith)) / 3600 / 24;
$this->age = floor($days);
}

public function eat()
{
echo $this->name . "在吃饭。";
}

public function getInfo()
{
echo "$this->name 的年龄是 $this->age 天,出生日期为 $this->brith";
}
}

// 使用
$cat = new Animal("cat", "2024-06-26");
$cat->getInfo(); // cat 的年龄是 3 天,出生日期为 2024-06-26

__destruct

  • 析构函数是一种特殊的方法,他在对象被销毁时自动调用
  • 它可以用来执行一些清理操作,例如释放资源或关闭数据库连接
  • 当对象不再被引用或脚本执行结束时,析构函数会被自动调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Class1
{
public function say($i)
{
echo 'saying-' . $i;
}

//
public function __destruct()
{
echo "析构函数被调用\n";
}
}

// 使用
$obj = new Class1();
for ($i = 0; $i < 3; $i++) {
if ($i == 2) {
unset($obj);
}
if ($obj)
$obj->say($i);
}
// saying-0saying-1析构函数被调用
// Warning: Undefined variable $obj in /php-study/3.php on line 23

静态变量和静态方法

静态指的是无需对类进行实例化,就可以直接调用这些属性和方法

所有静态变量进行的操作都会对所有对象起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal
{
public static $cat = "小花";
public $name = "小猫";

public function say()
{
echo $this->cat; // 打印为空
// Notice: Accessing static property Animal::$cat as non static in /php-study/3.php on line 10
//
//Warning: Undefined property: Animal::$cat in /php-study/3.php on line 10

// 这样可以取到
echo Animal::$cat;
// 但是一般不像上面这样用,一般使用 self
echo self::$cat;
// 也可以这样,但是一般也不用
echo $this::$cat;
}
}

$cat = new Animal();
$cat->say(); // 小花小花小花

$dog = new Animal();
$dog::$cat = "小黑";
$dog->say(); // 小黑小黑小黑
// 静态变量被修改之后,其他使用类的变量也会受到影响
$cat->say(); // 小黑小黑小黑

类常量

使用场景:所有的对象共用一个属性

静态属性与类常量相似,唯一的区分是类常量不可以更改,静态属性可以更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal
{
// 定义
const C1 = "c1";

public function say()
{
// 使用
echo self::C1;
}
}

// 或者另外一种使用方式
echo Animal::C1;

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal
{
public static $s1 = "s1";
public $name = "小猫";

public static function eat()
{
// 静态方法调用静态方法
echo self::say();
// 静态方法调用非静态的方法
echo (new self)->hello();
// 静态方法调用非静态的对象
echo (new self)->name;
}

public static function say()
{
// $name 不是静态变量,不能直接调用
// echo self::$name;
// 静态方法里面可以调用静态方法、静态变量
echo self::$s1;
}

public function hello()
{
echo $this->name . "hello";
}
}

$cat = new Animal();
$cat->eat();

类的继承

指可以创建一个新的类,该类继承(extends)了父类的属性和方法,并且可以添加自己的属性和方法。通过继承,可以避免重复编写相似的代码,并且可以实现代码的重用。

注意:继承不一定能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal
{
public $name = "小猫";
protected $age = 3;
private $color = "black";
}

class Cat extends Animal
{
}

// 只能打印类中的属性,不能打印方法
var_dump(new Animal);
// object(Animal)#1 (3) { ["name"]=> string(6) "小猫" ["age":protected]=> int(3) ["color":"Animal":private]=> string(5) "black" }
var_dump(new Cat);
// object(Cat)#1 (3) { ["name"]=> string(6) "小猫" ["age":protected]=> int(3) ["color":"Animal":private]=> string(5) "black" }

如果直接访问:

1
2
3
4
$cat = new Cat();
var_dump($cat->name);
var_dump($cat->age); // Fatal error: Uncaught Error: Cannot access protected property Cat::$age in /php-study/3.php:19 Stack trace: #0 {main} thrown in /php-study/3.php on line 19
var_dump($cat->color); // Warning: Undefined property: Cat::$color in /php-study/3.php on line 20

被保护的和私有的,都无法被外部访问。

虽然说被保护的可以被继承拿到和使用,但是只限于在子类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cat extends Animal
{
public function getAge()
{
echo $this->age;
}

public function getColor()
{
// 出现报错没定义
echo $this->color;
}
}

$cat = new Cat();
var_dump($cat->name);
$cat->getAge();

子类没有构造函数,但是可以继承自父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal
{
public $name = "小猫";
protected $age = 3;
private $color = "black";

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

public function eat()
{
echo $this->name . "在吃饭";
}
}

class Cat extends Animal
{
public function sleep()
{
echo $this->name . "在睡觉";
}
}

// 继承时,也会继承构造函数
$cat = new Cat("miao");
// 继承时,可直接使用父类的方法
$cat->eat();

方法和属性重写

如果从父类继承的方法或属性不能满足子类的需求,可以对其进行改写。(即使是构造函数也可以重写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Animal
{
public $name = "小猫";
protected $age = 3;
private $color = "black";

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

public function eat()
{
echo $this->name . "在吃饭";
}
}

class Cat extends Animal
{
public function sleep()
{
echo $this->name . "在睡觉";
}

// 重写
public function eat()
{
echo $this->name . "正在吃饭";
}
}

$cat = new Cat("miao");
// 子类在调用是,会执行子类重写的方法
$cat->eat(); // miao正在吃饭

final 关键字

  • 防止类被继承
  • 防止类的方法被重写

如果在一个类前面加 final,那么这个类就不能被继承

1
2
3
4
final class c1
{

}

如果在一个方法前加 final,那么这个方法就不能被重写

1
2
3
4
5
6
7
class C2
{
final public function eat()
{
echo '吃饭';
}
}

注意:final 不能用于属性(也就是内部的变量)。

调用父类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Cat extends Animal
{
// 这种写法也可以,子类构造的时候传两个参数,调用父类时只使用一个
public function __construct($name$age)
{
parent::__construct($name);
}

public function myEat()
{
// 调用父类的方法
parent::eat();
}
}

静态延迟绑定 static

是指在运行时根据实际调用的类来确定静态方法或属性的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal
{
protected static $name = "小猫";

public function eat()
{
// 小猫在吃饭 --- 小黑在吃饭
// 读取自己的变量
echo self::$name . "在吃饭";
echo ' --- ';
// 根据当前类的静态变量读取
echo static::$name . "在吃饭";
}
}

class Cat extends Animal
{
protected static $name = "小黑";
}

$cat = new Cat;
$cat->eat();

类的多态

  • 多态性允许不同类型的对象对象同的消息作出不同的响应
  • 多态性通过方法重写(覆盖)和方法重载来实现
  • 方法重写是指子类重写父类的方法,以改变方法的实现细节
  • 方法重载是指在同一个类中根据参数个数或者类型不同来实现不同功能
  • 需要注意的是,多态性只适用于继承关系的类。子类必须重写父类的方法才能实现多态性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Animal
{
protected $name = "动物";

public function makeSound()
{
echo $this->name . "在叫";
}
}

class Cat extends Animal
{
protected $name = "小猫";

public function makeSound()
{
echo $this->name . "在喵";
}
}

class Dog extends Animal
{
protected $name = "小狗";

public function makeSound()
{
echo $this->name . "在汪";
}
}

$animal = new Animal();
$cat = new Cat();
$dog = new Dog();
$animal->makeSound();
$cat->makeSound();
$dog->makeSound();

方法重载

需要通参数和参数数量来实现重载,需要用到这两个参数

普通函数中:

1
2
3
4
5
6
7
8
function test()
{
$args = func_get_args();
$numArgs = func_num_args();
var_dump($args, $numArgs); // array(4) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) } int(4)
}

test(1, 2, 3, 4);

类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Cat extends Animal
{
protected $name = "小猫";

public function makeSound()
{
$numArgs = func_num_args();
switch ($numArgs) {
case 1:
echo '执行 1 个参数的事件';
break;
case 2:
echo '执行 2 个参数的事件';
break;
default:
echo '执行默认事件';
}
}
}

$cat = new Cat();
$cat->makeSound(1);

接口和抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接口必须实现这三个方法
interface Animals
{
// 可以包含常量
const NAME = "mitaka";

// 可以包含静态方法
public static function getSound();

public function makeSound();

public function eat();
}

// 不能被实例化
new Animals; // 无法实例化接口 'Animals'

接口是指一组方法的集合,不是类,不能被实例化。

  • 可以指定某个类必须实现那些方法,但不需要定义这些方法的具体内容
  • 只可以使用 public
  • 通常用于定义一些规范,让代码更加有条理,不易出错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 表示这个类实现了方法
class AnimalA implements Animals
{
public static function getSound()
{
}

public function makeSound()
{

}

public function eat()
{

}
}

如果没有实现方法中所有的方法,就会出现报错。

1
2
3
4
5
6
// 可以直接使用类的常量
echo AnimalA::NAME;
// 但是在类中不能重写接口中的常量
class AnimalA implements Animals
{
const NAME = "mitaka"; // 无法从接口 'Animals' 继承先前继承的常量 'NAME' 或将其重写

接口可以有多个,定义类的时候也可以实现多个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Animals
{
public function eat();
}

interface People
{
public function run();
}

// 实现两个接口
class AnimalB implements Animals, People
{
public function eat(){}
public function run(){}
}

抽象类和抽象方法

和接口非常类似,使用它也是定义一种约束或规范,适合较大型的项目或者库使用

抽象类

使用关键字 abstract 定义一个类为抽象类

1
2
3
4
abstract class Animals
{

}
  • 抽象类是一种特殊的类,只能被继承,不能被实例化
  • 抽象类用于定义一组相关的方法,但这些方法的具体实现由继承它的子类来完成
  • 子类继承抽象类后,必须实现抽象类中的所有抽象方法
  • 抽象类可以包含抽象方法和普通方法

抽象方法

使用关键字 abstract 定义一个方法为抽象方法

1
2
3
4
5
6
abstract class Animals
{
abstract public function doSomething1();

abstract protected function doSomething2();
}
  • 抽象方法是没有具体实现的方法,只有方法的声明,而不需要方法体
  • 抽象方法只能存在于抽象类中
  • 可以使用 protected,但不能使用 private 私有

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
abstract class Animals
{
abstract public function doSomething1();

// 抽象类中可以带有普通方法,普通方法在子类中可以不用实现
public function play()
{
echo 'play';
}

abstract protected function doSomething2();
}

class C1 extends Animals
{
// 实现抽象类的方法
public function doSomething1()
{

}

public function eat()
{
echo 'eat';
}

protected function doSomething2()
{
}
}

//$a1 = new Animals; // 无法实例化抽象类 'Animals'
$a1 = new C1();
$a1->play();
$a1->eat();

抽象类和方法的区别

  1. 抽象类可以包含非抽象方法的实现,而接口只能包含方法的声明,没有方法的实现
  2. 类只能继承一个抽象类,但可以实现多个接口
  3. 抽象类可以有构造函数,而接口不能有构造函数
  4. 抽象类中的方法可以有 publicprotectedprivate 访问修饰符,而接口中的方法只能是 public
  5. 子类继承抽象类时,必须实现抽象类中的所有抽象方法,否则子类也必须声明为抽象类;子类实现接口时,必须实现接口中的所有方法

可以理解为:抽象类时对一种事物的抽象,接口是对行为的抽象。继承关注是不是,接口关注有没有

trait 代码复用

  • 解决类的单一继承问题
  • 可同时使用多个 trait,用逗号隔开
  • 把常用的、通用的代码抽离出来,写成 trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait A
{

}

trait B
{

}

class C
{
use A, B;
}

和类的继承非常像,但是 trait 里面不能有类常量,且 trait 不能被实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animals
{
public function eat()
{
echo 'eat';
}
}

class Cat extends Animals
{

}

$a1 = new Cat();
$a1->eat();

可以改写为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Animals
{
public function eat()
{
echo 'eat';
}
}

class Cat
{
use Animals;
}

$a1 = new Cat();
$a1->eat();
  • trait 中可以使用抽象方法
  • trait 中可以使用静态属性和静态方法
  • trait 中可以使用其他 trait
  • trait 中可以使用 parent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
trait T1
{
public function Test()
{
echo 'T1里面的' . __FUNCTION__;
}
}

trait Animals
{
use T1;

protected $name;

public function Test()
{
echo 'Animals里面的' . __FUNCTION__;
}

abstract function doSomething();

public function eat()
{
echo 'eat';
}
}

class Cat
{
use Animals;

function doSomething()
{

}
}

$a1 = new Cat();
$a1->Test(); // Animals里面的Test

方法重写时,会覆盖所继承的方法。

同名冲突

当一个类同时引入了多个 Trait,并且这些 Trait 中存在同名方法时,就会产生方法冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
trait A
{
public function doSomething()
{
echo '这是 A 的';
}
}

trait B
{
public function doSomething()
{
echo '这是 B 的';
}
}

class T2
{
// 由于与 'A' 冲突,因此不会应用特征方法 'doSomething'
//use A, B;
use A, B {
B::doSomething insteadof A; // 使用 B 而不使用 A
A::doSomething as ADo;// 使用别名
}
}

$a1 = new T2;
$a1->doSomething(); // 输出 B 的
$a1->ADo(); // 输出 A 的

还可以这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class T2
{
use A, B {
B::doSomething insteadof A;
// 重名为 protected,放在内部使用
A::doSomething as protected ADo;
// 或者改为 private
A::doSomething as private ADo;
}

public function doSomethingT2()
{
$this->ADo();
}
}

如果里面的属性(也就是变量)名冲突,就无法使用 insteadof 或者重名来解决。

但是如果在类中有跟 trait 里面有相同的属性和值(也就是变量名和值都相同)是可以的,但是属性相同、值不同就会报错。

其他一些用法

错误捕捉处理

1
2
3
4
5
6
7
8
9
function doSomething()
{
try {
// code
} catch (Exception $e) {
// 将错误捕捉到赋予 e
echo $e->getMessage();
}
}

预处理语句

Prepared Statements,用于执行带有参数的 SQL 语句。

  • 预处理语句可以提高安全性,对于防止 SQL 注入是非常有用的
  • 允许重复使用相同的 sql 模板而只需要更改参数,提高执行效率

SQL 注入:例如使用用户名密码登录时,要判断用户是否存在

1
SELECT * FROM users WHERE username = '?'

但是如果页面传入的是 admin'-- 则可能出现意想不到的情况,甚至是执行删除语法。

1
SELECT * FROM users WHERE username = 'admin' --'

预处理

1
2
3
4
5
6
$sql = "SELECT * FROM user WHERE username = ? AND password = ?";
$stmt = $conn->prepare($sql);
// 绑定
$stmt->bind_param("ss", $username, $password);
// 执行
$stmt->execute();