SplFileObject
ПХП, как известно, вырос из процедурного языка — во многих местах до сих пор видны «уши» процедурного подхода. Одно из таких мест — работа с файлами. Большинство ребят используют тут исключительно процедурный подход. Что-то вроде:
$fp = @fopen("test.log", "ab");
if ($fp !== false) {
// в реальном коде надо бы проверить результат этих функций,
// тут упрощённый код для иллюстрации принципа
flock($fp, LOCK_EX);
fwrite($fp, "Log line\n");
fclose($fp);
} else {
throw new \RuntimeException(error_get_last()['message']);
}
К счастью с 2005 года в языке существует класс SplFileObject, который умеет делать тоже самое, но в ООП-стиле.
Единственный его недостаток — у него нет операции закрытия файла. Видимо предлагается полагаться на сборку мусора, что не очень хорошо, ведь у блокирующего процесса сборка может затянуться в силу каких-то причин и блокируемый будет ждать непозволительно долго. Я думаю это проблему вполне можно решать ручным снятием блокировки:
$fp = new \SplObjectFile("test.log", "ab");
$fp->flock(LOCK_EX);
$fp->fwrite("Log line\n");
$fp->flock(LOCK_UN);
Интересно, что когда я пытался продемонстрировать проблему с блокировками, то довольно быстро накидал код, в котором она происходила:
$x = [
new \SplFileObject('test.log', 'ab'),
&$x
];
$x[0]->flock(LOCK_EX);
unset($x);
echo "Written\n";
sleep(10);
Если попробовать этот код запустить в двух разных консолях, то второй процесс будет ждать первый. По крайней мере по «МакОС» и ПХП 7.4.
Но, если заменить unset на $x = null, произойдёт удивительное — второй процесс получит возможность записать в файл сразу же, то есть сборщик мусора убирает его моментально. Я не знал о таком различии между этими двумя способами уничтожения объекта. Любопытно — с чем это связано и изменится ли в будущем?
Добавлено позднее: я догадался почему такой эффект, попробуйте догадаться тоже.
Так unset($x) и $x = null не равноценны-же. Даже в 5-ке, насколько помню, так-же.
Первый — удаляет объект, обнуляет ссылку. Второй делает только последнее.
Не понял, что значит «удаляет объект»?
https://3v4l.org/EDQEn/vld#output вот тут можно увидеть оп-коды, в них видна (3-я и 7-я строки) разница в работе unset vs =null.
https://github.com/php/php-src/blob/12fec7a14ef28a8193e4563cb9ce71d81ada56c0/Zend/zend_API.c#L3924 — тут (не уверен, что именно это место, даже если нет — сути не меняет) видна разная реализация, которая позволяет прикинуть что и как...
Не понимаю чем тут помогут опкоды.
Комменты не удобные ((
В чём неудобство?
unset($x) в вашем коде удаляет массив, а не объект. Это то, что вы хотели написать?
Да.
Перенесу комментарий Никиты Попова сюда:
Hi,
Nice example ;) The difference is this:
$x = [new \SplFileObject(’test.log’, ’ab’), &$x];
unset($x);
unset() here will unset the variable $x, but not look through the reference that’s stored inside. So you are still left with a self-referencing array, which will be collected later by the cycle collector. We say that unset() (and the =& operator) are «reference-breaking» for this reason.
$x = [new \SplFileObject(’test.log’, ’ab’), &$x];
$x = null;
The simple assignment looks through references. So both the variable $x will become null, and the reference to $x inside the array. You are left with [SplFileObject, null], which doesn’t have a cycle and is immediately destroyed.
In real code (where the cycle will usually be through objects, not references in arrays), you will not see a difference between unset() or $x = null.