Filesystem.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. namespace ba;
  3. use Throwable;
  4. use PhpZip\ZipFile;
  5. use FilesystemIterator;
  6. use RecursiveIteratorIterator;
  7. use RecursiveDirectoryIterator;
  8. /**
  9. * 访问和操作文件系统
  10. */
  11. class Filesystem
  12. {
  13. /**
  14. * 是否是空目录
  15. */
  16. public static function dirIsEmpty(string $dir): bool
  17. {
  18. if (!file_exists($dir)) return true;
  19. $handle = opendir($dir);
  20. while (false !== ($entry = readdir($handle))) {
  21. if ($entry != "." && $entry != "..") {
  22. closedir($handle);
  23. return false;
  24. }
  25. }
  26. closedir($handle);
  27. return true;
  28. }
  29. /**
  30. * 递归删除目录
  31. * @param string $dir 目录路径
  32. * @param bool $delSelf 是否删除传递的目录本身
  33. * @return bool
  34. */
  35. public static function delDir(string $dir, bool $delSelf = true): bool
  36. {
  37. if (!is_dir($dir)) {
  38. return false;
  39. }
  40. $files = new RecursiveIteratorIterator(
  41. new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
  42. RecursiveIteratorIterator::CHILD_FIRST
  43. );
  44. foreach ($files as $fileInfo) {
  45. if ($fileInfo->isDir()) {
  46. self::delDir($fileInfo->getRealPath());
  47. } else {
  48. @unlink($fileInfo->getRealPath());
  49. }
  50. }
  51. if ($delSelf) {
  52. @rmdir($dir);
  53. }
  54. return true;
  55. }
  56. /**
  57. * 删除一个路径下的所有相对空文件夹(删除此路径中的所有空文件夹)
  58. * @param string $path 相对于根目录的文件夹路径 如`c:BuildAdmin/a/b/`
  59. * @return void
  60. */
  61. public static function delEmptyDir(string $path): void
  62. {
  63. $path = str_replace(root_path(), '', rtrim(self::fsFit($path), DIRECTORY_SEPARATOR));
  64. $path = array_filter(explode(DIRECTORY_SEPARATOR, $path));
  65. for ($i = count($path) - 1; $i >= 0; $i--) {
  66. $dirPath = root_path() . implode(DIRECTORY_SEPARATOR, $path);
  67. if (!is_dir($dirPath)) {
  68. unset($path[$i]);
  69. continue;
  70. }
  71. if (self::dirIsEmpty($dirPath)) {
  72. self::delDir($dirPath);
  73. unset($path[$i]);
  74. } else {
  75. break;
  76. }
  77. }
  78. }
  79. /**
  80. * 检查目录/文件是否可写
  81. * @param $path
  82. * @return bool
  83. */
  84. public static function pathIsWritable($path): bool
  85. {
  86. if (DIRECTORY_SEPARATOR == '/' && !@ini_get('safe_mode')) {
  87. return is_writable($path);
  88. }
  89. if (is_dir($path)) {
  90. $path = rtrim($path, '/') . '/' . md5(mt_rand(1, 100) . mt_rand(1, 100));
  91. if (($fp = @fopen($path, 'ab')) === false) {
  92. return false;
  93. }
  94. fclose($fp);
  95. @chmod($path, 0777);
  96. @unlink($path);
  97. return true;
  98. } elseif (!is_file($path) || ($fp = @fopen($path, 'ab')) === false) {
  99. return false;
  100. }
  101. fclose($fp);
  102. return true;
  103. }
  104. /**
  105. * 路径分隔符根据当前系统分隔符适配
  106. * @param string $path 路径
  107. * @return string 转换后的路径
  108. */
  109. public static function fsFit(string $path): string
  110. {
  111. return str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
  112. }
  113. /**
  114. * 解压Zip
  115. * @param string $file ZIP文件路径
  116. * @param string $dir 解压路径
  117. * @return string 解压后的路径
  118. * @throws Throwable
  119. */
  120. public static function unzip(string $file, string $dir = ''): string
  121. {
  122. if (!file_exists($file)) {
  123. throw new Exception("Zip file not found");
  124. }
  125. $zip = new ZipFile();
  126. try {
  127. $zip->openFile($file);
  128. } catch (Throwable $e) {
  129. $zip->close();
  130. throw new Exception('Unable to open the zip file', 0, ['msg' => $e->getMessage()]);
  131. }
  132. $dir = $dir ?: substr($file, 0, strripos($file, '.zip'));
  133. if (!is_dir($dir)) {
  134. @mkdir($dir, 0755);
  135. }
  136. try {
  137. $zip->extractTo($dir);
  138. } catch (Throwable $e) {
  139. throw new Exception('Unable to extract ZIP file', 0, ['msg' => $e->getMessage()]);
  140. } finally {
  141. $zip->close();
  142. }
  143. return $dir;
  144. }
  145. /**
  146. * 创建ZIP
  147. * @param array $files 文件路径列表
  148. * @param string $fileName ZIP文件名称
  149. * @return bool
  150. * @throws Throwable
  151. */
  152. public static function zip(array $files, string $fileName): bool
  153. {
  154. $zip = new ZipFile();
  155. try {
  156. foreach ($files as $v) {
  157. if (is_array($v) && isset($v['file']) && isset($v['name'])) {
  158. $zip->addFile(root_path() . str_replace(root_path(), '', Filesystem::fsFit($v['file'])), $v['name']);
  159. } else {
  160. $saveFile = str_replace(root_path(), '', Filesystem::fsFit($v));
  161. $zip->addFile(root_path() . $saveFile, $saveFile);
  162. }
  163. }
  164. $zip->saveAsFile($fileName);
  165. } catch (Throwable $e) {
  166. throw new Exception('Unable to package zip file', 0, ['msg' => $e->getMessage(), 'file' => $fileName]);
  167. } finally {
  168. $zip->close();
  169. }
  170. if (file_exists($fileName)) {
  171. return true;
  172. } else {
  173. return false;
  174. }
  175. }
  176. /**
  177. * 递归创建目录
  178. * @param string $dir 目录路径
  179. * @return bool
  180. */
  181. public static function mkdir(string $dir): bool
  182. {
  183. if (!is_dir($dir)) {
  184. return mkdir($dir, 0755, true);
  185. }
  186. return false;
  187. }
  188. /**
  189. * 获取一个目录内的文件列表
  190. * @param string $dir 目录路径
  191. * @param array $suffix 要获取的文件列表的后缀
  192. * @return array
  193. */
  194. public static function getDirFiles(string $dir, array $suffix = ['php']): array
  195. {
  196. $files = new RecursiveIteratorIterator(
  197. new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY
  198. );
  199. $fileList = [];
  200. foreach ($files as $file) {
  201. if (!$file->isDir() && in_array($file->getExtension(), $suffix)) {
  202. $filePath = $file->getRealPath();
  203. $name = str_replace($dir, '', $filePath);
  204. $name = str_replace(DIRECTORY_SEPARATOR, "/", $name);
  205. $fileList[$name] = $name;
  206. }
  207. }
  208. return $fileList;
  209. }
  210. /**
  211. * 将一个文件单位转为字节
  212. * @param string $unit 将b、kb、m、mb、g、gb的单位转为 byte
  213. * @return int byte
  214. */
  215. public static function fileUnitToByte(string $unit): int
  216. {
  217. preg_match('/([0-9.]+)(\w+)/', $unit, $matches);
  218. if (!$matches) {
  219. return 0;
  220. }
  221. $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
  222. return (int)($matches[1] * pow(1024, $typeDict[strtolower($matches[2])] ?? 0));
  223. }
  224. }