PHP: fcloseを明示的に呼ばない場合、PHPはいつリソースをクローズしているのか？

PHPには`fclose`というリソースを開放する関数があるが、それを呼ばなくても実際はさほど問題になることが少ないように思える。なぜかというと、`fclose`が呼ばれなかったリソースはPHPが自動的に開放してくれるからだ。

では、PHPはいつ暗黙的にリソースをクローズしているのだろうか？

## 明示的にリソースを開放するパターン

まずは明示的にリソースを開放するパターンを調べておこうと思う。

```php:fclose.php
<?php

function open_file()
{
    echo 'begin of function';
    $fp = fopen('test', 'w+b');
    fwrite($fp, 'write something');
    fclose($fp);
    echo 'end of function';
}

echo 'before scope';

open_file();

echo 'after scope';
```

straceでLinuxのシステムコールを確認する:

```
strace php fclose.php
```

結果:

```c
// ...[略]...
write(1, "before scope", 12before scope)            = 12
write(1, "begin of function", 17begin of function)       = 17
getcwd("/app", 4096)                    = 5
lstat("/app/test", {st_mode=S_IFREG|0644, st_size=15, ...}) = 0
open("/app/test", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3

// ↑ここでfopen("test")がなされ、ファイルディスクリプタ3が得られる

fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
write(3, "write something", 15)         = 15
close(3)                                = 0

// ↑ここでファイルディスクリプタ3が開放される

write(1, "end of function", 15end of function)         = 15
write(1, "after scope", 11after scope)             = 11
// ...[略]...
```

これは期待通りのタイミングで開放されている。

## 明示的に`fclose`しない場合(ローカルスコープ)

次に明示的に`fclose`を呼ばない場合のシステムコールを見てみよう。

```php:fclose.php
<?php

function open_file()
{
    echo 'begin of function';
    $fp = fopen('test', 'w+b');
    fwrite($fp, 'write something');
    echo 'end of function';
}

echo 'before scope';

open_file();

echo 'after scope';
```

straceの結果:

```c
// ...[略]...
write(1, "before scope", 12before scope)            = 12
write(1, "begin of function", 17begin of function)       = 17
getcwd("/app", 4096)                    = 5
lstat("/app/test", {st_mode=S_IFREG|0644, st_size=15, ...}) = 0
open("/app/test", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3

// ↑ここでfopen("test")がなされ、ファイルディスクリプタ3が得られる

fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
write(3, "write something", 15)         = 15
write(1, "end of function", 15end of function)         = 15
close(3)                                = 0

// ↑ここでファイルディスクリプタ3が開放される

write(1, "after scope", 11after scope)             = 11
close(2)                                = 0
close(1)                                = 0
close(0)                                = 0
// ...[略]...
```

ファイルポインタのスコープ変数`$fp`がスコープ外になったタイミングでリソースが開放されるようだ。

## 明示的に`fclose`しない場合(グローバルスコープ)

もしファイルポインタの変数がグローバルスコープだったらリソースはいつ開放されるか？ 見てみよう。

```php:fclose.php
<?php

echo 'begin of script';

$fp = fopen('test', 'w+b');
fwrite($fp, 'write something');

echo 'end of script';
```

straceの結果:

```c
// ...[略]...
write(1, "begin of script", 15begin of script)         = 15
getcwd("/app", 4096)                    = 5
lstat("/app/test", {st_mode=S_IFREG|0644, st_size=15, ...}) = 0
open("/app/test", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3

// ↑ここでfopen("test")がなされ、ファイルディスクリプタ3が得られる

fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
write(3, "write something", 15)         = 15
write(1, "end of script", 13end of script)           = 13
close(3)                                = 0

// ↑ここでファイルディスクリプタ3が開放される

close(2)                                = 0
close(1)                                = 0
close(0)                                = 0
munmap(0x7f36219ef000, 2163704)         = 0
munmap(0x7f3621789000, 2513096)         = 0
munmap(0x7f362b6d4000, 196608)          = 0
munmap(0x7f3621c00000, 2097152)         = 0
munmap(0x7f362b65a000, 331776)          = 0
brk(0x56223a8d0000)                     = 0x56223a8d0000
futex(0x7f36298cca2c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
exit_group(0)                           = ?
+++ exited with 0 +++
// ...[略]...
```

スクリプトが終了するタイミングで開放されるようだ。

## 結論

以上の調査結果をまとめると:

* fclose()を明示的に呼んだときは、そのタイミングでリソースが開放される。
* ファイルポインタがローカル変数のときは、変数スコープ外になったときにリソースが暗黙的に開放される。
* ファイルポインタがグローバル変数のときは、スクリプトが終了するタイミングでリソースが暗黙的に開放される。

リソースのライフサイクルも変数のライフサイクルと同じと考えて良さそう。

## 参考文献

* [システムコールをstraceで確認する - Qiita](https://qiita.com/suin/items/24652a111eb81a31f0ef)