openMode = $openMode; if ($context === null) { parent::__construct($filename, $openMode, $useIncludePath); } else { parent::__construct($filename, $openMode, $useIncludePath, $context); } } /** * Create a file using the given access mode and return a instance of File open for writing * * @param string $path The path to the file * @param int $accessMode The access mode to set * @param bool $recursive Whether missing nested directories of the given path should be created * * @return File * * @throws RuntimeException In case the file cannot be created or the access mode cannot be set * @throws NotWritableError In case the path's (existing) parent is not writable */ public static function create($path, $accessMode, $recursive = true) { $dirPath = dirname($path); if ($recursive && !is_dir($dirPath)) { static::createDirectories($dirPath); } elseif (! is_writable($dirPath)) { throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath)); } $file = new static($path, 'x+'); if (! @chmod($path, $accessMode)) { $error = error_get_last(); throw new RuntimeException(sprintf( 'Cannot set access mode "%s" on file "%s" (%s)', decoct($accessMode), $path, $error['message'] )); } return $file; } /** * Create missing directories * * @param string $path * * @throws RuntimeException In case a directory cannot be created or the access mode cannot be set */ protected static function createDirectories($path) { $part = strpos($path, DIRECTORY_SEPARATOR) === 0 ? DIRECTORY_SEPARATOR : ''; foreach (explode(DIRECTORY_SEPARATOR, ltrim($path, DIRECTORY_SEPARATOR)) as $dir) { $part .= $dir . DIRECTORY_SEPARATOR; if (! is_dir($part)) { if (! @mkdir($part, static::$dirMode)) { $error = error_get_last(); throw new RuntimeException(sprintf( 'Failed to create missing directory "%s" (%s)', $part, $error['message'] )); } if (! @chmod($part, static::$dirMode)) { $error = error_get_last(); throw new RuntimeException(sprintf( 'Failed to set access mode "%s" for directory "%s" (%s)', decoct(static::$dirMode), $part, $error['message'] )); } } } } #[\ReturnTypeWillChange] public function fwrite($str, $length = null) { $this->assertOpenForWriting(); $this->setupErrorHandler(); $retVal = $length === null ? parent::fwrite($str) : parent::fwrite($str, $length); restore_error_handler(); return $retVal; } public function ftruncate($size): bool { $this->assertOpenForWriting(); $this->setupErrorHandler(); $retVal = parent::ftruncate($size); restore_error_handler(); return $retVal; } #[\ReturnTypeWillChange] public function ftell() { $this->setupErrorHandler(); $retVal = parent::ftell(); restore_error_handler(); return $retVal; } public function flock($operation, &$wouldblock = null): bool { $this->setupErrorHandler(); $retVal = parent::flock($operation, $wouldblock); restore_error_handler(); return $retVal; } #[\ReturnTypeWillChange] public function fgetc() { $this->setupErrorHandler(); $retVal = parent::fgetc(); restore_error_handler(); return $retVal; } public function fflush(): bool { $this->setupErrorHandler(); $retVal = parent::fflush(); restore_error_handler(); return $retVal; } /** * Setup an error handler that throws a RuntimeException for every emitted E_WARNING */ protected function setupErrorHandler() { set_error_handler( function ($errno, $errstr, $errfile, $errline) { restore_error_handler(); throw new ErrorException($errstr, 0, $errno, $errfile, $errline); }, E_WARNING ); } /** * Assert that the file was opened for writing and throw an exception otherwise * * @throws NotWritableError In case the file was not opened for writing */ protected function assertOpenForWriting() { if (!preg_match('@w|a|\+@', $this->openMode)) { throw new NotWritableError('File not open for writing'); } } }