Ресурсы 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
(содержимое страницы)