diff --git a/sources/classes/File.php b/sources/classes/File.php index ddb8077..f17c242 100644 --- a/sources/classes/File.php +++ b/sources/classes/File.php @@ -2,6 +2,7 @@ namespace goodboyalex\php_components_pack\classes; +use Exception; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -10,11 +11,35 @@ use RecursiveIteratorIterator; * * @author Александр Бабаев * @package php_components_pack - * @version 1.0.1 + * @version 1.0.2 * @since 1.0.21 */ final class File { + /** + * @var array Массив сообщений об ошибках при удалении директории. + */ + public const array REMOVE_DIRECTORY_ERROR_MESSAGES = [ + 'directory_not_exist' => "Директория не существует или нет доступа на запись!", + 'error_deleting_file_or_link' => 'Ошибка удаления файла или ссылки: %s!', + 'error_deleting_directory' => 'Ошибка удаления каталога: %s. Код возврата: %d!', + 'unhandled_error' => 'Ошибка удаления директории %s: %s!' + ]; + + /** + * @var array Массив сообщений об ошибках при получении размера файла. + */ + public const array FILE_SIZE_ERROR_MESSAGES = [ + 'file_not_exist' => 'Файл не существует!', + 'not_a_file' => 'Указанный путь не является файлом!', + 'cannot_get_size' => 'Не удалось получить размер файла!' + ]; + + /** + * @var array Массив локализации размеров файлов. + */ + public const array FILE_SIZE_UNITS = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ']; + /** * Получает список файлов в директории и поддиректориях, соответствующей шаблону $pattern. * @@ -109,34 +134,96 @@ final class File * Удаляет директорию вместе со всеми файлами и поддиректориями. * * @param string $directory Полный путь к директории. + * @param array $errorMessages Сообщения об ошибках удаления (по умолчанию, см. + * {@link REMOVE_DIRECTORY_ERROR_MESSAGES}). * - * @return bool Результат удаления. + * @return ActionState Результат удаления. */ - public static function RemoveDir (string $directory): bool + public static function RemoveDir (string $directory, + array $errorMessages = self::REMOVE_DIRECTORY_ERROR_MESSAGES): ActionState { - // Проверяем, существует ли директория - if (!self::DirectoryExists(directory: $directory, checkWriteAccess: true)) - // - если нет, то возвращаем false - return false; + // Создаю результат + $result = new ActionState(false); - // Получаем список файлов и каталогов в заданной директории - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($directory), - RecursiveIteratorIterator::CHILD_FIRST - ); + try { + // Проверяем наличие директории и доступ на запись + if (!self::DirectoryExists(directory: $directory, checkWriteAccess: true)) { + // - если нет, то добавляем ошибку + $result->AddError($errorMessages['directory_not_exist']); - // Перебираем файлы и каталоги - foreach ($files as $file) - // - если это каталог - if ($file->isDir()) - // -- то удаляем его - rmdir($file->getRealPath()); - else - // -- иначе удаляем файл - unlink($file->getRealPath()); + // - и возвращаем результат + return $result; + } - // Удаляем директорию - return rmdir($directory); + // Создаем рекурсивный итерационный объект для перебора всего дерева каталогов + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory), + RecursiveIteratorIterator::CHILD_FIRST + ); + + // Проходим по каждому элементу (каталогам и файлам) + foreach ($iterator as $item) { + // - получаем путь к файлу + $realPath = $item->getRealPath(); + + // - если это файл или ссылка + if ($item->isFile() || $item->isLink()) + // -- то удаляем его + if (!@unlink($realPath)) { + // --- если не удалось удалить, то добавляем ошибку + $result->AddError(sprintf($errorMessages['error_deleting_file_or_link'], $realPath)); + + // --- и возвращаем результат + return $result; + } + } + + // Определение текущей операционной системы + $os = strtolower(PHP_OS_FAMILY); + + // Экранируем аргумент для предотвращения инъекций + $escapedDirectory = escapeshellarg($directory); + + // Дальнейшие действия зависят от операционной системы + switch ($os) { + // - для Windows + case 'windows': + // -- выполняем команду Windows + exec("rd /s /q $escapedDirectory", $output, $returnCode); + break; + + // - для Linux/macOS + default: + // -- выполняем команду Linux/macOS + exec("rm -rf $escapedDirectory", $output, $returnCode); + break; + } + + // Проверяем код возврата + if ($returnCode !== 0) { + // - если не удалось удалить, то добавляем ошибку + $result->AddError(sprintf($errorMessages['error_deleting_directory'], $directory, $returnCode)); + + // --- и возвращаем результат + return $result; + } + + // Если все прошло успешно (а если мы сюда попали, то все должно быть хорошо), то добавляем результат true + $result->Value = true; + + // - и возвращаем его + return $result; + } + catch (Exception $exception) { + // Если произошла ошибка, то добавляем ошибку + $result->AddError(sprintf($errorMessages['unhandled_error'], $directory, $exception->getMessage())); + + // - задаем результат false + $result->Value = false; + + // - и возвращаем его + return $result; + } } /** @@ -177,4 +264,88 @@ final class File // Если все проверки пройдены успешно, то возвращаем true return true; } + + /** + * Получает размер файла в байтах. + * + * @param string $filename Имя файла. + * @param array $errorLocalization Массив сообщений об ошибках при получении размера файла (по умолчанию, см. + * {@link FILE_SIZE_ERROR_MESSAGES}). + * + * @return ActionState Результат с размером файла в байтах. + */ + public static function FileSize (string $filename, + array $errorLocalization = self::FILE_SIZE_ERROR_MESSAGES): ActionState + { + // Очищаем кэш + clearstatcache(); + + // Создаём результат + $result = new ActionState(-1); + + // Проверяем, существует ли файл + if (!file_exists($filename)) { + // - если нет, то добавляем ошибку + $result->AddError($errorLocalization['file_not_exist']); + // - и возвращаем результат + return $result; + } + + // Проверяем, является ли $filename файлом + if (!is_file($filename)) { + // - если нет, то добавляем ошибку + $result->AddError($errorLocalization['not_a_file']); + // - и возвращаем результат + return $result; + } + + // Получаем размер файла + $size = filesize($filename); + + // Проверяем, получилось ли получить размер файла + if ($size === false) { + // - если нет, то добавляем ошибку + $result->AddError($errorLocalization['cannot_get_size']); + // - и возвращаем результат + return $result; + } + + // Устанавливаем значение результата + $result->Value = $size; + + // Возвращаем результат + return $result; + } + + /** + * Преобразует размер файла в байтах в красивое строковое представление. + * + * @param int $fileSize Размер файла в байтах. + * @param array $fileSizeUnits Локализованные единицы измерения размера файла (по умолчанию, см. + * {@link FILE_SIZE_UNITS}). + * @param string $decimalSeparator Разделитель десятичной части (по умолчанию, запятая). + * + * @return string Размер файла в красивом строковом представлении. Например, если размер файла составляет 1500 + * байт, вывод будет «1.46 КБ». + */ + public static function FileSizeToString (int $fileSize, array $fileSizeUnits = self::FILE_SIZE_UNITS, + string $decimalSeparator = ','): string + { + /** + * Вычисление степени для преобразования: берём минимум из 4 и результата округления до ближайшего целого числа + * в меньшую сторону логарифма размера файла в байтах по основанию 1024 (это показывает, сколько раз нужно + * разделить размер файла на 1024, чтобы получить значение в более крупных единицах измерения). Ограничение в 4 + * необходимо для того, чтобы соответствовать единице измерения ТБ (терабайт). + */ + $power = min(4, floor(log($fileSize, 1024))); + + /** + * Преобразование размера файла: размер файла делим на 1024 в степени, равной степени $power, + * затем округляем полученное до 2 цифр после запятой. + */ + $size = number_format(round($fileSize / pow(1024, $power), 2), 2, $decimalSeparator); + + // Возвращаем преобразованное значение вместе с единицей измерения + return "$size $fileSizeUnits[$power]"; + } } \ No newline at end of file diff --git a/tests/classes/FileTest.php b/tests/classes/FileTest.php index c5a01c0..a75b3c5 100644 --- a/tests/classes/FileTest.php +++ b/tests/classes/FileTest.php @@ -68,9 +68,27 @@ class FileTest extends TestCase $this->PrepareForTest(); - $result = File::RemoveDir("D:\\TestDelete"); + $result = File::RemoveDir("D:/TestDelete"); - $this->assertTrue($result); + $this->assertTrue($result->Value); $this->assertFalse(File::DirectoryExists("D:\\TestDelete")); } + + public function testFileSize () + { + $this->PrepareForTest(); + + $size = File::FileSize("C:\\Windows/explorer.exe"); + + $this->assertEquals(2774080, $size->Value); + } + + public function testFileSizeString () + { + $this->PrepareForTest(); + + $size = File::FileSizeToString(2774080); + + $this->assertEquals("2,65 МБ", $size); + } } \ No newline at end of file