PHP 零基础教程

PHP 对象与资源

PHP 使用不同的数据类型来处理各种信息。除了前面学过的字符串、整数、浮点数和布尔值等基本数据类型,以及数组这种复合类型之外,PHP 还包含了对象 (Objects)资源 (Resources)。这两种数据类型分别代表了更复杂的数据结构和外部系统的连接。

虽然深入探讨“面向对象编程 (OOP)”是后续模块的主题,但本章将为你打下坚实的基础,带你初步了解什么是对象和资源,以及 PHP 通常是如何与它们交互的。

1. 理解对象 (Objects)

简单来说,对象是类 (Class) 的一个实例。你可以把“类”想象成一张设计图纸或模板,而“对象”就是根据这张图纸制造出来的具体实体。

对象将数据(称为属性 Properties)和操作这些数据的函数(称为方法 Methods)封装在一起。这种组合使得对象能够以一种非常结构化的方式来表示现实世界中的实体或抽象概念。

想象一个 Car(汽车)类。这个类可能会定义一些属性,比如 make(品牌)、model(型号)、year(年份)和 color(颜色)。它还可以定义一些方法,比如 startEngine()(启动引擎)、accelerate()(加速)或 brake()(刹车)。当你真正制造出一辆“2023款蓝色丰田凯美瑞”时,你就是基于 Car 这个设计图纸创建了一个对象

在 PHP 中,通常使用 new 关键字加上类名来创建对象。

<?php
// 定义一个简单的 Car 类(设计图纸)
class Car {
    public $make;
    public $model;
    public $year;

    // 一个用于显示汽车信息的方法
    public function getInfo() {
        return "品牌:" . $this->make . ",型号:" . $this->model . ",年份:" . $this->year;
    }
}

// 创建 Car 类的一个对象(一个实例)
$myCar = new Car();

// 给对象的属性赋值
$myCar->make = "丰田";
$myCar->model = "凯美瑞";
$myCar->year = 2023;

// 访问并显示对象的属性
echo "我的车是一辆 " . $myCar->year . " 年的 " . $myCar->make . " " . $myCar->model . "。\n";

// 调用对象的方法
echo $myCar->getInfo() . "\n";

// 创建另一辆车对象
$anotherCar = new Car();
$anotherCar->make = "本田";
$anotherCar->model = "思域";
$anotherCar->year = 2022;

echo $anotherCar->getInfo() . "\n";
?>

在这个例子中,Car 是类,而 $myCar$anotherCar 是该类的对象(实例)。每个对象都有自己独立的一套数据(品牌、型号、年份),并且可以执行动作(getInfo())。

重要概念: 当你将一个对象赋值给一个变量时,该变量存储的并不是对象本身,而是对该对象的引用 (Reference)。这意味着,如果你把一个对象变量赋值给另一个变量,这两个变量在内存中指向的将是同一个对象。

<?php
class Product {
    public $name;
    public $price;
}

$productA = new Product();
$productA->name = "笔记本电脑";
$productA->price = 1200;

$productB = $productA; // $productB 现在引用了与 $productA *相同的* 对象
$productB->price = 1150; // 改变 $productB 的价格也会改变 $productA 的价格

echo "产品 A 的价格:" . $productA->price . "\n"; // 输出:产品 A 的价格:1150
echo "产品 B 的价格:" . $productB->price . "\n"; // 输出:产品 B 的价格:1150

// 要创建一个真正的副本(一个全新、独立的对象),你需要使用 clone 关键字
$productC = clone $productA;
$productC->name = "台式机";
$productC->price = 1500;

echo "产品 A 的名称:" . $productA->name . "\n"; // 输出:产品 A 的名称:笔记本电脑
echo "产品 C 的名称:" . $productC->name . "\n"; // 输出:产品 C 的名称:台式机
?>

对象的概念是面向对象编程 (OOP) 的核心,这是一种用于构建复杂应用程序的强大编程范式。虽然本章只是浅尝辄止,但理解“对象是将相关数据和功能打包在一起”是你现阶段的关键收获。

2. 理解资源 (Resources)

资源 (Resources) 是 PHP 中的一种特殊变量,它们保存了对外部资源的引用(句柄)。这些资源通常由 PHP 的内置函数生成和处理,它们代表了 PHP 脚本本身之外的系统或设备的连接。常见的例子包括文件句柄数据库连接图像处理句柄

当 PHP 需要与服务器上的文件、数据库服务器或图像库等进行交互时,它通常会创建一个“资源”,充当指向该外部实体的指针或唯一标识符。PHP 负责管理这些资源的生命周期,通常要求在不再需要它们时显式地关闭它们,以释放系统内存。

资源的一个主要特征是,它们不能像其他数据类型那样被直接访问或查看。你不能简单地 echo 一个资源变量来查看它的内容。相反,你需要将资源变量传递给其他专门用来操作该特定资源的函数。

2.1 文件资源

最常见的资源类型之一是文件句柄。当你打开一个文件进行读取或写入时,PHP 会返回一个代表该打开文件的资源。

<?php
// 以只读模式 ('r') 打开一个文件
// fopen() 函数在成功时返回一个文件资源,失败时返回 false。
$fileHandle = fopen("example.txt", "r");

if ($fileHandle) {
    echo "文件打开成功。\$fileHandle 的类型是:" . get_resource_type($fileHandle) . "\n";
    
    // 从文件中读取内容
    $fileContent = fread($fileHandle, filesize("example.txt"));
    echo "文件内容:\n" . $fileContent . "\n";

    // 关闭文件资源
    fclose($fileHandle);
    echo "文件已关闭。\n";
} else {
    echo "打开文件失败。\n";
}

// 尝试读取一个不存在的文件的例子
$nonExistentFile = fopen("non_existent.txt", "r");
if ($nonExistentFile) {
    // 这个代码块不会执行
    fclose($nonExistentFile);
} else {
    echo "无法打开 non_existent.txt (这是预期结果)。\n";
}
?>

(在运行上面的代码之前,请在你的 PHP 脚本相同的目录下创建一个名为 example.txt 的文件,并随便写点内容)

这里使用了 get_resource_type() 函数来证明 $fileHandle 确实是一个 stream(流)类型的资源。当你不再需要访问该文件时,fclose() 会释放该资源,使其可供其他进程使用。忘记关闭资源会导致资源泄漏,这会消耗系统内存或文件句柄上限,从而降低应用程序的性能。

2.2 数据库连接资源

资源的另一个常见用途是数据库连接。虽然现代 PHP 通常使用面向对象的接口进行数据库交互(比如返回对象的 PDO),但较旧或较简单的数据库扩展可能会返回资源。

来看一个使用旧版数据库扩展的模拟场景

<?php
// 使用旧版数据库函数的模拟场景
// (注意:现代 PHP 应用程序通常使用带有对象的 PDO 或 mysqli)

// 模拟一个返回资源的数据库连接函数
function old_db_connect($host, $user, $pass, $db) {
    echo "尝试连接到数据库...\n";
    // 在真实场景中,这里会建立连接
    // 为了演示,我们只返回一个模拟的资源类型。
    return (object) ['type' => 'mysql link']; // 模拟一个资源
}

// 模拟一个查询数据库的函数
function old_db_query($connection, $sql) {
    if (gettype($connection) === 'object' && $connection->type === 'mysql link') {
        echo "执行查询:" . $sql . " (使用连接资源)。\n";
        // 在真实场景中,这里会执行 SQL 并返回一个结果资源
        return (object) ['type' => 'mysql result']; // 模拟一个结果资源
    }
    return false;
}

// 模拟一个关闭数据库连接的函数
function old_db_close($connection) {
    if (gettype($connection) === 'object' && $connection->type === 'mysql link') {
        echo "关闭数据库连接资源。\n";
        // 在真实场景中,这里会关闭连接
        return true;
    }
    return false;
}

// 建立数据库连接
$dbConnection = old_db_connect("localhost", "user", "password", "mydatabase");

if ($dbConnection) {
    echo "数据库连接已建立。类型:" . $dbConnection->type . "\n";
    
    // 执行查询
    $resultResource = old_db_query($dbConnection, "SELECT * FROM users");
    if ($resultResource) {
        echo "查询已执行,收到结果资源。类型:" . $resultResource->type . "\n";
        // 在真实的应用程序中,你接下来会从 $resultResource 中提取数据
    } else {
        echo "执行查询失败。\n";
    }

    // 关闭数据库连接
    old_db_close($dbConnection);
} else {
    echo "连接数据库失败。\n";
}
?>

上面这个例子使用模拟对象来“假装”资源,因为在 PHP 中无法直接手动创建一个 resource 类型的变量;它们总是由特定的内置函数返回。此处的目的是说明变量($dbConnection)将如何保存对外部数据库连接的引用,以及不同的函数(old_db_query, old_db_close)将如何与该资源进行交互。

3. 实用案例与演示

3.1 案例 1:操作图像资源 (GD 库)

PHP 用于处理图像的 GD 库经常使用图像资源。这个例子展示了如何创建一个简单的图像。

<?php
// 创建一个宽 200 像素、高 50 像素的图像资源
// imagecreatetruecolor() 函数返回一个图像资源。
$image = imagecreatetruecolor(200, 50);

if ($image) {
    echo "图像资源已创建。\$image 的类型是:" . get_resource_type($image) . "\n";
    
    // 分配一些颜色
    $white = imagecolorallocate($image, 255, 255, 255);
    $black = imagecolorallocate($image, 0, 0, 0);

    // 用白色填充背景
    imagefill($image, 0, 0, $white);

    // 在图像上添加一些文本
    imagestring($image, 5, 5, 10, "Hello, PHP Images!", $black);

    // 设置请求头以将图像输出为 PNG
    // header("Content-type: image/png"); // 如果你想直接在浏览器中输出,请取消注释此行

    // 将图像保存到文件
    $filename = "output_image.png";
    imagepng($image, $filename);
    echo "图像已保存为 " . $filename . "\n";

    // 销毁图像资源以释放内存
    imagedestroy($image);
    echo "图像资源已销毁。\n";
} else {
    echo "创建图像资源失败。\n";
}
?>

运行此脚本后,将在同一目录中创建一个名为 output_image.png 的文件。imagedestroy() 函数会显式释放与该图像资源关联的内存。

3.2 案例 2:检查资源类型与是否存在

你可以使用 get_resource_type() 函数来确定活动资源的类型,或者使用 is_resource() 来检查一个变量是否为资源。

<?php
// 创建一个文件资源
$fileResource = fopen("temp.txt", "w"); // 以写入模式打开

if (is_resource($fileResource)) {
    echo "\$fileResource 是一个资源。\n";
    echo "资源类型:" . get_resource_type($fileResource) . "\n";
    
    // 向文件写入内容
    fwrite($fileResource, "一些临时内容。");
    fclose($fileResource); // 关闭资源
} else {
    echo "\$fileResource 不是一个资源。\n";
}

echo "---------------------------------\n";

// 一个非资源变量
$myString = "Hello";
if (is_resource($myString)) {
    echo "\$myString 是一个资源。\n";
} else {
    echo "\$myString 不是一个资源。\n";
}

echo "---------------------------------\n";

// 一个对象变量
class MyObject {}
$myObj = new MyObject();

if (is_resource($myObj)) {
    echo "\$myObj 是一个资源。\n";
} else {
    echo "\$myObj 不是一个资源 (它是一个对象)。\n";
}
?>

输出结果清晰地表明,只有文件句柄被识别为 stream 类型的资源。这有助于我们在开发中进行调试并确保正确处理资源变量。