PHP Basics

学习驱动着超过 70% 网络的语言,轻松驾驭如 WordPress 之类的内容管理系统!快速了解 PHP 基础知识,包括语法、变量、循环和函数。处理表单、构建 API,并连接到 SQLite 数据库,打造动态、数据驱动的网络应用程序!

0-introduction

欢迎与讲师介绍

  • 课程名称: PHP Fundamentals (PHP 基础)
  • 讲师: Maximiliano Firtman (可以称呼 Max 或 Maxi)
    • 网站: third.dev
    • 背景:
      • 来自阿根廷,现居布宜诺斯艾利斯。
      • 移动 Web 开发者。
      • 1996 年开始 Web 开发 (HTML, CSS, JavaScript)。
      • 早期后端经验:ASP (使用 VBScript)。
      • 1999 年开始使用 PHP (从 PHP 3 开始)
      • 本次课程将讨论 PHP 8
      • 开发了超过 150 个 Web 应用 (并非全部使用 PHP)。
      • 著有 13 本书,大量关于 Web 开发、前端、移动应用开发和 Web 性能的课程。
      • 最新著作:《Learn PWA》(由 Google 在 web.dev 发布,免费)。
      • 在 Frontend Masters 上的内容:前端、PWA、原生 JavaScript、API、移动应用课程 (Kotlin, Swift, Dart with Flutter) 以及一些后端课程 (如 Go)。

课程内容概览

  • 什么是 PHP,为什么选择 PHP?
  • PHP 语法 (从零开始)。
  • 超全局变量 (Superglobal variables)。
  • PHP 的编程范式 (与其他语言如 JavaScript, Java, Python 的比较)。
  • PHP 的面向对象编程 (OOP)。
  • 处理数据:
    • 小型项目实战。
    • 使用 JSON 数据库。
    • 构建 API。
  • 高级技巧、调试和最佳实践。

课程先决条件

  • 任何语言的基础编程经验 (例如 JavaScript, C 语言都可以)。
  • 一些 OOP (面向对象编程) 概念 (例如了解什么是类 class, 超类 superclass)。
  • 基础 HTML 知识 (不会写很多 HTML,但会涉及)。

所需工具

  • PHP 解释器: (php.net, 免费, 开源)。稍后会解释如何安装。
  • 浏览器: 任何浏览器均可。
  • 代码编辑器: 讲师将使用 VS Code,但任何其他编辑器也可以。

1-why-php

什么是 PHP?

  • 定义 (来自 Wikipedia): PHP 是一种广泛使用的开源脚本语言,特别适用于 Web 开发,并且可以嵌入到 HTML 中。
  • 通常指服务器端脚本语言。

PHP 的市场份额与应用

  • 惊人的市场份额:76%
    • 在所有已知服务器端编程语言的网站中,PHP 占据了 76% 的份额。
    • 这个数字可能让年轻开发者惊讶,因为感觉上 PHP 似乎不再流行,但事实恰恰相反。
  • 数据解读:
    • 在小型网站中的渗透率高于大型创业公司或银行。
    • 大型网站仍在使用 PHP:
      • Meta (Facebook, Instagram 部分)。
      • Wikipedia。
  • WordPress 的影响:
    • 76% 的数据中,约 40% 可能来自 WordPress。WordPress 是最流行的 CMS (内容管理系统),它基于 PHP。
    • 这意味着并非 76% 的 Web 开发者都在写 PHP,很多 WordPress 网站的拥有者可能从未写过一行 PHP 代码。
    • 但如果你需要维护或扩展 WordPress (如创建主题、插件),就需要了解 PHP。

基于 PHP 的流行 CMS 和框架

  • CMS: WordPress, Drupal, Joomla 等。
  • 开发框架: Magento, Symfony, Laravel, CodeIgniter 等。
    • 这些是开发者使用的 PHP 框架,类似于 Node.js 的 Express.js 或 Python 的 Django。
    • 本次课程将使用原生 PHP (Vanilla PHP),不涉及框架。

PHP 的其他用途

  • 命令行脚本:
    • 可以用于计算机脚本任务,如清理数据、执行备份等,完全独立于 Web。
    • (尽管 99.9% 的 PHP 用途是 Web 后端)。
  • 桌面应用程序 (PHP-GTK):
    • 一个非官方但相关的框架,允许用 PHP 创建跨平台 (Windows, Mac, Linux) 的桌面应用。
    • 界面风格可能看起来比较老旧 (源于 90 年代)。
  • 实验性项目:
    • php-wasm: PHP 的 WebAssembly 解释器,可以在浏览器中客户端运行 PHP 代码 (实验性质)。
    • 将 PHP 编译为 WebAssembly 模块: 允许 JavaScript 调用编译后的 PHP 代码。

2-history-of-php

PHP 的起源与创造者

  • 创造者: Rasmus Lerdorf,1993 年,当时是一名网页设计师。
  • 初衷: 为他自己的个人网站创建一套工具。
  • 早期名称: Personal Homepage Tools (PHP 工具)。
  • PHP 名称的演变:
    • 最初:Personal Homepage (个人主页)。
    • 现在:PHP: Hypertext Preprocessor (一个递归缩写,P 代表 PHP)。

PHP 的设计哲学与影响

  • 非开发者导向的初衷:
    • Rasmus Lerdorf 当时并非专业程序员。
    • PHP 最初并非设计为一种编程语言,而是为网页设计师提供快速创建动态内容的工具集。
    • 这是 PHP 语言内部不一致性存在的原因之一 (例如函数命名规则不统一,如 is_null vs isset )。
  • 灵感来源: Perl, C, Java, JavaScript 等。
  • 语言的不一致性:
    • 由于其发展历程,PHP 在某些方面缺乏一致性,这可能让来自更规范语言的开发者感到困扰。
    • 例如:函数 is_null()isset(),前者有下划线,后者没有。
  • Facebook 的早期与 PHP:
    • Mark Zuckerberg 使用 PHP 创建了 facebook.com
    • Rasmus Lerdorf 曾评价其早期代码“非常糟糕”,但也为这段“糟糕”的代码能发展成一家数十亿美元的公司而自豪,这体现了 PHP 的某种力量:即使是初级开发者也能快速构建可运行的应用。

PHP 版本历史里程碑

  • PHP 3 (约 1998 年): 第一个被广泛使用的版本,也是讲师接触的第一个版本。
  • PHP 4 (约 2000 年): 使 PHP 大受欢迎的版本。
    • 令人惊讶的是,PHP 4 至今仍未被弃用 (仅 PHP 3 被弃用),仍在接受安全和错误修复。
  • PHP 5 (约 2004 年): 一个重大的转变,与 PHP 4 不完全兼容,修复了许多安全问题。
  • PHP 6: 从未发布
  • PHP 7 (约 2015 年): 在 PHP 5 之后大约 10 年发布。
  • PHP 8 (约 2020 年): 当前版本(疫情期间发布)。
    • 移除了许多历史遗留的“糟糕”语法。
    • 正努力成为一门更好的编程语言。

PHP 的吉祥物:大象 (elePHPant)

  • 形象: 一只大象。
  • 灵感来源: 设计师观察大写字母 "PHP" 的形状,觉得像大象的脸和身体。
  • 名称: ElePHPant (将 PHP 嵌入其中)。
  • (讲师幽默地提到,现在人们可能因为 PHP 历史悠久、体量大而觉得它像大象,但这并非最初的含义。)

3-php-language-features

PHP 的语言类型定位

  • 编译型语言 (Compiled Languages): 编写代码,交付机器码 (如 C, C++, Go)。
  • 字节码语言 (Bytecode Languages): 编写代码,编译成中间语言/字节码 (如 C#, Java)。
  • 解释型/脚本语言 (Interpreted/Scripting Languages): 直接交付源代码 (如 PHP, JavaScript)。
    • PHP 属于这一类,部署时直接将源代码发布到服务器。

PHP 的主要特性

  • 非常灵活 (Very flexible)。
  • 跨平台 (Platform independent): 解释器可用于 Unix, Windows, Linux, Mac,甚至有 Android 和 JavaScript (WASM) 中的解释器。
  • 广泛的数据库支持 (Wide database support): 几乎支持所有数据库。
  • 开源 (Open source)。
  • 多范式 (Multi-paradigm):
    • 支持面向对象编程 (OOP),但非强制。
    • 也可以编写过程式代码 ("spaghetti code") 并且能够运行。
  • 丰富的标准库 (Rich standard library):
    • 提供了大量的全局函数。
    • 这也是批评点之一:函数命名不一致 (如 is_null vs isset)。
    • 同一个功能可能有多种实现方式。
  • 内置会话管理 (Built-in session management):
    • PHP 的一大优势,能够轻松在服务器端跨请求存储和跟踪用户会话数据 (如 $_SESSION)。
    • 内部通过 Cookie 和会话 ID 实现。
  • 快速开发 (Rapid development):
    • 能快速地将想法转化为可运行的应用 (MVP)。
    • 上手简单,几行代码就能工作。

为什么现在学习 PHP 基础?

  • Web 的重要组成部分: 仍然是简历上的一个好技能。
  • 维护遗留 Web 应用: 大量现有应用使用 PHP 构建。
  • 扩展流行 CMS: 如 WordPress, Joomla, Drupal,自定义主题和插件需要 PHP。
  • 使用现代 PHP 框架: 如 Symfony, Laravel, CodeIgniter,这些框架在 PHP 开发者中非常流行。

4-installation-setup

开发工具与 IDE

  • VS Code:
    • 默认不支持 PHP,需要安装扩展。
    • 讲师推荐 "PHP Debug" 等扩展。
    • 注意:一些扩展可能有免费版和付费 Pro 版,免费版可能会有升级提示。
  • PhpStorm: 经典的 PHP IDE,功能强大,拥有忠实用户群。
  • NetBeans / Eclipse PDT: 在企业级 PHP 开发中仍有使用。
  • Zend Studio: 付费 IDE,主要面向企业用户和大型项目。

LAMP 技术栈简介

  • 一个曾经非常流行的后端技术栈组合:
    • Linux (操作系统)
    • Apache (Web 服务器)
    • MySQL (数据库,现在也可能是 MariaDB)
    • PHP (编程语言)
  • 注意:这只是一个经典的组合,如今 PHP 可以与各种技术搭配使用。本次课程仅关注 PHP 本身。

安装与设置 PHP

  • 获取 PHP:
    • 生产环境: 可以从 php.net 下载,甚至从源码编译。
    • 开发环境 (推荐): 使用包管理器。
      • Mac: 使用 Homebrew: brew install php
      • Windows: XAMPP 是一个推荐的集成环境包,它会安装 Apache, MariaDB, PHP 和 Perl。XAMPP 也可用于 Mac 和 Linux。
      • Linux: 使用系统自带的包管理器 (如 apt, yum)。
  • 验证安装: 打开终端,输入 php -v,应能看到 PHP 版本信息。
  • 其他方式:
    • 通过 Docker 安装。
    • 使用其他 LAMP/WAMP/MAMP 集成安装包。
  • 重要: Mac 上安装 XAMPP 可能因签名问题遇到安全提示,需要手动允许。

Web 服务器

  • PHP 脚本通常由 Web 服务器 (如 Apache, Nginx, Lightspeed, IIS) 调用来处理 HTTP 请求。
  • PHP 本身不是 Web 服务器,它负责处理请求并生成响应。
  • 大多数云服务提供商都支持 PHP 脚本的执行。

PHP 在现代托管平台 (如 Vercel)

  • 像 Vercel 这样的现代托管平台也支持 PHP(例如通过 Serverless Functions)。
  • 这意味着 PHP 也可以集成到现代化的开发和部署流程中,不仅仅是传统的 FTP 上传方式。

5-php-tags

VS Code PHP 扩展

  • 安装 PHP 相关扩展可以获得语法高亮、调试等功能。讲师使用的是 "PHP Debug"。

运行 PHP 脚本

  • 命令行运行:
    • 打开终端或命令提示符。
    • 进入 PHP 文件所在目录。
    • 执行命令:php your_script_name.php
    • 例如:php test.php
  • 注意:这只是在命令行执行脚本,尚未涉及 Web 服务器。

PHP 标记 (Tags)

  • 标准标记:

    <?php
    // PHP code goes here
    ?>
    
    
  • 文件默认行为:

    • 在一个 .php 文件中,位于 <?php ... ?> 标记之外的任何文本都会被 PHP 解释器直接输出。
    • 这源于 PHP 最初被设计为嵌入 HTML 中的语言。
  • echo 命令:

    • 用于输出字符串或变量内容。
    • echo "Hello World";
    • echo 是一个语言结构而非真正的函数,所以括号是可选的:echo("Hello"); 也是合法的。

混合输出与 PHP 代码

  • 可以在一个 PHP 文件中混合使用直接输出的文本 (如 HTML) 和 PHP 代码块。

    <p>This is static text.</p>
    <?php
        echo "<p>This is dynamic text from PHP.</p>";
    ?>
    <p>More static text.</p>
    
    

省略 PHP 结束标记 ?> 的情况

  • 条件: 如果一个 .php 文件的末尾 只有 PHP 代码,没有任何后续的 HTML、文本或空格。
  • 原因:
    • 防止在 ?> 之后意外地输出空格或换行符。
    • 这些意外的输出可能会在某些情况下导致问题,例如在发送 HTTP 头部信息 (headers) 之前输出了内容,会导致 "headers already sent" 错误。
  • 最佳实践:
    • 对于纯 PHP 文件 (例如类文件、配置文件等,它们不直接产生 HTML 输出),推荐省略文件末尾的 ?>
    • 一些代码规范检查工具 (linter) 可能会提示你移除它。

6-variables

变量定义与基本规则

  • 美元符号 $ 开头: 所有变量都以 $ 符号开始。

    • 例如:$name = "Max";
    • 例如:$year = 2024;
  • 无需声明关键字: 定义变量时不需要像 JavaScript 中的 var, let, const 或 C# 中的类型声明。直接赋值即可。

  • 分号 ; 结尾: PHP 中的大多数语句 (包括变量赋值) 都需要以分号结束。

    • 唯一的例外是:如果 PHP 代码块是文件的最后一部分,并且使用了结束标记 ?>,那么紧邻 ?> 前的最后一条 PHP 语句可以省略分号。但通常建议总是加上分号。
  • 动态类型: PHP 是一种动态类型语言。变量的类型是在运行时根据赋给它的值决定的,并且可以在脚本执行过程中改变。

    • 例如:

      $value = 100;       // $value 是整数
      $value = "Hello";   // $value 现在是字符串
      $value = true;      // $value 现在是布尔值
      
      

大小写敏感性

  • 变量名:大小写敏感。
    • $name$Name 被视为两个不同的变量。
  • 内建常量 (true, false, null):大小写不敏感。
    • true, TRUE, True 都表示布尔真值。
    • null, NULL, Null 都表示空值。
    • (但函数名、类名等通常是大小写不敏感的,这体现了 PHP 的一些不一致性)。

注释

  • 单行注释:

    • // 这是一个单行注释
    • # 这也是一个单行注释 (Shell 风格,较少见于 PHP)
  • 多行注释:

    /*
    这是一个
    多行注释块
    */
    
    

可变变量 (Variable Variables)

  • 语法: 使用两个美元符号 $$variableName

  • 解释:

    • 第一个变量 ($variableName) 的值会被用作第二个变量的名称。

    • 例如:

      $foo = "bar";
      $$foo = "Hello World"; // 这等价于 $bar = "Hello World";
      
      echo $bar; // 输出: Hello World
      echo ${$foo}; // 也可以这样访问,输出: Hello World
      
      
  • 用途与风险:

    • 非常灵活,允许动态地创建和访问变量。
    • 但会使代码难以阅读和调试,并可能引入安全风险(如果变量名来自不可信的输入)。
    • 在实际开发中很少使用。

7-strings

字符串定义

  • PHP 支持多种定义字符串的方式。

  • 1. 单引号 ('...'):

    • 行为: 字符串内容按字面解释。

    • 变量内插: 不进行变量内插。变量名会作为普通文本输出。

      $name = "Max";
      echo 'Hello $name'; // 输出: Hello $name
      
      
    • 转义序列: 只识别两个转义序列:

      • \':输出单引号本身。
      • \\:输出反斜杠本身。
      • 其他如 \n (换行符) 会被视为普通文本 \n
    • 性能: 通常比双引号字符串略快,因为 PHP 不需要扫描其中的变量。

  • 2. 双引号 ("..."):

    • 行为: 会解析字符串内容。

    • 变量内插: 进行变量内插。字符串中的变量会被其值替换。

      $name = "Max";
      echo "Hello $name"; // 输出: Hello Max
      // 对于复杂变量 (如数组元素或对象属性),建议使用花括号包裹:
      echo "User: {$user['name']}";
      echo "Value: {$obj->property}";
      
      
    • 转义序列: 识别多种转义序列,如:

      • \n:换行符
      • \r:回车符
      • \t:制表符
      • \$:美元符号本身
      • \":双引号本身
      • \\:反斜杠本身
    • 表达式内插: 可以使用 {} 包裹更复杂的表达式。

      花括号用途:主要用于复杂变量和消除歧义,不支持表达式计算。
      
      // 复杂变量示例
      echo "User: {$user['name']}";
      echo "Value: {$obj->property}";
      
      // 消除歧义示例
      $fruit = 'apple';
      echo "I like {$fruit}s"; // 输出: I like apples
      
      // 表达式需要在字符串外计算
      $sum = $a + $b;
      echo "Sum: $sum";
      

字符串拼接

  • 使用点操作符 (.) 进行字符串拼接。

    $firstName = "Max";
    $lastName = "Firtman";
    $fullName = $firstName . " " . $lastName;
    echo $fullName; // 输出: Max Firtman
    
    

Heredoc 语法

  • 用途: 定义复杂的多行字符串,行为类似双引号字符串 (支持变量内插和转义序列)。

  • 格式:

    $identifier = "PHP";
    $heredocString = <<<EOT
    This is a multi-line string.
    Welcome to the world of $identifier!
    You can use "double quotes" and 'single quotes' freely.
    Newlines are preserved.
    EOT;
    // 注意:
    // 1. <<< 后面紧跟一个标识符 (如 EOT, HTML, SQL 等,通常大写)。
    // 2. 标识符后不能有任何空格或字符,直接换行。
    // 3. 字符串内容开始。
    // 4. 结束标识符必须单独一行,且顶格书写 (前面不能有任何空格或制表符),其后可以紧跟分号。
    
    
  • 示例:

    $title = "PHP Fundamentals";
    $message = <<<MSG
    Welcome to "$title".
    This course covers the basics of PHP.
    Enjoy your learning!
    MSG;
    echo $message;
    
    

Nowdoc 语法

  • 用途: 定义复杂的多行字符串,行为类似单引号字符串 (行为类似单引号字符串,完全按字面意思处理所有内容)。

  • 格式:

    $nowdocString = <<<'EOT'
    This is a multi-line string.
    Variables like $identifier will NOT be parsed.
    \n will also be treated literally.
    EOT;
    // 注意:
    // 1. <<< 后面紧跟一个用单引号包裹的标识符 (如 'EOT')。
    // 2. 其他规则与 Heredoc 类似 (标识符后直接换行,结束标识符单独一行顶格)。
    
    
  • 示例:

    $codeExample = <<<'CODE'
    <?php
        $name = "World";
        echo "Hello $name"; // This $name will be literal
    ?>
    CODE;
    echo $codeExample;
    
    

8-arrays

数组定义

PHP 中的数组实际上是一种有序映射 (ordered map)。映射是一种把 values 关联到 keys 的类型。

  • 1. 旧语法 array() 构造函数:

    $numbers = array(1, 2, 3, 4, 5);
    $colors = array("red", "green", "blue");
    
    
  • 2. 短数组语法 [] (PHP 5.4+,推荐使用):

    $numbers = [1, 2, 3, 4, 5];
    $colors = ["red", "green", "blue"];
    
    

数组类型

PHP 的数组非常灵活,可以作为多种数据结构使用:

  • 1. 索引数组 (Numeric Indexed Arrays):

    • 数组的键是默认的数字索引,从 0 开始。
    $fruits = ["Apple", "Banana", "Cherry"];
    // $fruits[0] is "Apple"
    // $fruits[1] is "Banana"
    // $fruits[2] is "Cherry"
    
    
  • 2. 关联数组 (Associative Arrays):

    • 数组的键是自定义的字符串 (或数字)。类似于其他语言中的字典 (dictionary)、哈希表 (hash map) 或对象 (object literal)。
    • 使用 => 操作符来分隔键和值。
    $person = [
        "name" => "Maximiliano",
        "age" => 30, // 假设年龄
        "city" => "Buenos Aires"
    ];
    // $person["name"] is "Maximiliano"
    // $person["age"] is 30
    
    
  • 3. 混合数组:

    • PHP 数组可以混合数字索引和关联键。
    $mixedArray = [
        "Apple", // 索引 0
        "type" => "fruit",
        "Banana", // 索引 1 (PHP 会自动分配下一个可用数字索引)
        "color" => "yellow"
    ];
    // echo $mixedArray[0]; // "Apple"
    // echo $mixedArray["type"]; // "fruit"
    // echo $mixedArray[1]; // "Banana"
    
    

    讲师示例中提到一个元素有键,另一个没有:

    $list = ['id' => 123, 10]; // 10 的键是 0,因为 'id' 不是数字
    $list_v2 = [0 => 'value0', 'id' => 123, 'value_next_numeric_idx']; // 'value_next_numeric_idx' 的键是 1
    
    

访问数组元素

  • 使用方括号 [] 和键来访问数组元素。

    $fruits = ["Apple", "Banana", "Cherry"];
    echo $fruits[1]; // 输出: Banana
    
    $person = ["name" => "Max", "age" => 30];
    echo $person["name"]; // 输出: Max
    
    

获取数组长度/元素数量

  • 使用内置的 count() 函数来获取数组中元素的数量。

    $numbers = [10, 20, 30, 40];
    echo count($numbers); // 输出: 4
    
    $person = ["name" => "Max", "city" => "BA"];
    echo count($person); // 输出: 2
    
    
  • 注意: 不是像某些语言那样的 $array->length$array->size 属性。

数组长度 vs. 字符串长度

  • 获取数组长度:count($array)
  • 获取字符串长度:strlen($string)
  • 这体现了 PHP 标准库中函数命名和功能上的一些不一致性。

9-loops

PHP 提供了多种循环结构来重复执行代码块。

1. for 循环

  • 传统的 C 风格 for 循环,通常用于已知迭代次数的场景。

  • 语法:

    for (initialization; condition; increment/decrement) {
        // code to be executed
    }
    
    
  • 示例: 遍历索引数组

    $colors = ["Red", "Green", "Blue"];
    $count = count($colors); // 获取数组长度,避免在每次循环中重复计算
    
    for ($i = 0; $i < $count; $i++) {
        echo $colors[$i] . "\n";
    }
    // 注意变量 $i 需要美元符号 $
    
    

2. foreach 循环

  • 专门用于遍历数组和对象的元素,是遍历数组最常用和推荐的方式。

  • 语法 1:遍历值

    foreach ($array as $value) {
        // code to be executed using $value
    }
    
    
  • 语法 2:遍历键和值

    foreach ($array as $key => $value) {
        // code to be executed using $key and $value
    }
    
    
  • 示例:

    $fruits = ["Apple", "Banana", "Cherry"];
    foreach ($fruits as $fruit) {
        echo $fruit . "\n";
    }
    
    $person = ["name" => "Max", "age" => 30];
    foreach ($person as $attribute => $data) {
        echo $attribute . ": " . $data . "\n";
    }
    
    
  • 注意顺序:$collection as $item,这与某些语言 (如 JavaScript 的 for...of for (item of collection)) 的元素和集合顺序相反。

3. while 循环

  • 只要指定的条件为真,while 循环就会重复执行代码块。

  • 语法:

    while (condition) {
        // code to be executed
    }
    // 条件两边的括号是必需的
    
    
  • 示例:

    $i = 0;
    while ($i < 3) {
        echo "Number: " . $i . "\n";
        $i++;
    }
    
    

4. do-while 循环

  • while 循环类似,但它会先执行一次代码块,然后再检查条件。这意味着代码块至少会执行一次。

  • 语法:

    do {
        // code to be executed
    } while (condition);
    
    
  • 示例:

    $i = 5;
    do {
        echo "Number (do-while): " . $i . "\n"; // 这行会执行一次
        $i++;
    } while ($i < 3); // 条件 (5 < 3) 为假,循环结束
    
    

控制结构的替代语法 (Alternative Syntax)

  • 当 PHP 代码嵌入到 HTML 中时,为了提高可读性,可以使用一种替代的控制结构语法。将花括号 {}替换为冒号 : 和相应的 end...; 语句。

  • 适用于 if, while, for, foreach, switch

  • foreach 替代语法示例:

    <?php $items = ["Book", "Pen", "Laptop"]; ?>
    
    <ul>
        <?php foreach ($items as $item): ?>
            <li><?php echo htmlspecialchars($item); ?></li>
        <?php endforeach; ?>
    </ul>
    
    
  • if 替代语法示例:

    <?php $isLoggedIn = true; ?>
    
    <?php if ($isLoggedIn): ?>
        <p>Welcome back!</p>
    <?php else: ?>
        <p>Please log in.</p>
    <?php endif; ?>
    
    
  • 这种语法在模板文件中尤其有用,因为它能更清晰地分离 HTML 结构和 PHP 逻辑。

10-functions

函数是可重复使用的代码块,用于执行特定任务。

函数定义与调用

  • 定义语法:

    function functionName(parameter1, parameter2, ...) {
        // code to be executed
        // optional: return value;
    }
    
    
    • 使用 function 关键字。
    • 函数名通常使用驼峰式 (camelCase) 或下划线式 (snake_case)。函数名大小写不敏感 (但不推荐依赖此特性)。
    • 参数 (parameters) 在括号内定义,以 $ 开头。
    • 函数体包含在花括号 {} 内。
    • 可以使用 return 语句返回一个值。如果函数没有 return 语句,或者 return; 不带值,则默认返回 null
  • 调用语法:

    functionName(argument1, argument2, ...);
    
    
    • 通过函数名后加括号来调用。
    • 传递的实际值称为参数 (arguments)。
  • 示例:

    // 定义一个简单的问候函数
    function greet($name) {
        echo "Hello, " . $name . "!";
    }
    
    greet("Max"); // 调用函数,输出: Hello, Max!
    
    // 定义一个带返回值的加法函数
    function add($num1, $num2) {
        $sum = $num1 + $num2;
        return $sum;
    }
    
    $result = add(5, 3);
    echo "\\nSum is: " . $result; // 输出: Sum is: 8
    
    

类型提示 (Type Hinting / Type Declarations)

  • PHP 7+ 引入了对函数参数和返回值的类型声明。这有助于提高代码的健壮性和可读性。

  • 如果传递的参数或返回的值与声明的类型不匹配 (且无法安全转换),PHP 会抛出一个 TypeError (除非开启了 strict_types=1,否则 PHP 会尝试类型转换)。

  • 参数类型提示:

    function calculateTotalPrice(float $price, int $quantity): float {
        return $price * $quantity;
    }
    
    // $total = calculateTotalPrice(10.5, 3); // 正确
    // $total = calculateTotalPrice("10.5", "3");
    // 在非严格模式下可能工作 (PHP尝试转换)
    // 在 declare(strict_types=1); 下会抛出 TypeError
    
    
  • 返回值类型提示: 在函数定义的括号后使用冒号 : 指定返回类型。

    function getGreeting(string $name): string {
        return "Hello, " . $name;
    }
    
    function logMessage(string $message): void {
        echo $message;
    }
    
    
  • 常用类型:

    • 标量类型: int, float, string, bool

    • 复合类型: array, object, iterable (可以是数组或实现了 Traversable 接口的对象)

    • 特殊类型:

      • callable:可调用的,如函数名字符串、数组 [$object, 'method'] 或闭包。
      • self:表示当前类 (在类方法中使用)。
      • parent:表示父类 (在类方法中使用)。
      • void:表示函数不返回值 (PHP 7.1+)。
      • mixed:表示任何类型 (PHP 8.0+)。
      • static:用于后期静态绑定中的返回类型 (PHP 8.0+)。
    • 可空类型 (Nullable Types): 在类型前加问号 ?,表示参数或返回值可以是指定类型或 null (PHP 7.1+)。

      function findUser(int $id): ?User { // User 是一个类名
          // ... 查找用户
          // return $user_object; or return null;
      }
      
      

默认参数值 (Default Argument Values)

  • 可以为函数参数指定默认值。如果调用函数时未提供该参数,则使用默认值。

  • 带默认值的参数应放在参数列表的末尾。

    function setPower(int $level, string $unit = "MW") {
        echo "Power set to: " . $level . " " . $unit;
    }
    
    setPower(100);        // 输出: Power set to: 100 MW
    setPower(50, "kW"); // 输出: Power set to: 50 kW
    
    

命名参数 (Named Arguments) (PHP 8.0+)

  • 允许在调用函数时通过参数名指定值,这样参数的顺序就不重要了,并且可以跳过有默认值的可选参数。

  • 语法: parameterName: value

    function createUser(string $username, string $email, bool $isActive = true, string $role = "subscriber") {
        echo "User: $username, Email: $email, Active: " . ($isActive ? 'Yes':'No') . ", Role: $role\\n";
    }
    
    // 使用命名参数
    createUser(username: "Maxi", email: "[email protected]");
    // 输出: User: Maxi, Email: [email protected], Active: Yes, Role: subscriber
    
    createUser(email: "[email protected]", username: "JaneD", role: "admin");
    // 输出: User: JaneD, Email: [email protected], Active: Yes, Role: admin (顺序不重要)
    
    createUser(username: "inactiveUser", email: "[email protected]", isActive: false);
    // 输出: User: inactiveUser, Email: [email protected], Active: No, Role: subscriber
    
    
  • 混合位置参数和命名参数:

    • 位置参数必须在命名参数之前。
    • 一旦使用了命名参数,其后的所有参数都必须是命名参数 (如果提供的话)。
    // function calculateTax(float $price, float $taxRate = 0.05, string $taxName = "VAT")
    calculateTax(3000, taxName: "State Tax"); // 正确:第一个是位置参数,后续是命名参数
    // calculateTax(price: 3000, 0.10); // 错误:命名参数后不能是位置参数
    
    

变量命名约定

  • PHP 中对于变量和函数参数的命名约定比较灵活,常见的有:
    • 驼峰式 (camelCase): $taxName, $calculateTotal
    • 下划线式/蛇形 (snake_case): $tax_name, $calculate_total
  • PHP 内置函数库本身就混合使用了这两种风格。
  • 最重要的是在你的项目或团队中保持一致性。
  • 烤串式 (kebab-case,如 $tax-name) 不允许用于 PHP 变量名,因为 会被解析为减号。

11-starting-a-php-development-server

PHP 与 Web 服务器

  • PHP 本身不是服务器: PHP 核心功能是执行脚本并输出结果。

  • 需要 Web 服务器: 为了通过 Web (浏览器) 访问 PHP 脚本,通常需要一个 Web 服务器 (如 Apache, Nginx)。

  • PHP 内置开发服务器 (Development Server):

    • PHP 提供了一个仅用于开发目的的内置 Web 服务器。

    • 启动命令: 在终端中,进入你的项目文件夹,运行:

      php -S localhost:4000
      # 或者 php -S 0.0.0.0:4000 (允许其他设备在同一网络中访问)
      # -S (大写 S) 代表 Serve
      # localhost 是主机名
      # 4000 是端口号 (可以选择其他未被占用的端口)
      
      
    • 警告: 此服务器不适用于生产环境。生产环境应使用 Apache, Nginx 等更健壮的服务器。

使用内置服务器

  • 启动服务器后,可以通过浏览器访问 http://localhost:4000

  • 默认文件:

    • 如果服务器在根目录找不到默认文件,通常会显示 "Not Found" 或类似错误。
    • Web 服务器通常会寻找名为 index.htmlindex.php 的文件作为默认入口。
  • 提供静态文件: PHP 内置服务器也可以提供静态文件 (如 .html, .css, .js 文件)。

    • 创建一个 index.html 文件,内容如下 (可使用 Emmet 快速生成): 刷新浏览器,应该能看到 HTML 内容。

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>PHP Test</title>
        </head>
        <body>
          <h1>Hello from HTML!</h1>
        </body>
      </html>
      
      ```
      

.php 文件与 HTML

  • index.html 重命名为 index.php
  • 如果 index.php 文件内容仍然是纯 HTML,浏览器依然会正确显示它。
    • 这表明 PHP 文件默认可以输出 HTML。
  • 嵌入 PHP 代码:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>PHP Page</title>
    </head>
    <body>
        <h1>Welcome!</h1>
        <p>
            <?php
                echo "This message is from PHP! The current date is " . date('Y-m-d');
            ?>
        </p>
    </body>
    </html>
    

服务器端渲染 (Server-Side Rendering - SSR)

  • 当浏览器请求一个 .php 文件时:
    1. Web 服务器将请求传递给 PHP 解释器。
    2. PHP 解释器执行文件中的 PHP 代码。
    3. PHP 代码生成的任何输出 (通常是 HTML) 会与文件中的静态 HTML 结合。
    4. 最终生成的完整 HTML 页面被发送回浏览器。
  • 查看页面源代码: 在浏览器中查看页面源代码,看不到 PHP 代码本身,只能看到 PHP 执行后生成的最终 HTML。
  • 这与一些现代 Web 框架中的模板引擎 (如 Express.js 的 Pug/EJS,Django 的模板) 的概念类似。PHP 本身就具备模板能力。

HTTP 请求与响应

  • 浏览器与服务器之间的通信遵循 HTTP 协议。
  • 请求 (Request): 浏览器向服务器发送请求 (包括 URL、方法如 GET/POST、头部信息等)。
  • 响应 (Response): 服务器向浏览器返回响应 (包括状态码如 200 OK/404 Not Found、头部信息、响应体内容等)。
  • 可以使用浏览器开发者工具的 "Network" (网络) 标签页查看这些请求和响应的详细信息。

PHP 的请求处理模式:每个 URL 对应一个脚本

  • 基本模式: 默认情况下,PHP 的工作方式是每个 URL 直接映射到一个服务器上的 .php 文件。
    • 请求 http://localhost:4000/about.php 会执行服务器上名为 about.php 的文件。
  • 路由 (Routing):
    • 现代 Web 应用通常使用路由系统,使得 URL 更加友好 (例如 http://example.com/users/profile 而不是 http://example.com/users_profile_script.php)。
    • 这种“友好 URL”或路由功能不是 PHP 语言本身提供的,而是通过:
      • Web 服务器配置: 例如 Apache 的 mod_rewrite 或 Nginx 的配置,将友好 URL 内部重写或代理到实际的 .php 脚本。
      • PHP 框架: 像 Laravel, Symfony 这样的框架内置了强大的路由组件。
  • 移除 .php 扩展名: 从 URL 中移除 .php 扩展名是 Web 服务器配置的任务,不是 PHP 本身的问题。

12-creating-a-form

项目目标:加密货币转换器

  • 创建一个简单的 Web 页面,允许用户输入加密货币数量和类型 (如比特币),然后将其转换为美元。
  • 将使用一个公开的免费 API 来获取实时汇率。
    • API 端点示例: https://api.frontendmasters.com/api/crypto/btc (获取比特币对美元的汇率)

步骤 1:设置项目和启动开发服务器

  1. 创建项目文件夹 (例如 crypto-converter)。

  2. 在项目文件夹中创建一个 index.php 文件。

  3. 在项目文件夹的根目录下打开终端,启动 PHP 内置开发服务器:

    php -S localhost:4000
    
    

步骤 2:创建 HTML 表单 (index.php)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Crypto Masters</title>
    <!-- 稍后可以添加 CSS -->
</head>
<body>
    <h1>Crypto Converter</h1>

    <form action="convert.php" method="post">
        <p>
            <label for="amount">Amount:</label>
            <input type="number" id="amount" name="amount" step="any" required>
        </p>
        <p>
            <label for="crypto">Crypto:</label>
            <select id="crypto" name="crypto">
                <option value="btc">Bitcoin (BTC)</option>
                <option value="eth">Ethereum (ETH)</option>
                <!-- 可以添加更多加密货币选项,如 sol (Solana) -->
            </select>
        </p>
        <p>
            <button type="submit">Convert</button>
            <!-- 或者使用 <input type="submit" value="Convert"> -->
        </p>
    </form>

</body>
</html>

  • HTML 结构:
    • 一个简单的 HTML 页面。
    • 一个 <h1> 标题。
    • 一个 <form> 元素:
      • action="convert.php":表单数据将提交到 convert.php 文件进行处理。
      • method="post":表单数据将通过 HTTP POST 方法发送 (稍后会详细讨论 GET vs POST)。
    • 输入字段:
      • Amount (数量):
        • <label for="amount">:关联标签。
        • <input type="number" id="amount" name="amount" step="any" required>
          • type="number":数字输入框。
          • id="amount":用于 <label>for 属性和 CSS/JS。
          • name="amount"非常重要,这是服务器端用来识别该字段数据的键。
          • step="any":允许输入小数。
          • required:HTML5 表单验证,表示此字段必填。
      • Crypto (加密货币类型):
        • <label for="crypto">:关联标签。
        • <select id="crypto" name="crypto">:下拉选择框。
          • id="crypto"
          • name="crypto":服务器端用来识别所选加密货币的键。
        • <option value="btc">Bitcoin (BTC)</option>
          • value="btc":当此选项被选中时,发送到服务器的值。
      • 提交按钮:
        • <button type="submit">Convert</button>:点击后提交表单。
        • 或者 <input type="submit" value="Convert">,效果相同。
  • HTML 简洁性说明:
    • 讲师提到,严格来说,HTML5 中 <head>, <body>, <html> 标签在某些情况下是可选的,浏览器会自动补全。但为了结构清晰和最佳实践,通常都会包含它们。
    • 本次示例中,为了简洁和聚焦 PHP,可能会省略一些标签。
  • 当前状态:
    • 此时 index.php 仅包含 HTML,没有 PHP 代码。
    • 在浏览器中访问 http://localhost:4000,应该能看到表单。
    • 提交表单会尝试导航到 convert.php,但因为该文件尚不存在,会看到 404 错误。

13-superglobal-variables-url-parameters

表单提交与数据传递

  • action 属性: <form action="convert.php"> 指定了表单数据提交的目标 URL。
  • method 属性: 决定数据如何发送。
    • 如果未指定 method,或者 method="get" (默认),数据会附加到 URL 的查询字符串中。
    • 如果 method="post",数据会在 HTTP 请求体中发送。

HTTP GET 方法与 URL 参数

  • 默认行为: 如果表单的 method 未指定或为 get,浏览器会将表单数据编码为 URL 参数(也称为查询字符串),附加到 action URL 之后。

  • name 属性的重要性:

    • HTML 表单控件 (如 <input>, <select>) 的 name 属性值将作为 URL 参数的键 (key)

    • 控件的值将作为 URL 参数的值 (value)

    • 示例: 如果 index.php 中的表单代码是: 提交后,浏览器 URL 会变成类似:http://localhost:4000/convert.php?amount=3&crypto=btc

      <form action="convert.php" method="get">
        <!-- 注意这里暂时改为 get 以演示 -->
        <input type="number" name="amount" value="3" />
        <select name="crypto">
          <option value="btc" selected>Bitcoin</option>
        </select>
        <button type="submit">Convert</button>
      </form>
      
      ``` - `?` 分隔主 URL 和查询字符串。 - `&` 分隔多个参数。 - `amount=3` 和
      `crypto=btc` 是键值对。
      

PHP 超全局变量 (Superglobal Variables)

  • PHP 提供了一系列预定义的、始终可用的数组变量,称为超全局变量。它们可以在脚本的任何作用域内访问,无需 global 关键字。
  • 这些变量通常以 $_ (美元符号加下划线) 开头。
  • $_GET
    • 一个关联数组,包含了通过 HTTP GET 方法传递给当前脚本的变量。
    • 键是 URL 参数的名称,值是 URL 参数的值。
    • 例如,对于 URL convert.php?amount=3&crypto=btc
      • $_GET['amount'] 的值是字符串 "3"
      • $_GET['crypto'] 的值是字符串 "btc"

convert.php 中接收 GET 参数

  1. 创建 convert.php 文件。

  2. convert.php 中,可以使用 $_GET 来访问通过 URL 传递过来的数据。

    <?php
    // convert.php
    
    // 检查参数是否存在 (很重要,避免未定义索引的警告/错误)
    if (isset($_GET['amount']) && isset($_GET['crypto'])) {
        $amount = $_GET['amount']; // 获取 'amount' 参数的值
        $crypto_code = $_GET['crypto']; // 获取 'crypto' 参数的值
    
        // 输出,进行服务器端渲染
        echo "<!DOCTYPE html><html><head><title>Conversion Results</title></head><body>";
        echo "<h1>Conversion Results</h1>";
        echo "<p>You want to convert $amount $crypto_code.</p>";
        // 实际转换逻辑将在这里添加
        echo "</body></html>";
    
    } else {
        // 如果参数缺失,显示错误或提示
        echo "<!DOCTYPE html><html><head><title>Error</title></head><body>";
        echo "<h1>Error</h1>";
        echo "<p>Required parameters (amount and crypto) are missing.</p>";
        echo "<a href='index.php'>Try again</a>";
        echo "</body></html>";
    }
    ?>
    
    
  • 代码解释:
    • isset($_GET['key']):检查 $_GET 数组中是否存在名为 'key' 的索引。这是避免因访问不存在的数组键而产生 "Undefined array key" 警告/错误的好习惯。
    • 将获取到的值赋给局部变量 $amount$crypto_code
    • 使用 echo 将包含这些动态值的 HTML 输出到浏览器。
  • 服务器端渲染体现:
    • PHP 代码在服务器上执行。
    • $amount$crypto_code 的值被嵌入到生成的 HTML 字符串中。
    • 浏览器接收到的是最终的 HTML,其中动态数据已经填充完毕。
    • 查看浏览器中的页面源代码,会看到类似 <p>You want to convert 3 btc.</p>,而不是 PHP 代码。
  • 直接通过 URL 访问:
    • 由于数据是通过 URL 参数传递的,你可以直接在浏览器地址栏中构造 URL 来测试 convert.php,例如: http://localhost:4000/convert.php?amount=10&crypto=eth 这会直接执行脚本并显示结果,无需通过 index.php 的表单。

14-post-parameters

HTTP POST 方法

  • 目的: 当表单使用 method="post" 时,表单数据会包含在 HTTP 请求的主体 (body) 中发送,而不是附加到 URL 上。
  • 优点:
    • 更安全 (相对 GET): 数据不会显示在 URL 中,不会被浏览器历史记录、服务器日志(以明文形式)等轻易暴露。但不意味着它是加密的,仍需 HTTPS 来保证传输安全。
    • 无长度限制: URL 长度有限制,而 POST 请求体可以发送大量数据(如文件上传)。
    • 非幂等性: POST 请求通常用于创建或修改资源,重复提交可能会产生不同结果。

修改表单以使用 POST

index.php 中,将 <form> 标签的 method 属性改为 post

<form action="convert.php" method="post">
  <!-- ... 其他表单元素保持不变 ... -->
</form>

PHP 超全局变量 $_POST

  • $_POST
    • 一个关联数组,包含了通过 HTTP POST 方法传递给当前脚本的变量。
    • 键是表单控件的 name 属性值,值是控件提交的数据。

convert.php 中接收 POST 参数

修改 convert.php 以使用 $_POST 而不是 $_GET

<?php
// convert.php

// 检查 POST 参数是否存在
if (isset($_POST['amount']) && isset($_POST['crypto'])) {
    $amount = $_POST['amount'];
    $crypto_code = $_POST['crypto'];

    // 输出结果 (与之前 GET 示例类似,但现在从 $_POST 获取数据)
    echo "<!DOCTYPE html><html><head><title>Conversion Results</title></head><body>";
    echo "<h1>Conversion Results (POST)</h1>";
    echo "<p>You want to convert $amount $crypto_code.</p>";
    // 实际转换逻辑
    echo "</body></html>";

} else {
    // 如果参数缺失
    echo "<!DOCTYPE html><html><head><title>Error</title></head><body>";
    echo "<h1>Error</h1>";
    echo "<p>Required POST parameters (amount and crypto) are missing.</p>";
    echo "<a href='index.php'>Try again</a>";
    echo "</body></html>";
}
?>

  • 行为变化:
    • 现在提交表单后,URL 将保持为 http://localhost:4000/convert.php,参数不会显示在地址栏。
    • 如果直接在浏览器中访问 http://localhost:4000/convert.php (不通过表单提交),$_POST 数组将为空,会触发 else 块的错误提示。

处理参数缺失的情况 (健壮性)

  • 使用 isset() 检查参数是否存在非常重要。
  • 可以将逻辑包裹在 if (isset(...)) { ... } else { ... } 结构中。

PHP 代码与 HTML 混合的替代语法 (再次提及)

讲师演示了如何使用替代语法来避免大量的 echo HTML 字符串,使得 PHP 和 HTML 的混合更易读:

<?php
// convert.php

if (isset($_POST['amount']) && isset($_POST['crypto'])) {
    $amount = $_POST['amount'];
    $crypto_code = $_POST['crypto'];
?>
    <!DOCTYPE html>
    <html>
    <head><title>Conversion Results (POST)</title></head>
    <body>
        <h1>Conversion Results (POST)</h1>
        <p>You want to convert <?php echo htmlspecialchars($amount); ?> <?php echo htmlspecialchars($crypto_code); ?>.</p>
        <!-- 实际转换逻辑 -->
    </body>
    </html>
<?php
} else {
?>
    <!DOCTYPE html>
    <html>
    <head><title>Error</title></head>
    <body>
        <h1>Error</h1>
        <p>Required POST parameters (amount and crypto) are missing.</p>
        <a href='index.php'>Try again</a>
    </body>
    </html>
<?php
} // endif; // 如果使用 if (...): ... endif; 替代语法
?>

  • 注意:
    • 在 HTML 中输出变量时,使用 htmlspecialchars() 是一个好习惯,可以防止 XSS (跨站脚本) 攻击。
    • 讲师也提到了 if (...): ... else: ... endif; 这种替代语法的可读性问题,尤其是在嵌套较多时。对于简单情况,直接 echo 或如上所示的 PHP 块切换可能更清晰。

其他超全局变量简介

  • $_REQUEST
    • 一个关联数组,默认情况下包含了 $_GET$_POST$_COOKIE 的内容。
    • PHP 处理这些输入的顺序由 php.ini 中的 request_ordervariables_order 配置决定。
    • 注意: 通常不推荐直接使用 $_REQUEST,因为它可能引入不确定性 (数据来源不明确)。明确使用 $_GET$_POST 更安全。
  • $_COOKIE 包含通过 HTTP Cookie 传递给当前脚本的变量。
  • $_FILES 包含通过 HTTP POST 方法上传到脚本的文件信息。
  • $_SESSION 包含当前脚本的会话变量。
  • $_SERVER 包含诸如头信息 (headers)、路径 (paths)、脚本位置 (script locations) 等信息。服务器创建此数组。
  • $_ENV 包含通过环境方法传递给当前脚本的变量。

15-phpinfo-server-variables

phpinfo() 函数

  • 用途: phpinfo() 是一个非常有用的内置 PHP 函数,用于输出关于 PHP 当前状态的大量信息。

  • 使用方法:

    1. 创建一个新的 PHP 文件 (例如 info.php)。

    2. 在该文件中写入以下代码:

      <?php
      phpinfo();
      ?>
      
      
    3. 通过浏览器访问此文件 (例如 http://localhost:4000/info.php)。

  • 输出内容:

    • PHP 版本。
    • 服务器信息和环境 (Server API, Virtual Directory Support, Configuration File (php.ini) Path 等)。
    • PHP 核心配置指令的本地值和主值 (Local Value, Master Value)。
    • 已加载的 PHP 扩展 (如 MySQLi, PDO, JSON, XML 等) 及其配置。
    • HTTP 头信息。
    • PHP 许可证信息。
    • PHP 环境变量 ($_ENV)。
    • PHP 变量 ($_GET, $_POST, $_COOKIE, $_SERVER 等)。
  • 调试价值:

    • 快速检查 PHP 安装是否正确以及配置是否符合预期。
    • 查看哪些扩展已启用。
    • 了解服务器环境。
    • 安全警告: phpinfo() 会泄露大量敏感信息,绝不应该在生产服务器上公开访问。开发和调试完成后应立即移除或保护此文件。

$_SERVER 超全局变量

  • $_SERVER 是一个包含了由 Web 服务器提供的信息的数组,例如头信息、路径和脚本位置。
  • 这些条目的具体内容和可用性因服务器而异。
  • 常用 $_SERVER 键示例:
    • $_SERVER['DOCUMENT_ROOT']:当前脚本执行的文档根目录。
    • $_SERVER['REMOTE_ADDR']:正在浏览当前页面的用户的 IP 地址。
    • $_SERVER['REQUEST_METHOD']:访问页面使用的请求方法 (例如 'GET', 'POST', 'PUT')。
    • $_SERVER['SCRIPT_FILENAME']:当前执行脚本的绝对路径。
    • $_SERVER['HTTP_HOST']:当前请求的 Host 头信息。
    • $_SERVER['HTTP_USER_AGENT']:当前请求的用户代理 (浏览器) 信息。
    • $_SERVER['REQUEST_URI']:访问此页面所需的 URI,例如 /index.html
    • $_SERVER['PHP_SELF']:当前执行脚本的文件名,相对于文档根目录。
    • $_SERVER['SERVER_SOFTWARE']:服务器标识字符串,在响应头中给出。
    • $_SERVER['SERVER_PORT']:服务器正在使用的端口。

var_dump() 函数

  • 用途: var_dump() 用于打印变量的相关信息,包括其类型和值。数组和对象会递归展开其结构。

  • echo 的区别:

    • echo 只能输出简单类型 (如字符串、数字)。尝试 echo 一个数组或对象通常会导致 "Array to string conversion" 或类似错误,并只输出 "Array" 或 "Object"。
    • var_dump() 可以详细显示复杂类型的内部结构。
  • 调试: 非常适合在开发过程中调试变量内容。

  • 示例:convert.php 中查看 $_SERVER 的内容: 输出会比较冗长,但能清晰地看到 $_SERVER 中可用的所有键和值。

    <?php
      // ... (前面的代码) ...
      var_dump($_SERVER); // 这会输出 $_SERVER 数组的详细内容到浏览器
      // ... (后续的代码) ...
      ?>
    
    
  • 注意: var_dump() 的输出通常是给开发者看的,不适合直接展示给最终用户。在生产代码中应移除或用更友好的方式处理调试信息。

16-classes-methods-constructors

面向对象编程 (OOP) 简介

  • PHP 支持面向对象编程范式。
  • 可以将代码组织成类 (classes) 和对象 (objects),以实现更好的结构、可重用性和可维护性。

创建类 (Classes)

  • 语法:

    <?php
    class ClassName {
        // Properties (member variables)
        // Methods (member functions)
    }
    ?>
    
    
    • 使用 class 关键字。
    • 类名通常使用帕斯卡命名法 (PascalCase),例如 CryptoConverter
    • 类体包含在花括号 {} 内。
    • 文件名与类名: PHP 不强制文件名必须与类名相同 (不像 Java)。但为了清晰和遵循约定,通常建议文件名与它包含的主要类名匹配 (例如,CryptoConverter.php 文件包含 CryptoConverter 类)。

属性 (Properties)

  • 属性是类内部的变量。

  • 定义:

    class MyClass {
        public $publicProperty = "I am public";
        protected $protectedProperty = "I am protected";
        private $privateProperty = "I am private";
    
        public string $typedProperty; // 可以有类型提示 (PHP 7.4+)
        public ?string $nullableTypedProperty = null; // 可空类型提示
    }
    
    
    • 访问修饰符 (Visibility Modifiers):
      • public:属性或方法可以在任何地方被访问 (类内部、子类、类外部)。
      • protected:属性或方法只能在类本身及其子类中被访问。
      • private:属性或方法只能在定义它的类本身中被访问。
      • 如果省略访问修饰符,属性默认为 public (但明确写出是好习惯)。
    • 类型提示 (PHP 7.4+): 可以为属性声明类型。
    • 变量名: 属性名以 $ 开头,例如 $currencyCode

方法 (Methods)

  • 方法是类内部的函数。

  • 定义:

    class MyClass {
        public function myPublicMethod(string $param): string {
            // ...
            return "Result";
        }
    
        protected function myProtectedMethod() {
            // ...
        }
    
        private function myPrivateMethod() {
            // ...
        }
    }
    
    
    • 使用 function 关键字定义方法。
    • 访问修饰符 (public, protected, private) 的规则与属性相同。如果省略,方法默认为 public
    • 可以有参数类型提示和返回值类型提示。

构造函数 (Constructor)

  • 构造函数是一种特殊的方法,当使用 new 关键字创建类的新实例 (对象) 时自动调用。

  • 通常用于初始化对象的属性。

  • PHP 中的构造函数名: __construct (两个下划线开头)。

    class User {
        public string $username;
        public string $email;
    
        public function __construct(string $username, string $email) {
            $this->username = $username; // $this 指向当前对象实例
            $this->email = $email;
            echo "User object for {$this->username} created.\n";
        }
    }
    
    $user1 = new User("MaxF", "[email protected]"); // 调用构造函数
    
    
  • $this 关键字: 在类的方法内部,$this 关键字用于引用当前对象实例。

  • 访问属性和方法: 使用对象操作符 > (细箭头 / thin arrow) 来访问对象的属性和方法。

    • $this->propertyName
    • $this->methodName()
    • 注意:访问属性时,属性名不带 $ 符号 (例如 $this->currencyCode 而不是 $this->$currencyCode)。

析构函数 (Destructor)

  • 析构函数在对象即将被销毁时 (例如,当没有更多对它的引用或脚本结束时由垃圾回收器处理) 自动调用。

  • PHP 中的析构函数名: __destruct (两个下划线开头)。

    class FileHandler {
        private $fileResource;
        private $filename;
    
        public function __construct(string $filename) {
            $this->filename = $filename;
            $this->fileResource = fopen($filename, 'w');
            echo "File {$filename} opened.\n";
        }
    
        public function write(string $data) {
            fwrite($this->fileResource, $data);
        }
    
        public function __destruct() {
            if ($this->fileResource) {
                fclose($this->fileResource);
                echo "File {$this->filename} closed in destructor.\n";
            }
        }
    }
    
    
  • 用途: 通常用于执行清理操作,如关闭文件句柄、释放数据库连接等。

  • 注意: PHP 有垃圾回收机制 (garbage collector),大多数情况下不需要手动管理内存。析构函数主要用于资源管理。

构造函数属性提升 (Constructor Property Promotion) (PHP 8.0+)

  • PHP 8.0 引入了一种更简洁的方式来声明和初始化类属性,尤其是在构造函数中。

  • 可以在构造函数的参数列表中直接声明属性的访问修饰符和类型,PHP 会自动创建同名属性并赋值。

  • 示例: 这大大减少了模板代码。

      // 旧方法
      class Point_Old {
      public float $x;
      public float $y;
    
      public function __construct(float $x, float $y) {
              $this->x = $x;
              $this->y = $y;
          }
      }
    
        // 使用构造函数属性提升 (PHP 8.0+)
      class Point_New {
          public function __construct(
              public float $x, // 自动创建 public float $x; 并赋值
              public float $y  // 自动创建 public float $y; 并赋值
          ) {
              // 构造函数体可以为空,或包含其他逻辑
          }
      }
    
      $p = new Point_New(10.5, 20.3);
      echo $p->x; // 输出 10.5
    
    

创建和使用对象

  • 使用 new 关键字创建类的实例 (对象)。

  • 使用对象操作符 > 访问对象的公共属性和方法。

    // CryptoConverter.php
    <?php
    class CryptoConverter {
        public function __construct(public string $currencyCode = 'usd') {
            // 属性提升
        }
    
        public function convert(float $amount, string $fromCryptoSymbol): float {
            // 假设的转换逻辑
            $rate = 0; // 获取汇率的逻辑会在这里
            if ($fromCryptoSymbol === 'btc' && $this->currencyCode === 'usd') {
                $rate = 70000; // 假设的汇率
            } elseif ($fromCryptoSymbol === 'eth' && $this->currencyCode === 'usd') {
                $rate = 3500;  // 假设的汇率
            }
            return $amount * $rate;
        }
    }
    ?>
    
    // 在另一个文件 (如 convert.php) 中使用
    <?php
    require_once 'CryptoConverter.php'; // 确保类定义被加载
    
    $converter = new CryptoConverter('usd'); // 创建对象,currencyCode 设为 'usd'
    $btcAmount = 2;
    $usdValue = $converter->convert($btcAmount, 'btc');
    
    echo "$btcAmount BTC is approximately $usdValue USD.";
    ?>
    
    

17-include-require

代码组织与文件分离

  • 当项目变大时,将所有代码放在一个文件中是不切实际的。
  • PHP 允许将代码分散到多个文件中,例如将类定义放在单独的文件中。

问题:文件隔离

  • 默认情况下,一个 PHP 文件并不知道其他 PHP 文件的存在或内容,即使它们在同一个文件夹中。
  • 如果一个文件 (例如 convert.php) 尝试使用在另一个文件 (例如 CryptoConverter.php) 中定义的类,而没有明确加载该类的定义,PHP 会报错 (例如 "Class 'CryptoConverter' not found")。

加载外部文件

PHP 提供了几种语言结构来包含和执行其他文件的内容:

  • 1. include
    • 语法: include 'path/to/file.php';include('path/to/file.php'); (括号可选,因为它是一个语言结构,而非函数)。
    • 行为:
      • include 会获取指定文件中的所有文本/代码,并将其复制到 include 语句所在的位置,然后 PHP 解释器会执行这些代码。
      • 如果文件未找到,include 会产生一个 警告 (warning),但脚本会继续执行
    • 返回值: 如果包含成功,include 返回 1。如果文件在 include 语句中包含 return 语句,则 include 返回该 return 语句的值。
  • 2. require
    • 语法: require 'path/to/file.php';require('path/to/file.php');
    • 行为:
      • include 类似,它也会包含并执行指定文件的内容。
      • 主要区别: 如果文件未找到,require 会产生一个 致命错误 (fatal error) (E_COMPILE_ERROR),并停止脚本的执行
    • 用途: 当被包含的文件对于脚本的正确运行至关重要时 (例如包含核心库、配置文件或必要的类定义),应使用 require
  • 3. include_once
    • 语法: include_once 'path/to/file.php';
    • 行为:
      • include 类似,但它会检查该文件是否已经被包含过。
      • 如果文件已经被包含,include_once不会再次包含它
      • 这有助于防止因多次包含同一个文件而导致的函数或类重定义错误。
    • 性能:include 稍慢,因为它需要进行额外的检查。
  • 4. require_once
    • 语法: require_once 'path/to/file.php';
    • 行为:
      • 结合了 requireinclude_once 的特性。
      • 如果文件未找到,产生致命错误并停止脚本。
      • 如果文件已包含,则不再包含。
    • 用途: 这是包含关键文件并确保它们只被包含一次的最常用和推荐的方法。

使用示例

假设我们有 CryptoConverter.php (包含 CryptoConverter 类定义) 和 convert.php (需要使用该类)。

convert.php 的顶部:

<?php
// convert.php

// 推荐使用 require_once 来加载类定义
require_once 'classes/CryptoConverter.php'; // 假设 CryptoConverter.php 在 'classes' 子目录中

// 现在可以安全地使用 CryptoConverter 类
if (isset($_POST['amount']) && isset($_POST['crypto'])) {
    $amount = (float)$_POST['amount']; // 类型转换
    $crypto_code = $_POST['crypto'];

    $converter = new CryptoConverter('usd'); // 创建对象
    $result = $converter->convert($amount, $crypto_code);

    // ... (输出结果) ...
} else {
    // ... (处理参数缺失) ...
}
?>

包含路径

  • 可以使用相对路径 (相对于当前执行脚本的路径) 或绝对路径。
  • . 代表当前目录。
  • .. 代表上一级目录。
  • 示例:
    • include 'lib/utils.php'; (在当前目录的 lib 子目录中)
    • include '../config.php'; (在上一级目录中)

模块化与依赖管理

  • PHP 没有内置的模块系统 (如 ES Modules): 不能像 JavaScript 中那样 import { SpecificClass } from './file.js';include/require 会包含整个文件的内容。

  • 副作用: 如果被包含的文件不仅定义了类/函数,还执行了代码或输出了 HTML,这些都会在包含时发生。

  • 管理多个包含:

    • “主包含文件”模式: 创建一个中心文件 (例如 includes.phpbootstrap.php),该文件负责 require_once所有必要的类和库文件。然后,在其他需要这些功能的脚本中,只需 require_once 这个中心文件即可。

      // includes.php
      <?php
      require_once 'classes/Database.php';
      require_once 'classes/User.php';
      require_once 'classes/CryptoConverter.php';
      // ... 其他所有类和库
      ?>
      
      // convert.php
      <?php
      require_once 'includes.php'; // 只需要包含这一个文件
      // ... 现在所有在 includes.php 中加载的类都可用了
      ?>
      
      
    • 自动加载 (Autoloading): 更高级的解决方案。PHP 提供了 spl_autoload_register() 函数,允许你定义一个或多个函数,当代码尝试使用尚未定义的类时,这些函数会被自动调用。这些函数通常根据类名来推断文件路径并 require 它。这是现代 PHP 框架 (如 Composer 管理的依赖) 的核心机制。 (讲师提到稍后会讲到 autoload)

include 文件夹权限

  • Web 服务器通常配置为不允许直接通过 URL 访问某些文件夹中的 .php 文件 (例如包含类库或配置文件的文件夹),即使这些文件被其他 PHP 脚本 include
  • 这是 Web 服务器的配置问题,不是 PHP 本身的问题。例如,可以将类文件放在文档根目录之外,或者使用 .htaccess (Apache) 或 Nginx 配置来阻止直接访问。

18-loading-data-from-api-parsing-json

目标:在 CryptoConverter 类中实现转换逻辑

  1. 从外部 API 获取加密货币汇率。
  2. 解析 API 返回的 JSON 数据。
  3. 使用汇率计算转换后的金额。

从网络获取数据:file_get_contents()

  • file_get_contents(string $filename, ...)

    • 这是一个多功能的函数,通常用于读取文件内容到字符串。
    • 关键特性: 如果 $filename 参数是一个以协议 (如 http://https://) 开头的 URL,file_get_contents() 会尝试像浏览器一样访问该 URL 并获取其内容。
    • 返回值: 成功时返回文件/URL 的内容 (字符串),失败时返回 false
  • 示例: 获取比特币对美元的汇率

    // CryptoConverter.php
    class CryptoConverter {
        public string $baseApiUrl = '<https://api.frontendmasters.com/api/crypto/>';
    
        public function __construct(public string $targetCurrency = 'usd') {
            // ...
        }
    
        public function getRate(string $cryptoSymbol): float|false {
            $apiUrl = $this->baseApiUrl . strtolower($cryptoSymbol); // 例如: .../api/crypto/btc
    
            // 错误处理: 确保 URL 有效,或者使用 try-catch 进行更复杂的错误处理
            $jsonData = @file_get_contents($apiUrl); // 使用 @ 抑制潜在的 warning,然后检查 false
    
            if ($jsonData === false) {
                // API 请求失败,可以记录错误或抛出异常
                return false;
            }
    
            // ... 解析 JSON 的逻辑 ...
            return 0.0; // 占位符
        }
    
        public function convert(float $amount, string $fromCryptoSymbol): float|false {
            $rate = $this->getRate($fromCryptoSymbol);
            if ($rate === false) {
                return false; // 传播错误
            }
            // ... 解析并使用 rate ...
            return 0.0; // 占位符
        }
    }
    
    
    • 注意文件名和类名: 讲师在演示中遇到了文件名 (converter.php) 和类名 (CryptoConverter) 不匹配导致 require_once 后类未找到的问题。最佳实践是保持一致。
    • 创建 classes.php 进行统一包含: 讲师演示了创建一个 classes.php 文件,该文件 require_once 了所有类文件,然后在主脚本中只需要 require_once 'classes.php';

解析 JSON 数据:json_decode()

  • API 通常以 JSON (JavaScript Object Notation) 格式返回数据。

  • json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed

    • 将 JSON 编码的字符串转换为 PHP 变量。
    • $json 要解码的 JSON 字符串。
    • $associative (可选):
      • 当为 true 时,返回的对象将被转换为关联数组。
      • 当为 false (默认) 或省略时,返回的对象将是 PHP stdClass 对象。
    • 返回值:
      • 成功时返回转换后的 PHP 值 (对象、数组、字符串、数字、布尔值或 null)。
      • 如果 JSON 无法解码,或者编码数据深度超过递归限制,则返回 null。可以使用 json_last_error()json_last_error_msg() 来获取解码错误信息。
  • API 响应示例 (https://api.frontendmasters.com/api/crypto/btc):

    {
      "name": "Bitcoin",
      "symbol": "BTC",
      "price_usd": 70000.12, // 假设价格
      "last": 70000.12 // 我们将使用这个值
      // ... 其他数据
    }
    
  • getRate 方法中解析 JSON:

    // CryptoConverter.php (续)
    public function getRate(string $cryptoSymbol): float|false {
        $apiUrl = $this->baseApiUrl . strtolower($cryptoSymbol);
        $jsonData = @file_get_contents($apiUrl);
    
        if ($jsonData === false) {
            return false;
        }
    
        // 解析 JSON 为 PHP 对象 (默认行为)
        $dataObject = json_decode($jsonData);
    
        // 错误处理: 检查 JSON 是否成功解析
        if ($dataObject === null && json_last_error() !== JSON_ERROR_NONE) {
            // JSON 解析失败,可以记录错误 json_last_error_msg()
            return false;
        }
    
        // 访问对象属性 (假设 API 返回了 'last' 字段作为汇率)
        // 确保属性存在
        if (isset($dataObject->last) && is_numeric($dataObject->last)) {
            return (float)$dataObject->last;
        } else {
            // "last" 属性不存在或不是数字
            return false;
        }
    }
    
    
    • 访问对象属性: 如果 json_decode 返回一个对象,使用 > 操作符访问其属性 (例如 $dataObject->last)。
    • 解析为关联数组: 如果使用 json_decode($jsonData, true),则返回的是关联数组,使用 [] 访问元素 (例如 $dataArray['last'])。
    • 讲师在演示中遇到了 Cannot use object of type stdClass as array 的错误,这是因为他尝试用数组语法 [] 访问一个对象,后来改用了对象语法 >

完成转换逻辑

// CryptoConverter.php (续)
public function convert(float $amount, string $fromCryptoSymbol): float|false {
    $rate = $this->getRate($fromCryptoSymbol);

    if ($rate === false) {
        return false; // 如果获取汇率失败,则转换失败
    }

    return $amount * $rate;
}

更新 convert.php 以使用新的转换器

<?php
// convert.php

require_once 'classes.php'; // 或者直接 require_once 'classes/CryptoConverter.php';

if (isset($_POST['amount']) && isset($_POST['crypto'])) {
    $amount = (float)$_POST['amount'];
    $crypto_code = $_POST['crypto'];

    $converter = new CryptoConverter('usd'); // 目标货币为 USD
    $result = $converter->convert($amount, $crypto_code);

    // ... (HTML 输出部分) ...
    if ($result !== false) {
        echo "<p>$amount " . strtoupper($crypto_code) . " is approximately " . number_format($result, 2) . " USD.</p>";
    } else {
        echo "<p>Could not perform conversion. Please check the crypto code or try again later.</p>";
    }
    // ... (HTML 输出部分结束) ...

} else {
    // ... (处理参数缺失) ...
}
?>

  • 类型声明和联合类型 (Union Types) (PHP 8.0+):
    • 讲师在 convert 方法的返回类型中使用了联合类型 float|false,表示该方法可能返回一个浮点数或布尔值 false (表示失败)。
    • 这使得调用者可以明确地检查转换是否成功。

调试 echo vs var_dump

  • echo 一个复杂类型 (如对象或数组) 时,PHP 通常只会输出 "Object" 或 "Array" 并可能伴随一个转换错误。
  • 使用 var_dump($variable) 可以详细地打印出变量的结构和内容,非常有助于调试。

19-handling-null-values-safe-function-calls

创建 API 端点 (api.php)

  • 目标: 创建一个 PHP 脚本,它不返回 HTML,而是返回 JSON 数据。这个脚本可以被其他应用 (如移动应用、JavaScript 前端) 调用。
  • 步骤:
    1. 创建 api.php 文件。
    2. 包含必要的类 (如 CryptoConverter,通过 classes.php 或直接包含)。
    3. 从请求参数中获取输入 (例如,加密货币代码,可能通过 $_GET)。
    4. 使用 CryptoConverter 获取数据。
    5. 将结果格式化为 JSON 字符串并输出。
    6. 设置正确的 HTTP Content-Type 头部为 application/json
<?php
// api.php

require_once 'classes.php'; // 包含 CryptoConverter 类

// 1. 获取输入参数 (例如从 $_GET 获取加密货币代码)
//    并提供默认值
$cryptoCode = $_GET['code'] ?? 'btc'; // PHP 7.0+ Null Coalescing Operator
                                      // 如果 $_GET['code'] 存在且不为 null,则使用其值,否则使用 'btc'

// 也可以获取数量,如果API需要的话
// $amount = (float)($_GET['amount'] ?? 1); // 默认为 1 单位

// 2. 创建转换器实例
$converter = new CryptoConverter('usd'); // 假设目标货币固定为 USD

// 3. 获取汇率 (或者更复杂的转换结果)
//    这里简化为直接调用 convert 方法获取 1 单位加密货币的价值
$rate = $converter->convert(1, $cryptoCode); // 获取1单位的汇率

// 4. 准备要返回的数据 (通常是一个关联数组)
$responseData = [];
if ($rate !== false) {
    $responseData = [
        'crypto_code' => strtoupper($cryptoCode),
        'target_currency' => 'USD',
        'rate' => $rate,
        // 可以添加更多信息,如时间戳等
        // 'timestamp' => time()
    ];
} else {
    // 如果获取汇率失败,可以返回错误信息
    // http_response_code(400); // 设置 HTTP 状态码为 Bad Request 或其他
    $responseData = [
        'error' => 'Could not retrieve rate for the specified crypto code.',
        'crypto_code' => strtoupper($cryptoCode)
    ];
}

// 5. 设置 HTTP Content-Type 头部
header('Content-Type: application/json');

// 6. 将数据编码为 JSON 并输出
echo json_encode($responseData);

?>

PHP 7.0+ Null Coalescing Operator (??)

  • 语法: $variable = $value_to_check ?? $default_value;
  • 行为:
    • 如果 $value_to_check 存在并且不为 null,则 $variable 被赋值为 $value_to_check 的值。
    • 否则 (如果 $value_to_check 不存在或为 null),$variable 被赋值为 $default_value
  • isset() 的三元运算符比较:
    • $code = isset($_GET['code']) ? $_GET['code'] : 'btc'; (旧方法)
    • $code = $_GET['code'] ?? 'btc'; (PHP 7.0+,更简洁)
  • 这对于提供参数的默认值非常方便。

PHP 8.0+ Nullsafe Operator (?->)

  • 语法: $result = $object?->method();$result = $object?->property;

  • 行为:

    • 如果 $object 不为 null,则调用其方法或访问其属性,行为与普通 > 一样。
    • 如果 $object null,则整个表达式的结果为 null,并且不会尝试调用方法或访问属性,从而避免了因在 null 上调用方法而产生的致命错误。
  • 用途: 当你处理一个可能为 null 的对象,并且想安全地调用其方法或访问其属性时非常有用,可以避免写很多 if ($object !== null) 的检查。

  • 示例 (非本课程 CryptoConverter 案例,仅为演示):

    class User {
        public function getProfile(): ?Profile { /* ... */ }
    }
    class Profile {
        public function getAddress(): ?Address { /* ... */ }
    }
    class Address {
        public string $street;
    }
    
    function getStreetAddress(?User $user): ?string {
        // 如果 $user, $user->getProfile(), 或 $user->getProfile()->getAddress() 中任何一个是 null,
        // $street 会是 null,且不会报错。
        $street = $user?->getProfile()?->getAddress()?->street;
        return $street;
    }
    
    
  • 讲师提到这个操作符可以减少很多 if 判断,使代码更简洁。

20-formatting-returning-json-data

API 端点 (api.php) 的完善

  • 目标: 确保 api.php 正确返回 JSON 数据,并设置适当的 HTTP 头部。

问题 1:手动构造 JSON 字符串的风险

  • api.php 中,如果直接使用字符串拼接或 echo 来手动构造 JSON,很容易出错:

    • 引号问题: JSON 规范要求字符串使用双引号 "。如果 PHP 字符串使用单引号 ',或者双引号没有正确转义,生成的输出将不是有效的 JSON。
    • 特殊字符转义: JSON 字符串中的特殊字符 (如换行符 \n,制表符 \t, 双引号 " , 反斜杠 \\ 等) 需要正确转义。
    • 数据类型: 布尔值应为 true/false (无引号),数字不应有引号。
  • 示例 (错误的方式):

    // 错误示范:手动构造 JSON,容易出错
    // echo "{'rate': $rate, 'code': '$cryptoCode'}"; // 单引号错误
    // echo "{\\"rate\\": $rate, \\"code\\": \\"$cryptoCode\\"}"; // 如果 $cryptoCode 含特殊字符则可能出错
    
    

解决方案:使用 json_encode()

  • json_encode(mixed $value, int $flags = 0, int $depth = 512): string|false
    • 将 PHP 变量 (通常是关联数组或对象) 转换为 JSON 格式的字符串。
    • 这是创建 JSON 响应的正确且安全的方法。
  • 步骤:
    1. 将要返回的数据组织成一个 PHP 关联数组。
    2. 调用 json_encode() 将该数组转换为 JSON 字符串。
    3. echo 该 JSON 字符串。
<?php
// api.php (续)

// ... (获取 $cryptoCode, $rate 的代码) ...

// 4. 准备要返回的数据 (关联数组)
$responseData = [];
if ($rate !== false) {
    $responseData = [
        'crypto_code' => strtoupper($cryptoCode),
        'target_currency' => 'USD',
        'rate' => $rate, // $rate 是数字
        'success' => true // 布尔值
    ];
} else {
    $responseData = [
        'error' => 'Could not retrieve rate.',
        'crypto_code' => strtoupper($cryptoCode),
        'success' => false
    ];
    // 考虑设置 HTTP 错误状态码,例如:
    // http_response_code(400); // Bad Request
}

// 5. 设置 HTTP Content-Type 头部 (重要!)
header('Content-Type: application/json');

// 6. 将数据编码为 JSON 并输出
echo json_encode($responseData);
?>

问题 2:HTTP Content-Type 头部

  • 默认行为: PHP 脚本 (尤其是以 .php 结尾的文件) 通常被 Web 服务器配置为默认发送 Content-Type: text/html 头部。

  • API 的要求: 对于返回 JSON 数据的 API,客户端 (如 JavaScript fetch 或移动应用) 期望 Content-Type 头部为 application/json

  • 后果: 如果 Content-Type 不正确:

    • 某些客户端库可能无法正确解析响应。
    • 浏览器开发者工具可能无法正确显示 JSON。
    • 严格的客户端可能会拒绝处理响应。
  • 解决方案: 使用 header() 函数在脚本输出任何内容之前设置正确的 Content-Type

    header('Content-Type: application/json');
    
    

header() 函数的注意事项

  • 调用时机: header() 函数必须在脚本向客户端发送任何输出 (包括 HTML、空格、换行符,甚至 PHP 错误/警告信息) 之前调用。
  • 原因: HTTP 头部是响应的第一部分,一旦响应体 (body) 开始发送,就不能再修改头部了。
  • 常见错误:
    • <?php 标签之前有空格或换行符。
    • 在调用 header() 之前有 echoprint 语句。
    • PHP 配置文件 (php.ini) 中的 output_buffering 设置可能会影响此行为 (如果开启,输出会被缓冲,允许稍后发送头部,但不应依赖此特性)。
  • 最佳实践: 将所有 header() 调用放在脚本的最顶部,紧随 <?php 标签之后。
<?php // 确保这是文件的第一行,前面没有任何字符
header('Content-Type: application/json');
// header('Access-Control-Allow-Origin: *'); // 如果需要CORS

require_once 'classes.php';

// ... (后续的 API 逻辑) ...

echo json_encode($responseData);
?>

CORS (Cross-Origin Resource Sharing)

  • 问题: 如果你的 API 托管在一个域名下 (例如 api.example.com),而调用它的 JavaScript 代码运行在另一个域名下 (例如 app.otherdomain.com),浏览器出于安全原因会阻止这种跨域请求,除非服务器明确允许。
  • 解决方案: 服务器需要在响应中包含特定的 CORS 头部。
    • Access-Control-Allow-Origin 最基本的 CORS 头部。
      • header('Access-Control-Allow-Origin: *'); // 允许来自任何域的请求 (不安全,仅用于公共 API)。
      • header('Access-Control-Allow-Origin: <https://app.otherdomain.com>'); // 只允许来自特定域的请求。
    • 还有其他 CORS 头部用于更复杂的场景 (如允许特定的 HTTP 方法、头部等)。
  • 注意: CORS 是浏览器实施的安全策略。服务器到服务器的请求通常不受 CORS 限制。PHP 本身不处理 CORS,但可以通过 header() 函数发送必要的 CORS 响应头。

通过这些步骤,api.php 就能正确地作为 JSON API 端点工作了。

21-using-php-in-a-client-application

项目目标:前端博物馆应用 (服务器端渲染改造)

  • 初始状态: 一个现有的客户端 Web 应用 (HTML, CSS, JavaScript),它从某个数据源 (可能是硬编码的 JS 对象或 JSON 文件) 加载数据并在前端渲染一个图片画廊。
  • 项目文件结构:
    • index.html (主页面)
    • script.js (负责数据加载和 DOM 操作)
    • style.css
    • images/ (存放图片)
    • data/ (存放数据源)
      • data.php (PHP 数组格式的数据)
      • data.json (JSON 格式的数据)
      • data.db (SQLite 数据库文件)
  • 改造目标:
    1. 将应用从客户端渲染 (Client-Side Rendering - CSR) 迁移到服务器端渲染 (Server-Side Rendering - SSR) 使用 PHP。
    2. 原因:
      • 性能: 某些情况下 SSR 可以提供更快的初始加载体验 (FCP - First Contentful Paint)。
      • SEO (搜索引擎优化): 确保搜索引擎 (包括像 ChatGPT 这样的 AI,它们可能不执行 JavaScript) 能够抓取和索引页面内容。
    3. 移除原有的 JavaScript 数据加载和渲染逻辑。
    4. 使用 PHP 从不同的数据源 (PHP 数组, JSON 文件, SQLite 数据库) 读取数据并在服务器端生成 HTML。

步骤 1:准备工作

  1. 打开项目文件夹 FRONTENDMUSEUM

  2. 启动 PHP 开发服务器: (确保你位于 FRONTENDMUSEUM 文件夹的根目录)

    php -S localhost:5000
    
        ```
    
    
  3. 观察现有应用:

    • 访问 http://localhost:5000/index.html (或 http://localhost:5000 如果服务器默认提供 index.html)。
    • 查看页面源代码,会发现 <main> 标签内可能只有一个模板化的 <article> 元素。
    • 使用浏览器开发者工具检查 DOM,会看到 <main> 标签内有多个由 JavaScript 动态生成的 <article> 元素。

步骤 2:移除客户端 JavaScript 渲染

  1. 删除 script.js 文件 (或将其重命名)。

  2. index.html 中移除对 script.js 的引用:

    <!-- <script src="script.js" defer></script> -->
    
  3. 刷新页面,现在应该只看到 HTML 中硬编码的那个模板 <article>

步骤 3:将 HTML 转换为 PHP 文件

  1. 重命名 index.htmlindex.php
    • 这使得我们可以在文件中嵌入 PHP 代码。
    • 此时,如果访问 http://localhost:5000/index.php,页面看起来应该和之前一样 (只有一个模板文章),但现在它是由 PHP 提供的。

步骤 4:使用 PHP 数组数据进行服务器端渲染

  • 目标:data/data.php 文件中加载展品数据 (一个名为 $exhibits 的 PHP 数组),并使用 foreach 循环在服务器端生成多个 <article> 元素。
  1. index.php 顶部包含数据文件:

    <?php
    require_once 'data/data.php'; // $exhibits 数组现在可用
    ?>
    <!DOCTYPE html>
    <!-- ... rest of the HTML ... -->
    
    
  2. 找到 HTML 中用于渲染单个展品的模板 <article> 结构。

  3. 使用 foreach 循环包裹该模板,并动态填充数据:

    <main>
        <?php foreach ($exhibits as $object): ?>
            <article>
                <img src="images/<?php echo htmlspecialchars($object['image']); ?>" alt="<?php echo htmlspecialchars($object['title']); ?>">
                <div class="info">
                    <h2><?php echo htmlspecialchars($object['title']); ?></h2>
                    <p><?php echo htmlspecialchars($object['description']); ?></p>
                </div>
            </article>
        <?php endforeach; ?>
    </main>
    
    
    • $exhibits 来自 data.php
    • $object 在每次循环中代表一个展品 (它本身是一个关联数组,包含 title, description, image键)。
    • 使用 <?php echo ...; ?> (或短标签 <?= ... ?>) 将 PHP 变量的值插入到 HTML 的相应位置。
    • 使用 htmlspecialchars() 是一个好习惯,用于防止 XSS 攻击,确保特殊字符被正确编码。
    • 使用了 foreach (...): ... endforeach; 的替代语法,这在混合 HTML 和 PHP 时更易读。
    • 注意图片路径是 images/ 加上从数据中获取的文件名。
  • 关于 endforeach; 后的分号: 讲师提到,如果 endforeach; 是 PHP 块中的最后一条语句,则其后的分号是可选的。但为了防止将来在该块中添加更多代码时忘记补充分号,通常建议加上。

22-displaying-dynamic-data-exercise

(这是对上一节练习的完成和调试过程的总结)

任务:完成 index.php 中展品数据的动态显示

  • 背景: 已经设置了 foreach 循环来遍历 $exhibits 数组。现在需要将每个 $object (代表一个展品) 的数据显示在 HTML 模板中。

调试技巧:使用 var_dump()

  • 在循环内部,可以使用 var_dump($object); 来查看每个 $object 的结构和内容,确保数据如预期。 这会帮助确认键名 (如 title, description, image) 是否正确。

    <?php foreach ($exhibits as $object): ?>
    <?php // var_dump($object); // 临时用于调试 ?>
    <article>
    <!-- ... -->
    </article>
    <?php endforeach; ?>
    
        ```
    

填充 HTML 模板

  • 标题 (<h2>):
    <h2><?php echo htmlspecialchars($object['title']); ?></h2>
    
    或者使用短 echo 标签:
    <h2><?= htmlspecialchars($object['title']) ?></h2>
    
  • 描述 (<p>):
    <p><?php echo htmlspecialchars($object['description']); ?></p>
    
  • 图片 (<img>):
    • 关键点:
      • 图片 src 属性的值需要拼接正确的路径 (例如,images/ 目录加上图片文件名)。
      • 可以在 HTML 属性值内部嵌入 PHP echo 语句。
    <img
      src="images/<?php echo htmlspecialchars($object['image']); ?>"
      alt="<?php echo htmlspecialchars($object['title']); ?>"
    />
    
    • 常见错误 (调试过程):
      • 忘记 echo 如果在 src 属性的 PHP 块中只写了 $object['image'] 而没有 echo,那么图片路径将不会被输出,导致图片无法显示。PHP 不会因为没有 echo 而报错,它只是计算了表达式的值但没有输出。
      • 路径问题: 确保 images/ 目录和图片文件名 ($object['image']) 的组合是正确的相对路径。

最终的循环体示例:

<main>
    <?php
    // 假设 require_once 'data/data.php'; 已经在文件顶部执行
    // $exhibits 数组可用

    foreach ($exhibits as $object):
    ?>
        <article>
            <img src="images/<?= htmlspecialchars($object['image']) ?>" alt="<?= htmlspecialchars($object['title']) ?>">
            <div class="info">
                <h2><?= htmlspecialchars($object['title']) ?></h2>
                <p><?= htmlspecialchars($object['description']) ?></p>
            </div>
        </article>
    <?php
    endforeach;
    ?>
</main>

  • 刷新页面后,应该能看到所有展品都已通过服务器端渲染正确显示出来,包括图片、标题和描述。

23-working-with-external-data-sources

目标:扩展应用,实现主从视图导航并从数据库加载数据

  1. 主从视图 (Master-Detail):
    • 主视图 (index.php): 显示所有展品的标题列表。每个标题是一个链接。
    • 从视图 (details.php): 点击主视图中的链接后,显示该展品的详细信息 (图片、标题、描述)。
  2. 数据源切换:
    • 从 PHP 数组 (data.php)。
    • 从 JSON 文件 (data.json)。
    • 最终目标: 从 SQLite 数据库 (data.db)。
  3. 使用 OOP: 创建一个 DB 类来封装数据库操作。

1. 从 JSON 文件加载数据 (快速演示)

  • 如果数据源是 data.json,可以使用 file_get_contents() 读取文件内容,然后用 json_decode() 解析。

    // index.php (或任何需要数据的脚本)
    <?php
    $jsonString = file_get_contents('data/data.json');
    if ($jsonString === false) {
        die("Error: Could not read data.json");
    }
    
    // true 参数使 json_decode 返回关联数组,而不是 stdClass 对象
    // 这与 data.php 中 $exhibits 的结构更一致
    $exhibits = json_decode($jsonString, true);
    
    if ($exhibits === null && json_last_error() !== JSON_ERROR_NONE) {
        die("Error: Could not decode JSON from data.json - " . json_last_error_msg());
    }
    
    // 现在 $exhibits 数组可用,后续渲染逻辑与使用 data.php 类似
    // ...
    ?>
    
    
  • 讲师提到,使用 JSON 作为数据源很简单,重点将放在数据库操作上。

2. SQLite 数据库简介

  • SQLite: 一个轻量级的、基于文件的、自包含的 SQL 数据库引擎。
    • 无需服务器: 与 MySQL 或 PostgreSQL 不同,SQLite 不需要单独的服务器进程。数据库就是一个普通文件。
    • SQL 兼容: 支持标准的 SQL 语法 (创建表、插入、查询、更新、删除等)。
    • 广泛使用: 内置于许多操作系统 (Android, iOS) 和应用程序中。
    • PHP 支持: PHP 通过 SQLite3 扩展 (通常默认启用) 或 PDO_SQLite (与 PDO 一起使用) 提供对 SQLite 的支持。
  • 项目中的 data.db
    • 这是一个已经创建好的 SQLite 数据库文件,其中包含一个名为 exhibits 的表,表结构与 data.php 中的数组类似 (有 title, description, image 等列)。
  • 查看 SQLite 数据库:
    • 由于是二进制文件,不能直接用文本编辑器查看。
    • 需要使用 SQLite 数据库浏览器/管理器工具。
    • 讲师演示了使用 Chrome 扩展 "SQLite Viewer" 来打开 data.db 文件,查看 exhibits 表的内容。其他工具如 DB Browser for SQLite (桌面应用) 也很常用。

3. 创建 DB 类 (classes/DB.php)

  • 目标: 封装数据库连接和查询逻辑。

  • 初始结构:

    <?php
    // classes/DB.php
    class DB {
        private $pdo; // 或 $sqlite_connection; 根据选择的API
        private $dbPath = 'data/data.db'; // 数据库文件路径
    
        public function __construct() {
            // 连接数据库的逻辑
        }
    
        public function executeQuery(string $sql, array $params = []) {
            // 执行查询并返回结果的逻辑
        }
    
        // 可以添加其他方法,如 executeUpdate, getLastInsertId 等
    }
    ?>
    
    
  • 抽象类 (Abstract Classes) 概念提及:

    • 讲师提到,可以创建一个抽象的 DB 基类,然后为不同的数据库系统 (SQLite, MySQL) 创建具体的子类。

    • 抽象类不能被直接实例化,必须被继承。它可以包含具体实现的方法和抽象方法 (没有实现,由子类提供)。

    • 例如:

      abstract class AbstractDB {
          abstract public function connect();
          abstract public function query(string $sql);
      
          public function log(string $message) {
              // 通用日志记录方法
          }
      }
      class SQLiteDB extends AbstractDB { /* ... */ }
      
      
    • (本次课程不会深入抽象类,仅作概念介绍。)

PHP 数据库 API 选择

  • 1. 特定数据库扩展 (例如 SQLite3 类, mysqli for MySQL):
    • 直接使用针对特定数据库的 API。
    • 优点: 可能提供对数据库特定功能的最完整访问。
    • 缺点: 如果将来需要更换数据库系统,可能需要重写大量数据库交互代码。
  • 2. PDO (PHP Data Objects):
    • 一个数据库访问抽象层。
    • 提供了一致的 API 来与多种数据库 (MySQL, PostgreSQL, SQLite, Oracle, SQL Server 等) 进行交互,只需更改连接字符串和可能的一些 SQL 方言差异。
    • 优点: 数据库无关性,代码更具可移植性。
    • 缺点: 可能无法利用某些数据库特有的高级功能。
    • PDO 通常是推荐的方式,尤其是在可能需要支持多种数据库或希望代码更灵活的项目中。
    • PDO 扩展 (php_pdo) 和特定数据库的 PDO 驱动 (php_pdo_sqlite, php_pdo_mysql 等) 需要在 PHP 中启用。通常默认启用。

讲师决定先演示使用 SQLite3 类的直接方式。

24-connecting-to-a-sqlite-database

DB 类中实现 SQLite 连接和查询 (使用 SQLite3 类)

<?php
// classes/DB.php
class DB {
    private $sqlite; // 用于存储 SQLite3 对象
    private $dbPath = __DIR__ . '/../data/data.db'; // 数据库文件路径,使用 __DIR__ 确保相对路径正确

    public function __construct() {
        try {
            // SQLite3 的构造函数参数是数据库文件的路径
            // 如果文件不存在,它会尝试创建它 (取决于权限)
            $this->sqlite = new SQLite3($this->dbPath);
        } catch (Exception $e) {
            // 处理连接错误,例如记录日志并退出,或抛出自定义异常
            die("SQLite Connection Error: " . $e->getMessage());
        }
    }

    /**
     * 执行一个 SQL 查询并返回所有结果行作为关联数组。
     *
     * @param string $sql SQL 查询语句.
     * @return array|false 结果数组,或在失败时返回 false.
     */
    public function executeQuery(string $sql): array|false {
        if (!$this->sqlite) {
            return false; // 如果连接未建立
        }

        // $this->sqlite->query($sql) 执行查询,返回一个 SQLite3Result 对象或 false
        $result = $this->sqlite->query($sql);

        if ($result === false) {
            // 查询失败,可以记录错误: $this->sqlite->lastErrorMsg()
            return false;
        }

        $rows = [];
        // $result->fetchArray(SQLITE3_ASSOC) 从结果集中获取下一行作为关联数组
        // 当没有更多行时返回 false
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
            $rows[] = $row;
        }

        // SQLite3Result 对象在不再需要时应被释放 (通常在对象销毁时自动处理,但明确调用更好)
        // fetchArray 会在内部处理结果集指针,循环结束后结果集通常会被消耗完。
        // 对于 SELECT 查询,一旦所有数据被提取,结果集通常会自动清理。
        // $result->finalize(); // 可以显式调用,但对于 fetchArray 循环不是严格必需

        return $rows;
    }

    // 在对象销毁时关闭数据库连接 (好习惯)
    public function __destruct() {
        if ($this->sqlite) {
            $this->sqlite->close();
        }
    }
}
?>

  • 数据库路径 ($dbPath):
    • 使用 __DIR__ . '/../data/data.db'__DIR__ 是一个魔术常量,代表当前文件 (DB.php) 所在的目录。
    • ../ 表示上一级目录。
    • 这样可以确保无论 DB.php 从哪里被包含,数据库文件的相对路径都是正确的。
  • new SQLite3($this->dbPath) 创建 SQLite3 类的实例,尝试打开 (或创建) 数据库文件。
  • $this->sqlite->query($sql)
    • 执行 SQL 查询。
    • 对于 SELECT 查询,成功时返回一个 SQLite3Result 对象,失败时返回 false
  • $result->fetchArray(SQLITE3_ASSOC)
    • SQLite3Result 对象中获取一行数据。
    • SQLITE3_ASSOC 参数指定返回关联数组 (列名为键)。
    • 其他选项:
      • SQLITE3_NUM:返回数字索引数组。
      • SQLITE3_BOTH (默认):同时返回关联和数字索引。
    • 每次调用 fetchArray(),内部指针会移向结果集中的下一行。当没有更多行时,返回 false
  • 循环获取所有行: 使用 while 循环和 fetchArray() 来遍历所有结果行,并将它们收集到一个数组 ($rows) 中。
  • 错误处理:
    • 在构造函数中使用 try-catch 来捕获连接时可能发生的异常。
    • 检查 query()fetchArray() 的返回值是否为 false 以判断操作是否成功。
    • 可以使用 $this->sqlite->lastErrorMsg() 获取 SQLite 的最后错误信息。
  • __destruct() 在对象被销毁时,通过 $this->sqlite->close() 关闭数据库连接,释放资源。

index.php 中使用 DB

<?php
// index.php

// 假设 classes.php 或 autoloading 已经设置好,DB 类可用
// 或者直接 require_once 'classes/DB.php';

// 包含头部 (如果使用了分离的 header.inc.php)
// require_once 'includes/header.inc.php';

$db = new DB(); // 创建 DB 对象,会自动连接数据库

$sql = "SELECT title, description, image FROM exhibits ORDER BY title ASC"; // 获取所有展品
$exhibits = $db->executeQuery($sql);

if ($exhibits === false) {
    echo "<p>Error retrieving data from the database.</p>";
    // 可能需要包含页脚并退出
    // require_once 'includes/footer.inc.php';
    exit;
}
?>
<main>
    <!-- <h1>Museum Exhibits</h1> --> <!-- 标题可能在 header.inc.php 中 -->
    <?php if (empty($exhibits)): ?>
        <p>No exhibits found.</p>
    <?php else: ?>
        <ul> <!-- 改为列表显示标题 -->
            <?php foreach ($exhibits as $exhibit): ?>
                <li>
                    <a href="details.php?id=<?php echo urlencode($exhibit['title']); /* 假设 title 为唯一标识,实际应使用 ID */ ?>">
                        <?php echo htmlspecialchars($exhibit['title']); ?>
                    </a>
                </li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</main>
<?php
// 包含页脚 (如果使用了分离的 footer.inc.php)
// require_once 'includes/footer.inc.php';
?>

  • new DB() 创建 DB 类的实例,构造函数会自动连接到 SQLite 数据库。
  • $db->executeQuery($sql) 执行 SQL 查询并获取结果。
  • 渲染逻辑:
    • 如果 $exhibits 获取成功且不为空,则遍历它。
    • 这里改为只显示展品标题列表,每个标题链接到 details.php
    • 注意链接参数: details.php?id=<?php echo urlencode($exhibit['title']); ?>
      • 暂时使用 title 作为标识符传递给 details.php。在实际应用中,通常会有一个唯一的数字 ID 列。
      • urlencode() 用于确保参数值在 URL 中正确编码。
  • 问题提及 (SQL 注入): 讲师指出,直接将用户输入或不可信数据拼接到 SQL 查询中是不安全的,会导致 SQL 注入漏洞。正确的做法是使用预处理语句 (prepared statements) 和参数绑定。 (SQLite3 类和 PDO 都支持预处理语句)。由于时间关系,本次课程可能不会详细展开,但这是数据库安全的关键点。

25-autoloading-classes

问题:手动 require_once 多个类文件

  • 当项目中有很多类时,在每个需要它们的文件顶部写一长串 require_once 语句会变得繁琐且容易出错。
  • 每次添加新类,都需要记得去更新这些包含列表。

解决方案:自动加载 (Autoloading)

  • 概念: 自动加载是一种机制,当 PHP 遇到一个尚未定义的类名时 (例如,在 new ClassName()ClassName::staticMethod() 时),它会触发一个预先注册的回调函数。这个回调函数负责根据类名找到并包含对应的类文件。
  • spl_autoload_register()
    • PHP 推荐的注册自动加载函数的方式。
    • 可以注册多个自动加载函数,它们会按注册顺序依次尝试加载类。
    • 语法: spl_autoload_register(?callable $autoload_function = null, bool $throw = true, bool $prepend = false): bool
      • $autoload_function:一个可回调的参数 (例如函数名字符串、匿名函数、[$object, 'method'] 数组等)。这个函数接收一个参数:需要加载的类名。

创建自动加载文件 (classes.phpautoload.php)

我们可以创建一个中心文件 (例如,讲师使用的 classes.php,或者更通用的 autoload.php) 来设置自动加载逻辑。这个文件只需要在应用的入口点 (如 index.php, details.php, api.php) require_once 一次。

<?php
// classes.php (或 autoload.php)

spl_autoload_register(function ($className) {
    // 假设所有类文件都存放在 'classes' 目录下
    // 并且文件名与类名完全一致 (例如 DB 类在 classes/DB.php)
    $filePath = __DIR__ . '/' . $className . '.php';
    // __DIR__ 是当前文件 (classes.php) 所在的目录
    // 所以 $filePath 会是类似 /path/to/project/classes/DB.php

    // 检查文件是否存在,然后包含它
    if (file_exists($filePath)) {
        require_once $filePath;
    } else {
        // 可选:如果文件未找到,可以记录错误或抛出异常
        // error_log("Autoload error: Could not load class {$className}. File not found: {$filePath}");
        // 或者不处理,让 PHP 后续抛出 "Class not found" 错误
    }
});

// 注意:如果类文件在不同的子目录或遵循更复杂的命名约定 (如 PSR-4),
// 这里的逻辑需要相应调整,例如将命名空间转换为目录路径。
// Composer 的自动加载器就是这样做的。
?>

  • 匿名函数作为回调: function ($className) { ... } 是一个匿名函数 (闭包),它被传递给 spl_autoload_register
  • 参数 $className 当 PHP 尝试使用一个未定义的类 (例如 new DB()) 时,这个匿名函数会被调用,$className 的值就是 "DB"
  • 路径构建:
    • 代码假设类文件与类名同名,并带有 .php 扩展名,且都位于 classes.php 文件所在的目录 (即 classes/ 目录,如果 classes.php 位于项目根目录下的 classes/ 文件夹,则应为 __DIR__ . '/' . $className . '.php',确保与实际文件结构匹配)。
    • 讲师的示例中,classes.phpDB.php 在同一 classes/ 目录下,所以是 __DIR__ . '/' . $className . '.php'。如果 classes.php 在项目根目录,而类在 classes/ 子目录,则应为 __DIR__ . '/classes/' . $className . '.php'。 (确保理解 __DIR__ 指向的是包含 spl_autoload_register 的文件所在的目录。)
  • file_exists() 检查文件是否存在,避免 require_once 不存在的文件时产生错误。
  • require_once 加载找到的类文件。

在应用入口点使用自动加载

现在,在 index.php, details.php 等文件的顶部,只需要包含这个自动加载配置文件:

<?php
// index.php (或 details.php 等)

// 包含自动加载设置文件
require_once 'classes/classes.php'; // 假设 classes.php 在 classes 目录下
// 或者如果 classes.php 在根目录,则 require_once 'classes.php';

// 现在可以直接使用类,PHP 会在需要时自动加载它们
$db = new DB();
// $converter = new CryptoConverter(); (如果也有这个类且遵循命名约定)

// ... 后续代码 ...
?>

  • 当执行 new DB() 时,如果 DB 类尚未定义,spl_autoload_register 注册的函数会被调用,$className 传入 "DB"
  • 自动加载函数会尝试 require_once 'classes/DB.php' (根据路径构建逻辑)。
  • 如果成功,DB 类就定义好了,new DB() 就可以继续执行。

魔术方法 (Magic Methods) 提及

  • 讲师简要提到了 PHP 中的“魔术方法”,它们是以双下划线 __ 开头的特殊方法名 (如 __construct, __destruct, __get, __set, __call, __toString 等)。
  • 这些方法在特定情况下会被 PHP 自动调用。例如:
    • __get($name):当读取一个不可访问 (私有或保护的) 或不存在的属性时调用。
    • __set($name, $value):当给一个不可访问或不存在的属性赋值时调用。
    • __call($name, $arguments):当调用一个不可访问或不存在的对象方法时调用。
  • 自动加载在早期 PHP 版本中曾通过一个名为 __autoload() 的魔术函数实现,但现在 spl_autoload_register() 是推荐的方式,因为它更灵活 (可以注册多个加载器)。
  • 这些魔术方法使得 PHP 非常灵活,但也可能使代码行为更难预测,需要谨慎使用。

26-header-footer-details

问题:HTML 结构重复

  • 在多个 PHP 页面 (如 index.php, details.php) 中,通常会有相同的 HTML 头部 (DOCTYPE, <html>, <head>, 导航栏等) 和页脚 (版权信息,闭合标签等)。
  • 直接在每个文件中复制粘贴这些通用部分会导致:
    • 代码冗余: 相同代码多处存在。
    • 维护困难: 如果需要修改头部或页脚,必须在所有文件中都进行修改。

解决方案:分离头部和页脚到独立文件

  • 将通用的 HTML 头部内容提取到一个单独的文件 (例如 includes/header.inc.php)。
  • 将通用的 HTML 页脚内容提取到另一个单独的文件 (例如 includes/footer.inc.php)。
  • 在每个主页面文件的适当位置使用 require_once (或 include) 来包含这些头部和页脚文件。

1. 创建 includes/header.inc.php

<?php
// includes/header.inc.php
// 这里可以包含任何需要在每个页面顶部执行的 PHP 逻辑,
// 例如会话启动、配置加载、权限检查等。

// 也可以在这里定义通用的变量,如页面标题
if (!isset($pageTitle)) {
    $pageTitle = "Frontend Museum"; // 默认标题
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo htmlspecialchars($pageTitle); ?></title>
    <link rel="stylesheet" href="style.css"> <!-- 假设 style.css 在根目录 -->
    <!-- 可以有其他通用的 <meta>, <link> 标签 -->
</head>
<body>
    <header>
        <h1><a href="index.php">Frontend Museum</a></h1>
        <!-- 可以有导航菜单等 -->
    </header>
    <div class="container"> <!-- 一个包裹主要内容的容器 -->

  • 文件名后缀 .inc.php 是一种常见的约定,表示这是一个被包含的文件,通常不直接通过 URL 访问。
  • 可以定义一个 $pageTitle 变量,并在主 PHP 文件中设置它,以便每个页面有不同的标题。

2. 创建 includes/footer.inc.php

<?php
// includes/footer.inc.php
?>
    </div> <!-- 关闭 .container -->
    <footer>
        <p>&copy; <?php echo date('Y'); ?> Frontend Museum. All rights reserved.</p>
    </footer>
    <!-- 可以有通用的 JavaScript 文件引用 -->
    <!-- <script src="main.js"></script> -->
</body>
</html>

3. 在主页面 (如 index.php, details.php) 中使用:

<?php
// index.php (或 details.php)

// (可选) 设置特定于此页面的变量,如标题
$pageTitle = "All Exhibits - Frontend Museum";

// 包含自动加载和数据库类 (如果需要)
require_once 'classes/classes.php'; // 或 autoload.php
$db = new DB();

// 包含头部
require_once 'includes/header.inc.php';

// 获取数据 (示例)
$exhibits = $db->executeQuery("SELECT title FROM exhibits");
?>

<main class="master"> <!-- 特定于此页面的内容 -->
    <?php if ($exhibits): ?>
        <ul>
            <?php foreach ($exhibits as $exhibit): ?>
                <li>
                    <a href="details.php?title=<?php echo urlencode($exhibit['title']); ?>">
                        <?php echo htmlspecialchars($exhibit['title']); ?>
                    </a>
                </li>
            <?php endforeach; ?>
        </ul>
    <?php else: ?>
        <p>No exhibits to display.</p>
    <?php endif; ?>
</main>

<?php
// 包含页脚
require_once 'includes/footer.inc.php';
?>

  • 优点:
    • 减少重复: 通用 HTML 只需编写一次。
    • 易于维护: 修改头部或页脚只需在一个地方进行。
    • 结构清晰: 主页面文件更专注于其核心内容。
  • 文件夹结构: 将包含文件放在一个单独的目录 (如 includes/partials/) 是一个好习惯。

路径问题和数据库连接调试

  • DB.php 中的数据库路径:

    • 之前在 DB.php 的构造函数中,数据库路径使用了 '../data/data.db'
    • 讲师解释,includerequire 的行为类似复制粘贴。当 DB.phpindex.php (位于项目根目录) 包含时,DB.php 中的代码实际上是在 index.php 的上下文中执行的。
    • 因此,相对路径 '../data/data.db' (从 classes 目录出发) 可能会导致找不到文件,因为执行上下文是根目录。
    • 解决方案:
      • 使用 __DIR__ private $dbPath = __DIR__ . '/../data/data.db'; (如果 DB.phpclasses/ 目录,这会正确指向 project_root/data/data.db)。这是更健壮的方式。
      • 或者,如果 DB.php 的代码是在根目录的上下文中执行,则路径应相对于根目录: private $dbPath = 'data/data.db';。 (讲师在演示中改为这个,因为 include 后的执行上下文是 index.php 所在的根目录)。
  • SQLite3Result::fetchArray() 的行为:

    • 讲师最初认为 fetchArray() 会返回所有行,但实际上它每次只获取结果集中的一行,并将内部指针移到下一行。

    • 为了获取所有行,需要在 DB::executeQuery() 方法中使用一个 while 循环来持续调用 fetchArray(),直到它返回 false (表示没有更多行),并将每一行收集到一个数组中。

      // DB.php - executeQuery 方法内的修改
      $rows = [];
      while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
          $rows[] = $row;
      }
      return $rows; // 返回包含所有行的数组
      
      

"主从视图" (index.phpdetails.php) 逻辑

  • index.php (主视图):
    • 从数据库获取所有展品的标题 (或 ID 和标题)。
    • 遍历结果,为每个展品创建一个列表项 <li>
    • 每个列表项包含一个链接 <a>,指向 details.php
    • 链接中通过 URL 参数传递展品的唯一标识符 (例如 details.php?id=EXHIBIT_IDdetails.php?title=URL_ENCODED_TITLE)。
  • details.php (从视图):
    • 从 URL 参数 ($_GET) 中获取传递过来的展品标识符。
    • 使用该标识符从数据库中查询特定展品的详细信息。 (理想情况下,SQL 查询应使用 WHERE 子句来只获取那一条记录)。
    • 将获取到的详细信息渲染到 HTML 结构中。
  • 讲师在演示中,为了简化,details.php 仍然获取了所有展品,然后根据 URL 中的索引从数组中选择一个。这不是最高效的方式,但能演示页面间数据传递。更优化的做法是 details.php 只查询所需的单个展品。

27-passing-data-to-the-details-page

index.php (Master View) - 生成链接

  • 目标:index.php 中为每个展品生成一个链接,点击后能导航到 details.php 并显示该展品的详细信息。
  • 传递标识符: 需要通过 URL 参数将选定展品的唯一标识符传递给 details.php
    • 理想情况: 数据库表应有一个主键 (如 id 列)。链接会是 details.php?id=123
    • 当前情况 (无 id 列): 讲师暂时使用展品的 title 作为标识符。由于标题可能包含空格或特殊字符,需要使用 urlencode()
    • 改进 (使用数组索引): 后来讲师改为使用 foreach 循环的索引 $i 作为参数传递:details.php?index=0details.php?index=1 等。这在当前数据源是整个数组时可行,但如果 details.php 直接从数据库按 ID 查询则更好。
<?php
// index.php (部分)
require_once 'classes/classes.php';
require_once 'includes/header.inc.php';

$db = new DB();
// 假设 executeQuery 返回所有展品
$exhibits = $db->executeQuery("SELECT title, description, image FROM exhibits");
?>
<main class="master">
    <?php if ($exhibits): ?>
        <ul>
            <?php foreach ($exhibits as $index => $exhibit): // 获取索引 $index ?>
                <li>
                    <a href="details.php?index=<?php echo $index; ?>">
                        <?php echo htmlspecialchars($exhibit['title']); ?>
                    </a>
                </li>
            <?php endforeach; ?>
        </ul>
    <?php else: ?>
        <p>No exhibits found.</p>
    <?php endif; ?>
</main>
<?php
require_once 'includes/footer.inc.php';
?>

details.php (Detail View) - 接收参数并显示数据

  1. 获取 URL 参数: 使用 $_GET 超全局变量来获取从 index.php 传递过来的参数。

    <?php
    // details.php (顶部)
    $pageTitle = "Exhibit Details"; // 设置页面标题
    require_once 'classes/classes.php';
    require_once 'includes/header.inc.php';
    
    $db = new DB();
    $exhibits = $db->executeQuery("SELECT title, description, image FROM exhibits"); // 仍然获取所有数据
    
    // 获取 URL 中的 'index' 参数,如果不存在则默认为 0
    $selectedIndex = $_GET['index'] ?? 0;
    $selectedIndex = (int)$selectedIndex; // 确保是整数
    
    $object = null; // 初始化选定的展品对象
    
    // 检查索引是否有效,并获取对应的展品
    if ($exhibits && isset($exhibits[$selectedIndex])) {
        $object = $exhibits[$selectedIndex];
        $pageTitle = htmlspecialchars($object['title']) . " - Details"; // 更新页面标题
        // 需要在 header.inc.php 输出 $pageTitle 之前重新包含或更新它,
        // 或者将页面标题逻辑移到 header.inc.php 之后
    } else {
        // 处理索引无效或展品不存在的情况
        echo "<p>Error: Exhibit not found.</p>";
        // 可以重定向到错误页面或 index.php
    }
    ?>
    
    
    • 注意: 上述代码仍然加载所有展品,然后通过索引选取。更高效的做法是修改 DB::executeQuery 或添加一个新方法 (如 getExhibitByIndexgetExhibitById),使其能够只从数据库获取特定的一个展品,例如使用 WHERE 子句和可能的 LIMIT 1

      // 假设有一个 id 列
      // $sql = "SELECT title, description, image FROM exhibits WHERE id = :id LIMIT 1";
      // 然后使用预处理语句绑定 $id
      
      
    • Null Coalescing Operator (??) 用于提供默认索引。

    • (int)$selectedIndex 将参数转换为整数。

  2. 渲染展品详情: 如果找到了对应的展品 ($object 不为 null),则使用与之前 index.php 渲染单个展品类似的 HTML 结构来显示其详情。

    <main>
        <?php if ($object): ?>
            <article class="detail-view">
                <img src="images/<?= htmlspecialchars($object['image']) ?>" alt="<?= htmlspecialchars($object['title']) ?>">
                <div class="info">
                    <h2><?= htmlspecialchars($object['title']) ?></h2>
                    <p><?= htmlspecialchars($object['description']) ?></p>
                </div>
                <p><a href="index.php">&laquo; Back to all exhibits</a></p>
            </article>
        <?php else: ?>
            <p>The requested exhibit could not be found. Please <a href="index.php">return to the main list</a>.</p>
        <?php endif; ?>
    </main>
    <?php
    require_once 'includes/footer.inc.php';
    ?>
    
    
    • 这里使用了简化的 article 结构,可以根据需要调整。
    • 添加了一个返回主列表的链接。

优化 details.php 的数据获取

理想情况下,details.php 不应该加载所有展品。DB 类应该有一个方法,例如 getExhibitById(int $id)getExhibitByTitle(string $title) (如果 title 是唯一的),它会执行一个带有 WHERE 子句的 SQL 查询。

示例 (假设 DB 类有一个新方法):

// DB.php (新增方法,使用预处理语句更安全)
public function getExhibitByIndex(int $index): ?array {
    // 注意:直接使用索引作为查询条件通常不适用于数据库,除非数据保证顺序
    // 更好的方式是基于 ID。这里仅为演示概念。
    // 实际上,如果基于索引,你可能需要获取所有数据然后选择,
    // 或者使用 LIMIT 和 OFFSET (但 OFFSET 性能可能不佳)。
    // 最好的方式是有一个主键 ID。

    // 假设我们有一个名为 'row_id' (或类似) 的自增主键,或者使用 LIMIT/OFFSET (不推荐)
    // 为了简单起见,如果必须按“索引”获取,但数据库没有直接的行号概念,
    // 我们可能还是需要获取所有然后选择,或者使用数据库特定的行号函数。

    // 以下是基于主键 ID 的更现实的例子
    // public function getExhibitById(int $id): ?array {
    //     $stmt = $this->sqlite->prepare("SELECT title, description, image FROM exhibits WHERE id = :id");
    //     $stmt->bindValue(':id', $id, SQLITE3_INTEGER);
    //     $result = $stmt->execute();
    //     if ($result) {
    //         $row = $result->fetchArray(SQLITE3_ASSOC);
    //         return $row ?: null; // 如果没有找到行,fetchArray 返回 false
    //     }
    //     return null;
    // }

    // 沿用讲师的“获取所有然后按索引选择”的简化逻辑,但应意识到其局限性
    $allExhibits = $this->executeQuery("SELECT title, description, image FROM exhibits");
    if ($allExhibits && isset($allExhibits[$index])) {
        return $allExhibits[$index];
    }
    return null;
}

// details.php (修改数据获取部分)
<?php
// ... (includes) ...
$db = new DB();

$selectedIndex = (int)($_GET['index'] ?? 0);

// 使用新方法获取单个展品
$object = $db->getExhibitByIndex($selectedIndex); // (或者 getExhibitById 如果有 ID)

if ($object) {
    $pageTitle = htmlspecialchars($object['title']) . " - Details";
    // 可能需要重新包含 header 或在 header 输出前设置好 pageTitle
}
// ... (后续渲染逻辑) ...
?>

这样 details.php 就只处理它需要的数据了。

28-error-handling

PHP 错误类型和处理的复杂性

  • PHP 中的错误处理机制比较多样,历史上也有演变。
  • 错误可以分为不同级别:
    • Notices (通知): 运行时发生的非严重错误,脚本通常会继续执行 (例如,访问未定义的变量或数组索引)。
    • Warnings (警告): 更严重的运行时错误,脚本通常也会继续执行 (例如,include 一个不存在的文件)。
    • Errors (错误):
      • Parse errors (解析错误/语法错误): 代码不符合 PHP 语法,脚本根本无法开始执行。
      • Fatal errors (致命错误): 严重的运行时错误,导致脚本立即终止 (例如,调用未定义的函数,require 不存在的文件,在 null 上调用方法)。
      • Recoverable errors (可恢复错误): 可以被捕获和处理的致命错误 (例如,类型提示不匹配但可以转换)。
  • Exceptions (异常):
    • PHP 5 引入了更现代的面向对象的异常处理机制 (try-catch-finally)。
    • 许多内置函数和操作现在在出错时会抛出异常,而不是仅仅产生传统错误。
    • 用户也可以自定义并抛出异常。

try-catch

  • 用于捕获和处理异常。

  • 语法:

    try {
        // 可能抛出异常的代码
        $result = someFunctionThatMightThrow();
        if ($result === false) {
            throw new Exception("Something went wrong with the result.");
        }
    } catch (SpecificExceptionType $e) {
        // 处理特定类型的异常
        // echo "Caught SpecificException: " . $e->getMessage();
    } catch (AnotherExceptionType $e) {
        // 处理另一种特定类型的异常
    } catch (Throwable $e) { // Throwable 是所有 Error 和 Exception 的基接口 (PHP 7+)
        // 捕获所有可抛出的错误和异常 (作为最后的捕获)
        // echo "An error or exception occurred: " . $e->getMessage();
        // $e->getFile(), $e->getLine(), $e->getTraceAsString() 等方法可获取更多信息
    } finally {
        // (可选) 无论是否发生异常,finally 块中的代码总会执行
        // 通常用于资源清理
        // closeDatabaseConnection();
    }
    
    
  • Throwable 在 PHP 7+ 中,ExceptionError (代表传统 PHP 错误被封装成的对象) 都实现了 Throwable 接口。所以 catch (Throwable $e) 可以捕获几乎所有可捕获的运行时问题。

  • 注意: 不是所有的 PHP 传统错误 (如 E_NOTICE, E_WARNING) 都会自动转换为可以被 try-catch 捕获的 Error 对象。这取决于 PHP 版本和配置。

显示/隐藏错误和错误报告级别 (开发 vs. 生产)

  • 开发环境:

    • 应该显示所有错误、警告和通知,以便开发者及时发现和修复问题。

    • 可以在 PHP 脚本顶部设置:

      <?php
      ini_set('display_errors', 1); // 1 = On, 0 = Off
      ini_set('display_startup_errors', 1);
      error_reporting(E_ALL); // 报告所有类型的 PHP 错误
      ?>
      
      
      • ini_set():临时修改 php.ini 配置指令的值 (仅对当前脚本有效)。
      • display_errors:控制错误是否直接输出到浏览器。
      • error_reporting(E_ALL):设置报告所有级别的错误。PHP 定义了许多 E_ 系列常量 (如 E_ERROR, E_WARNING, E_NOTICE, E_PARSE, E_DEPRECATED 等)。
  • 生产环境:

    • 绝对不能直接向用户显示详细的 PHP 错误信息,因为这会:
      • 暴露敏感信息 (如文件路径、数据库凭据、代码片段)。
      • 影响用户体验。
    • 应该:
      • ini_set('display_errors', 0); 或在 php.ini 中设置为 Off
      • 将错误记录到服务器日志文件中 (log_errors = On, error_log = /path/to/php-error.logphp.ini 中配置)。
      • 设置自定义错误处理函数 (set_error_handler()) 来捕获传统错误,并以友好的方式处理它们 (例如,显示通用错误页面,记录错误详情)。
      • 使用 try-catch 块来处理预期可能发生的异常,并提供适当的用户反馈或回退逻辑。

示例:处理无效索引

details.php 中,如果用户在 URL 中提供了一个不存在的 index

<?php
// details.php
// ... (includes and setup) ...

$selectedIndex = (int)($_GET['index'] ?? 0);
$object = null;

// 假设 $exhibits 已经从数据库加载
if ($exhibits && $selectedIndex >= 0 && $selectedIndex < count($exhibits)) {
    $object = $exhibits[$selectedIndex];
} else {
    // 处理无效索引
    header("HTTP/1.0 404 Not Found"); // (可选) 发送 404 状态码
    echo "<h1>Exhibit Not Found</h1>";
    echo "<p>The exhibit you requested does not exist.</p>";
    echo "<p><a href='index.php'>Return to gallery</a></p>";
    require_once 'includes/footer.inc.php'; // 确保页面结构完整
    exit; // 停止脚本执行,防止后续代码输出
}

// ... (后续渲染 $object 的代码) ...
?>

  • count($exhibits) 获取数组中的元素数量。
  • 检查 $selectedIndex 是否在有效范围内。
  • 如果索引无效,输出错误消息并使用 exit; (或 die();) 终止脚本,防止它尝试渲染不存在的数据。
  • 可以考虑发送适当的 HTTP 状态码 (如 404 Not Found)。

exitdie

  • exit;die(); 功能相同,它们都会终止当前脚本的执行。
  • 可以接受一个字符串参数,在终止前输出该字符串:exit("An error occurred.");
  • 常用于在发生严重错误或处理完请求后立即停止脚本。

29-wrapping-up

课程回顾与总结

  • PHP 基础: 涵盖了 PHP 的核心概念和语法。
  • 目标: 让学员能够理解 PHP 文件,掌握服务器端渲染的基本模式。
  • 适用场景:
    • WordPress, Joomla 等 CMS 的定制 (插件、主题开发)。
    • 学习 Laravel, CodeIgniter, Symfony 等 PHP 框架的基础。
    • 维护或开发基于 PHP 的 Web 应用。

主要内容回顾

  1. 什么是 PHP?
    • 一种服务器端脚本语言。
    • PHP 本身不是 Web 服务器,它依赖 Web 服务器 (如 Apache, Nginx,或内置开发服务器) 来处理 HTTP 请求并执行 PHP 脚本。
  2. 为什么学习 PHP?
    • 仍然是 Web 开发领域的重要技能。
    • 市场需求 (维护现有系统,WordPress 生态,PHP 框架)。
    • 许多开发者仍在学习和使用。
  3. PHP 语法基础:
    • 变量 ($),数据类型 (动态类型,但支持类型提示)。
    • 字符串 (单引号、双引号、Heredoc, Nowdoc),拼接 (.),内插。
    • 数组 (索引数组、关联数组)。
    • 控制结构 (if, else, switch, for, foreach, while) 及其替代语法。
    • 函数 (定义、参数、返回值、类型提示、默认参数、命名参数)。
  4. 超全局变量:
    • $_GET, $_POST (用于处理表单数据和 URL 参数)。
    • $_SERVER (服务器和执行环境信息)。
    • $_COOKIE, $_SESSION (用于 Cookie 和会话管理,课程中简要提及)。
  5. 面向对象编程 (OOP) in PHP:
    • 类 (class),属性,方法。
    • 构造函数 (__construct),析构函数 (__destruct)。
    • 访问修饰符 (public, protected, private)。
    • 构造函数属性提升 (PHP 8.0+)。
    • 继承 (extends),接口 (implements) (简要提及)。
  6. 处理数据:
    • 从 PHP 数组加载数据。
    • 从 JSON 文件加载数据 (file_get_contents, json_decode)。
    • 数据库交互 (SQLite):
      • 使用 SQLite3 类连接和查询数据库。
      • 执行 SQL (query),获取结果 (fetchArray)。
      • 提及 PDO 作为数据库抽象层。
    • 创建 API 端点,返回 JSON 数据 (json_encode, header('Content-Type: application/json'))。
  7. 文件包含与代码组织:
    • include, require, include_once, require_once
    • 分离头部和页脚到独立文件。
    • 自动加载 (Autoloading): 使用 spl_autoload_register 动态加载类文件,避免手动 require_once
  8. 调试与错误处理:
    • var_dump() 用于调试变量。
    • phpinfo() 查看 PHP 配置。
    • try-catch 块处理异常。
    • ini_set('display_errors', ...)error_reporting(E_ALL) 控制错误显示 (开发 vs. 生产)。
    • exitdie 终止脚本。

未涵盖或可深入学习的内容

  • Traits (特性): PHP 中一种代码复用机制,用于在类之间共享方法 (类似其他语言中的 mixins)。
  • Namespaces (命名空间): 用于组织代码和避免名称冲突,尤其在大型项目和库中非常重要。
  • 高级 OOP: 抽象类、接口的深入使用、设计模式等。
  • 更多标准库函数: PHP 拥有庞大的函数库,用于处理字符串、数组、文件系统、日期时间、网络等。
  • 引用 (References): 变量按引用传递 (&$variable),函数参数按引用传递。
  • Composer: PHP 的依赖管理工具,用于管理项目库和自动加载。
  • PHP 框架: Laravel, Symfony, CodeIgniter, Laminas (原 Zend Framework), CakePHP 等。
  • 安全性: XSS 防护 ( htmlspecialchars ), SQL 注入防护 (预处理语句), CSRF 防护, 文件上传安全等。
  • 性能优化。
  • 单元测试和集成测试。

结语

  • 本课程旨在提供 PHP 的坚实基础,使学员能够理解和开始使用 PHP 进行 Web 开发。
  • 鼓励学员继续学习和探索 PHP 的更高级特性和生态系统。