Skip to content

Instantly share code, notes, and snippets.

@ProjectOrangeBox
Last active June 29, 2022 18:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ProjectOrangeBox/43e9823f44d61e2e959056fb9c8a5ac7 to your computer and use it in GitHub Desktop.
Save ProjectOrangeBox/43e9823f44d61e2e959056fb9c8a5ac7 to your computer and use it in GitHub Desktop.
File System Wrappers based on ROOT folder
<?php
/**
* File System Functions
*
* File System Abstraction which automatically
* works in a given root path
*
* Can be added with composer by adding a composer.json file with:
*
*"autoload": {
* "files": ["src/fs.php"]
* }
*/
class fs
{
const JSONFLAGS = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE;
protected static $rootPath = '';
protected static $rootLength = 0;
protected static $autoGenerateDirectories = true;
protected static $exceptionOnMissing = true;
/**
* set application root directory
*
* @param string $path
* @return void
*/
public static function setRoot(string $path, bool $chdir = true): void
{
/* Returns canonicalized absolute pathname */
$realpath = \realpath($path);
if (!$realpath) {
throw new \FileSystemException(__METHOD__ . ' "' . $path . '" is not a valid directory.');
}
self::$rootPath = $realpath;
/* calculate it once here */
self::$rootLength = \strlen($realpath);
if ($chdir) {
\chdir(self::$rootPath);
}
}
/**
* returns the current root
*
* @return string
*/
public static function getRoot(): string
{
return self::$rootPath;
}
public static function exceptionOnMissing(bool $bool = null): bool
{
if (\is_bool($bool)) {
self::$exceptionOnMissing = $bool;
}
return self::$exceptionOnMissing;
}
public static function autoGenerateDirectories(bool $bool = null): bool
{
if (\is_bool($bool)) {
self::$autoGenerateDirectories = $bool;
}
return self::$autoGenerateDirectories;
}
/**
* Format a given path so it's based on the applications root folder __ROOT__.
*
* Either add or remove __ROOT__ from path
*
* @param string $path
* @param bool $remove true
* @return string
*/
public static function resolve(string $path, bool $remove = false): string
{
if (!self::$rootPath) {
throw new \FileSystemException(__METHOD__ . ' root path is not defined. Use fs::setRoot(...).');
}
/* strip it if root path is already present */
$pathFromRoot = (\substr($path, 0, self::$rootLength) == self::$rootPath) ? \substr($path, self::$rootLength) : $path;
/* now resolve - stripped or added? */
return ($remove) ? \rtrim($pathFromRoot, DIRECTORY_SEPARATOR) : self::$rootPath . DIRECTORY_SEPARATOR . \trim($pathFromRoot, DIRECTORY_SEPARATOR);
}
/**
* Method required
*
* @param string $path [explicite description]
*
* @return bool
*/
public static function required(string $path): bool
{
$path = self::resolve($path);
$success = \file_exists($path);
if (self::$exceptionOnMissing && !$success) {
throw new \FileSystemException('No such file or directory. ' . $path);
}
return $success;
}
/**
* @param string $path
* @return mixed
* @throws Exception
*/
public static function require(string $path)
{
return (self::required($path)) ? require(self::resolve($path)) : false;
}
/**
* @param string $path
* @return mixed
* @throws Exception
*/
public static function require_once(string $path)
{
return (self::required($path)) ? require_once(self::resolve($path)) : false;
}
public static function requireOnce(string $path)
{
return self::require_once($path);
}
/**
* @param string $path
* @return mixed
* @throws Exception
*/
public static function include(string $path)
{
return (self::required($path)) ? include(self::resolve($path)) : false;
}
/**
* @param string $path
* @return mixed
* @throws Exception
*/
public static function include_once(string $path)
{
return (self::required($path)) ? include_once(self::resolve($path)) : false;
}
public static function includeOnce(string $path)
{
return self::include_once($path);
}
/**
* Find pathnames matching a pattern
*
* @param string $pattern
* @param int $flags
* @param bool $recursive false
* @return array
*/
public static function glob(string $pattern, int $flags = 0, bool $recursive = false, bool $strip = true): array
{
$pattern = self::resolve($pattern);
if ($recursive) {
$files = self::globr($pattern, $flags, $strip);
} else {
$files = \glob($pattern, $flags);
$files = ($strip) ? self::stripRootPath($files) : $files;
}
return $files;
}
/**
* internal recursive loop for globr
*
* @param string $pattern
* @param int $flags
* @return array
*/
public static function globr(string $pattern, int $flags = 0, bool $strip = true): array
{
$pattern = self::resolve($pattern);
$files = \glob($pattern, $flags);
foreach (\glob(\dirname($pattern) . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR | GLOB_NOSORT) as $directory) {
/* recursive loop */
$files = \array_merge($files, self::globr($directory . DIRECTORY_SEPARATOR . \basename($pattern), $flags, $strip));
}
return ($strip) ? self::stripRootPath($files) : $files;
}
/**
* Reads entire file into a string
*
* @param string $path
* @return string
*/
public static function file_get_contents(string $path): string
{
return (self::required($path)) ? \file_get_contents(self::resolve($path)) : '';
}
public static function fileGetContents(string $path): string
{
return self::file_get_contents($path);
}
/**
* Returns trailing name component of path
*
* @param string $path
* @param string $suffix
* @return string
*/
public static function basename(string $path, string $suffix = ''): string
{
return (self::required($path)) ? \basename(self::resolve($path), $suffix) : '';
}
/**
* Returns information about a file path
*
* @param string $path
* @param int $options
* @return mixed
*/
public static function pathinfo(string $path, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME) /* mixed */
{
$pathinfo = ($options > 0) ? [] : '';
if (self::required($path)) {
$pathinfo = \pathinfo(self::resolve($path), $options);
/* resolve path */
if (\is_array($pathinfo)) {
if (isset($pathinfo['dirname'])) {
$pathinfo['dirname'] = self::resolve($pathinfo['dirname'], true);
}
} elseif ($options == PATHINFO_DIRNAME) {
$pathinfo = self::resolve($pathinfo, true);
}
}
return $pathinfo;
}
/**
* Reads a file and writes it to the output buffer.
*
* @param string $path
* @return int
*/
public static function readfile(string $path) /* int|false */
{
return (self::required($path)) ? \readfile(self::resolve($path)) : false;
}
/**
* dirname — Returns a parent directory's path
*
* @param string $path
* @param int $levels The number of parent directories to go up.
* @return string
*/
public static function dirname(string $path, int $levels = 1): string
{
return (self::required($path)) ? self::resolve(\dirname(self::resolve($path, true), $levels), true) : '';
}
/**
* filesize — Gets file size
*
* @param string $path
* @return int
*/
public static function filesize(string $path): int
{
return (self::required($path)) ? \filesize(self::resolve($path)) : 0;
}
/**
* is_dir — Tells whether the filename is a directory
*
* @param string $path
* @return bool
*/
public static function is_dir(string $path): bool
{
return (self::required($path)) ? \is_dir(self::resolve($path)) : false;
}
public static function isDir(string $path): bool
{
return self::is_dir($path);
}
/**
* is_writable — Tells whether the filename is writable
*
* @param string $path
* @return bool
*/
public static function is_writable(string $path): bool
{
return (self::required($path)) ? \is_writable(self::resolve($path)) : false;
}
/**
* Method is_writeable
*
* @param string $path [explicite description]
*
* @return bool
*/
public static function is_writeable(string $path): bool
{
return self::is_writable($path);
}
public static function isWriteable(string $path): bool
{
return self::is_writable($path);
}
/**
* chgrp — Changes file group
*
* @param string $path
* @param mixed $group
* @return bool
*/
public static function chgrp(string $path, $group): bool
{
return (self::required($path)) ? \chgrp(self::resolve($path), $group) : false;
}
/**
* Method changeGroup
*
* @param string $path [explicite description]
* @param $group $group [explicite description]
*
* @return bool
*/
public static function changeGroup(string $path, $group): bool
{
return self::chgrp($path, $group);
}
/**
* chmod — Changes file mode
*
* @param string $path
* @param int $mode
* @return bool
*/
public static function chmod(string $path, int $mode): bool
{
return (self::required($path)) ? \chmod(self::resolve($path), $mode) : false;
}
/**
* Method changeModify
*
* @param string $path [explicite description]
* @param int $mode [explicite description]
*
* @return bool
*/
public static function changeModify(string $path, int $mode): bool
{
return self::chmod($path, $mode);
}
/**
* chown — Changes file owner
*
* @param string $path
* @param string $user
* @return bool
*/
public static function chown(string $path, string $user): bool
{
return (self::required($path)) ? \chown(self::resolve($path), $user) : false;
}
/* wrapper */
public static function changeOwner(string $path, string $user): bool
{
return self::chown($path, $user);
}
/**
* is_file — Tells whether the filename is a regular file
*
* @param string $path
* @return bool
*/
public static function is_file(string $path): bool
{
return (self::required($path)) ? \is_file(self::resolve($path)) : false;
}
public static function isFile(string $path): bool
{
return self::is_file($path);
}
/**
* fileatime — Gets last access time of file
*
* @param string $path
* @return int
*/
public static function fileatime(string $path): int
{
return (self::required($path)) ? \fileatime(self::resolve($path)) : 0;
}
/**
* filectime — Gets inode change time of file
*
* @param string $path
* @return int
*/
public static function filectime(string $path): int
{
return (self::required($path)) ? \filectime(self::resolve($path)) : 0;
}
/**
* filemtime — Gets file modification time
*
* @param string $path
* @return int
*/
public static function filemtime(string $path): int
{
return (self::required($path)) ? \filemtime(self::resolve($path)) : 0;
}
/**
* filegroup — Gets file group
*
* @param string $path
* @return int
*/
public static function filegroup(string $path): int
{
return (self::required($path)) ? \filegroup(self::resolve($path)) : 0;
}
/**
* fileowner — Gets file owner
*
* @param string $path
* @return int
*/
public static function fileowner(string $path): int
{
return (self::required($path)) ? \fileowner(self::resolve($path)) : 0;
}
/**
* fileperms — Gets file permissions
*
* @param string $path
* @return int
*/
public static function fileperms(string $path): int
{
return (self::required($path)) ? \fileperms(self::resolve($path)) : 0;
}
/**
* fileinode — Gets file inode
*
* @param string $path
* @return int
*/
public static function fileinode(string $path): int
{
return (self::required($path)) ? \fileinode(self::resolve($path)) : 0;
}
/**
* filetype — Gets file type
*
* @param string $path
* @return string
*/
public static function filetype(string $path): string
{
return (self::required($path)) ? \filetype(self::resolve($path)) : 0;
}
/**
* stat — Gives information about a file
*
* @param string $path
* @return array
* @throws Exception
*/
public static function stat(string $path): array
{
return (self::required($path)) ? \stat(self::resolve($path)) : [];
}
/**
* file_exists — Checks whether a file or directory exists
*
* @param string $path
* @return bool
*/
public static function file_exists(string $path): bool
{
return \file_exists(self::resolve($path));
}
public static function fileExists(string $path): bool
{
return self::file_exists($path);
}
/**
* file — Reads entire file into an array
*
* @param string $path
* @param int $flags
* @return array
*/
public static function file(string $path, int $flags = 0): array
{
return (self::required($path)) ? \file(self::resolve($path), $flags) : [];
}
/**
* fopen — Opens file or URL
*
* @param string $path
* @param string $mode
* @return resource
*/
public static function fopen(string $path, string $mode) /* resource */
{
/* after you get back the resource there is no other reason to not use PHPs regular fclose, fgets, fwrite */
return (self::required($path)) ? \fopen(self::resolve($path), $mode) : false;
}
/* wrapper */
public static function fclose($handle)
{
return \fclose($handle);
}
/* wrapper */
public static function fwrite($handle, string $string, int $length = null): int
{
return \fwrite($handle, $string, $length);
}
/* wrapper */
public static function feof($stream): bool
{
return \feof($stream);
}
/* wrapper */
public static function fgetc($stream)
{
return \fgetc($stream);
}
/* wrapper */
public static function fgetcsv($stream, int $length = 0, string $separator = ",", string $enclosure = '"', string $escape = "\\"): array
{
return \fgetcsv($stream, $length, $separator, $enclosure, $escape);
}
/* wrapper */
public static function fgets($handle, int $length = null)
{
return \fgets($handle, $length);
}
/* wrapper */
public static function flock($stream, int $operation, int &$wouldBlock = null): bool
{
return \flock($stream, $operation, $wouldBlock);
}
/* wrapper */
public static function is_readable(string $path): bool
{
return (self::required($path)) ? \is_readable(self::resolve($path)) : false;
}
public static function isReadable(string $path): bool
{
return self::is_readable($path);
}
/**
* file_put_contents — Write data to a file
*
* This should have thrown an error before not being able to write a file_exists
* This writes the file in a atomic fashion unless you use $flags
*
* @param string $path
* @param mixed $content
* @param int $flags
* @return mixed returns the number of bytes that were written to the file, or FALSE on failure.
*/
public static function file_put_contents(string $path, $content, int $flags = 0) /* mixed */
{
$bytes = 0;
/* if they aren't using any special flags just make it atomic that way locks aren't needed or partially written files aren't read */
if ($flags != 0) {
$path = self::resolve($path);
if (self::$autoGenerateDirectories) {
self::makeDir(dirname($path));
}
$bytes = \file_put_contents($path, $content, $flags);
} else {
$bytes = self::atomic_file_put_contents($path, $content);
}
return $bytes;
}
public static function filePutContents(string $path, $content, int $flags = 0) /* mixed */
{
return self::file_put_contents($path, $content, $flags);
}
/**
* unlink — Deletes a file
*
* @param string $path
* @return bool
*/
public static function unlink(string $path): bool
{
self::remove_php_file_from_opcache($path);
return (self::required($path)) ? \unlink(self::resolve($path)) : false;
}
/* wrapper */
public static function delete(string $path): bool
{
return self::unlink($path);
}
/**
* rmdir — Removes directory
*
* @param string $path
* @return bool
*/
public static function rmdir(string $path, bool $recursive = false): bool
{
$success = false;
if (self::required($path)) {
$path = self::resolve($path);
$success = ($recursive) ? self::_rmdir($path) : \rmdir($path);
}
return $success;
}
/**
* Method removeDir
*
* @param string $path [explicite description]
*
* @return bool
*/
public static function removeDir(string $path): bool
{
return self::rmdir($path, true);
}
/**
* Method _rmdir
*
* @param string $path [explicite description]
*
* @return bool
*/
protected static function _rmdir(string $path): bool
{
$success = false;
if (self::required($path)) {
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $fileinfo) {
$function = ($fileinfo->isDir() ? '_rmdir' : 'unlink');
$function($fileinfo->getRealPath());
}
$success = \rmdir($path);
}
return $success;
}
/**
* mkdir — Makes directory
*
* @param string $path
* @param int $mode
* @param bool $recursive
* @return bool
*/
public static function mkdir(string $path, int $mode = 0777, bool $recursive = false): bool
{
$path = self::resolve($path);
if (!\file_exists($path)) {
$umask = \umask(0);
$bool = \mkdir($path, $mode, $recursive);
\umask($umask);
} else {
$bool = true;
}
return $bool;
}
/* wrapper */
public static function makeDir(string $path, int $mode = 0777): bool
{
return self::mkdir($path, $mode, true);
}
/**
* rename — Renames a file or directory
*
* @param string $oldname
* @param string $newname
* @return bool
*/
public static function rename(string $oldname, string $newname): bool
{
return (self::required($oldname)) ? \rename(self::resolve($oldname), self::resolve($newname)) : false;
}
/* wrapper */
public static function move(string $oldname, string $newname): bool
{
return self::rename($oldname, $newname);
}
/**
* copy — Copies file
*
* @param string $source
* @param string $dest
* @return bool
*/
public static function copy(string $source, string $dest): bool
{
return (self::required($source)) ? \copy(self::resolve($source), self::resolve($dest)) : false;
}
/**
* Method copyFile
*
* @param string $source [explicite description]
* @param string $dest [explicite description]
*
* @return bool
*/
public static function copyFile(string $source, string $dest): bool
{
return (self::required($source)) ? self::copy($source, $dest) : false;
}
/**
* Method copyFolder
*
* @param string $source [explicite description]
* @param string $dest [explicite description]
*
* @return bool
*/
public static function copyFolder(string $source, string $dest): bool
{
$success = false;
if (self::required($source)) {
$source = self::resolve($source);
$dest = self::resolve($dest);
$dir = \opendir($source);
self::makeDir($dest);
while (false !== ($file = \readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (\is_dir($source . '/' . $file)) {
self::copyFolder($source . '/' . $file, $dest . '/' . $file);
} else {
\copy($source . '/' . $file, $dest . '/' . $file);
}
}
}
\closedir($dir);
$success = true;
}
return $success;
}
/**
* New (but used automatically by unlink and atomic_file_put_contents)
*
* Invalidates a cached script
*
* @param string $path
* @return bool
*/
public static function remove_php_file_from_opcache(string $path): bool
{
$path = self::resolve($path);
/* return true if the function doesn't exist */
$success = true;
/* flush from the cache */
if (\function_exists('opcache_invalidate')) {
$success = \opcache_invalidate($path, true);
}
return $success;
}
public static function removePhpFileFromOpcache(string $path): bool
{
return self::remove_php_file_from_opcache($path);
}
/**
* New (but used automatically by file_put_contents when no flags are used)
*
* atomic_file_put_contents - atomic file_put_contents
*
* @param string $path
* @param mixed $content
* @return int returns the number of bytes that were written to the file.
*/
public static function atomic_file_put_contents(string $path, $content): int
{
/* create absolute path */
$path = self::resolve($path);
if (self::$autoGenerateDirectories) {
self::makeDir(dirname($path));
}
/* get the path where you want to save this file so we can put our file in the same directory */
$dirname = \dirname($path);
/* is this directory writeable */
if (!is_writable($dirname)) {
throw new \FileSystemException($dirname . ' is not writable.');
}
/* create a temporary file with unique file name and prefix */
$tmpfname = \tempnam($dirname, 'afpc_');
/* did we get a temporary filename */
if ($tmpfname === false) {
throw new \FileSystemException('Could not create temporary file ' . $tmpfname . '.');
}
/* write to the temporary file */
$bytes = \file_put_contents($tmpfname, $content);
/* did we write anything? */
if ($bytes === false) {
throw new \FileSystemException('No bytes written by file_put_contents');
}
/* changes file permissions so php user can read/write and everyone else read */
if (\chmod($tmpfname, 0644) === false) {
throw new \FileSystemException('Could not chmod temporary file ' . $tmpfname . '.');
}
/* move it into place - this is the atomic function */
if (\rename($tmpfname, $path) === false) {
throw new \FileSystemException('Could not rename temporary file ' . $tmpfname . ' ' . $path . '.');
}
/* if it's cached we need to flush it out so the old one isn't loaded */
self::remove_php_file_from_opcache($path);
/* return the number of bytes written */
return $bytes;
}
public static function atomicFilePutContents(string $path, $content): int
{
return self::atomic_file_put_contents($path, $content);
}
/**
* Method stripRootPath
*
* @param string|array $files [array of file pathes or single file path]
*
* @return string|array
*/
public static function stripRootPath($files)
{
/* strip the root path */
if (is_array($files)) {
$files = array_map(function ($file) {
return self::resolve($file, true);
}, $files);
} else {
$files = self::resolve($files, true);
}
return $files;
}
/** Export Array|Object to String */
public static function varPhp($input): string
{
$string = '';
if (\is_array($input) || \is_object($input)) {
$string = '<?php return ' . \str_replace(['Closure::__set_state', 'stdClass::__set_state'], '(object)', \var_export($input, true)) . ';';
} elseif (\is_scalar($input)) {
$string = '<?php return "' . \str_replace('"', '\"', $input) . '";';
} else {
throw new \FileSystemException('Unknown input type.');
}
return $string;
}
public static function varJson($input, bool $pretty = false, ?int $flags = null, ?int $depth = 512): string
{
$flags = ($flags) ?? self::JSONFLAGS;
$depth = ($depth) ?? 512;
if ($pretty) {
$flags = $flags | JSON_PRETTY_PRINT;
}
return json_encode($input, $flags, $depth);
}
public static function varIni(array $array, array $parent = []): string
{
$ini = '';
foreach ($array as $key => $value) {
if (\is_array($value)) {
//subsection case
//merge all the sections into one array...
$subsection = \array_merge((array) $parent, (array) $key);
//add section information to the output
$ini .= '[' . \join('.', $subsection) . ']' . PHP_EOL;
//recursively traverse deeper
$ini .= self::varIni($value, $subsection);
} else {
//plain key->value case
$ini .= "$key=$value" . PHP_EOL;
}
}
return $ini;
}
/* read different file types */
/**
* New
*
* var_import_file — import a var_export_file(...) file
*
* @param string $path
* @return mixed
* @throws Exception
*/
public static function getFilePhp(string $path)
{
$return = null;
if (self::required($path)) {
$return = include self::resolve($path);
}
return $return;
}
public static function getFileJson(string $path)
{
$json = false;
if (self::required($path)) {
$json = json_decode(\file_get_contents(self::resolve($path)), true);
if ($json === null) {
throw new \FileSystemException('JSON file "' . $path . '" is not valid JSON.');
}
}
return $json;
}
public static function getFileIni(string $path, bool $processSections = false, int $scannerMode = INI_SCANNER_NORMAL)
{
return self::parse_ini_file($path, $processSections, $scannerMode);
}
/**
* parse_ini_file — Parse a configuration file
*
* @param string $path
* @param bool $process_sections create a multidimensional array
* @param int $scanner_mode INI_SCANNER_NORMAL, INI_SCANNER_RAW, INI_SCANNER_TYPED
* @return mixed
*/
public static function parse_ini_file(string $path, bool $processSections = false, int $scannerMode = INI_SCANNER_NORMAL) /* mixed */
{
$ini = false;
if (self::required($path)) {
$ini = \parse_ini_file(self::resolve($path), $processSections, $scannerMode);
if (!$ini) {
throw new \FileSystemException('INI file "' . $path . '" is not valid.');
}
}
return $ini;
}
/* save files */
public static function putFileJson(string $path, $jsonObj, bool $pretty = false, ?int $flags = null, ?int $depth = 512, ?int $chmod = null): int
{
return self::chmodOnWrite(self::atomic_file_put_contents($path, self::varJson($jsonObj, $pretty, $flags, $depth)), $path, $chmod);
}
public static function putFilePhp(string $path, $data, ?int $chmod = null): int
{
return self::chmodOnWrite(self::atomic_file_put_contents($path, self::varPhp($data)), $path, $chmod);
}
public static function putFileIni(string $path, array $array, ?int $chmod = null): int
{
return self::chmodOnWrite(self::atomic_file_put_contents($path, self::varIni($array)), $path, $chmod);
}
protected static function chmodOnWrite(int $bytes, string $path, ?int $chmod): int
{
if ($bytes > 0 && $chmod) {
self::chmod($path, $chmod);
}
return $bytes;
}
/* auto detect reads and writes */
public static function getFile(string $path, $arg1 = null, $arg2 = null) /* mixed */
{
$output = null;
switch (\pathinfo($path, PATHINFO_EXTENSION)) {
case 'ini':
$arg1 = ($arg1) ?? true;
$arg2 = ($arg2) ?? INI_SCANNER_TYPED;
$output = self::getFileIni($path, $arg1, $arg2);
break;
case 'json':
$output = self::getFileJson($path);
break;
case 'php':
$output = self::getFilePhp($path);
break;
default:
throw new FileSystemException('Unknown Type');
}
return $output;
}
public static function putFile(string $path, $input, int $chmod = null, $arg1 = null): int
{
switch (\pathinfo($path, PATHINFO_EXTENSION)) {
case 'ini':
$bytes = self::putFileIni($path, $input, $chmod);
break;
case 'json':
$arg1 = ($arg1) ?? false;
$bytes = self::putFileJson($path, $input, $arg1, null, null, $chmod);
break;
case 'php':
$bytes = self::putFilePhp($path, $input, $chmod);
break;
default:
throw new FileSystemException('Unknown Type');
}
return $bytes;
}
} /* end class */
class FileSystemException extends \Exception
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment