<?php
namespace Engine\Mvc\Response\Components; class File { protected $filePath = ""; protected $options = [ 'deleteAfterSend' => false, 'debug' => false, 'ignoreUserAbort' => false, 'downloadMethod' => 'readfile', ]; public function __construct($filePath) { if ($filePath && !\Engine\File::check($filePath)) { throw new \Exception(sprintf("File %s does not exist", $filePath)); } $this->filePath = ($filePath?:""); } public function append($filePath) { throw new \Exception("You cannot append data on a file reponse"); } public function set($filePath) { if ($filePath && !\Engine\File::check($filePath)) { throw new \Exception(sprintf("File %s does not exist", $filePath)); } $this->filePath = $filePath; } public function get() { return new \SplFileInfo($this->filePath); } public function setOption($key, $value) { $this->options[$key] = $value; return $this; } public function hasContent() { return \Engine\File::check($this->filePath); } public function asString() { $previousIgnoreUserAbort = null; try { if ($this->options['debug']) { $memBefore = memory_get_usage(true); $connStatus = $this->getConnectionStatusText(connection_status()); \Log::getInstance()->info(sprintf("[%s] Starting file download: %s, Memory: %d, Connection: %s", __METHOD__, $this->filePath, $memBefore, $connStatus)); } if ($this->options['ignoreUserAbort']) { $previousIgnoreUserAbort = ignore_user_abort(); ignore_user_abort(true); if ($this->options['debug']) { \Log::getInstance()->info(sprintf("[%s] Enabled ignore_user_abort for file: %s", __METHOD__, $this->filePath)); } } switch ($this->options['downloadMethod']) { case 'fread:50kbps': $fp = fopen($this->filePath, 'rb'); if ($fp === FALSE) { throw new \Exception(sprintf("Cannot open file %s", $this->filePath)); } while (!feof($fp)) { $chunk = fread($fp, 50 * 1024); echo $chunk; flush(); sleep(1); } fclose($fp); break; case 'readfile': default: if (readfile($this->filePath) === FALSE) { if ($this->options['debug']) { \Log::getInstance()->error(sprintf("[%s] Failed to read file: %s", __METHOD__, $this->filePath)); } throw new \Exception(sprintf("Cannot read data from file %s", $this->filePath)); } break; } if ($this->options['debug']) { $memAfter = memory_get_usage(true); $connStatus = $this->getConnectionStatusText(connection_status()); \Log::getInstance()->info(sprintf("[%s] File successfully sent: %s, Memory: %d (Diff: %d), Connection: %s", __METHOD__, $this->filePath, $memAfter, ($memAfter - $memBefore), $connStatus)); } if ($this->options['deleteAfterSend']) { if ($this->options['debug']) { \Log::getInstance()->info(sprintf("[%s] Attempting to delete file: %s", __METHOD__, $this->filePath)); } \Engine\File::delete($this->filePath); if ($this->options['debug']) { \Log::getInstance()->info(sprintf("[%s] File deleted: %s", __METHOD__, $this->filePath)); } } return ''; } catch (\Exception $e) { if ($this->options['debug']) { \Log::getInstance()->error(sprintf("[%s] Exception in asString: %s", __METHOD__, $e->getMessage())); } throw $e; } finally { if ($this->options['ignoreUserAbort'] && $previousIgnoreUserAbort !== null) { ignore_user_abort($previousIgnoreUserAbort); if ($this->options['debug']) { \Log::getInstance()->info(sprintf("[%s] Restored ignore_user_abort to previous value for file: %s", __METHOD__, $this->filePath)); } } } } public function __destruct() { if ($this->options['deleteAfterSend'] && \Engine\File::check($this->filePath)) { if ($this->options['debug']) { $memUsage = memory_get_usage(true); $connStatus = $this->getConnectionStatusText(connection_status()); \Log::getInstance()->info(sprintf("[%s] Destructor: Deleting file: %s, Memory: %d, Connection: %s", __METHOD__, $this->filePath, $memUsage, $connStatus)); } \Engine\File::delete($this->filePath); } } protected function getConnectionStatusText($status) { switch ($status) { case CONNECTION_NORMAL: return 'normal'; case CONNECTION_ABORTED: return 'aborted'; case CONNECTION_TIMEOUT: return 'timeout'; case CONNECTION_ABORTED | CONNECTION_TIMEOUT: return 'aborted, timeout'; default: return 'unknown (' . $status . ')'; } } } 