Ресурсы PHP: от ручного управления к автоматическому освобождению

Раздел: Архитектура PHP -> Управление ресурсами PHP

В PHP тип resource представляет собой ссылку на внешний ресурс (файл, соединение с БД, поток). До PHP 8 такие ссылки требовали явного освобождения, иначе возникали утечки. Современные версии постепенно заменяют ресурсы объектами, но legacy код все еще использует resource.

Основные подходы к управлению ресурсами

Как гарантировать освобождение ресурса в любом сценарии?

Наиболее эффективное решение - инкапсуляция ресурса в класс с деструктором и, по возможности, использование объектных типов из PHP 8 (CurlHandle, GdImage).


class FileResource {
    private $handle;
    public function __construct($filename, $mode) {
        $this->handle = fopen($filename, $mode);
        if (!$this->handle) throw new \RuntimeException('Не удалось открыть файл');
    }
    public function read() {
        return fread($this->handle, 1024);
    }
    public function __destruct() {
        if ($this->handle) {
            fclose($this->handle);
        }
    }
}
$file = new FileResource('test.txt', 'r');
echo $file->read();
// при завершении скрипта деструктор закроет файл автоматически

Resource php (ресурсы в php)

Проблемы: деструктор может быть вызван в неожиданный момент, если объект долго живет. Циклические ссылки не дают вызвать деструктор - нужно использовать сборщик мусора, но в PHP он справляется. Для критичных ресурсов (соединения БД) лучше явно закрывать в finally.

Как избежать утечки при возникновении исключений?

Традиционный способ - блок try-finally, где в finally выполняется закрытие ресурса.


$handle = fopen('log.txt', 'a');
try {
    fwrite($handle, 'Данные');
} finally {
    fclose($handle);
}

Проблемы: при нескольких ресурсах код становится громоздким, легко забыть закрыть все. Решение - использовать вложенные try-finally или обертку.

Какие объектные альтернативы существуют для работы с файлами и базами данных?

Вместо fopen/fclose рекомендуется использовать SplFileObject, PDO, mysqli. Они сами управляют ресурсом.


$file = new SplFileObject('data.csv');
foreach ($file as $line) {
    echo $line;
}
// закрытие происходит при уничтожении объекта

Проблемы: не все типы ресурсов имеют объектные альтернативы (например, расширения stream).

Расширенные примеры работы с ресурсами

Ниже приведены примеры с подробными пояснениями и результатами выполнения.

Пример 1: Ручное закрытие нескольких файлов с вложенным try-finally

Пример

$src = fopen('source.txt', 'r');
$dst = fopen('dest.txt', 'w');
try {
    while (!feof($src)) {
        $data = fread($src, 4096);
        fwrite($dst, $data);
    }
} finally {
    fclose($src);
    fclose($dst);
}
Файлы закрыты независимо от успеха операции.

Пример 2: Класс-обертка для ресурса изображения GD

Пример

class GdImageResource {
    private $image;
    public function __construct($width, $height) {
        $this->image = imagecreatetruecolor($width, $height);
        if (!$this->image) throw new \RuntimeException('Не удалось создать изображение');
    }
    public function getImage() { return $this->image; }
    public function __destruct() {
        if ($this->image) {
            imagedestroy($this->image);
        }
    }
}
$img = new GdImageResource(100, 100);
$color = imagecolorallocate($img->getImage(), 255, 0, 0);
imagefill($img->getImage(), 0, 0, $color);
imagepng($img->getImage(), 'red.png');
// деструктор автоматически вызовет imagedestroy
Изображение red.png создано. Ресурс освобожден при завершении скрипта.

Пример 3: Использование SplFileObject для записи и чтения

Пример

$file = new SplFileObject('example.csv', 'w');
$file->fputcsv(['Name', 'Age']);
$file->fputcsv(['Alice', 30]);
unset($file);

$file = new SplFileObject('example.csv', 'r');
foreach ($file as $row) {
    echo $row;
}
Name,Age
Alice,30

Пример 4: PDO с автоматическим закрытием соединения

Пример

$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query('SELECT * FROM users');
foreach ($stmt as $row) {
    echo $row['name'];
}
// при уничтожении объекта PDO соединение закрывается
(вывод данных из БД)

Пример 5: Универсальная обертка для любого ресурса

Пример

class ResourceWrapper {
    private $resource;
    private $closer;
    public function __construct($resource, callable $closer) {
        $this->resource = $resource;
        $this->closer = $closer;
    }
    public function getResource() { return $this->resource; }
    public function __destruct() {
        if ($this->resource) {
            ($this->closer)($this->resource);
            $this->resource = null;
        }
    }
}
$curl = curl_init('https://example.com');
$wrapper = new ResourceWrapper($curl, 'curl_close');
// ... работа с $wrapper->getResource()
// при выходе из области видимости curl будет закрыт
(нет вывода, ресурс закрыт автоматически)

Пример 6: Использование CurlHandle в PHP 8

Пример

$handle = curl_init('https://example.com');
// $handle - объект CurlHandle, а не resource
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($handle);
// деструктор объекта CurlHandle автоматически вызовет curl_close
(содержимое страницы)

Ресурсы в PHP - comments

En
Resource php (php)