<?php
namespace SouthCoast\Helpers;
use SouthCoast\Helpers\Error\FileError;
class File
{
const BASE_DIRECTORY = 'base_directory';
const DIRECTORY_MAP_IDENTIFIER = '$';
const NOTHING = '';
const Minified = 'minify_contents';
/**
* Holds the path to the base directory
*
* @var string
*/
protected static $base_directory = null;
/**
* Holds the directory mapping
*
* @var array
*/
protected static $directory_map = [];
/**
* Sets the base directory
*
* @param string $path
*/
public static function setBaseDirectory(string $path)
{
/* Check if the path is valid */
if (!Validate::path($path)) {
throw new FileError(FileError::NOT_VALID_PATH, $path);
}
/* Check if the path leads to a directory */
if (!Validate::isDirectory($path)) {
throw new FileError(FileError::NOT_A_DIRECTORY, $path);
}
/* Define the directory */
self::defineDirectory(File::BASE_DIRECTORY, $path);
}
/**
* Lists all files and directories in the provided path
*
* @param string $path
* @param string $extension
* @param boolean $files_only
* @param boolean $subtract_base_path
* @return array
* @throws FileError
*/
function list(string $path, string $extension = null, bool $files_only = true, bool $subtract_base_path = true): array
{
/* First get the full path */
$path = self::getPath($path);
/* We can only list directories so check if this is one */
if (!Validate::isDirectory($path)) {
throw new FileError(FileError::NOT_A_DIRECTORY, $path);
}
/* Build the query for the file listing */
if (is_null($extension)) {
$query = $path . (($files_only) ? DIRECTORY_SEPARATOR . '*.*' : '*');
} else {
$query = $path . DIRECTORY_SEPARATOR . '*' . ($extension ? '.' . $extension : '');
}
/* Get the list */
$list = glob($query);
/* Check if there was anything found */
if ($list === false) {
/* if not, just return an empty array */
return [];
}
/* Check if we need to clean up the paths */
if ($subtract_base_path) {
/* If so, strip the path from the list results */
$list = self::stripEachBasePath($path, $list);
}
/* Finally return the list */
return $list;
}
/**
* Strips the base path from the full path
*
* @param string $path
* @param array $list
* @return array
*/
public static function stripEachBasePath(string $path, array $list): array
{
/* Loop over the provided list */
foreach ($list as &$subject) {
/* Strip the base path from the item */
$subject = self::stripBasePath($path, $subject);
}
/* return the list */
return $list;
}
/**
* Strips the base path from the full path
*
* @param string $base_path
* @param string $subject
*/
public static function stripBasePath(string $base_path, string $subject): string
{
/* replace the base path by nothing in the to be striped value */
return str_replace($base_path . DIRECTORY_SEPARATOR, self::NOTHING, $subject);
}
/**
* Checks if the directory identifier is known
*
* @param string $path
* @return bool
*/
public static function isKnownDirectory(string $path): bool
{
/* Check if the path starts with the identifier token */
if (!StringHelper::startsWith(self::DIRECTORY_MAP_IDENTIFIER, $path)) {
/* if not, simple, its not. return false */
return false;
}
/* Extract the identifier from the path */
$identifier = self::extractIdentifier($path);
/* Check if the identifier is in the directory map, return a boolean */
return array_key_exists($identifier, self::$directory_map) ? true : false;
}
/**
* Extracts the directory identifier from the provided path
*
* @param string $path
* @return string
*/
public static function extractIdentifier(string $path): string
{
/* Get the first element in the provided path */
$identifier = explode(DIRECTORY_SEPARATOR, $path)[0];
/* Remove the identifier token */
$identifier = ltrim($identifier, self::DIRECTORY_MAP_IDENTIFIER);
/* return the identifier */
return $identifier;
}
/**
* Returns te full existing path to the file
* Converts the identifier based paths into real paths
*
* @param string $path
* @return string
* @throws FileError
*/
public static function getPath(string $path): string
{
if (!StringHelper::startsWith(DIRECTORY_SEPARATOR, $path) && !StringHelper::startsWith(self::DIRECTORY_MAP_IDENTIFIER, $path)) {
$path = '$' . self::BASE_DIRECTORY . DIRECTORY_SEPARATOR . $path;
}
/* Check if this is a predefined directory */
if (self::isKnownDirectory($path)) {
/* get the real path to the directory */
$path = self::getRealPath($path);
}
/* Check if this is a valid path */
if (!Validate::path($path)) {
throw new FileError(FileError::NOT_VALID_PATH, $path);
}
/* return the path */
return $path;
}
/**
* Returns the real path to the identifier path
*
* Example:
* File::getRealPath('$cache/some_cached_file.tmp') = '/path/to/cache/directory/some_cached_file.tmp';
* File::getRealPath('$cache') = '/path/to/cache/directory';
*
* @param string $path The file or directory path based on the identifier
* @return string The full actual path to the file or directory
* @throws FileError If: UNKNOWN_DIRECTORY_IDENTIFIER
*/
public static function getRealPath(string $path): string
{
/* get the identifier */
$identifier = self::extractIdentifier($path);
/* Check if this is a known identifier */
if (!self::isKnownIdentifier($identifier)) {
throw new FileError(FileError::UNKNOWN_DIRECTORY_IDENTIFIER, $identifier);
}
/* Get the directory path from the identifier */
$directory_path = self::getPathFromIdentifier($identifier);
/* Explode the provided path */
$path_array = explode(DIRECTORY_SEPARATOR, $path);
/* Remove the Identifier */
array_shift($path_array);
if (count($path_array) === 1) {
return $directory_path . DIRECTORY_SEPARATOR . $path_array[0];
}
/* Build and return the new Path from the directory path and the provided path */
return $directory_path . implode(DIRECTORY_SEPARATOR, $path_array);
}
/**
* Checks if the identifier is known
*
* @param string $identifier
* @return bool
*/
public static function isKnownIdentifier(string $identifier): bool
{
return array_key_exists($identifier, self::$directory_map);
}
/**
* Cleans up the path to get a consistent format
*
* Example:
* File::cleanUpPath('/foo/bar/foobar/') = '/foo/bar/foobar';
*
* @param string $path The to-be cleaned path
* @return string The cleaned path
*/
public static function cleanUpPath(string $path): string
{
if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $path)) {
$path = rtrim($path, DIRECTORY_SEPARATOR);
}
return $path;
}
/**
* Returns the path associated with the identifier
*
* @param string $identifier The directory identifier
* @return string The full path to the directory
* @throws FileError If: UNKNOWN_DIRECTORY_IDENTIFIER
*/
public static function getPathFromIdentifier(string $identifier): string
{
/* Lets make sure its a known directory */
if (!self::isKnownIdentifier($identifier)) {
throw new FileError(FileError::UNKNOWN_DIRECTORY_IDENTIFIER, $identifier);
}
/* Get the path associated with this identifier */
return self::$directory_map[$identifier];
}
/**
* Defines a directory by its identifier
*
* Example:
* File::defineDirectory('$cache', '/the/full/path/to/the/dir');
*
* @param string $identifier The directory identifier
* @param string $path The full path to the directory
* @return void
* @throws FileError If: NOT_VALID_PATH, NOT_A_DIRECTORY, IDENTIFIER_ALREADY_IN_USE
*/
public static function defineDirectory(string $identifier, string $path)
{
/* First clean up the path */
$path = self::cleanUpPath($path);
/* Check if the path is valid */
if (!Validate::path($path)) {
throw new FileError(FileError::NOT_VALID_PATH, $path);
}
/* Check if the path is a directory */
if (!Validate::isDirectory($path)) {
throw new FileError(FileError::NOT_A_DIRECTORY, $path);
}
/* Check if this is an already known identifier */
if (self::isKnownIdentifier($identifier)) {
throw new FileError(FileError::IDENTIFIER_ALREADY_IN_USE, $identifier);
}
/* If the identifier is the base directory */
if ($identifier === File::BASE_DIRECTORY) {
/* Add it to the base directory */
self::$base_directory = $path;
}
/* Add it to the directory map */
self::$directory_map[$identifier] = $path;
}
/**
* Loads a directory map from array
*
* @param array $map
* @return void
*/
public static function loadDirectoryMap(array $map)
{
/* Loop over all the entries */
foreach ($map as $identifier => $path) {
/* Define each directory */
self::defineDirectory($identifier, $path);
}
}
/**
* Removes all defined paths
*
* @param bool $removeBasePath
* @return void
*/
public static function clearDirectoryMap(bool $removeBasePath = false)
{
/* Save the base path */
$base_directory_path = self::$directory_map[self::BASE_DIRECTORY];
/* Empty the mapping */
self::$directory_map = [];
/* Check if we need to keep the base directory */
if (!$removeBasePath) {
/* Set the base path again */
self::defineDirectory(self::BASE_DIRECTORY, $base_directory_path);
}
}
/**
* Recursively removed a directory.
*
* @param string $directory The to-be removed directory
* @return bool
*/
private static function recursiveRemoveDirectory(string $directory)
{
/* Fist get the actual path */
$directory = self::getPath($directory);
/* If it's not a directory */
if (!Validate::isDirectory($directory)) {
return false;
}
/* Loop over all the items in the directory */
foreach (self::list($directory, null, false, false) as $path) {
/* Check if it's a directory */
if (Validate::isDirectory($path)) {
/* Recursively call this method */
File::recursiveRemoveDirectory($path);
} else {
/* Else, unlink the file */
File::delete($path);
}
}
/* Finally remove the directory itself */
return @rmdir($directory);
}
/**
* Performs a recursive listing.
* Returns a list of all files in the provided directory and sub directories.
*
* @param string $directory The to be scanned directory
* @param string $pattern The REGEX search pattern
* @return array The list of all directories and files.
*/
protected static function recursiveList(string $directory, string $pattern = '/.*$/'): array
{
/* Get the real path to the directory */
$path = self::getPath($directory);
/* Create a new Recursive directory object */
$recursive_directory = new RecursiveDirectoryIterator($path);
/* Create a new Recursive Iterator object */
$iterator_object = new RecursiveIteratorIterator($recursive_directory);
/* Setup the filter pattern and perform it on all the files in the provided directory */
$expression_result = new RegexIterator($iterator_object, $pattern, RegexIterator::GET_MATCH);
/* Initialize the list variable */
$list = [];
/* Loop over all the directories */
foreach ($expression_result as $files) {
/* add the files to the file list */
$list = array_merge($list, $files);
}
/* Garbage Collection */
unset($recursive_directory, $iterator_object, $expression_result);
/* Return the list */
return $list;
}
/**
* Get the extension of a file
*
* Example:
* File::getExtension('$base_directory/myAwesomeFile.txt') = 'txt';
* File::getExtension('$cache_directory/some_cached_file') = null;
*
* @param string $file The file for which you want the extension
* @return string|null Returns a string with the extension, null if non found
*/
public function getExtension(string $file)
{
/* Get the real path to the directory */
$path = self::getPath($file);
/* Get the path info */
$pathinfo = pathinfo($path);
/* Return the filename or null if not present */
return $pathinfo['extension'] ?? null;
}
/**
* Change the extension of an existing file
*
* Example:
* File::changeExtension('$base_directory/LOG_1234.log', 'txt');
*
* @param string $file The to be changed file
* @param string $new_extension The new file extension
* @return bool Returns true is all went good, false if not
*/
public function changeExtension(string $file, string $new_extension): bool
{
/* Get the real path to the directory */
$path = self::getPath($file);
/* Get the file extension */
$extension = self::getExtension($path);
/* Add the new extension */
$new_path = (!is_null($extension) ? rtrim($path, $extension) : $path . '.') . $new_extension;
/* rename the file to reflect the new extension and return the result */
return rename($path, $new_path);
}
/**
* @param string $file
*/
public static function getFilename(string $file)
{
/* Get the real path to the directory */
$path = self::getPath($file);
/* Get the path info */
$pathinfo = pathinfo($path);
/* Return the filename */
return $pathinfo['filename'];
}
/**
* Rename an existing file
*
* Example:
* File::rename('$base_directory/myAwesomeFile.md', 'myNotSoAwesomeFile');
*
* @param string $file The to-be renamed file
* @param string $new_name The new file name (no extension)
* @return bool Returns true is all went good, false if not
*/
public static function rename(string $file, string $new_name): bool
{
/* Get the real path to the directory */
$path = self::getPath($file);
/* Get the file extension */
$extension = self::getExtension($path);
/* Get the base path to the file */
$base_path = str_replace(basename($path), '', $path);
/* Rename the file and return the result */
return rename($path, $base_path . $new_name . ($extension ? '.' . $extension : ''));
}
/**
* Move an existing file to a new location
*
* Example:
* File::move('$base_directory/myAwesomeFile.txt', '$some_other_directory');
*
* @param string $file The to-be moved file
* @param string $new_location The new directory (no file name should be present)
* @return bool Returns true is all went good, false if not
*/
public static function move(string $file, string $new_location): bool
{
/* Get the real path to the original directory */
$path = self::getPath($file);
/* Get the real path to the new directory and append the filename */
$new_path = self::getPath($new_location) . basename($path);
/* move the file to the new location and return the result */
return rename($path, $new_path);
}
/**
* Deletes a file or directory.
* Directories will be removed recursively.
* All files and sub directories wil also be deleted!
*
* @param string $path The to-be deleted path
* @return bool The status of the deletion
* @throws FileError If: NEITHER_FILE_NOR_DIRECTORY
*/
public static function delete(string $path): bool
{
/* Get the real path to the directory or file */
$path = self::getPath($path);
if (Validate::isDirectory($path)) {
return self::recursiveRemoveDirectory($path);
}
if (Validate::isFile($path)) {
return self::deleteFile($path);
}
throw new FileError(FileError::NEITHER_FILE_NOR_DIRECTORY, $path);
}
/**
* @param string $path
*/
public static function deleteFile(string $path): bool
{
/* Get the real path to the directory or file */
$path = self::getPath($path);
/* Check if the path leads to a file */
if (!Validate::isFile($path)) {
throw new FileError(FileError::NOT_A_FILE, $path);
}
/* unlink the file */
return unlink($path);
}
/**
* Returns an array that describes the file
*
* @param string $file The file that's need to be described
* @return array An array with the file description
*/
public static function describe(string $file): array
{
/* Get the real path to the file */
$path = self::getPath($file);
if (!Validate::isFile($path)) {
throw new FileError(FileError::NOT_A_FILE, $path);
}
return [
'filename' => self::getFilename($path),
'extension' => self::getExtension($path),
'type' => filetype($path) ?? 'unknown',
'size' => filesize($path),
'last_access' => fileatime($path),
'last_change' => filectime($path),
'last_modified' => filemtime($path),
'permissions' => substr(sprintf('%o', fileperms($path)), -4),
'owner' => fileowner($path),
'group' => filegroup($path),
];
}
/**
* SETTERS
*/
/**
* Returns an instantiated stream object with read permissions to the file.
*
* @param string $file The to-be read file
* @param string $mode The mode which you want to open the file in
* @return Objects\Stream The instantiated file stream
*/
public static function stream(string $file, $mode = 'r'): Objects\Stream
{
return new Objects\Stream($file, $mode);
}
/**
* Returns an instantiated stream object with read permissions to the file.
*
* @param string $file The to-be read file
* @return Objects\Stream The instantiated file stream
*/
public static function readStream(string $file): Objects\Stream
{
return new Objects\Stream($file);
}
/**
* Returns an instantiated stream object with write permissions to the file.
*
* @param string $file The to-be written file
* @param string $mode The mode which you want to open the file in
* @return Objects\Stream The instantiated file stream
*/
public static function writeStream(string $file, string $mode = 'w+'): Objects\Stream
{
return new Objects\Stream($file, $mode);
}
/**
* @param $path
*/
public static function get($path)
{
/* Get the real path to the original directory */
$path = self::getPath($path);
/* Get the contents from the file */
$content = file_get_contents($path);
/* Return the contents or null if there was an error */
return $content === false ? null : $content;
}
/**
* @param $path
*/
public static function getAsArray($path, $flags = null, $context = null)
{
/* Get the real path to the original directory */
$path = self::getPath($path);
/* Read the file into an array */
return file($path, $flags, $context);
}
/**
* @param string $path
* @param bool $returnObject
*/
public static function getJson(string $path, bool $returnObject = true)
{
return Json::parse(self::get($path), ($returnObject) ? false : true);
}
/**
* Reads the content of a file and parses it as CSV.
*
* @param string $path The path to the csv file
* @param bool|array $hasHeaders If the file contains headers. Or pass an array with the headers.
* @param string $delimiter The delimiter that is used in the file
* @param string $enclosure The enclosing character
* @return array The parsed csv file
*/
public static function getCsv(string $path, $hasHeaders = true, string $delimiter = ',', string $enclosure = null)
{
/* Get the real path to the original directory */
$path = self::getPath($path);
$content = File::getAsArray($path);
if (is_array($hasHeaders)) {
$headers = $hasHeaders;
$hasHeaders = true;
}
if (is_bool($hasHeaders) && $hasHeaders) {
$headers = self::parseCsvLine(array_shift($content), $delimiter, $enclosure);
}
$array = [];
foreach ($content as $line) {
if ($hasHeaders) {
$array[] = ArrayHelper::combineAndFill($headers, self::parseCsvLine($line, $delimiter, $enclosure));
}
if (!$hasHeaders) {
$array[] = self::parseCsvLine($line, $delimiter, $enclosure);
}
}
return $array;
}
/**
* @param string $line
* @param string $delimiter
* @param string $enclosure
*/
public static function parseCsvLine(string $line, string $delimiter = ',', string $enclosure = null): array
{
/* Explode the line by the delimiter */
$line_array = explode($delimiter, $line);
/* Loop over all the fields in the line */
foreach ($line_array as &$value) {
if ($enclosure !== null || $enclosure !== '')
/* Trim the enclosure */ {
$value = trim($value, $enclosure);
}
if (Number::isFloat($value)) {
$value = (float) $value;
}
if (Number::isInteger($value)) {
$value = (int) $value;
}
if (strtolower($value) === 'true') {
$value = true;
}
if (strtolower($value) === 'false') {
$value = false;
}
if (strtolower($value) === 'null') {
$value = null;
}
}
return $line_array;
}
public static function getBasePath()
{
return self::getPathFromIdentifier(self::BASE_DIRECTORY);
}
/**
* @param string $path
* @param string $directory
*/
public function makeDirectory(string $path, string $directory)
{
/* Get the real path to the original directory */
$path = self::getPath($path);
/* Format the new path */
$directory_path = $path . DIRECTORY_SEPARATOR . $directory;
/* Create the directory */
mkdir($directory_path);
/* Check if it now is there and return the result */
return Validate::path($directory_path) && Validate::isDirectory($directory_path) ? true : false;
}
/**
* @param $path
* @param $structure
*/
public function makeDirectoryStructure(string $path, array $structure)
{
$directory = ArrayHelper::firstKey($structure);
}
/**
* GETTERS
*/
}
|