找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 31|回复: 0

PHP基础 _ 查漏补缺

[复制链接]

10

主题

1

回帖

68

积分

管理员

积分
68
发表于 2025-10-12 00:53:53 | 显示全部楼层 |阅读模式

PHP 变量名称对大小写敏感!($y 与$Y 是两个不同的变量)

  • 下划线命名法 $first_name = 'zhang';
  • 小驼峰命名法 $firstName = 'zhang';
  • 大驼峰命名法 $FirstName = 'zhang';

不必告知 PHP 变量的数据类型,php会根据它的值,自动把变量转换为正确的数据类型

// 空白符
echo '
123
444

999
';

输出: 123 444 999

$a = "hello";
echo <<<EOF1
    23<br>标签会被解析
    ew
    $a
EOF1;

输出:
    23
    标签会被解析
    ew
    hello

var_dump() 会返回变量的数据类型和值,一般用于开发调试时使用

如果只获取数据类型,则可以使用:echo gettype($a);

输出:

$name = "张三";
echo "我是 {$name}";

print("你好\n");

printf("我是 %s", "张三");

$a = sprintf("我是 %s", "张三");

print_r(array(999,"gre"));

var_dump("232");

$type_str = gettype("weq");

echo <<EOF999
    你好啊 
    $a,
<<<;

echo <<<'EOD'
这是nowdoc语法输出的内容
不会解析变量如:$b
EOD;

// json_encode() - 输出JSON格式
echo json_encode(['name' => '张三', 'age' => 25]) . "\n";
{"name":"\u5f20\u4e09","age":25}

// 使用exit/die输出并终止脚本
exit("脚本终止输出\n");

// 使用serialize()序列化输出
echo serialize(['a' => 1, 'b' => 2]) . "\n";
a:2:{s:1:"a";i:1;s:1:"b";i:2;}

// 使用ob_start()和ob_get_clean()缓冲输出
ob_start();
echo "999666你好啊!";
$output = ob_get_clean();
echo "从缓冲区获取的内容: " . $output . "\n";
从缓冲区获取的内容: 999666你好啊!

字符串可以使用 单引号 或 双引号,但是要注意双引号和单引号的区别,那就是 双引号 能输出变量的值;

$a = '猫叔';
var_dump($a);  //  string(6) "猫叔"

echo "a的值是$a";  // a的值是猫叔

echo gettype($a);  //  string

php中,十六进制 0x开头,八进制 0开头

$a = 0x8c;   // 十六进制
var_dump($a);  // int(140)

echo "<br>";

$a = 047;  // 八进制
var_dump($a);   // int(39)

$b = 2.4e3;  //double(2400)
var_dump($b);

数组

// 两种初始化数组:
$a = array("aa", "bb", 999);
$b = ["aa", "bb", "ccc"];
var_dump($a);

array(3) {
  [0] =>
  string(2) "aa"
  [1] =>
  string(2) "bb"
  [2] =>
  int(999)
}

NULL

特殊的 NULL 值表示变量无值。NULL 是数据类型 NULL 唯一可能的值
注意:可以通过设置变量值为 NULL 来清空变量数据

$a = 123;
$a = NULL;
var_dump($a);    // NULL

模板字符串

$name = "张三";
echo "hello,{$name}";   // 注意:必须是双引号
输出:
hello,张三

echo "<br>";

$name1 = ["name"=>"Maoshu"];
echo "hello, {$name1['name']}";    // hello, Maoshu

printf() 和 sprintf()

都是格式化输出函数

  • printf是直接将格式化后的字符串输出到标准输出(通常是浏览器或命令行)
  • sprintf 会将格式化后的字符串返回,而不是直接输出,可以将其赋值给变量或用于其他操作,即有返回值
$name= "张三";

printf("我是 %s,我 %d 岁啦", $name, 11);  // 我是 张三,我 11 岁啦

echo sprintf("我是 %s,我身高是:%.2f", $name, 161.726);  // 我是 张三,我身高是:161.73

向下取整、向上取整、四舍五入

// 1. 向上取整(向更大的方向取整)
$ceil = ceil(3.1);  // 结果为4

// 2. 向下取整(向更小的方向取整)
$floor = floor(3.8); // 结果为3

// 3. 四舍五入
$round = round(3.5); // 结果为4

// 4. 四舍五入到指定小数位
$rounded = round(3.14159, 2); // 结果为3.14

// 5. 向零取整(去掉小数部分)
$truncate = (int)$number; // 结果为3

// 整数除法的函数,它会返回两个数相除后的整数部分(向下取整)
var_dump(intdiv(10, 3));  // 10/3=3.33333...
结果:
int(3)

// 除数为0会抛出异常
try {
    echo intdiv(10, 0);
} catch (DivisionByZeroError $e) {
    echo "除数不能为0";
}

自加自减

$x = 10;
echo ++$x;   // 11

echo "<br>";

$y = 10;
echo $x++;   // 10
echo $y;   // 11

== 和 ===

$a = 10;
$b = '10';
var_dump($a == $b);    // bool(true)

echo "<br>";

var_dump($a === $b);    // bool(false)

<> 和 != 都表示不等于
!== 绝对不等于

逻辑运算符

  • x and y   与
  • x or y 或
  • x xor y  异或,如果x和y有且仅有一个为 tue,则返回 true,否则都为false
  • x && y  与
  • x || y 或
  • !x 非

&&||的优先级高于 andor

&&的优先级高于 ||

and的优先级高于 or

and 和 or 有短路效果

//or-前面语句值为真,or后面不执行;否则,执行
$result = 0 or var_dump('执行我的语句');  //输出-执行我的语句
var_dump($result);  //int 0

$result = 2 or var_dump('执行我的语句');  //不输出
var_dump($result);  //int 2
switch(n):
{
    case '1':
        ...
        break;
    ...
        default:
        ...
}

PHP_EOL;

php内置常量,表示:回车换行,即:define('PHP_EOL', '\n');

echo "1111".PHP_EOL."22222";

1111
22222

break跳出打断 和 continue跳出继续循环

foreach 循环,用于循环数组

对于数组的输出,建议使用print_r:

$a = array("aa", "bb", 999);
$b = ["aa", 666, "ccc"];
var_dump($a);
输出:
array(3) {
  [0] =>
  string(2) "aa"
  [1] =>
  string(2) "bb"
  [2] =>
  int(999)
}

print_r($b);   // 输出格式化数组
输出:
Array
(
    [0] => aa
    [1] => 666
    [2] => ccc
)
$a = [];
$a[3] = 213;
print_r($a);
输出:
Array
(
    [3] => 213
)

$arr = [1, 2, 3];
var_export($arr);
array (
  0 => 1,
  1 => 2,
  2 => 3,
)

关联数组?

使用你分配给数组的指定的键的数组

$age = ['zhangsan'=>12,'lisi'=> '18'];
$age['wangwu'] = '23';
输出:
Array
(
    [zhangsan] => 12
    [lisi] => 18
    [wangwu] => 23
)

数组长度(数量):print_r(count($age));  3

数值数组 和 关联数组 :

$b = [
    '2',
    "zhangsan" => 66,
    "43",
    "zhangsan" => 44
];
print_r($b);
输出:
Array
(
    [0] => 2
    [zhangsan] => 44    // 注意这里的键名重复被后来的值覆盖
    [1] => 43   // 注意这里的键
)

数值数组循环 - for

$age = [12,'18'];
for($i=0; $i < count($age); $i++){
    echo $age[$i].PHP_EOL;
}
输出:
12
18

关联数组循环 - foreach

$b = [
    '2',
    "zhangsan" => 66,
    "43",
    "zhangsan" => 44
];
foreach($b as $key => $value){
    echo $key.'---'.$value.PHP_EOL;
}
输出:
0---2
zhangsan---44
1---43
------------------------------------------
foreach($b as $vvv){
    echo $vvv.PHP_EOL;
}
输出:
2
44
43

多维数组

包含一个或多个数组的数组

$maoshuArr = [
  [
      "111" ,432,"sdfs"
  ],
   [
      "免费" =>[

      ],
      "收费" => [

      ]
       ...
  ], 
];

内置函数-时间日期

var_dump(time());    // int(1760172761)   10位

echo "<br>";

var_dump(date('Y-m-d H:i:s'));   // string(19) "2025-10-11 16:52:41"

传参 严格模式

function fun1($a){}   // 这里没有定义$a的类型,你传什么类型都可以
fun1(['333']);

function fun2(int $a){}   // 这里定义$a的类型,但是你传string类型也可以
fun2('333');

declare(strict_types=1);
function fun3(int $a){}   // 这里定义$a的类型,且定义了严格模式,你只能传int类型
fun3(34);

局部和全局作用于 global

  • 在所有函数外部定义的变量,拥有全局作用域
  • 除了函数外,全局变量可以被脚本中的任何部分访问
  • 要在一个函数中访问一个全局变量,需要使用 global 关键字
  • 函数内部声明的变量是局部变量,仅能在函数内部访问
$maoshu="写代码的猫叔";  // 该变量处于 全局作用域 中
function test(){
    global $maoshu;    // 在函数中访问一个全局变量,需要使用 global 关键字
    $age="35";
    echo $maoshu;    // 写代码的猫叔
}
test();
echo $age;   // 报错

static 作用域

当一个函数完成时,它的所有变量通常都会被删除。然而,有时候希望某个局部变量不要被删除。
要做到这一点,请在第一次声明变量时使用 static 关键字

每次调用该函数时,该变量将会保留着函数前一次被调用时的值,但是该变量仍然是函数的局部变量

function myTest()
{
    static $a=0;
    echo $a;
    $a++;
    echo PHP_EOL;    // 换行符,类似 \n
}

myTest();
myTest();
myTest();

unset()可以删除变量,isset() 可以判断变量是否存在

全局变量在函数内通过unset删除,只会在函数内删除,全局上还可以使用该变量的值:

$maoshu="写代码的猫叔";  // 该变量处于 全局作用域 中
function test(){
    global $maoshu;    // 在函数中访问一个全局变量,需要使用 global 关键字
    unset($maoshu);
}
test();
echo $maoshu;   // 写代码的猫叔

超级全局变量

  • $GLOBALS  是一个包含了全部变量的全局组合数组。变量的名字就是数组的键,
  • $_SERVER
  • $_REQUEST
  • $_POST
  • $_GET
  • $_FILES
  • $_ENV
  • $_COOKIE
  • $_SESSON
  • ...

$GLOBAL

print_r($GLOBALS);

$maoshu="猫叔";

print_r($GLOBALS);

function test3(){
    global $maoshu;
    $maoshu = "写代码的猫叔";
    $GLOBALS['maoshu'] = "写代码的猫叔999";
}
test3();
echo $maoshu;   // 写代码的猫叔999

$_SERVER

  • $_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。
  • 这个数组中的项目由 Web 服务器创建。
  • 不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。
print_r($_SERVER);

实战:

  • 编写一个函数getFactors,接受一个正整数参数,计算并返回该整数的所有因子(除了1和自身的所有正整数因子)。
  • 注:因子是能够整除给定数的正整数(例如,对于数值 24,它的因子包括 1、2、3、4、6、8、12 和 24。因为这些数都可以整除 24,没有余数。)
  • 补充知识:怎么往数组内插入数据
  • 要求使用函数
function getFactors($number){
    $arrays = [];

    for($i = 2; $i< $number /2 ; $i++){
        if($number % $i === 0){
            $arrays[] = $i;
        }
    }
    return $arrays;
}

$num = 24;
$arr = getFactors($num);
print_r($arr);

Array
(
    [0] => 2
    [1] => 3
    [2] => 4
    [3] => 6
    [4] => 8
)

数组运算符 +号 合并数组/数组合并

+号合并两个数组时,只会保留第一个数组中的键值对,而忽略后面数组中相同键名的元素;

如果想要合并两个数组并覆盖相同键名的元素,可以使用 array_merge() 函数;

$a = [
    "m" => "测试",
    "n" => "猫叔"
];

$b = [
    "m" => "测试2",
    "o"=>"测试o",
    "p"=>"测试p",
    "q"=>[
        "cs"=>"二维测试"
    ]
];

$c=[
    "m" => "测试3",
    "p" => "打卡11",
    "cc"=>123
];

------------------------------------------
print_r($a+$b+$c);
Array
(
    [m] => 测试
    [n] => 猫叔
    [o] => 测试o
    [p] => 测试p
    [q] => Array
        (
            [cs] => 二维测试
        )

    [cc] => 123
)
------------------------------------------
array_merge($a, $b ,$c);
Array
(
    [m] => 测试3
    [n] => 猫叔
    [o] => 测试o
    [p] => 打卡11
    [q] => Array
        (
            [cs] => 二维测试
        )

    [cc] => 123
)

判断两个数组相等

==   

===    有相同的键/值对,且顺序相同、类型相同

数值数组

$a = [1,2,3];
$b = [1,2,"3"];
$c = [1,3,2];
var_dump($a == $b);   // bool(true)
var_dump($a === $b);   // bool(false)
var_dump($a == $c);    //  bool(false)
关联数组

$a = [
    "a"=>123,
    "b"=>456,
];

$b = [
    "b"=>456,
    "a"=>123,
];

var_dump($a == $b);   // bool(true)
var_dump($a === $b);   // bool(false)

空合并运算符 ??

php>=7

用于简化处理可能为null的变量或数组元素的情况。
它的作用是判断一个变量是否未定义或者为null,如果未定义或为null,则返回指定的默认值;否则返回该变量的值

$name = $username ?? "Maoshu";
echo $name;

组合比较符 <=>

可比较整型 浮点型 字符串

$c = $a <=> $b;

如果 $a &gt;$b, 则 $c 的值为 1 如果$a == $b, 则$c 的值为 0
如果 $a &lt;$b, 则 $c 的值为 -1

$a = "acd";
$b = "ace";

var_dump($a <=> $b);  // int(-1)   注意,字符串比较会比较每个字符的ascii码

如果只想比较类型是否相同,那么可以:

$a= 1;
$b = '1';
var_dump(gettype($a) === gettype($b));  // bool(false)

比较 undefined、0、false、null、空值

var_dump(!isset($name));   // bool(true)  变量 $name 未被声明
var_dump(is_null($name));   // bool(true)  变量 $name 是null,但是声没声名不知道
$name = null;
var_dump(!isset($name));   // bool(true)  变量 $name 未被声明
var_dump(is_null($name));   // bool(true)  变量 $name 是null
var_dump(empty($name));    // bool(true)  变量 $name 为空值

注意:empty() 函数对于未定义的变量也会返回true,因此在使用empty() 函数之前,应该确保变量已经被定义

$var1 = ""; // 空字符串
$var2 = 0; // 整数0
$var3 = null; // null
$var4 = false; // false
$var5 = array(); // 空数组 []

var_dump(empty($var1));  // bool(true)
var_dump(empty($var2));  // bool(true)
var_dump(empty($var3));  // bool(true)
var_dump(empty($var4));  // bool(true)
var_dump(empty($var5));  // bool(true)

var_dump(0 == false);   // bool(true)
var_dump(0 === false);   // bool(false)

字符串相关函数

  $strZh = "我是写代码的猫叔";

  $strEn = "Hello MaoShu,I am a person who loves to share";
  • strlen($strZh)  获取字符串长度(中文占3,英文占1)

  • strpos($strZh, '猫叔') 在字符串内查找一个字符或一段指定的文本,返回第一次出现的位置或false

  • stripos() 同上,但不区分大小写

  • strrpos() 同上上,返回最后一次出现的位置或false

  • strripos() 同上,但不区分大小写

  • explode(" ", $strEn)  把字符串打散成数组

  • implode(" -- ", $arrEn) 把数组拼接成字符串

  • strtoupper($strEn) 把字符串转换为大写

  • strtolower($strEn) 把字符串转换为小写

  • ucfirst($strEn) 将单词的首字母转换为大写

  • lcfirst() 将单词的首字母转换为小写

  • ucwords($strEn) 将字符串中每个单词的首字母转换为大写

  • str_replace("猫叔", "maomao", $strZh) 将字符串中的某个子字符串替换为另一个字符串,输出:我是写代码的maomao

  • strrev("nihao"):将字符串反转,仅限非中文  输出:oahin

  • trim(" nihao   "):去除字符串两端的空格

  • substr($strEn, 2, 6);  截取字符串的一部分, 截取$strEn字符串中下表为2后的6个字符,仅限非中文   输出:llo Ma

  • mb_substr($strZh, 2, 3) ,同上,截取字符串的一部分(中文)  需要安装扩展mbstring   输出:写代码

数组相关函数

$array = ["a","b","c","d"];
  1. array():创建一个数组。

    1. $array = array("a","b","c","d")
  2. count($array):返回数组中元素的数量。

  3. array_push($array,$newElement):将一个或多个元素添加到数组的末尾。

    1. $array[] = 5;  和 array_push($array, 5, 6)
  4. array_unshift($array,$newElement):将一个或多个元素添加到数组的开头。

  5. array_pop():删除并返回数组中的最后一个元素。

  6. array_shift():删除并返回数组中的第一个元素。

  7. array_slice():从数组中提取一部分元素,组成新的数组。

  8. array_merge():合并两个或多个数组。

  9. array_reverse():反转数组中的元素的顺序。

  10. in_array():检查数组中是否存在某个值。

  11. array_key_exists():检查数组中是否存在某个键。

  12. array_keys():返回数组中的所有键,组成新数组。

  13. array_values():返回数组中的所有值,组成新数组。

  14. array_search():在数组中搜索给定的值,并返回对应的键。

  15. array_unique():移除数组中的重复值(原数组不变)。

  16. max() min() 最大值和最小值

  17. sort() 数组排序(升序)

  18. rsort() 数组排序(降序)

  19. array_sum()  数组求和

  20. array_product() 数组求乘积

保留2位小数

number_format("3.141592654", 2);round("3.141592654", 2); 结果都一样,它们有什么不同?

时间、日期相关知识

函数

  • time() 获取当前时间戳(10位 秒),例如:1760179147

  • microtime(true) 返回一个浮点数时间戳(秒数和微秒数的总和,即13位时间戳)  1760179169.493

  • date(格式,时间戳) 日期格式化

    $time = time();
    echo date('Y-m-d H:i:s',$time);     // 时间戳(秒) 转 日期格式
  • strtotime(string)

    $time = strtotime("next Monday");
    echo date('Y-m-d H:i:s',$time);
    -----------------------
    $baseTime = strtotime("2025-10-01");
    $time = strtotime("next Monday", $baseTime);
    echo date('Y-m-d H:i:s',$time);
    -----------------------
    $baseTime = time();
    $time = strtotime("+1 day", $baseTime); // -2 hours
    echo date('Y-m-d H:i:s',$time);
  • mktime($hour, $minute, $second, $month, $day, $year)  生成时间戳

  • date_create() 来创建一个日期时间对象 date_create('2023-11-01')

  • date_format() 用于将日期和时间格式化为指定的字符串格式

    date_format($date, "Y-m-d H:i:s");
    date_format($date, "Y年m月d日 H:i:s")
  • date_diff() 计算两个日期之间的差

$date1 = date_create('2023-10-20 12:00:00');
$date2 = date_create('2023-10-21 12:00:00');
$diff = date_diff($date2, $date1);
echo $diff->format('%a 天');
echo $diff->format('%m 月');
echo $diff->format('%y 年 %m 月 %d 天');

-  %Y :完整年份的差异 
-  %y :年份的差异(两位数) 
-  %m :月份的差异 
-  %d :天数的差异 
-  %a :总共的天数差异 
-  %H :小时的差异 
-  %h :小时的差异(12小时制) 
-  %I :分钟的差异 
-  %S :秒数的差异 
-  %R :正负
-  %r :正负(必须是负的才会显示)
  • strftime($format, $timestamp) :根据指定的格式,将时间戳格式化为可读的日期和时间字符串,支持本地化的日期和时间格式 (php8已废弃)。
$timestamp = time();
$dateString = strftime("%A %Y-%m-%d %H:%M:%S", $timestamp);
echo $dateString;
  • gmdate($format, $timestamp) :根据指定的格式,将GMT时间戳格式化为可读的日期和时间字符串。
    gmdate()date()的区别
    gmdate() 函数使用格林威治标准时间(GMT)作为默认的日期/时间基准,会忽略服务器的时区设置,始终返回格林威治标准时间(GMT)的日期和时间;
    date() 函数则使用本地时间作为基准,它会根据当前服务器的时区设置来格式化日期和时间。

  • date_default_timezone_set($timezone) :设置默认的时区。

"UTC", "localtime"本地时区, "Asia/Shanghai"上海

注意:只能在脚本开始时设置默认时区,不能在运行时动态设置。

  • timezone_identifiers_list() :返回所有可用时区标识符的数组。

DateTime对象

$dateTime = new DateTime("2023-11-01 12:34:56");
//增加或减少指定的时间间隔
$dateTime->modify('+1 day');
$dateTime->modify('-1 hour');

//设置日期部分 setDate($year, $month, $day)
$dateTime->setDate(2024, 1, 3);

//设置时间部分 setTime($hour, $minute, $second)
$dateTime->setTime(13, 1, 5);

// 设置时区
$dateTime->setTimezone(new DateTimeZone("Asia/Shanghai")); 
// 获取时区
$dateTimeZone = $dateTime->getTimezone(); 
print_r($dateTimeZone->getName());
print_r($dateTimeZone->getLocation());

//将日期时间格式化为指定的字符串格式
echo $dateTime->format("Y-m-d H:i:s");

//获取时间戳
$timestamp = $dateTime->getTimestamp();
echo $timestamp;

//计算时间差
$dateTime2 = new DateTime('2023-12-05');
$diff = $dateTime->diff($dateTime2);
echo $diff->format('%R %m %d %h %i %s');
$datetime = new DateTime('2024-01-01');
$interval = new DateInterval('P1D'); // 一天的时间间隔
$datetime->add($interval);
//$datetime->sub($interval);

echo $datetime->format('Y-m-d');

常量

  • 常量值定义后在脚本任何地方都不能修改
  • 严格区分大小写,无需 $ 符号
  • 默认全局,可再整个运行的脚本的任何地方使用;
  • 最好全大写,最好开头以两个下划线开头
define("__MAOSHU", "写代码");
const MAOSHU2 = "写网站";

$a = 2;
define("MAOSHU".$a, "猫叔1");  // 这种用变量拼接的产量名,需要用下面方式打印出来
echo constant("MAOSHU".$a);  // 只能通过这种输出

const 和 define 的区别:

  1. const不能再条件语句中定义常量:
$a = 1;
if($a == 1){
    define("MAOSHU".$a, "猫叔1");
    echo constant("MAOSHU".$a);

    const MAOSHU2 = "猫大叔";    // 报错
    echo MAOSHU2;
}
  1. const 用于类成员变量的定义,define不可,但是可用于全局常量。
  2. const可再类中使用,define不能;
class ceshi {

    const Ceshi = '猫叔';   // 可以
    define('MS1','写代码的猫叔');    // 报错
    public function cc()
    {
        define('MS','写代码的猫叔');   // 类中的函数中可以
        echo self::Ceshi;
    }

}
$c = new ceshi;
$c->cc();

echo '----';
echo MS;
echo Ceshi;  // 报错

常量和变量的区别:

  • 常量前面没有美元符号($),而且不能有

  • 常量可以不用理会变量的作用域在任何地方定义和访问

  • 常量一旦定义就不能重新定义或取消定义

  • 常量的值只能是标量(字符串、整数、浮点数、布尔值),注意:现在也支持数组了

获取所有的常量

print_r(get_defined_constants());
print_r(get_defined_constants(true));

const __MYCONST = 'nihao';
echo get_defined_constants(true)["user"]['__MYCONST'];

魔术常量

魔术常量:它的值随着它在代码中的位置改变而改变

__LINE__

文件中的当前行号

echo '这是第 " ' . __LINE__ . ' " 行';
这是第 " 3 " 行
__FILE__

文件的完整路径和文件名,包含(盘符)根目录

echo '该文件的完整路径为 " ' . __FILE__ . ' " ';
该文件的完整路径为 " C:\Users\Administrator\Desktop\php_demo\1.php " 
__DIR__

文件所在的目录

echo '该文件位于 " ' . __DIR__ . ' " ';
该文件位于 " C:\Users\Administrator\Desktop\php_demo " 
__FUNCTION__

该函数被定义时的名字(区分大小写)

function test() {
    echo  '函数名为:' . __FUNCTION__ ;
}
test();
函数名为:test
__CLASS__

该类被定义时的名字(区分大小写)

<?php
class testClass {
    function test() {
        echo '类名为:'  . __CLASS__ . "<br>";
        echo  '函数名为:' . __FUNCTION__ ;
    }
}
$t = new testClass();
$t->test();

类名为:testClass
函数名为:test
__NAMESPACE__

命名空间

namespace MyProject; 
echo '命名空间为:"', __NAMESPACE__, '"'; // 输出 "MyProject"
__METHOD__

包含了:命名空间 类名 函数名

namespace MAOSHU;

class MyClass {
    public function myMethod() {
        echo __METHOD__; // 输出:MAOSHU\MyClass::myMethod
    }
}

$obj = new MyClass();
$obj->myMethod();
__TRAIT__

当前使用的 trait 的名称

trait MyTrait {
    public function myMethod() {
        echo "trait的名称为: " . __TRAIT__;
    }
}

class MyClass {
    use MyTrait;
}

$obj = new MyClass();
$obj->myMethod();

包含文件

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

include 和 require 语句

区别:
include 和 require 除了处理错误的方式不同之外,在其他方面都是相同的:

  • require 生成一个致命错误(E_COMPILE_ERROR),在错误发生后脚本会停止执行。
  • include 生成一个警告(E_WARNING),在错误发生后脚本会继续执行。

因此:
如果你希望被包含的文件是必需的且缺少文件会导致脚本无法正常运行,应使用require(推荐)。
如果你希望被包含的文件是可选的,或者即使缺少文件也希望脚本继续执行,可以使用include。

例如:

header.php
<?php 
    $siteTitle = "猫叔星球(maoshu.fun)";
?>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $siteTitle;?></title>
</head>
index.php
<?php include 'header.php'; ?>
<body>
<h1>欢迎来到<?php echo $siteTitle;?> !</h1>
<p>有问必答。</p>

</body>
</html>

include_once 和 require_once 语句

和上面的一样,但他只会调用一次,防止重复调用

面向对象OO

面向对象编程的三个主要特性:

  • 封装:指将对象的属性和方法封装在一起,使得外部无法直接访问和修改对象的内部状态。通过使用访问控制修饰符(public、private、protected)来限制属性和方法的访问权限,从而实现封装。

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

  • 多态:指可以使用一个父类类型的变量来引用不同子类类型的对象,从而实现对不同对象的统一操作。多态可以使得代码更加灵活,具有更好的可扩展性和可维护性。在 PHP 中,多态可以通过实现接口(interface)和使用抽象类(abstract class)来实现。

class Animal {
    public $name = "小猫仔";   // 默认值为小猫仔

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

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

$cat = new Animal;
//$cat = new Animal();  // 在php中,如果没有参数,可以省略括号
echo $cat->name.PHP_EOL;   //对象调用使用 ->
$cat->name = "小花";
$cat->eat();

$dog = new Animal;
$dog->name = "小黑";
$dog->say();

小猫仔
小花 在吃饭.
小黑 在说话.
访问控制

关键字 public、private、protected

  • public(公有):公有的类成员可以在任何地方被访问(类内部、子类、外部类)。
  • protected(受保护):受保护的类成员则可以被其自身(内部类)以及其子类和父类访问(咱家的),类外部不可直接访问。
  • private(私有):私有的类成员则只能被其定义所在的类访问(自己的、类内部),子类都不行,别说类外部了;
class Animal {
    protected $name = "小猫仔";
}
$cat = new Animal;
$cat->name = "小花";    // 错误
echo $cat->name;    // 错误
<?php
class ParentClass {
    public $publicVar = 'Public';
    protected $protectedVar = 'Protected';
    private $privateVar = 'Private';

    public function showProperties() {
        echo $this->publicVar . "\n";    // 可访问
        echo $this->protectedVar . "\n"; // 可访问
        echo $this->privateVar . "\n";   // 可访问
    }
}

class ChildClass extends ParentClass {
    public function showChildProperties() {
        echo $this->publicVar . "\n";    // 可访问
        echo $this->protectedVar . "\n"; // 可访问
        // echo $this->privateVar . "\n"; // 报错:不可访问
    }
}

// 测试代码
$parent = new ParentClass();
$child = new ChildClass();

// 1. 公有属性访问
echo $parent->publicVar . "\n";      // 输出: Public
// echo $parent->protectedVar . "\n"; // 报错:不可访问
// echo $parent->privateVar . "\n";   // 报错:不可访问

// 2. 类内部方法访问
$parent->showProperties();  // 输出: Public Protected Private
$child->showChildProperties(); // 输出: Public Protected

// 3. 尝试修改属性
$parent->publicVar = 'New Public';
// $parent->protectedVar = 'New Protected'; // 报错
// $parent->privateVar = 'New Private';     // 报错

// 4. 私有属性在继承中的表现
class TestPrivate {
    private function privateMethod() {
        echo "Private method\n";
    }
}

class TestChild extends TestPrivate {
    public function tryCallPrivate() {
        // $this->privateMethod(); // 报错:不可访问
    }
}
__construct构造函数
  • 构造函数是一种特殊的方法,在创建一个新对象时,它会被自动调用。
  • 它可以用来 初始化 对象的属性或执行其他必要的操作
  • 没有返回值
class Animal {
    private $birth;
    private $age;

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

        $days = (time() - strtotime($this->birth))/3600/24;
        $this->age = floor($days);
    }
    public function getAge(){
        return $this->age;
    }
}

$cat = new Animal("1994-05-23");
echo $cat->getAge()."天";

11465天
__destruct析构函数
  • 析构函数是一种特殊的方法,它在对象被销毁时自动调用
  • 它可以用来执行一些清理操作,例如释放资源或关闭数据库连接。
  • 当对象不再被引用或脚本执行结束时,析构函数会被自动调用。
class Animal {
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }
    public function __destruct() {
        echo "$this->name 析构函数被调用\n";
    }
}

$cat = new Animal("小花");
$dog = new Animal("小黑");
unset($cat);
echo "运行结束\n";

小花 析构函数被调用
运行结束
小黑 析构函数被调用
static 静态成员 和 self

「静态」指的是无需对类进行实例化,就可以直接调用这些属性和方法
,所有对静态变量进行的操作都会对所有对象起作用;

静态(static)成员是类级别的属性和方法,静态成员属于类本身,与类的实例化对象无关。

所有实例共享同一个静态属性,修改静态属性会影响所有实例

静态方法中不能使用$this(因为没有实例)

静态方法中应该使用self::static::访问静态成员

静态属性只在内存中存在一份,普通属性每个实例都有独立副本

使用场景:

  1. 计数器功能
  2. 工具类方法(如数学计算)
  3. 全局配置存储
  4. 单例模式实现
  5. 日志记录系统

举例:小猫小狗听到指令来吃饭,指令变化,全部都要听从

class Animal {
    // 静态属性(类属性)
    public static $name = "猫";

    // 静态方法(类方法)
    public static function eat() {
        // 静态方法中不能使用$this
        echo self::$name." 在吃饭\n";
    }
}

Animal::$name = '狗';   // 无需实例化类即可直接通过类调用静态成员,注意调用静态变量需要使用双冒号::

$cat = new Animal;
echo $cat->name;  // 输出 狗,说明:所有对静态变量进行的操作都会对所有对象起作用,注意:通过对象也可以调用静态成员(但是非常不推荐)

echo Animal::eat();

狗 在吃饭
类常量

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

共同点:都可以通过类直接访问,每个类都一份,子类可以重定义

不同点:一个用const修饰一个用static修饰,const可以不可修改static可以修改值,const默认publiic,且不能用protected和private,static可以;const的值只能是标量或数组而static可以是任何php类型,const(编译时优化)性能略高于static(运行时解析)

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

class Animal {
    const MAOSHU = "猫";
    static $Age = 9;

    public static function eat() {
        echo self::MAOSHU." 在吃饭\n";
        echo self::$Age;
    }
}

echo Animal::MAOSHU.PHP_EOL;  // 类常量通过::访问时不需要$符号
echo Animal::$Age.PHP_EOL;   // 静态属性通过::访问时需要$符号
// $cat = new Animal;
echo Animal::eat();

猫
9
猫 在吃饭
9
static 静态方法
class Animal {
    public static $name = '猫';
    public $age = 18;

    public static function say() {
        echo self::$name." 在叫\n";
        // 在静态方法中,可以调用 静态方法 和 静态变量
        // 在静态方法中,还可以调用 非静态方法 和 非静态变量
        echo (new self)->age . " 岁了\n";
    }
}

echo Animal::say().PHP_EOL;

猫 在叫
18 岁了

静态成员和非静态成员之间的互相调用

静态方法不能直接调用非静态成员(属性和方法),因为静态方法属于类级别而非实例级别。

<?php
class Demo {
    // 非静态属性
    public $instanceProperty = '实例属性';

    // 静态属性
    public static $staticProperty = '静态属性';

    // 非静态方法
    public function instanceMethod() {
        echo "非静态方法被调用\n";
    }

    // 静态方法
    public static function staticMethod() {
        // 尝试调用非静态成员(错误示范)
        // echo $this->instanceProperty; // 致命错误:不能使用$this
        // echo self::$instanceProperty; // 错误:未定义静态属性
        // $this->instanceMethod();     // 致命错误

        // 正确调用静态成员
        echo self::$staticProperty . "\n";

        // 间接调用非静态成员的方法(需先创建实例)
        $instance = new self();
        echo $instance->instanceProperty . "\n";
        $instance->instanceMethod();
    }
}

// 测试调用
Demo::staticMethod();

// 特殊情况:在非静态上下文中调用静态方法
$demo = new Demo();
$demo->staticMethod(); // 可以但不推荐

类的继承(extends)

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

class Animal {
    public $name="小动物";
    protected $age=3;
    private $birth='2023';
}

class Cat extends Animal {
    public $name = "小猫";
    public $sex = "公";
    public function getAge(){
        return $this->age;
    }

    public function getBirth(){
        return $this->birth;   // 错误,无法获取私有属性
    }
}

var_dump(new Animal);
var_dump(new Cat);

$cat1 = new Cat;
echo $cat1->name.PHP_EOL;    // 小猫
echo $cat1->age.PHP_EOL;     // Error: Cannot access protected property Cat::$age 
echo $cat1->birth.PHP_EOL;    // 未定义错误
echo $cat1->sex.PHP_EOL;      // 公
echo $cat1->getAge().PHP_EOL;    // 通过函数获取受保护的属性值
echo $cat1->getBirth().PHP_EOL;    // 通过函数获取父类私有的属性值

输出:
class Animal#1 (3) {
  public $name =>
  string(9) "小动物"
  protected $age =>
  int(3)
  private $birth =>
  string(4) "2023"
}
C:\Users\Administrator\Desktop\php_demo\1.php:22:
class Cat#1 (4) {
  public $name =>
  string(6) "小猫"
  public $sex =>
  string(3) "公"
  protected $age =>
  int(3)
  private $birth =>
  string(4) "2023"
}
小猫

公
3

例子2,构造函数在继承中的运行顺序:

class Animal {
    protected $name;

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

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

class Cat extends Animal {
    public function __construct($name) {
        // 2
        parent::__construct($name);
    }

    public function meow() {
        // 8
        echo $this->name . " 在喵呜.".PHP_EOL;
    }
}

$cat = new Cat("Tom");   // 1
var_dump($cat); // 4
$cat->eat();  // 5 继承自父类 Animal 的方法
$cat->meow();  // 7 子类 Cat 自己的方法

输出:
class Cat#1 (1) {
  protected $name =>
  string(3) "Tom"
}
Tom 在吃饭.
Tom 在喵呜.
方法和属性重写

如果从父类继承的方法或属性不能满足子类的需求,可以对其进行改写

class Animal {
    protected $name = "小动物";

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

class Cat extends Animal {
    protected $name = "小猫";   // 覆盖父类属性
    public function eat() {    // 重写父类的方法
        echo $this->name . " 大口大口地吃饭.";
    }
}

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

输出:
class Cat#1 (1) {
  protected $name =>
  string(6) "小猫"
}
小猫 大口大口地吃饭.
final 关键字

作用:

  • 防止类被继承
  • 防止类的方法被重写
  • 无法防止属性。属性无效

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

// 子类无法继承该类
final class myClass {
    // 类的内容
}

class SubClass extends myClass {}  // 错误,无法继承myClass类

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

class myClass {
    // 子类无法重写该方法
    final public function eat() {
        echo $this->name . " 在吃饭.";
    }
}
class SubClass extends myClass {
    public function eat() {}  // 错误,无法重写

    public function diy_eat(){
        $this->eat();   // 虽然无法重写,但是final的方法能继承下来
    }
}

(new SubClass)->diy_eat();

注意:

  • final不能用于属性
  • 接口(interface)中的方法不能声明为final
  • 抽象类可以包含final方法
  • final类中的所有方法都自动成为final方法

场景建议:

  1. 安全考虑:关键算法、必须保持行为一致的方法,防止核心类被恶意修改保证关键方法的行为一致性
  2. 设计意图:明确表示该类/方法不应被修改作为API的一部分时保证稳定性
  3. 性能优化:final方法和类可能获得轻微的性能提升(JIT优化)
子类调用父类方法 或 父类构造函数

parent::父类方法()
parent::__construct()

class myClass {
    public $name = "小动物";

    public function __construct($name) {
        $this->name = $name;
    }
    // 子类无法重写该方法
    public function eat() {
        echo $this->name . " 在吃饭.".PHP_EOL;
    }
}
class SubClass extends myClass {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function say(){
        parent::eat();
        echo $this->name . " 在说话.".PHP_EOL;
    }
}
$obj = new SubClass("小猫猫");
$obj->say();

小猫猫 在吃饭.
小猫猫 在说话.
静态延迟绑定 static

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

class Animal {
    protected static $name="小动物";

    public static function eat() {
        echo self::$name. " 在吃饭.".PHP_EOL;
        echo static::$name. " 在吃饭.".PHP_EOL;
    }
}

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

Animal::eat();
echo ' ---- '.PHP_EOL;
Cat::eat();

小动物 在吃饭.
小动物 在吃饭.
 ---- 
小动物 在吃饭.
小猫 在吃饭.

类的多态(难点)

  • 多态性允许不同类的对象对相同的消息作出不同的响应。如下代码,狗类、猫类这些不同类中都有makeSound方法(相同的消息),他们都有不同的响应
  • 多态性通过方法重写(覆盖,前面已学)和 方法重载 来实现。
  • 方法 重写 是指子类重写父类的方法,以改变方法的实现细节。
  • 方法 重载 是指在同一个类中根据参数个数或类型不同来实现不同功能。
  • 需要注意的是,多态性只适用于继承关系的类。子类必须重写父类的方法才能实现多态性。
class Animal {
    protected $name="动物";
    public function makeSound() {
        echo "$this->name 在吼叫。";
    }
}

class Dog extends Animal {
    protected $name="小狗";
    public function makeSound() {   // 重写
        echo "$this->name 在汪汪。";
    }
}

class Cat extends Animal {
    protected $name="小猫";
    public function makeSound() {    // 重写
        echo "$this->name 在喵喵。";
    }
}

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

$animal->makeSound();
$dog->makeSound();
$cat->makeSound();

方法重载

先熟悉两个内置函数:

$args = func_get_args();   // 获取方法的参数
$numArgs = func_num_args();   // 获取方法的参数数量

用法例子:

function test(){
    $args = func_get_args(); 
    $numArgs = func_num_args();

    var_dump($args[1]);   // int(2)
    var_dump($numArgs);   // int(4)
}

test(1,2,3,4);

类中重载的使用:

class Animal {
    public function makeSound() {
        echo "动物在吼叫";
    }
}

class Cat extends Animal {
    public function makeSound() {
        // $args = func_get_args();
        $numArgs = func_num_args();

        switch($numArgs){
            case 2:
                echo '执行参数个数为2的事件';
                break;
            case 3:
                echo '执行参数个数为3的事件';
                break;
            default:
                echo '执行默认事件';
        }
    }
}

// $animal = new Animal();
$cat = new Cat();

$cat->makeSound("测试","2");

当然也可以使用可变参数或参数默认值

// ========== 传统OOP的多态性方法重载 ==========

class Animal {
    public function makeSound() {
        echo "动物在吼叫";
    }
}

class Cat extends Animal {
    // 可变参数或参数默认值
    public function makeSound(...$args) {
        $numArgs = count($args);        
        switch($numArgs){
            case 2:
                echo '执行参数个数为2的事件';
                break;
            case 3:
                echo '执行参数个数为3的事件';
                break;
            default:
                echo '执行默认事件';
        }
    }
}

// $animal = new Animal();
$cat = new Cat();

$cat->makeSound("测试","2");
// ========== PHP风格的重载(动态成员处理) ==========
class PhpOverloadExample {    
    // 1. 属性重载(Property Overloading)
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    public function __get($name) {
        return $this->data[$name] ?? null;
    }

    public function __isset($name) {
        return isset($this->data[$name]);
    }

    public function __unset($name) {
        unset($this->data[$name]);
    }

    // 2. 方法重载(Method Overloading)
    public function __call($name, $arguments) {
        echo "调用不存在的方法: $name\n";
        echo "参数: " . implode(', ', $arguments) . "\n";
    }

    public static function __callStatic($name, $arguments)     {
        echo "调用不存在的静态方法: $name\n";
        echo "参数: " . implode(', ', $arguments) . "\n";
    }
}

// 测试属性重载
$obj = new PhpOverloadExample();
$obj->dynamicProperty = "动态属性值"; // 触发__set
echo $obj->dynamicProperty . "\n";    // 触发__get
var_dump(isset($obj->dynamicProperty)); // 触发__isset
unset($obj->dynamicProperty);         // 触发__unset

// 测试方法重载
$obj->undefinedMethod(1, 2, 3);       // 调用不存在/不可访问的方法时触发__call
PhpOverloadExample::undefinedStatic(4, 5); // 触发__callStatic

结果:
bool(false)
调用不存在的方法: undefinedMethod
参数: 1, 2, 3
调用不存在的静态方法: undefinedStatic
参数: 4, 5

接口 interface 和 抽象类 abstract

接口

开开眼:

// 定义一个接口
interface Animals {
    public function a();
    public function b();
}
// 实现这个Animals接口
class Cat implements Animals {
    public function a(){

    };
    public function b(){

    };
}

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

  • 可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容
  • 只可以使用public
  • 通常用于定义一些规范,让代码更加有条理 不易出错。
    例如:小动物必须要吃饭和睡觉,否则就会死!这是必须的,每个小动物都必须有这2个方法!
interface Animals {
    // 接口中不能有构造函数
    const MAOSHU = "猫叔";    // 接口里可以定义常量,但是不能定义普通属性;
    public function eat();    // 接口里的方法签名默认且必须只能是public,不能是protected和private
    public function sleep($hours);
    public static function jump();   // 接口里也可以有静态方法签名,同样的继承了这个接口的类也需要实现这个静态方法
}

class Cat implements Animals {   // 注意:接口的实现需要使用 implements 关键字
    // 一个类要继承接口,这个类就必须要全部实现接口中的所有签名方法

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

    public function sleep($hours) {
        echo "Cat 要睡 $hours 小时.";
    }

    public static function jump(){
        echo '跳跳跳';
    }
}

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

echo Cat::MAOSHU;   // 调用常量
echo Cat::jump();   // 调用静态方法

一个类可以实现多个接口:

interface Animals {
    public function eat();
    public function sleep($hours);
}
interface Sports {  // 体育
    public function run();
    public function jump();
}
class Cat implements Animals,Sports {   // Cat类实现了两个接口:Animals和Sports
    public function eat() {
        echo "Cat 在吃饭";
    }

    public function sleep($hours) {
        echo "Cat 要睡 $hours 小时";
    }

    public function run() {
        echo "Cat 在跑步";
    }

    public function jump() {
        echo "Cat 在蹦跳";
    }
}
抽象类和抽象方法

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

抽象类
abstract class Animals{

}
  • 抽象类是一种特殊的类,只能被继承,不能被实例化
  • 抽象类用于定义一组相关的方法,但这些方法的具体实现由继承它的子类来完成。
  • 子类继承抽象类后,必须实现抽象类中的所有抽象方法。
  • 抽象类可以包含抽象方法和普通方法
抽象方法
abstract public function xxx();
abstract protected function xxx();
  • 抽象方法是没有具体实现的方法,只有方法的声明,而不需要方法体。
  • 抽象方法只能存在于抽象类中。
  • 可以使用public/protected,但不能使用private私有。
// 定义一个抽象类
abstract class Animals
{
    public $name;

    // 抽象类中可以有构造函数,该构造函数是抽象类中唯一可以定义的函数,且必须是public,调用该构造函数是通过继承的子类对象调用的。
    function __construct(){

    }

    // 定义一个抽象方法
    abstract public function eat();
    // 定义一个protected的抽象方法
    abstract protected function sleep($hours);
    abstract private function miaomiao();    // 不能有私有抽象方法

    // 定义一个普通方法
    public function play()
    {
      echo '玩耍';
    }

    private function die(){   // 可以有私有普通方法
        echo "抽象类私有方法".PHP_EOL;
    }

}

class Cat extends Animals   // 继承抽象类使用 extends 关键字
{
    function __construct(){
        $this->name = "小猫";
        parent::__construct();
    }

    // 子类继承抽象类后,子类就必须实现抽象类中的所有抽象方法

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

    protected function sleep($hours)
    {
        echo "Cat 要睡 $hours 小时.";
    }
}

$cat = new Cat();
$cat->eat();
//$cat->sleep(12);
$cat->play();
抽象类与接口的区别
  1. 抽象类可以包含非抽象方法的实现,而接口只能包含方法的声明,没有方法的实现。
  2. 类只能继承一个抽象类,但可以实现多个接口。
  3. 抽象类可以有构造函数,而接口不能有构造函数。
  4. 抽象类中的方法可以有public、protected和private访问修饰符,而接口中的方法只能是public。
  5. 子类继承抽象类时,必须实现抽象类中的所有抽象方法,否则子类也必须声明为抽象类。
    子类实现接口时,必须实现接口中的所有方法。

接口 和 抽象类 都用在哪些地方??

PHP中的接口(Interface)和抽象类(Abstract Class)初看很像,它们都不能被直接实例化,并且都要求子类实现某些方法。但它们的定位和用途有本质区别

接口和抽象类的核心差异在于:抽象类关注“是什么”(is-a关系,强调所属关系和代码复用),而接口关注“能做什么”(has-a能力或行为契约,强调特定功能的实现)

特性维度 接口 (Interface) 抽象类 (Abstract Class)
核心关系 具有(has-a 某种能力或行为 是(is-a 某种类型,属于同一家族
关键字 implements extends
方法实现 只能声明方法(PHP 8.0前不能有方法体) 可以包含具体实现的方法和抽象方法
成员变量 不能定义普通属性,只能定义常量 可以定义各种类型的属性(变量)
继承方式 一个类可以实现多个接口 一个类只能继承一个抽象类(PHP单继承)
构造函数 不能有构造函数 可以有构造函数
方法可见性 方法默认且必须是 public 方法可以是 public, protected, 或 private
典型场景 定义行为契约,让不相关的类具备相同能力(如支付、日志) 紧密相关的类提供共享代码和通用框架(如游戏角色基类)

1. 何时使用接口?

当您需要为可能完全不相关的类定义一套必须实现的行为标准时,接口是最佳选择。它关注“能做什么”,而不关心“是什么”以及如何实现。

  • 经典场景1:支付系统假设系统有CreditCardPayment(信用卡支付)和AlipayPayment(支付宝支付)两个类,它们本身没有继承关系,但都需要实现支付行为。您可以定义一个Payable接口。
interface Payable {
    public function pay($amount);
    public function getPaymentStatus();
}

class CreditCardPayment implements Payable {
    public function pay($amount) { /* 实现信用卡支付逻辑 */ }
    public function getPaymentStatus() { /* ... */ }
}

class AlipayPayment implements Payable {
    public function pay($amount) { /* 实现支付宝支付逻辑 */ }
    public function getPaymentStatus() { /* ... */ }
}

这样,在控制器中您就可以统一处理所有实现了Payable接口的对象,而不必关心其具体类型。

function processPayment(Payable $paymentMethod, $amount) {
    $paymentMethod->pay($amount);
}
  • 经典场景2:日志记录器系统可能需要将日志记录到文件或数据库。可以定义一个LoggerInterface,让不同的记录器去实现,便于灵活切换。

2. 何时使用抽象类?

当您为一组有密切血缘关系、共享大量基础逻辑的类设计一个模板或基类时,应该使用抽象类。它封装了共性,同时规定了子类必须完成的个性化部分。

  • 经典场景:游戏角色基类假设您在开发一个游戏,有Warrior(战士)、Mage(法师)等角色。它们都有生命值、攻击力等属性,也都有移动、攻击等行为。但攻击方式各不相同。这时可以用抽象类。
abstract class GameCharacter {
    protected $health;
    protected $attackPower;

    // 具体方法:所有角色移动方式一样,直接复用代码
    public function move($direction) {
        echo "Moving towards $direction.\n";
    }

    // 抽象方法:每个子类必须实现自己独特的攻击方式
    abstract public function attack();
}

class Warrior extends GameCharacter {
    public function attack() {
        echo "Warrior slashes with a sword!\n";
    }
}

class Mage extends GameCharacter {
    public function attack() {
        echo "Mage casts a fireball!\n";
    }
}

抽象类GameCharacter在这里提供了共享的属性$health和具体方法move(),避免了重复代码,同时又强制要求子类实现个性化的attack()方法。

总结:

情况 选择 原因
要定义一组行为规范,让不同类别的对象都能做某件事 接口 关注“能力”,支持多重继承,灵活性高
要为一组相似的类建立一个模板,它们共享很多代码和属性 抽象类 关注“本质”,提供代码复用和部分实现
两者特点都需要(既要有统一行为,又要有共享代码) 结合使用:抽象类实现接口 先定义接口规范行为,再用抽象类实现公共代码

简单概括:接口定义“契约”,抽象类提供“蓝图”

Trait

略过

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Discuz! X

GMT+8, 2025-10-20 09:55 , Processed in 0.062549 second(s), 2 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表