diff --git a/system/Files/Exceptions/FileException.php b/system/Files/Exceptions/FileException.php index ddaac3639b72..ffcfb61c0bbc 100644 --- a/system/Files/Exceptions/FileException.php +++ b/system/Files/Exceptions/FileException.php @@ -23,4 +23,24 @@ public static function forUnableToMove(string $from = null, string $to = null, s { return new static(lang('Files.cannotMove', [$from, $to, $error])); } + + /** + * Throws when an item is expected to be a directory but is not or is missing. + * + * @param string $caller The method causing the exception + */ + public static function forExpectedDirectory(string $caller) + { + return new static(lang('Files.expectedDirectory', [$caller])); + } + + /** + * Throws when an item is expected to be a file but is not or is missing. + * + * @param string $caller The method causing the exception + */ + public static function forExpectedFile(string $caller) + { + return new static(lang('Files.expectedFile', [$caller])); + } } diff --git a/system/Files/FileCollection.php b/system/Files/FileCollection.php new file mode 100644 index 000000000000..128d884940d5 --- /dev/null +++ b/system/Files/FileCollection.php @@ -0,0 +1,405 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CodeIgniter\Files; + +use CodeIgniter\Files\Exceptions\FileException; +use CodeIgniter\Files\Exceptions\FileNotFoundException; +use Countable; +use Generator; +use InvalidArgumentException; +use IteratorAggregate; +use Traversable; + +/** + * File Collection Class + * + * Representation for a group of files, with utilities for locating, + * filtering, and ordering them. + */ +class FileCollection implements Countable, IteratorAggregate +{ + /** + * The current list of file paths. + * + * @var string[] + */ + protected $files = []; + + //-------------------------------------------------------------------- + // Support Methods + //-------------------------------------------------------------------- + + /** + * Resolves a full path and verifies it is an actual directory. + * + * @param string $directory + * + * @return string + * + * @throws FileException + */ + final protected static function resolveDirectory(string $directory): string + { + if (! is_dir($directory = set_realpath($directory))) + { + $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; + throw FileException::forExpectedDirectory($caller['function']); + } + + return $directory; + } + + /** + * Resolves a full path and verifies it is an actual file. + * + * @param string $file + * + * @return string + * + * @throws FileException + */ + final protected static function resolveFile(string $file): string + { + if (! is_file($file = set_realpath($file))) + { + $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; + throw FileException::forExpectedFile($caller['function']); + } + + return $file; + } + + /** + * Removes files that are not part of the given directory (recursive). + * + * @param string[] $files + * @param string $directory + * + * @return string[] + */ + final protected static function filterFiles(array $files, string $directory): array + { + $directory = self::resolveDirectory($directory); + + return array_filter($files, static function (string $value) use ($directory): bool { + return strpos($value, $directory) === 0; + }); + } + + /** + * Returns any files whose `basename` matches the given pattern. + * + * @param string[] $files + * @param string $pattern Regex or pseudo-regex string + * + * @return string[] + */ + final protected static function matchFiles(array $files, string $pattern): array + { + // Convert pseudo-regex into their true form + if (@preg_match($pattern, '') === false) + { + $pattern = str_replace( + ['#', '.', '*', '?'], + ['\#', '\.', '.*', '.'], + $pattern + ); + $pattern = "#{$pattern}#"; + } + + return array_filter($files, static function ($value) use ($pattern) { + return (bool) preg_match($pattern, basename($value)); + }); + } + + //-------------------------------------------------------------------- + // Class Core + //-------------------------------------------------------------------- + + /** + * Loads the Filesystem helper and adds any initial files. + * + * @param string[] $files + */ + public function __construct(array $files = []) + { + helper(['filesystem']); + + $this->add($files)->define(); + } + + /** + * Applies any initial inputs after the constructor. + * This method is a stub to be implemented by child classes. + * + * @return void + */ + protected function define(): void + { + } + + /** + * Optimizes and returns the current file list. + * + * @return string[] + */ + public function get(): array + { + $this->files = array_unique($this->files); + sort($this->files, SORT_STRING); + + return $this->files; + } + + /** + * Sets the file list directly, files are still subject to verification. + * This works as a "reset" method with []. + * + * @param string[] $files The new file list to use + * + * @return $this + */ + public function set(array $files) + { + $this->files = []; + + return $this->addFiles($files); + } + + /** + * Adds an array/single file or directory to the list. + * + * @param string|string[] $paths + * @param boolean $recursive + * + * @return $this + */ + public function add($paths, bool $recursive = true) + { + $paths = (array) $paths; + + foreach ($paths as $path) + { + if (! is_string($path)) + { + throw new InvalidArgumentException('FileCollection paths must be strings.'); + } + + // Test for a directory + try + { + $directory = self::resolveDirectory($path); + } + catch (FileException $e) + { + return $this->addFile($path); + } + + $this->addDirectory($path, $recursive); + } + + return $this; + } + + //-------------------------------------------------------------------- + // File Handling + //-------------------------------------------------------------------- + + /** + * Verifies and adds files to the list. + * + * @param string[] $files + * + * @return $this + */ + public function addFiles(array $files) + { + foreach ($files as $file) + { + $this->addFile($file); + } + + return $this; + } + + /** + * Verifies and adds a single file to the file list. + * + * @param string $file + * + * @return $this + */ + public function addFile(string $file) + { + $this->files[] = self::resolveFile($file); + + return $this; + } + + /** + * Removes files from the list. + * + * @param string[] $files + * + * @return $this + */ + public function removeFiles(array $files) + { + $this->files = array_diff($this->files, $files); + + return $this; + } + + /** + * Removes a single file from the list. + * + * @param string $file + * + * @return $this + */ + public function removeFile(string $file) + { + return $this->removeFiles([$file]); + } + + //-------------------------------------------------------------------- + // Directory Handling + //-------------------------------------------------------------------- + + /** + * Verifies and adds files from each + * directory to the list. + * + * @param string[] $directories + * @param bool $recursive + * + * @return $this + */ + public function addDirectories(array $directories, bool $recursive = false) + { + foreach ($directories as $directory) + { + $this->addDirectory($directory, $recursive); + } + + return $this; + } + + /** + * Verifies and adds all files from a directory. + * + * @param string $directory + * @param boolean $recursive + * + * @return $this + */ + public function addDirectory(string $directory, bool $recursive = false) + { + $directory = self::resolveDirectory($directory); + + // Map the directory to depth 2 to so directories become arrays + foreach (directory_map($directory, 2, true) as $key => $path) + { + if (is_string($path)) + { + $this->addFile($directory . $path); + } + elseif ($recursive && is_array($path)) + { + $this->addDirectory($directory . $key, true); + } + } + + return $this; + } + + //-------------------------------------------------------------------- + // Filtering + //-------------------------------------------------------------------- + + /** + * Removes any files from the list that match the supplied pattern + * (within the optional scope). + * + * @param string $pattern Regex or pseudo-regex string + * @param string|null $scope The directory to limit the scope + * + * @return $this + */ + public function removePattern(string $pattern, string $scope = null) + { + if ($pattern === '') + { + return $this; + } + + // Start with all files or those in scope + $files = is_null($scope) ? $this->files : self::filterFiles($this->files, $scope); + + // Remove any files that match the pattern + return $this->removeFiles(self::matchFiles($files, $pattern)); + } + + /** + * Keeps only the files from the list that match + * (within the optional scope). + * + * @param string $pattern Regex or pseudo-regex string + * @param string|null $scope A directory to limit the scope + * + * @return $this + */ + public function retainPattern(string $pattern, string $scope = null) + { + if ($pattern === '') + { + return $this; + } + + // Start with all files or those in scope + $files = is_null($scope) ? $this->files : self::filterFiles($this->files, $scope); + + // Matches the pattern within the scoped files and remove their inverse. + return $this->removeFiles(array_diff($files, self::matchFiles($files, $pattern))); + } + + //-------------------------------------------------------------------- + // Interface Methods + //-------------------------------------------------------------------- + + /** + * Returns the current number of files in the collection. + * Fulfills Countable. + * + * @return int + */ + public function count(): int + { + return count($this->files); + } + + /** + * Yields as an Iterator for the current files. + * Fulfills IteratorAggregate. + * + * @throws FileNotFoundException + * + * @return Generator + */ + public function getIterator(): Generator + { + foreach ($this->get() as $file) + { + yield new File($file, true); + } + } +} diff --git a/system/Language/en/Files.php b/system/Language/en/Files.php index ccb9ab306876..f50764898fe3 100644 --- a/system/Language/en/Files.php +++ b/system/Language/en/Files.php @@ -11,6 +11,8 @@ // Files language settings return [ - 'fileNotFound' => 'File not found: {0}', - 'cannotMove' => 'Could not move file {0} to {1} ({2}).', + 'fileNotFound' => 'File not found: {0}', + 'cannotMove' => 'Could not move file {0} to {1} ({2}).', + 'expectedDirectory' => '{0} expects a valid directory.', + 'expectedFile' => '{0} expects a valid file.', ]; diff --git a/system/Language/en/Publisher.php b/system/Language/en/Publisher.php index 2d7ae8418b25..77d805a78ff2 100644 --- a/system/Language/en/Publisher.php +++ b/system/Language/en/Publisher.php @@ -12,8 +12,6 @@ // Publisher language settings return [ 'collision' => 'Publisher encountered an unexpected {0} while copying {1} to {2}.', - 'expectedDirectory' => 'Publisher::{0} expects a valid directory.', - 'expectedFile' => 'Publisher::{0} expects a valid file.', 'destinationNotAllowed' => 'Destination is not on the allowed list of Publisher directories: {0}', 'fileNotAllowed' => '{0} fails the following restriction for {1}: {2}', diff --git a/system/Publisher/Exceptions/PublisherException.php b/system/Publisher/Exceptions/PublisherException.php index d54420881ec7..c3e85a994ebe 100644 --- a/system/Publisher/Exceptions/PublisherException.php +++ b/system/Publisher/Exceptions/PublisherException.php @@ -31,26 +31,6 @@ public static function forCollision(string $from, string $to) return new static(lang('Publisher.collision', [filetype($to), $from, $to])); } - /** - * Throws when an object is expected to be a directory but is not or is missing. - * - * @param string $caller The method causing the exception - */ - public static function forExpectedDirectory(string $caller) - { - return new static(lang('Publisher.expectedDirectory', [$caller])); - } - - /** - * Throws when an object is expected to be a file but is not or is missing. - * - * @param string $caller The method causing the exception - */ - public static function forExpectedFile(string $caller) - { - return new static(lang('Publisher.expectedFile', [$caller])); - } - /** * Throws when given a destination that is not in the list of allowed directories. * diff --git a/system/Publisher/Publisher.php b/system/Publisher/Publisher.php index 1bfd4b91768d..ab4484cdea0b 100644 --- a/system/Publisher/Publisher.php +++ b/system/Publisher/Publisher.php @@ -12,7 +12,7 @@ namespace CodeIgniter\Publisher; use CodeIgniter\Autoloader\FileLocator; -use CodeIgniter\Files\File; +use CodeIgniter\Files\FileCollection; use CodeIgniter\HTTP\URI; use CodeIgniter\Publisher\Exceptions\PublisherException; use RuntimeException; @@ -28,13 +28,13 @@ * path to a verified file while a "path" is relative to its source * or destination and may indicate either a file or directory of * unconfirmed existence. - * class failures throw the PublisherException, but some underlying + * Class failures throw the PublisherException, but some underlying * methods may percolate different exceptions, like FileException, * FileNotFoundException or InvalidArgumentException. * Write operations will catch all errors in the file-specific * $errors property to minimize impact of partial batch operations. */ -class Publisher +class Publisher extends FileCollection { /** * Array of discovered Publishers. @@ -51,13 +51,6 @@ class Publisher */ private $scratch; - /** - * The current list of files. - * - * @var string[] - */ - private $files = []; - /** * Exceptions for specific files from the last write operation. * @@ -94,6 +87,10 @@ class Publisher */ protected $destination = FCPATH; + //-------------------------------------------------------------------- + // Support Methods + //-------------------------------------------------------------------- + /** * Discovers and returns all Publishers in the specified namespace directory. * @@ -134,95 +131,6 @@ final public static function discover(string $directory = 'Publishers'): array return self::$discovered[$directory]; } - //-------------------------------------------------------------------- - - /** - * Resolves a full path and verifies it is an actual directory. - * - * @param string $directory - * - * @return string - * - * @throws PublisherException - */ - private static function resolveDirectory(string $directory): string - { - if (! is_dir($directory = set_realpath($directory))) - { - $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; - throw PublisherException::forExpectedDirectory($caller['function']); - } - - return $directory; - } - - /** - * Resolves a full path and verifies it is an actual file. - * - * @param string $file - * - * @return string - * - * @throws PublisherException - */ - private static function resolveFile(string $file): string - { - if (! is_file($file = set_realpath($file))) - { - $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]; - throw PublisherException::forExpectedFile($caller['function']); - } - - return $file; - } - - //-------------------------------------------------------------------- - - /** - * Removes files that are not part of the given directory (recursive). - * - * @param string[] $files - * @param string $directory - * - * @return string[] - */ - private static function filterFiles(array $files, string $directory): array - { - $directory = self::resolveDirectory($directory); - - return array_filter($files, function ($value) use ($directory) { - return strpos($value, $directory) === 0; - }); - } - - /** - * Returns any files whose `basename` matches the given pattern. - * - * @param array $files - * @param string $pattern Regex or pseudo-regex string - * - * @return string[] - */ - private static function matchFiles(array $files, string $pattern): array - { - // Convert pseudo-regex into their true form - if (@preg_match($pattern, null) === false) // @phpstan-ignore-line - { - $pattern = str_replace( - ['#', '.', '*', '?'], - ['\#', '\.', '.*', '.'], - $pattern - ); - $pattern = "#{$pattern}#"; - } - - return array_filter($files, function ($value) use ($pattern) { - return (bool) preg_match($pattern, basename($value)); - }); - } - - //-------------------------------------------------------------------- - /* * Removes a directory and all its files and subdirectories. * @@ -248,6 +156,8 @@ private static function wipeDirectory(string $directory): void } } + //-------------------------------------------------------------------- + // Class Core //-------------------------------------------------------------------- /** @@ -305,12 +215,14 @@ public function publish(): bool // Safeguard against accidental misuse if ($this->source === ROOTPATH && $this->destination === FCPATH) { - throw new RuntimeException('Child classes of Publisher should provide their own source and destination or publish method.'); + throw new RuntimeException('Child classes of Publisher should provide their own publish method or a source and destination.'); } return $this->addPath('/')->merge(true); } + //-------------------------------------------------------------------- + // Property Accessors //-------------------------------------------------------------------- /** @@ -369,142 +281,8 @@ final public function getPublished(): array return $this->published; } - /** - * Optimizes and returns the current file list. - * - * @return string[] - */ - final public function getFiles(): array - { - $this->files = array_unique($this->files, SORT_STRING); - sort($this->files, SORT_STRING); - - return $this->files; - } - //-------------------------------------------------------------------- - - /** - * Sets the file list directly, files are still subject to verification. - * This works as a "reset" method with []. - * - * @param string[] $files The new file list to use - * - * @return $this - */ - final public function setFiles(array $files) - { - $this->files = []; - - return $this->addFiles($files); - } - - /** - * Verifies and adds files to the list. - * - * @param string[] $files - * - * @return $this - */ - final public function addFiles(array $files) - { - foreach ($files as $file) - { - $this->addFile($file); - } - - return $this; - } - - /** - * Verifies and adds a single file to the file list. - * - * @param string $file - * - * @return $this - */ - final public function addFile(string $file) - { - $this->files[] = self::resolveFile($file); - - return $this; - } - - /** - * Removes files from the list. - * - * @param string[] $files - * - * @return $this - */ - final public function removeFiles(array $files) - { - $this->files = array_diff($this->files, $files); - - return $this; - } - - /** - * Removes a single file from the list. - * - * @param string $file - * - * @return $this - */ - final public function removeFile(string $file) - { - return $this->removeFiles([$file]); - } - - //-------------------------------------------------------------------- - - /** - * Verifies and adds files from each - * directory to the list. - * - * @param string[] $directories - * @param bool $recursive - * - * @return $this - */ - final public function addDirectories(array $directories, bool $recursive = false) - { - foreach ($directories as $directory) - { - $this->addDirectory($directory, $recursive); - } - - return $this; - } - - /** - * Verifies and adds all files from a directory. - * - * @param string $directory - * @param boolean $recursive - * - * @return $this - */ - final public function addDirectory(string $directory, bool $recursive = false) - { - $directory = self::resolveDirectory($directory); - - // Map the directory to depth 2 to so directories become arrays - foreach (directory_map($directory, 2, true) as $key => $path) - { - if (is_string($path)) - { - $this->addFile($directory . $path); - } - elseif ($recursive && is_array($path)) - { - $this->addDirectory($directory . $key, true); - } - } - - return $this; - } - + // Additional Handlers //-------------------------------------------------------------------- /** @@ -535,23 +313,11 @@ final public function addPaths(array $paths, bool $recursive = true) */ final public function addPath(string $path, bool $recursive = true) { - $full = $this->source . $path; + $this->add($this->source . $path, $recursive); - // Test for a directory - try - { - $directory = self::resolveDirectory($full); - } - catch (PublisherException $e) - { - return $this->addFile($full); - } - - return $this->addDirectory($full, $recursive); + return $this; } - //-------------------------------------------------------------------- - /** * Downloads and stages files from an array of URIs. * @@ -588,53 +354,7 @@ final public function addUri(string $uri) } //-------------------------------------------------------------------- - - /** - * Removes any files from the list that match the supplied pattern - * (within the optional scope). - * - * @param string $pattern Regex or pseudo-regex string - * @param string|null $scope The directory to limit the scope - * - * @return $this - */ - final public function removePattern(string $pattern, string $scope = null) - { - if ($pattern === '') - { - return $this; - } - - // Start with all files or those in scope - $files = is_null($scope) ? $this->files : self::filterFiles($this->files, $scope); - - // Remove any files that match the pattern - return $this->removeFiles(self::matchFiles($files, $pattern)); - } - - /** - * Keeps only the files from the list that match - * (within the optional scope). - * - * @param string $pattern Regex or pseudo-regex string - * @param string|null $scope A directory to limit the scope - * - * @return $this - */ - final public function retainPattern(string $pattern, string $scope = null) - { - if ($pattern === '') - { - return $this; - } - - // Start with all files or those in scope - $files = is_null($scope) ? $this->files : self::filterFiles($this->files, $scope); - - // Matches the pattern within the scoped files and remove their inverse. - return $this->removeFiles(array_diff($files, self::matchFiles($files, $pattern))); - } - + // Write Methods //-------------------------------------------------------------------- /** @@ -649,8 +369,6 @@ final public function wipe() return $this; } - //-------------------------------------------------------------------- - /** * Copies all files into the destination, does not create directory structure. * @@ -662,7 +380,7 @@ final public function copy(bool $replace = true): bool { $this->errors = $this->published = []; - foreach ($this->getFiles() as $file) + foreach ($this->get() as $file) { $to = $this->destination . basename($file); @@ -693,7 +411,7 @@ final public function merge(bool $replace = true): bool $this->errors = $this->published = []; // Get the files from source for special handling - $sourced = self::filterFiles($this->getFiles(), $this->source); + $sourced = self::filterFiles($this->get(), $this->source); // Handle everything else with a flat copy $this->files = array_diff($this->files, $sourced); diff --git a/tests/system/Files/FileCollectionTest.php b/tests/system/Files/FileCollectionTest.php new file mode 100644 index 000000000000..ae2df9178f5f --- /dev/null +++ b/tests/system/Files/FileCollectionTest.php @@ -0,0 +1,568 @@ +getPrivateMethodInvoker(FileCollection::class, 'resolveDirectory'); + + $this->assertSame($this->directory, $method($this->directory)); + } + + public function testResolveDirectoryFile() + { + $method = $this->getPrivateMethodInvoker(FileCollection::class, 'resolveDirectory'); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedDirectory', ['invokeArgs'])); + + $method($this->file); + } + + public function testResolveDirectorySymlink() + { + // Create a symlink to test + $link = sys_get_temp_dir() . DIRECTORY_SEPARATOR . bin2hex(random_bytes(4)); + symlink($this->directory, $link); + + $method = $this->getPrivateMethodInvoker(FileCollection::class, 'resolveDirectory'); + + $this->assertSame($this->directory, $method($link)); + + unlink($link); + } + + //-------------------------------------------------------------------- + + public function testResolveFileFile() + { + $method = $this->getPrivateMethodInvoker(FileCollection::class, 'resolveFile'); + + $this->assertSame($this->file, $method($this->file)); + } + + public function testResolveFileSymlink() + { + // Create a symlink to test + $link = sys_get_temp_dir() . DIRECTORY_SEPARATOR . bin2hex(random_bytes(4)); + symlink($this->file, $link); + + $method = $this->getPrivateMethodInvoker(FileCollection::class, 'resolveFile'); + + $this->assertSame($this->file, $method($link)); + + unlink($link); + } + + public function testResolveFileDirectory() + { + $method = $this->getPrivateMethodInvoker(FileCollection::class, 'resolveFile'); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedFile', ['invokeArgs'])); + + $method($this->directory); + } + + //-------------------------------------------------------------------- + + public function testConstructorAddsFiles() + { + $expected = [ + $this->directory . 'apple.php', + $this->file, + ]; + + $collection = new class([$this->file]) extends FileCollection { + + protected $files = [ + SUPPORTPATH . 'Files/able/apple.php', + ]; + }; + + $this->assertSame($expected, $collection->get()); + } + + public function testConstructorCallsDefine() + { + $collection = new class() extends FileCollection { + + protected function define(): void + { + $this->add(SUPPORTPATH . 'Files/baker/banana.php'); + } + }; + + $this->assertSame([$this->file], $collection->get()); + } + + //-------------------------------------------------------------------- + + public function testAddStringFile() + { + $files = new FileCollection(); + + $files->add(SUPPORTPATH . 'Files/baker/banana.php'); + + $this->assertSame([$this->file], $files->get()); + } + + public function testAddStringFileRecursiveDoesNothing() + { + $files = new FileCollection(); + + $files->add(SUPPORTPATH . 'Files/baker/banana.php', true); + + $this->assertSame([$this->file], $files->get()); + } + + public function testAddStringDirectory() + { + $files = new FileCollection(); + + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + ]; + + $files->add(SUPPORTPATH . 'Files/able'); + + $this->assertSame($expected, $files->get()); + } + + public function testAddStringDirectoryRecursive() + { + $files = new FileCollection(); + + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $files->add(SUPPORTPATH . 'Files'); + + $this->assertSame($expected, $files->get()); + } + + public function testAddArray() + { + $files = new FileCollection(); + + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $files->add([ + SUPPORTPATH . 'Files/able', + SUPPORTPATH . 'Files/baker/banana.php', + ]); + + $this->assertSame($expected, $files->get()); + } + + public function testAddArrayRecursive() + { + $files = new FileCollection(); + + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + SUPPORTPATH . 'Log/Handlers/TestHandler.php', + ]; + + $files->add([ + SUPPORTPATH . 'Files', + SUPPORTPATH . 'Log', + ], true); + + $this->assertSame($expected, $files->get()); + } + + //-------------------------------------------------------------------- + + public function testAddFile() + { + $collection = new FileCollection(); + $this->assertSame([], $this->getPrivateProperty($collection, 'files')); + + $collection->addFile($this->file); + $this->assertSame([$this->file], $this->getPrivateProperty($collection, 'files')); + } + + public function testAddFileMissing() + { + $collection = new FileCollection(); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedFile', ['addFile'])); + + $collection->addFile('TheHillsAreAlive.bmp'); + } + + public function testAddFileDirectory() + { + $collection = new FileCollection(); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedFile', ['addFile'])); + + $collection->addFile($this->directory); + } + + public function testAddFiles() + { + $collection = new FileCollection(); + $files = [ + $this->file, + $this->file, + ]; + + $collection->addFiles($files); + $this->assertSame($files, $this->getPrivateProperty($collection, 'files')); + } + + //-------------------------------------------------------------------- + + public function testGet() + { + $collection = new FileCollection(); + $collection->addFile($this->file); + + $this->assertSame([$this->file], $collection->get()); + } + + public function testGetSorts() + { + $collection = new FileCollection(); + $files = [ + $this->file, + $this->directory . 'apple.php', + ]; + + $collection->addFiles($files); + + $this->assertSame(array_reverse($files), $collection->get()); + } + + public function testGetUniques() + { + $collection = new FileCollection(); + $files = [ + $this->file, + $this->file, + ]; + + $collection->addFiles($files); + $this->assertSame([$this->file], $collection->get()); + } + + public function testSet() + { + $collection = new FileCollection(); + + $collection->set([$this->file]); + $this->assertSame([$this->file], $collection->get()); + } + + public function testSetInvalid() + { + $collection = new FileCollection(); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedFile', ['addFile'])); + + $collection->set(['flerb']); + } + + //-------------------------------------------------------------------- + + public function testRemoveFile() + { + $collection = new FileCollection(); + $files = [ + $this->file, + $this->directory . 'apple.php', + ]; + + $collection->addFiles($files); + + $collection->removeFile($this->file); + + $this->assertSame([$this->directory . 'apple.php'], $collection->get()); + } + + public function testRemoveFiles() + { + $collection = new FileCollection(); + $files = [ + $this->file, + $this->directory . 'apple.php', + ]; + + $collection->addFiles($files); + + $collection->removeFiles($files); + + $this->assertSame([], $collection->get()); + } + + //-------------------------------------------------------------------- + + public function testAddDirectoryInvalid() + { + $collection = new FileCollection(); + + $this->expectException(FileException::class); + $this->expectExceptionMessage(lang('Files.expectedDirectory', ['addDirectory'])); + + $collection->addDirectory($this->file); + } + + public function testAddDirectory() + { + $collection = new FileCollection(); + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + ]; + + $collection->addDirectory($this->directory); + + $this->assertSame($expected, $collection->get()); + } + + public function testAddDirectoryRecursive() + { + $collection = new FileCollection(); + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $this->assertSame($expected, $collection->get()); + } + + public function testAddDirectories() + { + $collection = new FileCollection(); + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->addDirectories([ + $this->directory, + SUPPORTPATH . 'Files/baker', + ]); + + $this->assertSame($expected, $collection->get()); + } + + public function testAddDirectoriesRecursive() + { + $collection = new FileCollection(); + $expected = [ + $this->directory . 'apple.php', + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + SUPPORTPATH . 'Files/baker/banana.php', + SUPPORTPATH . 'Log/Handlers/TestHandler.php', + ]; + + $collection->addDirectories([ + SUPPORTPATH . 'Files', + SUPPORTPATH . 'Log', + ], true); + + $this->assertSame($expected, $collection->get()); + } + + //-------------------------------------------------------------------- + + public function testRemovePatternEmpty() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $files = $collection->get(); + + $collection->removePattern(''); + + $this->assertSame($files, $collection->get()); + } + + public function testRemovePatternRegex() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + $this->directory . 'apple.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->removePattern('#[a-z]+_.*#'); + + $this->assertSame($expected, $collection->get()); + } + + public function testRemovePatternPseudo() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + $this->directory . 'apple.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->removePattern('*_*.php'); + + $this->assertSame($expected, $collection->get()); + } + + public function testRemovePatternScope() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->removePattern('*.php', $this->directory); + + $this->assertSame($expected, $collection->get()); + } + + //-------------------------------------------------------------------- + + public function testRetainPatternEmpty() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $files = $collection->get(); + + $collection->retainPattern(''); + + $this->assertSame($files, $collection->get()); + } + + public function testRetainPatternRegex() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + $this->directory . 'fig_3.php', + $this->directory . 'prune_ripe.php', + ]; + + $collection->retainPattern('#[a-z]+_.*#'); + + $this->assertSame($expected, $collection->get()); + } + + public function testRetainPatternPseudo() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + $this->directory . 'fig_3.php', + ]; + + $collection->retainPattern('*_?.php'); + + $this->assertSame($expected, $collection->get()); + } + + public function testRetainPatternScope() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $expected = [ + $this->directory . 'fig_3.php', + SUPPORTPATH . 'Files/baker/banana.php', + ]; + + $collection->retainPattern('*_?.php', $this->directory); + + $this->assertSame($expected, $collection->get()); + } + + //-------------------------------------------------------------------- + + public function testCount() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $this->assertCount(4, $collection); + } + + public function testIterable() + { + $collection = new FileCollection(); + $collection->addDirectory(SUPPORTPATH . 'Files', true); + + $count = 0; + foreach ($collection as $file) + { + $this->assertInstanceOf(File::class, $file); + $count++; + } + + $this->assertSame($count, 4); + } +} diff --git a/tests/system/Publisher/PublisherInputTest.php b/tests/system/Publisher/PublisherInputTest.php index e7450f5c02a9..29d48ef78816 100644 --- a/tests/system/Publisher/PublisherInputTest.php +++ b/tests/system/Publisher/PublisherInputTest.php @@ -35,219 +35,13 @@ public static function setUpBeforeClass(): void //-------------------------------------------------------------------- - public function testAddFile() - { - $publisher = new Publisher(); - $this->assertSame([], $this->getPrivateProperty($publisher, 'files')); - - $publisher->addFile($this->file); - $this->assertSame([$this->file], $this->getPrivateProperty($publisher, 'files')); - } - - public function testAddFileMissing() - { - $publisher = new Publisher(); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedFile', ['addFile'])); - - $publisher->addFile('TheHillsAreAlive.bmp'); - } - - public function testAddFileDirectory() - { - $publisher = new Publisher(); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedFile', ['addFile'])); - - $publisher->addFile($this->directory); - } - - public function testAddFiles() - { - $publisher = new Publisher(); - $files = [ - $this->file, - $this->file, - ]; - - $publisher->addFiles($files); - $this->assertSame($files, $this->getPrivateProperty($publisher, 'files')); - } - - //-------------------------------------------------------------------- - - public function testGetFiles() - { - $publisher = new Publisher(); - $publisher->addFile($this->file); - - $this->assertSame([$this->file], $publisher->getFiles()); - } - - public function testGetFilesSorts() - { - $publisher = new Publisher(); - $files = [ - $this->file, - $this->directory . 'apple.php', - ]; - - $publisher->addFiles($files); - - $this->assertSame(array_reverse($files), $publisher->getFiles()); - } - - public function testGetFilesUniques() - { - $publisher = new Publisher(); - $files = [ - $this->file, - $this->file, - ]; - - $publisher->addFiles($files); - $this->assertSame([$this->file], $publisher->getFiles()); - } - - public function testSetFiles() - { - $publisher = new Publisher(); - - $publisher->setFiles([$this->file]); - $this->assertSame([$this->file], $publisher->getFiles()); - } - - public function testSetFilesInvalid() - { - $publisher = new Publisher(); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedFile', ['addFile'])); - - $publisher->setFiles(['flerb']); - } - - //-------------------------------------------------------------------- - - public function testRemoveFile() - { - $publisher = new Publisher(); - $files = [ - $this->file, - $this->directory . 'apple.php', - ]; - - $publisher->addFiles($files); - - $publisher->removeFile($this->file); - - $this->assertSame([$this->directory . 'apple.php'], $publisher->getFiles()); - } - - public function testRemoveFiles() - { - $publisher = new Publisher(); - $files = [ - $this->file, - $this->directory . 'apple.php', - ]; - - $publisher->addFiles($files); - - $publisher->removeFiles($files); - - $this->assertSame([], $publisher->getFiles()); - } - - //-------------------------------------------------------------------- - - public function testAddDirectoryInvalid() - { - $publisher = new Publisher(); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedDirectory', ['addDirectory'])); - - $publisher->addDirectory($this->file); - } - - public function testAddDirectory() - { - $publisher = new Publisher(); - $expected = [ - $this->directory . 'apple.php', - $this->directory . 'fig_3.php', - $this->directory . 'prune_ripe.php', - ]; - - $publisher->addDirectory($this->directory); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testAddDirectoryRecursive() - { - $publisher = new Publisher(); - $expected = [ - $this->directory . 'apple.php', - $this->directory . 'fig_3.php', - $this->directory . 'prune_ripe.php', - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testAddDirectories() - { - $publisher = new Publisher(); - $expected = [ - $this->directory . 'apple.php', - $this->directory . 'fig_3.php', - $this->directory . 'prune_ripe.php', - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->addDirectories([ - $this->directory, - SUPPORTPATH . 'Files/baker', - ]); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testAddDirectoriesRecursive() - { - $publisher = new Publisher(); - $expected = [ - $this->directory . 'apple.php', - $this->directory . 'fig_3.php', - $this->directory . 'prune_ripe.php', - SUPPORTPATH . 'Files/baker/banana.php', - SUPPORTPATH . 'Log/Handlers/TestHandler.php', - ]; - - $publisher->addDirectories([ - SUPPORTPATH . 'Files', - SUPPORTPATH . 'Log', - ], true); - - $this->assertSame($expected, $publisher->getFiles()); - } - - //-------------------------------------------------------------------- - public function testAddPathFile() { $publisher = new Publisher(SUPPORTPATH . 'Files'); $publisher->addPath('baker/banana.php'); - $this->assertSame([$this->file], $publisher->getFiles()); + $this->assertSame([$this->file], $publisher->get()); } public function testAddPathFileRecursiveDoesNothing() @@ -256,7 +50,7 @@ public function testAddPathFileRecursiveDoesNothing() $publisher->addPath('baker/banana.php', true); - $this->assertSame([$this->file], $publisher->getFiles()); + $this->assertSame([$this->file], $publisher->get()); } public function testAddPathDirectory() @@ -271,7 +65,7 @@ public function testAddPathDirectory() $publisher->addPath('able'); - $this->assertSame($expected, $publisher->getFiles()); + $this->assertSame($expected, $publisher->get()); } public function testAddPathDirectoryRecursive() @@ -287,7 +81,7 @@ public function testAddPathDirectoryRecursive() $publisher->addPath('Files'); - $this->assertSame($expected, $publisher->getFiles()); + $this->assertSame($expected, $publisher->get()); } public function testAddPaths() @@ -306,7 +100,7 @@ public function testAddPaths() 'baker/banana.php', ]); - $this->assertSame($expected, $publisher->getFiles()); + $this->assertSame($expected, $publisher->get()); } public function testAddPathsRecursive() @@ -326,7 +120,7 @@ public function testAddPathsRecursive() 'Log', ], true); - $this->assertSame($expected, $publisher->getFiles()); + $this->assertSame($expected, $publisher->get()); } //-------------------------------------------------------------------- @@ -338,7 +132,7 @@ public function testAddUri() $scratch = $this->getPrivateProperty($publisher, 'scratch'); - $this->assertSame([$scratch . 'composer.json'], $publisher->getFiles()); + $this->assertSame([$scratch . 'composer.json'], $publisher->get()); } public function testAddUris() @@ -351,122 +145,6 @@ public function testAddUris() $scratch = $this->getPrivateProperty($publisher, 'scratch'); - $this->assertSame([$scratch . 'LICENSE', $scratch . 'composer.json'], $publisher->getFiles()); - } - - //-------------------------------------------------------------------- - - public function testRemovePatternEmpty() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $files = $publisher->getFiles(); - - $publisher->removePattern(''); - - $this->assertSame($files, $publisher->getFiles()); - } - - public function testRemovePatternRegex() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - $this->directory . 'apple.php', - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->removePattern('#[a-z]+_.*#'); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testRemovePatternPseudo() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - $this->directory . 'apple.php', - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->removePattern('*_*.php'); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testRemovePatternScope() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->removePattern('*.php', $this->directory); - - $this->assertSame($expected, $publisher->getFiles()); - } - - //-------------------------------------------------------------------- - - public function testRetainPatternEmpty() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $files = $publisher->getFiles(); - - $publisher->retainPattern(''); - - $this->assertSame($files, $publisher->getFiles()); - } - - public function testRetainPatternRegex() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - $this->directory . 'fig_3.php', - $this->directory . 'prune_ripe.php', - ]; - - $publisher->retainPattern('#[a-z]+_.*#'); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testRetainPatternPseudo() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - $this->directory . 'fig_3.php', - ]; - - $publisher->retainPattern('*_?.php'); - - $this->assertSame($expected, $publisher->getFiles()); - } - - public function testRetainPatternScope() - { - $publisher = new Publisher(); - $publisher->addDirectory(SUPPORTPATH . 'Files', true); - - $expected = [ - $this->directory . 'fig_3.php', - SUPPORTPATH . 'Files/baker/banana.php', - ]; - - $publisher->retainPattern('*_?.php', $this->directory); - - $this->assertSame($expected, $publisher->getFiles()); + $this->assertSame([$scratch . 'LICENSE', $scratch . 'composer.json'], $publisher->get()); } } diff --git a/tests/system/Publisher/PublisherSupportTest.php b/tests/system/Publisher/PublisherSupportTest.php index 42f22d721525..081b831f1241 100644 --- a/tests/system/Publisher/PublisherSupportTest.php +++ b/tests/system/Publisher/PublisherSupportTest.php @@ -53,75 +53,11 @@ public function testDiscoverNothing() public function testDiscoverStores() { $publisher = Publisher::discover()[0]; - $publisher->setFiles([])->addFile($this->file); + $publisher->set([])->addFile($this->file); $result = Publisher::discover(); $this->assertSame($publisher, $result[0]); - $this->assertSame([$this->file], $result[0]->getFiles()); - } - - //-------------------------------------------------------------------- - - public function testResolveDirectoryDirectory() - { - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveDirectory'); - - $this->assertSame($this->directory, $method($this->directory)); - } - - public function testResolveDirectoryFile() - { - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveDirectory'); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedDirectory', ['invokeArgs'])); - - $method($this->file); - } - - public function testResolveDirectorySymlink() - { - // Create a symlink to test - $link = sys_get_temp_dir() . DIRECTORY_SEPARATOR . bin2hex(random_bytes(4)); - symlink($this->directory, $link); - - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveDirectory'); - - $this->assertSame($this->directory, $method($link)); - - unlink($link); - } - - //-------------------------------------------------------------------- - - public function testResolveFileFile() - { - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveFile'); - - $this->assertSame($this->file, $method($this->file)); - } - - public function testResolveFileSymlink() - { - // Create a symlink to test - $link = sys_get_temp_dir() . DIRECTORY_SEPARATOR . bin2hex(random_bytes(4)); - symlink($this->file, $link); - - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveFile'); - - $this->assertSame($this->file, $method($link)); - - unlink($link); - } - - public function testResolveFileDirectory() - { - $method = $this->getPrivateMethodInvoker(Publisher::class, 'resolveFile'); - - $this->expectException(PublisherException::class); - $this->expectExceptionMessage(lang('Publisher.expectedFile', ['invokeArgs'])); - - $method($this->directory); + $this->assertSame([$this->file], $result[0]->get()); } //-------------------------------------------------------------------- diff --git a/user_guide_src/source/libraries/files.rst b/user_guide_src/source/libraries/files.rst index e6482642d698..c128558c56ab 100644 --- a/user_guide_src/source/libraries/files.rst +++ b/user_guide_src/source/libraries/files.rst @@ -109,3 +109,124 @@ The move() method returns a new File instance that for the relocated file, so yo resulting location is needed:: $file = $file->move(WRITEPATH.'uploads'); + +**************** +File Collections +**************** + +Working with groups of files can be cumbersome, so the framework supplies the ``FileCollection`` class to facilitate +locating and working with groups of files across the filesystem. At its most basic, ``FileCollection`` is an index +of files you set or build:: + + $files = new FileCollection([ + FCPATH . 'index.php', + ROOTPATH . 'spark', + ]); + $files->addDirectory(APPPATH . 'Filters'); + +After you have input the files you would like to work with you may remove files or use the filtering commands to remove +or retain files matching a certain regex or glob-style pattern:: + + $files->removeFile(APPPATH . 'Filters/DevelopToolbar'); + + $files->removePattern('#\.gitkeep#'); + $files->retainPattern('*.php'); + +When your collection is complete, you can use ``get()`` to retrieve the final list of file paths, or take advantage of +``FileCollection`` being countable and iterable to work directly with each ``File``:: + + echo 'My files: ' . implode(PHP_EOL, $files->get()); + echo 'I have ' . count($files) . ' files!'; + + foreach ($files as $file) + { + echo 'Moving ' . $file->getBasename() . ', ' . $file->getSizeByUnit('mb'); + $file->move(WRITABLE . $file->getRandomName()); + } + +Below are the specific methods for working with a ``FileCollection``. + +Starting a Collection +===================== + +**__construct(string[] $files = [])** + +The constructor accepts an optional array of file paths to use as the initial collection. These are passed to +**add()** so any files supplied by child classes in the **$files** will remain. + +**define()** + +Allows child classes to define their own initial files. This method is called by the constructor and allows +predefined collections without having to use their methods. Example:: + + class ConfigCollection extends \CodeIgniter\Files\FileCollection + { + protected function define(): void + { + $this->add(APPPATH . 'Config', true)->retainPattern('*.php'); + } + } + +Now you may use the ``ConfigCollection`` anywhere in your project to access all App Config files without +having to re-call the collection methods every time. + +**set(array $files)** + +Sets the list of input files to the provided string array of file paths. This will remove any existing +files from the collection, so ``$collection->set([])`` is essentially a hard reset. + +Inputting Files +=============== + +**add(string[]|string $paths, bool $recursive = true)** + +Adds all files indicated by the path or array of paths. If the path resolves to a directory then ``$recursive`` +will include sub-directories. + +**addFile(string $file)** +**addFiles(array $files)** + +Adds the file or files to the current list of input files. Files are absolute paths to actual files. + +**removeFile(string $file)** +**removeFiles(array $files)** + +Removes the file or files from the current list of input files. + +**addDirectory(string $directory, bool $recursive = false)** +**addDirectories(array $directories, bool $recursive = false)** + +Adds all files from the directory or directories, optionally recursing into sub-directories. Directories are +absolute paths to actual directories. + +Filtering Files +=============== + +**removePattern(string $pattern, string $scope = null)** +**retainPattern(string $pattern, string $scope = null)** + +Filters the current file list through the pattern (and optional scope), removing or retaining matched +files. ``$pattern`` may be a complete regex (like ``'#[A-Za-z]+\.php#'``) or a pseudo-regex similar +to ``glob()`` (like ``*.css``). +If a ``$scope`` is provided then only files in or under that directory will be considered (i.e. files +outside of ``$scope`` are always retained). When no scope is provided then all files are subject. + +Examples:: + + $files = new FileCollection(); + $files->add(APPPATH . 'Config', true); // Adds all Config files and directories + + $files->removePattern('*tion.php'); // Would remove Encryption.php, Validation.php, and boot/production.php + $files->removePattern('*tion.php', APPPATH . 'Config/boot'); // Would only remove boot/production.php + + $files->retainPattern('#A.+php$#'); // Would keep only Autoload.php + $files->retainPattern('#d.+php$#', APPPATH . 'Config/boot'); // Would keep everything but boot/production.php and boot/testing.php + +Retrieving Files +================ + +**get(): string[]** + +Returns an array of all the loaded input files. + +.. note:: ``FileCollection`` is an ``IteratorAggregate`` so you can work with it directly (e.g. ``foreach ($collection as $file)``). diff --git a/user_guide_src/source/libraries/publisher.rst b/user_guide_src/source/libraries/publisher.rst index 84b430ba144b..dc2ec08347a8 100644 --- a/user_guide_src/source/libraries/publisher.rst +++ b/user_guide_src/source/libraries/publisher.rst @@ -28,8 +28,8 @@ Concept and Usage * How can I update my project when the framework or modules change? * How can components inject new content into existing projects? -At its most basic, publishing amounts to copying a file or files into a project. ``Publisher`` uses fluent-style -command chaining to read, filter, and process input files, then copies or merges them into the target destination. +At its most basic, publishing amounts to copying a file or files into a project. ``Publisher`` extends ``FileCollection`` +to enact fluent-style command chaining to read, filter, and process input files, then copies or merges them into the target destination. You may use ``Publisher`` on demand in your Controllers or other components, or you may stage publications by extending the class and leveraging its discovery with ``spark publish``. @@ -320,6 +320,8 @@ Now when your module users run ``php spark auth:publish`` they will have the fol Library Reference ***************** +.. note:: ``Publisher`` is an extension of :doc:`FileCollection ` so has access to all those methods for reading and filtering files. + Support Methods =============== @@ -346,33 +348,6 @@ files and changes, and this provides the path to a transient, writable directory Returns any errors from the last write operation. The array keys are the files that caused the error, and the values are the Throwable that was caught. Use ``getMessage()`` on the Throwable to get the error message. -**getFiles(): string[]** - -Returns an array of all the loaded input files. - -Inputting Files -=============== - -**setFiles(array $files)** - -Sets the list of input files to the provided string array of file paths. - -**addFile(string $file)** -**addFiles(array $files)** - -Adds the file or files to the current list of input files. Files are absolute paths to actual files. - -**removeFile(string $file)** -**removeFiles(array $files)** - -Removes the file or files from the current list of input files. - -**addDirectory(string $directory, bool $recursive = false)** -**addDirectories(array $directories, bool $recursive = false)** - -Adds all files from the directory or directories, optionally recursing into sub-directories. Directories are -absolute paths to actual directories. - **addPath(string $path, bool $recursive = true)** **addPaths(array $path, bool $recursive = true)** @@ -388,29 +363,6 @@ file to the list. .. note:: The CURL request made is a simple ``GET`` and uses the response body for the file contents. Some remote files may need a custom request to be handled properly. -Filtering Files -=============== - -**removePattern(string $pattern, string $scope = null)** -**retainPattern(string $pattern, string $scope = null)** - -Filters the current file list through the pattern (and optional scope), removing or retaining matched -files. ``$pattern`` may be a complete regex (like ``'#[A-Za-z]+\.php#'``) or a pseudo-regex similar -to ``glob()`` (like ``*.css``). -If a ``$scope`` is provided then only files in or under that directory will be considered (i.e. files -outside of ``$scope`` are always retained). When no scope is provided then all files are subject. - -Examples:: - - $publisher = new Publisher(APPPATH . 'Config'); - $publisher->addPath('/', true); // Adds all Config files and directories - - $publisher->removePattern('*tion.php'); // Would remove Encryption.php, Validation.php, and boot/production.php - $publisher->removePattern('*tion.php', APPPATH . 'Config/boot'); // Would only remove boot/production.php - - $publisher->retainPattern('#A.+php$#'); // Would keep only Autoload.php - $publisher->retainPattern('#d.+php$#', APPPATH . 'Config/boot'); // Would keep everything but boot/production.php and boot/testing.php - Outputting Files ================