readfile()・file_get_contents()・file_put_contents()を例外処理・ロック処理できるようにしたクラス

More than 5 years have passed since last update.

<?php

/**
* File
*
* A wrapper class for lockable readfile(), file_get_contents(), file_put_contents().
* Requires PHP 5.0.0 or later.
*
* @Version 2.1.0
* @Author CertaiN
* @License BSD 2-Clause
* @GitHub http://github.com/Certainist/File
*/

class File {

/**
* @constant integer For setBlockMode().
*/

const BLOCKMODE_YES = 0;
const BLOCKMODE_NO = 4;

/**
* @access private
* @static
*/

private static $blockmode = self::BLOCKMODE_YES;

/**
* @access private
*/

private $fp;
private $locked;
private $headers_preset;
private $headers_list;

/**
* Set blocking mode on locking.
* This only means FILE-LOCK-BLOCKING-MODE, not means STREAM-BLOCKING-MODE.
* BLOCKMODE_NO is not supported on windows.
*
* @access public
* @static
* @param integer $mode File::BLOCKMODE_XXX
*/

public static function setBlockMode($mode) {
if (!is_int($mode) || $mode !== 0 || $mode !== 4) {
throw new InvalidArgumentException('Invalid block mode');
}
self::$blockmode = $mode;
}

/**
* Use instead of file_get_contents().
* Automatically locked by LOCK_SH mode.
*
* @access public
* @static
* @param string $filename
* @return string
*/

public static function get($filename) {
$file = self::factory($filename, 'rb');
if (!$file->locked = flock($file->fp, LOCK_SH | self::$blockmode)) {
throw new RuntimeException('Failed to lock file ' . $filename);
} elseif (false === $ret = stream_get_contents($file->fp)) {
throw new RuntimeException('Unknown stream error on ' . $filename);
}
return $ret;
}

/**
* Use instead of file_put_contents().
* Automatically locked by LOCK_EX mode.
*
* @access public
* @static
* @param string $filename
* @param string $data
* @return integer The number of written bytes.
*/

public static function write($filename, $data) {
$file = self::factory($filename, 'ab');
if (!$file->locked = flock($file->fp, LOCK_EX | self::$blockmode)) {
throw new RuntimeException('Failed to lock file ' . $filename);
} elseif (!ftruncate($file->fp, 0) or false === $ret = fwrite($file->fp, $data)) {
throw new RuntimeException('Unknown stream error on ' . $filename);
}
return $ret;
}

/**
* Use instead of readfile().
* Automatically locked by LOCK_SH mode.
* This also supports automatically sending Content-Type header.
*
* @access public
* @static
* @param string $filename
* @param boolean [$send_header] Whether automatically send Content-Type header.
* @return integer The number of read bytes.
*/

public static function read($filename, $send_header = false) {
$file = self::factory($filename, 'rb');
if ($send_header && headers_sent()) {
throw new RuntimeException('Headers are already sent');
} elseif (!$file->locked = flock($file->fp, LOCK_SH | self::$blockmode)) {
throw new RuntimeException('Failed to lock file ' . $filename);
}
if ($send_header) {
$finfo = new finfo(FILEINFO_MIME);
if (false === $type = $finfo->file($filename)) {
throw new RuntimeException('Failed to get content type of ' . $filename);
}
header('Content-Type: ' . $type);
$file->headers_preset = true;
}
if (false === $ret = fpassthru($file->fp)) {
throw new RuntimeException('Unknown stream error on ' . $filename);
}
$file->headers_preset = false;
return $ret;
}

/**
* Headers for downloading are automatically sent.
* This also enables users to download multibyte-named files properly.
*
* @access public
* @param string $input_filename Filename in local,
* and used for downloading when $output_filename omitted.
* @param string [$output_filename] Filename used for downloading, automatically converted to UTF-8.
* @return integer The number of read bytes.
*/

public static function download($input_filename, $output_filename = '') {
static $pattern = '/Chrome|Firefox|(Opera)|(MSIE|IEMobile)|(Safari)/';
$file = self::factory($input_filename, 'rb');
if (headers_sent()) {
throw new RuntimeException('Headers are already sent');
} elseif (!$file->locked = flock($file->fp, LOCK_SH | self::$blockmode)) {
throw new RuntimeException('Failed to lock file ' . $input_filename);
}
$finfo = new finfo(FILEINFO_MIME);
if (false === $type = $finfo->file($input_filename)) {
throw new RuntimeException('Failed to get content type of ' . $input_filename);
} elseif (false === $size = filesize($input_filename)) {
throw new RuntimeException('Failed to get size of ' . $input_filename);
} elseif ($output_filename === '') {
$output_filename = $input_filename;
}
$output_filename = mb_convert_encoding(
$output_filename,
'UTF-8',
'ASCII,JIS,UTF-8,CP51932,SJIS-win'
);
header('Content-Type: ' . $type);
header('Content-Length: ' . $size);
switch (true) {
case !isset($_SERVER['HTTP_USER_AGENT']):
case !preg_match($pattern, $_SERVER['HTTP_USER_AGENT'], $matches):
case !isset($matches[1]):
$enc = '=?utf-8?B?' . base64_encode($output_filename) . '?=';
header('Content-Disposition: attachment; filename="' . $enc . '"');
break;
case !isset($matches[2]):
$enc = "utf-8'ja'" . urlencode($output_filename);
header('Content-Disposition: attachment; filename*=' . $enc);
break;
case !isset($matches[3]):
$enc = urlencode($output_filename);
header('Content-Disposition: attachment; filename="' . $enc . '"');
break;
default:
header('Content-Disposition: attachment; filename="' .$output_filename . '"');
}
$file->headers_preset = true;
if (false === $ret = fpassthru($file->fp)) {
throw new RuntimeException('Unknown stream error on ' . $filename);
}
$file->headers_preset = false;
return $ret;
}

private static function factory($filename, $mode) {
if (!$fp = @fopen($filename, $mode)) {
$error = error_get_last();
throw new RuntimeException(sprintf(
'Failed to open file: %s; %s',
$filename,
$error['message']
));
}
return new self($fp);
}

private function __construct($fp) {
$this->fp = $fp;
$this->headers_list = headers_list();
}

private function __destruct() {
if ($this->locked) {
flock($this->fp, LOCK_UN);
}
if ($this->headers_preset && !headers_sent()) {
header_remove();
foreach ($this->headers_list as $header) {
header($header, false);
}
}
fclose($this->fp);
}

}

機能を追加しました。

勝手ながら、CC0ライセンスから2条項BSDライセンスに切り替えさせていただきます。

https://github.com/Certainist/File