init
This commit is contained in:
+278
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 4.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Utility;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use CallbackFilterIterator;
|
||||
use Closure;
|
||||
use FilesystemIterator;
|
||||
use Iterator;
|
||||
use RecursiveCallbackFilterIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* This provides convenience wrappers around common filesystem queries.
|
||||
*
|
||||
* This is an internal helper class that should not be used in application code
|
||||
* as it provides no guarantee for compatibility.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Filesystem
|
||||
{
|
||||
/**
|
||||
* Directory type constant
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_DIR = 'dir';
|
||||
|
||||
/**
|
||||
* Find files / directories (non-recursively) in given directory path.
|
||||
*
|
||||
* @param string $path Directory path.
|
||||
* @param \Closure|string|null $filter If string will be used as regex for filtering using
|
||||
* `RegexIterator`, if callable will be as callback for `CallbackFilterIterator`.
|
||||
* @param int|null $flags Flags for FilesystemIterator::__construct();
|
||||
* @return \Iterator
|
||||
*/
|
||||
public function find(string $path, Closure|string|null $filter = null, ?int $flags = null): Iterator
|
||||
{
|
||||
$flags ??= FilesystemIterator::KEY_AS_PATHNAME
|
||||
| FilesystemIterator::CURRENT_AS_FILEINFO
|
||||
| FilesystemIterator::SKIP_DOTS;
|
||||
$directory = new FilesystemIterator($path, $flags);
|
||||
|
||||
if ($filter === null) {
|
||||
return $directory;
|
||||
}
|
||||
|
||||
return $this->filterIterator($directory, $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find files/ directories recursively in given directory path.
|
||||
*
|
||||
* @param string $path Directory path.
|
||||
* @param \Closure|string|null $filter If string will be used as regex for filtering using
|
||||
* `RegexIterator`, if callable will be as callback for `CallbackFilterIterator`.
|
||||
* Hidden directories (starting with dot e.g. .git) are always skipped.
|
||||
* @param int|null $flags Flags for FilesystemIterator::__construct();
|
||||
* @return \Iterator
|
||||
*/
|
||||
public function findRecursive(string $path, Closure|string|null $filter = null, ?int $flags = null): Iterator
|
||||
{
|
||||
$flags ??= FilesystemIterator::KEY_AS_PATHNAME
|
||||
| FilesystemIterator::CURRENT_AS_FILEINFO
|
||||
| FilesystemIterator::SKIP_DOTS;
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
|
||||
$dirFilter = new RecursiveCallbackFilterIterator(
|
||||
$directory,
|
||||
function (SplFileInfo $current) {
|
||||
if (str_starts_with($current->getFilename(), '.') && $current->isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
$flatten = new RecursiveIteratorIterator(
|
||||
$dirFilter,
|
||||
RecursiveIteratorIterator::CHILD_FIRST,
|
||||
);
|
||||
|
||||
if ($filter === null) {
|
||||
return $flatten;
|
||||
}
|
||||
|
||||
return $this->filterIterator($flatten, $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap iterator in additional filtering iterator.
|
||||
*
|
||||
* @param \Iterator $iterator Iterator
|
||||
* @param \Closure|string $filter Regex string or callback.
|
||||
* @return \Iterator
|
||||
*/
|
||||
protected function filterIterator(Iterator $iterator, Closure|string $filter): Iterator
|
||||
{
|
||||
if (is_string($filter)) {
|
||||
return new RegexIterator($iterator, $filter);
|
||||
}
|
||||
|
||||
return new CallbackFilterIterator($iterator, $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump contents to file.
|
||||
*
|
||||
* @param string $filename File path.
|
||||
* @param string $content Content to dump.
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException When dumping fails.
|
||||
*/
|
||||
public function dumpFile(string $filename, string $content): void
|
||||
{
|
||||
$dir = dirname($filename);
|
||||
if (!is_dir($dir)) {
|
||||
$this->mkdir($dir);
|
||||
}
|
||||
|
||||
$exists = file_exists($filename);
|
||||
|
||||
if ($this->isStream($filename)) {
|
||||
// phpcs:ignore
|
||||
$success = @file_put_contents($filename, $content);
|
||||
} else {
|
||||
// phpcs:ignore
|
||||
$success = @file_put_contents($filename, $content, LOCK_EX);
|
||||
}
|
||||
|
||||
if ($success === false) {
|
||||
throw new CakeException(sprintf('Failed dumping content to file `%s`', $dir));
|
||||
}
|
||||
|
||||
if (!$exists) {
|
||||
chmod($filename, 0666 & ~umask());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory.
|
||||
*
|
||||
* @param string $dir Directory path.
|
||||
* @param int $mode Octal mode passed to mkdir(). Defaults to 0777.
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException When directory creation fails.
|
||||
*/
|
||||
public function mkdir(string $dir, int $mode = 0777): void
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old = umask(0);
|
||||
// phpcs:ignore
|
||||
if (@mkdir($dir, $mode, true) === false) {
|
||||
umask($old);
|
||||
throw new CakeException(sprintf('Failed to create directory `%s`', $dir));
|
||||
}
|
||||
|
||||
umask($old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete directory along with all it's contents.
|
||||
*
|
||||
* @param string $path Directory path.
|
||||
* @return bool
|
||||
* @throws \Cake\Core\Exception\CakeException If path is not a directory.
|
||||
*/
|
||||
public function deleteDir(string $path): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
throw new CakeException(sprintf('`%s` is not a directory', $path));
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST,
|
||||
);
|
||||
|
||||
$result = true;
|
||||
/** @var \SplFileInfo $fileInfo */
|
||||
foreach ($iterator as $fileInfo) {
|
||||
$isWindowsLink = DIRECTORY_SEPARATOR === '\\' && $fileInfo->getType() === 'link';
|
||||
if ($fileInfo->getType() === self::TYPE_DIR || $isWindowsLink) {
|
||||
// phpcs:ignore
|
||||
$result = $result && @rmdir($fileInfo->getPathname());
|
||||
unset($fileInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
// phpcs:ignore
|
||||
$result = $result && @unlink($fileInfo->getPathname());
|
||||
// possible inner iterators need to be unset too in order for locks on parents to be released
|
||||
unset($fileInfo);
|
||||
}
|
||||
|
||||
// unsetting iterators helps releasing possible locks in certain environments,
|
||||
// which could otherwise make `rmdir()` fail
|
||||
unset($iterator);
|
||||
|
||||
// phpcs:ignore
|
||||
return $result && @rmdir($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies directory with all it's contents.
|
||||
*
|
||||
* @param string $source Source path.
|
||||
* @param string $destination Destination path.
|
||||
* @return bool
|
||||
*/
|
||||
public function copyDir(string $source, string $destination): bool
|
||||
{
|
||||
$destination = (new SplFileInfo($destination))->getPathname();
|
||||
|
||||
if (!is_dir($destination)) {
|
||||
$this->mkdir($destination);
|
||||
}
|
||||
|
||||
/** @var \FilesystemIterator<\SplFileInfo> $iterator */
|
||||
$iterator = new FilesystemIterator($source);
|
||||
|
||||
$result = true;
|
||||
foreach ($iterator as $fileInfo) {
|
||||
if ($fileInfo->isDir()) {
|
||||
$result = $result && $this->copyDir(
|
||||
$fileInfo->getPathname(),
|
||||
$destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename(),
|
||||
);
|
||||
} else {
|
||||
// phpcs:ignore
|
||||
$result = $result && @copy(
|
||||
$fileInfo->getPathname(),
|
||||
$destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given path is a stream path.
|
||||
*
|
||||
* @param string $path Path.
|
||||
* @return bool
|
||||
*/
|
||||
public function isStream(string $path): bool
|
||||
{
|
||||
return str_contains($path, '://');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user