mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Add Guide, tweak class
This commit is contained in:
parent
c18a08fae8
commit
ea6ca9fec0
@ -15,6 +15,7 @@ use CodeIgniter\Autoloader\FileLocator;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\Publisher\Exceptions\PublisherException;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@ -64,6 +65,13 @@ class Publisher
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* List of file published curing the last write operation.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $published = [];
|
||||
|
||||
/**
|
||||
* Base path to use for the source.
|
||||
*
|
||||
@ -85,7 +93,7 @@ class Publisher
|
||||
*
|
||||
* @return self[]
|
||||
*/
|
||||
public static function discover(string $directory = 'Publishers'): array
|
||||
final public static function discover(string $directory = 'Publishers'): array
|
||||
{
|
||||
if (isset(self::$discovered[$directory]))
|
||||
{
|
||||
@ -301,7 +309,7 @@ class Publisher
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads files in the sources and copies them out to their destinations.
|
||||
* Reads files from the sources and copies them out to their destinations.
|
||||
* This method should be reimplemented by child classes intended for
|
||||
* discovery.
|
||||
*
|
||||
@ -309,17 +317,42 @@ class Publisher
|
||||
*/
|
||||
public function publish(): bool
|
||||
{
|
||||
if ($this->source === ROOTPATH && $this->destination === FCPATH)
|
||||
{
|
||||
throw new RuntimeException('Child classes of Publisher should provide their own source and destination or publish method.');
|
||||
}
|
||||
|
||||
return $this->addPath('/')->merge(true);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the source directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function getSource(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function getDestination(): string
|
||||
{
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temporary workspace, creating it if necessary.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getScratch(): string
|
||||
final public function getScratch(): string
|
||||
{
|
||||
if (is_null($this->scratch))
|
||||
{
|
||||
@ -335,17 +368,27 @@ class Publisher
|
||||
*
|
||||
* @return array<string,Throwable>
|
||||
*/
|
||||
public function getErrors(): array
|
||||
final public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the files published by the last write operation.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
final public function getPublished(): array
|
||||
{
|
||||
return $this->published;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes and returns the current file list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFiles(): array
|
||||
final public function getFiles(): array
|
||||
{
|
||||
$this->files = array_unique($this->files, SORT_STRING);
|
||||
sort($this->files, SORT_STRING);
|
||||
@ -353,6 +396,8 @@ class Publisher
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sets the file list directly, files are still subject to verification.
|
||||
* This works as a "reset" method with [].
|
||||
@ -361,15 +406,13 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFiles(array $files)
|
||||
final public function setFiles(array $files)
|
||||
{
|
||||
$this->files = [];
|
||||
|
||||
return $this->addFiles($files);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Verifies and adds files to the list.
|
||||
*
|
||||
@ -377,7 +420,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addFiles(array $files)
|
||||
final public function addFiles(array $files)
|
||||
{
|
||||
foreach ($files as $file)
|
||||
{
|
||||
@ -394,7 +437,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addFile(string $file)
|
||||
final public function addFile(string $file)
|
||||
{
|
||||
$this->files[] = self::resolveFile($file);
|
||||
|
||||
@ -408,7 +451,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFiles(array $files)
|
||||
final public function removeFiles(array $files)
|
||||
{
|
||||
$this->files = array_diff($this->files, $files);
|
||||
|
||||
@ -422,7 +465,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFile(string $file)
|
||||
final public function removeFile(string $file)
|
||||
{
|
||||
return $this->removeFiles([$file]);
|
||||
}
|
||||
@ -438,7 +481,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirectories(array $directories, bool $recursive = false)
|
||||
final public function addDirectories(array $directories, bool $recursive = false)
|
||||
{
|
||||
foreach ($directories as $directory)
|
||||
{
|
||||
@ -456,7 +499,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDirectory(string $directory, bool $recursive = false)
|
||||
final public function addDirectory(string $directory, bool $recursive = false)
|
||||
{
|
||||
$directory = self::resolveDirectory($directory);
|
||||
|
||||
@ -486,7 +529,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPaths(array $paths, bool $recursive = true)
|
||||
final public function addPaths(array $paths, bool $recursive = true)
|
||||
{
|
||||
foreach ($paths as $path)
|
||||
{
|
||||
@ -504,7 +547,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPath(string $path, bool $recursive = true)
|
||||
final public function addPath(string $path, bool $recursive = true)
|
||||
{
|
||||
$full = $this->source . $path;
|
||||
|
||||
@ -530,7 +573,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addUris(array $uris)
|
||||
final public function addUris(array $uris)
|
||||
{
|
||||
foreach ($uris as $uri)
|
||||
{
|
||||
@ -547,7 +590,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addUri(string $uri)
|
||||
final public function addUri(string $uri)
|
||||
{
|
||||
// Figure out a good filename (using URI strips queries and fragments)
|
||||
$file = $this->getScratch() . basename((new URI($uri))->getPath());
|
||||
@ -569,7 +612,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removePattern(string $pattern, string $scope = null)
|
||||
final public function removePattern(string $pattern, string $scope = null)
|
||||
{
|
||||
if ($pattern === '')
|
||||
{
|
||||
@ -592,7 +635,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function retainPattern(string $pattern, string $scope = null)
|
||||
final public function retainPattern(string $pattern, string $scope = null)
|
||||
{
|
||||
if ($pattern === '')
|
||||
{
|
||||
@ -613,7 +656,7 @@ class Publisher
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function wipe()
|
||||
final public function wipe()
|
||||
{
|
||||
self::wipeDirectory($this->destination);
|
||||
|
||||
@ -629,9 +672,9 @@ class Publisher
|
||||
*
|
||||
* @return boolean Whether all files were copied successfully
|
||||
*/
|
||||
public function copy(bool $replace = true): bool
|
||||
final public function copy(bool $replace = true): bool
|
||||
{
|
||||
$this->errors = [];
|
||||
$this->errors = $this->published = [];
|
||||
|
||||
foreach ($this->getFiles() as $file)
|
||||
{
|
||||
@ -640,6 +683,7 @@ class Publisher
|
||||
try
|
||||
{
|
||||
self::safeCopyFile($file, $to, $replace);
|
||||
$this->published[] = $to;
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
@ -658,9 +702,9 @@ class Publisher
|
||||
*
|
||||
* @return boolean Whether all files were copied successfully
|
||||
*/
|
||||
public function merge(bool $replace = true): bool
|
||||
final public function merge(bool $replace = true): bool
|
||||
{
|
||||
$this->errors = [];
|
||||
$this->errors = $this->published = [];
|
||||
|
||||
// Get the file from source for special handling
|
||||
$sourced = self::filterFiles($this->getFiles(), $this->source);
|
||||
@ -678,6 +722,7 @@ class Publisher
|
||||
try
|
||||
{
|
||||
self::safeCopyFile($file, $to, $replace);
|
||||
$this->published[] = $to;
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
|
@ -4,13 +4,62 @@ namespace Tests\Support\Publishers;
|
||||
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
|
||||
class TestPublisher extends Publisher
|
||||
final class TestPublisher extends Publisher
|
||||
{
|
||||
/**
|
||||
* Runs the defined Operations.
|
||||
* Fakes an error on the given file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public static function setError(string $file)
|
||||
{
|
||||
self::$error = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* A file to cause an error
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $error = '';
|
||||
|
||||
/**
|
||||
* Base path to use for the source.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $source = SUPPORTPATH . 'Files';
|
||||
|
||||
/**
|
||||
* Base path to use for the destination.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $destination = WRITEPATH;
|
||||
|
||||
/**
|
||||
* Fakes a publish event so no files are actually copied.
|
||||
*/
|
||||
public function publish(): bool
|
||||
{
|
||||
$this->downloadFromUrls($urls)->mergeToDirectory(FCPATH . 'assets');
|
||||
$this->errors = $this->published = [];
|
||||
|
||||
$this->addPath('');
|
||||
|
||||
// Copy each sourced file to its relative destination
|
||||
foreach ($this->getFiles() as $file)
|
||||
{
|
||||
if ($file === self::$error)
|
||||
{
|
||||
$this->errors[$file] = new RuntimeException('Have an error, dear.');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resolve the destination path
|
||||
$this->published[] = $this->destination . substr($file, strlen($this->source));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->errors === [];
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
|
||||
$result = $publisher->copy(true);
|
||||
$this->assertTrue($result);
|
||||
$this->assertSame([$this->root->url() . '/banana.php'], $publisher->getPublished());
|
||||
}
|
||||
|
||||
public function testCopyIgnoresCollision()
|
||||
@ -121,10 +122,10 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
mkdir($this->root->url() . '/banana.php');
|
||||
|
||||
$result = $publisher->addFile($this->file)->copy(false);
|
||||
$errors = $publisher->getErrors();
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertSame([], $errors);
|
||||
$this->assertSame([], $publisher->getErrors());
|
||||
$this->assertSame([$this->root->url() . '/banana.php'], $publisher->getPublished());
|
||||
}
|
||||
|
||||
public function testCopyCollides()
|
||||
@ -140,6 +141,7 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
$this->assertFalse($result);
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertSame([$this->file], array_keys($errors));
|
||||
$this->assertSame([], $publisher->getPublished());
|
||||
$this->assertSame($expected, $errors[$this->file]->getMessage());
|
||||
}
|
||||
|
||||
@ -148,6 +150,12 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
public function testMerge()
|
||||
{
|
||||
$publisher = new Publisher(SUPPORTPATH . 'Files', $this->root->url());
|
||||
$expected = [
|
||||
$this->root->url() . '/able/apple.php',
|
||||
$this->root->url() . '/able/fig_3.php',
|
||||
$this->root->url() . '/able/prune_ripe.php',
|
||||
$this->root->url() . '/baker/banana.php',
|
||||
];
|
||||
|
||||
$this->assertFileDoesNotExist($this->root->url() . '/able/fig_3.php');
|
||||
$this->assertDirectoryDoesNotExist($this->root->url() . '/baker');
|
||||
@ -157,23 +165,36 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
$this->assertTrue($result);
|
||||
$this->assertFileExists($this->root->url() . '/able/fig_3.php');
|
||||
$this->assertDirectoryExists($this->root->url() . '/baker');
|
||||
$this->assertSame($expected, $publisher->getPublished());
|
||||
}
|
||||
|
||||
public function testMergeReplace()
|
||||
{
|
||||
$this->assertFalse(same_file($this->directory . 'apple.php', $this->root->url() . '/able/apple.php'));
|
||||
$publisher = new Publisher(SUPPORTPATH . 'Files', $this->root->url());
|
||||
$expected = [
|
||||
$this->root->url() . '/able/apple.php',
|
||||
$this->root->url() . '/able/fig_3.php',
|
||||
$this->root->url() . '/able/prune_ripe.php',
|
||||
$this->root->url() . '/baker/banana.php',
|
||||
];
|
||||
|
||||
$result = $publisher->addPath('/')->merge(true);
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertTrue(same_file($this->directory . 'apple.php', $this->root->url() . '/able/apple.php'));
|
||||
$this->assertSame($expected, $publisher->getPublished());
|
||||
}
|
||||
|
||||
public function testMergeCollides()
|
||||
{
|
||||
$publisher = new Publisher(SUPPORTPATH . 'Files', $this->root->url());
|
||||
$expected = lang('Publisher.collision', ['dir', $this->directory . 'fig_3.php', $this->root->url() . '/able/fig_3.php']);
|
||||
$published = [
|
||||
$this->root->url() . '/able/apple.php',
|
||||
$this->root->url() . '/able/prune_ripe.php',
|
||||
$this->root->url() . '/baker/banana.php',
|
||||
];
|
||||
|
||||
mkdir($this->root->url() . '/able/fig_3.php');
|
||||
|
||||
@ -183,6 +204,7 @@ class PublisherOutputTest extends CIUnitTestCase
|
||||
$this->assertFalse($result);
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertSame([$this->directory . 'fig_3.php'], array_keys($errors));
|
||||
$this->assertSame($published, $publisher->getPublished());
|
||||
$this->assertSame($expected, $errors[$this->directory . 'fig_3.php']->getMessage());
|
||||
}
|
||||
|
||||
|
@ -126,13 +126,26 @@ class PublisherSupportTest extends CIUnitTestCase
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
public function testGetSource()
|
||||
{
|
||||
$publisher = new Publisher(ROOTPATH);
|
||||
|
||||
$this->assertSame(ROOTPATH, $publisher->getSource());
|
||||
}
|
||||
|
||||
public function testGetDestination()
|
||||
{
|
||||
$publisher = new Publisher(ROOTPATH, SUPPORTPATH);
|
||||
|
||||
$this->assertSame(SUPPORTPATH, $publisher->getDestination());
|
||||
}
|
||||
|
||||
public function testGetScratch()
|
||||
{
|
||||
$publisher = new Publisher();
|
||||
$this->assertNull($this->getPrivateProperty($publisher, 'scratch'));
|
||||
|
||||
$method = $this->getPrivateMethodInvoker($publisher, 'getScratch');
|
||||
$scratch = $method();
|
||||
$scratch = $publisher->getScratch();
|
||||
|
||||
$this->assertIsString($scratch);
|
||||
$this->assertDirectoryExists($scratch);
|
||||
|
437
user_guide_src/source/libraries/publisher.rst
Normal file
437
user_guide_src/source/libraries/publisher.rst
Normal file
@ -0,0 +1,437 @@
|
||||
#########
|
||||
Publisher
|
||||
#########
|
||||
|
||||
The Publisher library provides a means to copy files within a project using robust detection and error checking.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
*******************
|
||||
Loading the Library
|
||||
*******************
|
||||
|
||||
Because Publisher instances are specific to their source and destination this library is not available
|
||||
through ``Services`` but should be instantiated or extended directly. E.g.
|
||||
|
||||
$publisher = new \CodeIgniter\Publisher\Publisher();
|
||||
|
||||
*****************
|
||||
Concept and Usage
|
||||
*****************
|
||||
|
||||
``Publisher`` solves a handful of common problems when working within a backend framework:
|
||||
|
||||
* How do I maintain project assets with version dependencies?
|
||||
* How do I manage uploads and other "dynamic" files that need to be web accessible?
|
||||
* 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.
|
||||
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``.
|
||||
|
||||
On Demand
|
||||
=========
|
||||
|
||||
Access ``Publisher`` directly by instantiating a new instance of the class::
|
||||
|
||||
$publisher = new \CodeIgniter\Publisher\Publisher();
|
||||
|
||||
By default the source and destination will be set to ``ROOTPATH`` and ``FCPATH`` respectively, giving ``Publisher``
|
||||
easy access to take any file from your project and make it web-accessible. Alternatively you may pass a new source
|
||||
or source and destination into the constructor::
|
||||
|
||||
$vendorPublisher = new Publisher(ROOTPATH . 'vendor');
|
||||
$filterPublisher = new Publisher('/path/to/module/Filters', APPPATH . 'Filters');
|
||||
|
||||
Once the source and destination are set you may start adding relative input files::
|
||||
|
||||
$frameworkPublisher = new Publisher(ROOTPATH . 'vendor/codeigniter4/codeigniter4');
|
||||
|
||||
// All "path" commands are relative to $source
|
||||
$frameworkPublisher->addPath('app/Config/Cookie.php');
|
||||
|
||||
// You may also add from outside the source, but the files will not be merged into subdirectories
|
||||
$frameworkPublisher->addFiles([
|
||||
'/opt/mail/susan',
|
||||
'/opt/mail/ubuntu',
|
||||
]);
|
||||
$frameworkPublisher->addDirectory(SUPPORTPATH . 'Images');
|
||||
|
||||
Once all the files are staged use one of the output commands (**copy()** or **merge()**) to process the staged files
|
||||
to their destination(s)::
|
||||
|
||||
// Place all files into $destination
|
||||
$frameworkPublisher->copy();
|
||||
|
||||
// Place all files into $destination, overwriting existing files
|
||||
$frameworkPublisher->copy(true);
|
||||
|
||||
// Place files into their relative $destination directories, overwriting and saving the boolean result
|
||||
$result = $frameworkPublisher->merge(true);
|
||||
|
||||
See the Library Reference for a full description of available methods.
|
||||
|
||||
Automation and Discovery
|
||||
========================
|
||||
|
||||
You may have regular publication tasks embedded as part of your application deployment or upkeep. ``Publisher`` leverages
|
||||
the powerful ``Autoloader`` to locate any child classes primed for publication::
|
||||
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
|
||||
foreach (Publisher::discover() as $publisher)
|
||||
{
|
||||
$result = $publisher->publish();
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
CLI::write(get_class($publisher) . ' failed to publish!', 'red');
|
||||
}
|
||||
}
|
||||
|
||||
By default ``discover()`` will search for the "Publishers" directory across all namespaces, but you may specify a
|
||||
different directory and it will return any child classes found::
|
||||
|
||||
$memePublishers = Publisher::discover('CatGIFs');
|
||||
|
||||
Most of the time you will not need to handle your own discovery, just use the provided "publish" command::
|
||||
|
||||
> php spark publish
|
||||
|
||||
By default on your class extension ``publish()`` will add all files from your ``$source`` and merge them
|
||||
out to your destination, overwriting on collision.
|
||||
|
||||
********
|
||||
Examples
|
||||
********
|
||||
|
||||
Here are a handful of example use cases and their implementations to help you get started publishing.
|
||||
|
||||
File Sync Example
|
||||
=================
|
||||
|
||||
You want to display a "photo of the day" image on your homepage. You have a feed for daily photos but you
|
||||
need to get the actual file into a browsable location in your project at **public/images/daily_photo.jpg**.
|
||||
You can set up :doc:`Custom Command </cli/cli_commands>` to run daily that will handle this for you::
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
use Throwable;
|
||||
|
||||
class DailyPhoto extends BaseCommand
|
||||
{
|
||||
protected $group = 'Publication';
|
||||
protected $name = 'publish:daily';
|
||||
protected $description = 'Publishes the latest daily photo to the homepage.';
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$publisher = new Publisher('/path/to/photos/', FCPATH . 'assets/images');
|
||||
|
||||
try
|
||||
{
|
||||
$publisher->addPath('daily_photo.jpg')->copy($replace = true);
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
$this->showError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Now running ``spark publish:daily`` will keep your homepage's image up-to-date. What if the photo is
|
||||
coming from an external API? You can use ``addUri()`` in place of ``addPath()`` to download the remote
|
||||
resource and publish it out instead::
|
||||
|
||||
$publisher->addUri('https://example.com/feeds/daily_photo.jpg')->copy($replace = true);
|
||||
|
||||
Asset Dependencies Example
|
||||
==========================
|
||||
|
||||
You want to integrate the frontend library "Bootstrap" into your project, but the frequent updates makes it a hassle
|
||||
to keep up with. You can create a publication definition in your project to sync frontend assets by adding extending
|
||||
``Publisher`` in your project. So **app/Publishers/BootstrapPublisher.php** might look like this::
|
||||
|
||||
namespace App\Publishers;
|
||||
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
|
||||
class BootstrapPublisher extends Publisher
|
||||
{
|
||||
/**
|
||||
* Tell Publisher where to get the files.
|
||||
* Since we will use Composer to download
|
||||
* them we point to the "vendor" directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $source = 'vendor/twbs/bootstrap/';
|
||||
|
||||
/**
|
||||
* FCPATH is always the default destination,
|
||||
* but we may want them to go in a sub-folder
|
||||
* to keep things organized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $destination = FCPATH . 'bootstrap';
|
||||
|
||||
/**
|
||||
* Use the "publish" method to indicate that this
|
||||
* class is ready to be discovered and automated.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function publish(): bool
|
||||
{
|
||||
return $this
|
||||
// Add all the files relative to $source
|
||||
->addPath('dist')
|
||||
|
||||
// Indicate we only want the minimized versions
|
||||
->retainPattern('*.min.*)
|
||||
|
||||
// Merge-and-replace to retain the original directory structure
|
||||
->merge(true);
|
||||
}
|
||||
|
||||
Now add the dependency via Composer and call ``spark publish`` to run the publication::
|
||||
|
||||
> composer require twbs/bootstrap
|
||||
> php spark publish
|
||||
|
||||
... and you'll end up with something like this:
|
||||
|
||||
public/.htaccess
|
||||
public/favicon.ico
|
||||
public/index.php
|
||||
public/robots.txt
|
||||
public/
|
||||
bootstrap/
|
||||
css/
|
||||
bootstrap.min.css
|
||||
bootstrap-utilities.min.css.map
|
||||
bootstrap-grid.min.css
|
||||
bootstrap.rtl.min.css
|
||||
bootstrap.min.css.map
|
||||
bootstrap-reboot.min.css
|
||||
bootstrap-utilities.min.css
|
||||
bootstrap-reboot.rtl.min.css
|
||||
bootstrap-grid.min.css.map
|
||||
js/
|
||||
bootstrap.esm.min.js
|
||||
bootstrap.bundle.min.js.map
|
||||
bootstrap.bundle.min.js
|
||||
bootstrap.min.js
|
||||
bootstrap.esm.min.js.map
|
||||
bootstrap.min.js.map
|
||||
|
||||
Module Deployment Example
|
||||
=========================
|
||||
|
||||
You want to allow developers using your popular authentication module the ability to expand on the default behavior
|
||||
of your Migration, Controller, and Model. You can create your own module "publish" command to inject these components
|
||||
into an application for use::
|
||||
|
||||
namespace Math\Auth\Commands;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
use Throwable;
|
||||
|
||||
class Publish extends BaseCommand
|
||||
{
|
||||
protected $group = 'Auth';
|
||||
protected $name = 'auth:publish';
|
||||
protected $description = 'Publish Auth components into the current application.';
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
// Use the Autoloader to figure out the module path
|
||||
$source = service('autoloader')->getNamespace('Math\\Auth');
|
||||
|
||||
$publisher = new Publisher($source, APPATH);
|
||||
|
||||
try
|
||||
{
|
||||
// Add only the desired components
|
||||
$publisher->addPaths([
|
||||
'Controllers',
|
||||
'Database/Migrations',
|
||||
'Models',
|
||||
])->merge(false); // Be careful not to overwrite anything
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
$this->showError($e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If publication succeeded then update namespaces
|
||||
foreach ($publisher->getFiles as $original)
|
||||
{
|
||||
// Get the location of the new file
|
||||
$file = str_replace($source, APPPATH, $original);
|
||||
|
||||
// Replace the namespace
|
||||
$contents = file_get_contents($file);
|
||||
$contents = str_replace('namespace Math\\Auth', 'namespace ' . APP_NAMESPACE, );
|
||||
file_put_contents($file, $contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Now when your module users run ``php spark auth:publish`` they will have the following added to their project::
|
||||
|
||||
app/Controllers/AuthController.php
|
||||
app/Database/Migrations/2017-11-20-223112_create_auth_tables.php.php
|
||||
app/Models/LoginModel.php
|
||||
app/Models/UserModel.php
|
||||
|
||||
*****************
|
||||
Library Reference
|
||||
*****************
|
||||
|
||||
Support Methods
|
||||
===============
|
||||
|
||||
**[static] discover(string $directory = 'Publishers'): Publisher[]**
|
||||
|
||||
Discovers and returns all Publishers in the specified namespace directory. For example, if both
|
||||
**app/Publishers/FrameworkPublisher.php** and **myModule/src/Publishers/AssetPublisher.php** exist and are
|
||||
extensions of ``Publisher`` then ``Publisher::discover()`` would return an instance of each.
|
||||
|
||||
**publish(): bool**
|
||||
|
||||
Processes the full input-process-output chain. By default this is the equivalent of calling ``addPath($source)``
|
||||
and ``merge(true)`` but child classes will typically provide their own implementation. ``publish()`` is called
|
||||
on all discovered Publishers when running ``spark publish``.
|
||||
Returns success or failure.
|
||||
|
||||
**getScratch(): string**
|
||||
|
||||
Returns the temporary workspace, creating it if necessary. Some operations use intermediate storage to stage
|
||||
files and changes, and this provides the path to a transient, writable directory that you may use as well.
|
||||
|
||||
**getErrors(): array<string,Throwable>**
|
||||
|
||||
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)**
|
||||
|
||||
Adds all files indicated by the relative paths. Paths are references to actual files or directories relative
|
||||
to ``$source``. If the relative path resolves to a directory then ``$recursive`` will include sub-directories.
|
||||
|
||||
**addUri(string $uri)**
|
||||
**addUris(array $uris)**
|
||||
|
||||
Downloads the contents of a URI using ``CURLRequest`` into the scratch workspace then adds the resulting
|
||||
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
|
||||
================
|
||||
|
||||
**wipe()**
|
||||
|
||||
Removes all files, directories, and sub-directories from ``$destination``.
|
||||
|
||||
.. important:: Use wisely.
|
||||
|
||||
**copy(bool $replace = true): bool**
|
||||
|
||||
Copies all files into the ``$destination``. This does not recreate the directory structure, so every file
|
||||
from the current list will end up in the same destination directory. Using ``$replace`` will cause files
|
||||
to overwrite when there is already an existing file. Returns success or failure, use ``getPublished()``
|
||||
and ``getErrors()`` to troubleshoot failures.
|
||||
Be mindful of duplicate basename collisions, for example::
|
||||
|
||||
$publisher = new Publisher('/home/source', '/home/destination');
|
||||
$publisher->addPaths([
|
||||
'pencil/lead.png',
|
||||
'metal/lead.png',
|
||||
]);
|
||||
|
||||
// This is bad! Only one file will remain at /home/destination/lead.png
|
||||
$publisher->copy(true);
|
||||
|
||||
**merge(bool $replace = true): bool**
|
||||
|
||||
Copies all files into the ``$destination`` in appropriate relative sub-directories. Any files that
|
||||
match ``$source`` will be placed into their equivalent directories in ``$destination``, effectively
|
||||
creating a "mirror" or "rsync" operation. Using ``$replace`` will cause files
|
||||
to overwrite when there is already an existing file; since directories are merged this will not
|
||||
affect other files in the destination. Returns success or failure, use ``getPublished()`` and
|
||||
``getErrors()`` to troubleshoot failures.
|
||||
|
||||
Example::
|
||||
|
||||
$publisher = new Publisher('/home/source', '/home/destination');
|
||||
$publisher->addPaths([
|
||||
'pencil/lead.png',
|
||||
'metal/lead.png',
|
||||
]);
|
||||
|
||||
// Results in "/home/destination/pencil/lead.png" and "/home/destination/metal/lead.png"
|
||||
$publisher->merge();
|
Loading…
x
Reference in New Issue
Block a user