PHP 入門

PHP オブジェクトとリソース

PHP は、さまざまな情報を処理するために異なるデータタイプ(データ型)を使用します。これまでに学んだストリング、インテジャー、フロート、ブーリアンなどの基本データタイプ、およびアレイ(配列)というコンポジットタイプに加えて、PHP には オブジェクト (Objects)リソース (Resources) が含まれています。これら 2 つのデータタイプは、それぞれより複雑なデータ構造と外部システムとの接続を表します。

「オブジェクト指向プログラミング (OOP)」の詳細な探究は後のモジュールのテーマとなりますが、本章ではオブジェクトとリソースとは何か、そして PHP が通常どのようにそれらとインタラクションするのかについて、強固な基礎を築いていきましょう。

1. オブジェクト (Objects) の理解

簡単に言えば、オブジェクトは クラス (Class) の一つの インスタンス (Instance) です。「クラス」を設計図やテンプレートだと考えれば、「オブジェクト」はその設計図に基づいて作られた具体的な実体です。

オブジェクトは、データ(プロパティ (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())を実行できます。

1.1 オブジェクトのリファレンスとコピー

重要なコンセプト: オブジェクトをバリアブル(変数)に代入する際、そのバリアブルに格納されるのはオブジェクト自体ではなく、そのオブジェクトへの リファレンス (Reference) です。つまり、あるオブジェクトバリアブルを別のバリアブルに代入すると、これら 2 つのバリアブルはメモリ上の 同じ オブジェクトを指すことになります。

<?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"; // 出力:1150
echo "プロダクト B の価格:" . $productB->price . "\n"; // 出力:1150

// 本物のコピー(完全に独立した新しいオブジェクト)を作成するには、clone キーワードを使用します
$productC = clone $productA;
$productC->name = "デスクトップPC";
$productC->price = 1500;

echo "プロダクト A の名称:" . $productA->name . "\n"; // 出力:ノートパソコン
echo "プロダクト C の名称:" . $productC->name . "\n"; // 出力:デスクトップPC
?>

オブジェクトの概念は、複雑なアプリケーションを構築するための強力なパラダイムである「オブジェクト指向プログラミング (OOP)」の核となります。本章では表面をなぞるだけですが、「オブジェクトは関連するデータと機能をパッケージ化したものである」と理解することが現段階での鍵となります。

2. リソース (Resources) の理解

リソース (Resources) は PHP における特殊なバリアブルで、外部リソースへの参照(ハンドル (Handle))を保持します。これらリソースは通常、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
// 幅 200px、高さ 50px の画像リソースを作成
// 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 タイプのリソースとして認識されていることが明確にわかります。これは、開発中のデバッグや、リソースバリアブルを正しく処理できているかを確認する際に非常に役立ちます。