发布者

发布者库提供了一种使用强大的检测和错误检查在项目中复制文件的方法。

加载库

由于发布者实例特定于其源和目标,因此此库不可通过 Services 获得,但应直接实例化或扩展。例如

<?php

$publisher = new \CodeIgniter\Publisher\Publisher();

概念和用法

Publisher 在后端框架中工作时,解决了几个常见问题。

  • 如何维护具有版本依赖关系的项目资产?

  • 如何管理需要 Web 访问的上传和其他“动态”文件?

  • 当框架或模块更改时,如何更新我的项目?

  • 组件如何将新内容注入到现有项目中?

在最基本的情况下,发布相当于将一个或多个文件复制到项目中。 Publisher 扩展了 FileCollection,以执行流畅的命令链,以读取、过滤和处理输入文件,然后将它们复制或合并到目标目的地。您可以在控制器或其他组件中按需使用 Publisher,或者可以通过扩展该类并利用其与 spark publish 的发现来进行出版。

按需

通过实例化该类的新的实例,直接访问 Publisher

<?php

$publisher = new \CodeIgniter\Publisher\Publisher();

默认情况下,源和目标将分别设置为 ROOTPATHFCPATH,这使得 Publisher 可以轻松地从您的项目中获取任何文件并使其可供 Web 访问。或者,您可以在构造函数中传递新的源或源和目标

<?php

use CodeIgniter\Publisher\Publisher;

$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');

一旦所有文件都已准备就绪,请使用其中一个输出命令(**copy()** 或 **merge()**)将准备好的文件处理到其目的地

<?php

// 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);

有关可用方法的完整描述,请参阅 库参考

自动化和发现

您可能在应用程序部署或维护中嵌入了定期发布任务。 Publisher 利用强大的 Autoloader 来定位任何为发布准备好的子类

<?php

use CodeIgniter\CLI\CLI;
use CodeIgniter\Publisher\Publisher;

foreach (Publisher::discover() as $publisher) {
    $result = $publisher->publish();

    if ($result === false) {
        CLI::error(get_class($publisher) . ' failed to publish!', 'red');
    }
}

默认情况下,discover() 将在所有命名空间中搜索“Publishers”目录,但您可以指定不同的目录,它将返回找到的任何子类

<?php

use CodeIgniter\Publisher\Publisher;

$memePublishers = Publisher::discover('CatGIFs');

大多数情况下,您不需要处理自己的发现,只需使用提供的“publish”命令即可

php spark publish

默认情况下,在您的类扩展中,publish() 将添加来自您的 $source 的所有文件,并将它们合并到您的目的地,在发生冲突时覆盖。

安全

为了防止模块将恶意代码注入您的项目,Publisher 包含一个配置文件,用于定义允许作为目标的目录和文件模式。默认情况下,文件只能发布到您的项目(以防止访问文件系统的其余部分),并且 **public/** 文件夹 (FCPATH) 将只接收以下扩展名的文件

  • Web 资源:css、scss、js、map

  • 不可执行的 Web 文件:htm、html、xml、json、webmanifest

  • 字体:ttf、eot、woff、woff2

  • 图像:gif、jpg、jpeg、tif、tiff、png、webp、bmp、ico、svg

如果您需要添加或调整项目的安全性,请更改 **app/Config/Publisher.php** 中 Config\Publisher$restrictions 属性。

示例

这里有一些示例用例及其实现,以帮助您开始发布。

文件同步示例

您想在主页上显示一张“每日照片”图片。您有一个每日照片的 feed,但您需要将实际文件放到项目中可浏览的位置,即 **public/images/daily_photo.jpg**。您可以设置一个 自定义命令,每天运行一次,为您处理此事

<?php

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(true); // `true` to enable overwrites
        } catch (Throwable $e) {
            $this->showError($e);
        }
    }
}

现在运行 spark publish:daily 将使您的主页图片保持最新。如果照片来自外部 API 呢?您可以使用 addUri() 代替 addPath() 来下载远程资源并发布它

<?php

$publisher->addUri('https://example.com/feeds/daily_photo.jpg')->copy(true);

资产依赖项示例

您想将前端库“Bootstrap”集成到您的项目中,但频繁的更新使得跟进变得很麻烦。您可以在项目中创建一个发布定义,通过扩展项目中的 Publisher 来同步前端资源。因此,**app/Publishers/BootstrapPublisher.php** 可能看起来像这样

<?php

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 = VENDORPATH . '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.
     */
    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);
    }
}

注意

目录 $destination 必须在执行命令之前创建。

现在通过 Composer 添加依赖项,并调用 spark publish 来运行发布

composer require twbs/bootstrap
php spark publish

… 您最终会得到类似这样的结果

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

模块部署示例

您想允许使用您流行的身份验证模块的开发人员扩展您迁移、控制器和模型的默认行为。您可以创建自己的模块“发布”命令,将这些组件注入应用程序以供使用

<?php

namespace Math\Auth\Commands;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\Publisher\Publisher;
use Throwable;

class AuthPublish 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')[0];

        $publisher = new Publisher($source, APPPATH);

        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->getPublished() as $file) {
            // Replace the namespace
            $contents = file_get_contents($file);
            $contents = str_replace('namespace Math\\Auth', 'namespace ' . APP_NAMESPACE, $contents);
            file_put_contents($file, $contents);
        }
    }
}

现在,当您的模块用户运行 php spark auth:publish 时,他们的项目将添加以下内容

app/Controllers/AuthController.php
app/Database/Migrations/2017-11-20-223112_create_auth_tables.php.php
app/Models/LoginModel.php
app/Models/UserModel.php

库参考

注意

PublisherFileCollection 的扩展,因此可以访问所有用于读取和过滤文件的那些方法。

支持方法

[static] discover(string $directory = ‘Publishers’): Publisher[]

在指定的命名空间目录中发现并返回所有发布者。例如,如果 **app/Publishers/FrameworkPublisher.php** 和 **myModule/src/Publishers/AssetPublisher.php** 都存在并且是 Publisher 的扩展,那么 Publisher::discover() 将返回每个实例。

publish(): bool

处理完整的输入-处理-输出链。默认情况下,这等同于调用 addPath($source)merge(true),但子类通常会提供自己的实现。当运行 spark publish 时,会对所有发现的发布者调用 publish()。返回成功或失败。

getScratch(): string

返回临时工作区,如果需要则创建它。某些操作使用中间存储来暂存文件和更改,这提供了您可以使用的瞬态可写目录的路径。

getErrors(): array<string, Throwable>

返回上次写入操作的任何错误。数组键是导致错误的文件,值是捕获的 Throwable。使用 getMessage() 在 Throwable 上获取错误消息。

addPath(string $path, bool $recursive = true)

添加由相对路径指示的所有文件。路径是对相对于 $source 的实际文件或目录的引用。如果相对路径解析为目录,则 $recursive 将包含子目录。

addPaths(array $paths, bool $recursive = true)

添加由相对路径指示的所有文件。路径是对相对于 $source 的实际文件或目录的引用。如果相对路径解析为目录,则 $recursive 将包含子目录。

addUri(string $uri)

使用 CURLRequest 从 URI 下载内容到临时工作区,然后将结果文件添加到列表中。

addUris(array $uris)

使用 CURLRequest 从 URI 下载内容到临时工作区,然后将结果文件添加到列表中。

注意

发出的 CURL 请求是一个简单的 GET 请求,并使用响应主体作为文件内容。某些远程文件可能需要自定义请求才能正确处理。

输出文件

wipe()

$destination 中删除所有文件、目录和子目录。

重要

谨慎使用。

copy(bool $replace = true): bool

将所有文件复制到 $destination 中。这不会重新创建目录结构,因此当前列表中的所有文件最终都将位于同一个目标目录中。使用 $replace 将导致文件在已存在文件时被覆盖。返回成功或失败,使用 getPublished()getErrors() 来排查故障。注意重复的基名冲突,例如

<?php

use CodeIgniter\Publisher\Publisher;

$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

将所有文件复制到 $destination 中的适当的相对子目录中。任何与 $source 匹配的文件都将被放置到 $destination 中的等效目录中,有效地创建了一个“镜像”或“rsync”操作。使用 $replace 将导致文件在已存在文件时被覆盖;由于目录被合并,这不会影响目标中的其他文件。返回成功或失败,使用 getPublished()getErrors() 来排查故障。

示例

<?php

use CodeIgniter\Publisher\Publisher;

$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();

修改文件

replace(string $file, array $replaces): bool

版本 4.3.0 中的新增功能。

替换 $file 的内容。第二个参数 $replaces 数组指定搜索字符串作为键,替换字符串作为值。

<?php

use CodeIgniter\Publisher\Publisher;

$source    = service('autoloader')->getNamespace('CodeIgniter\\Shield')[0];
$publisher = new Publisher($source, APPPATH);

$file = APPPATH . 'Config/Auth.php';

$publisher->replace(
    $file,
    [
        'use CodeIgniter\Config\BaseConfig;' . "\n" => '',
        'class App extends BaseConfig'              => 'class App extends \Some\Package\SomeConfig',
    ]
);

addLineAfter(string $file, string $line, string $after): bool

版本 4.3.0 中的新增功能。

在包含特定字符串 $after 的行之后添加 $line

<?php

use CodeIgniter\Publisher\Publisher;

$source    = service('autoloader')->getNamespace('CodeIgniter\\Shield')[0];
$publisher = new Publisher($source, APPPATH);

$file = APPPATH . 'Config/App.php';

$publisher->addLineAfter(
    $file,
    '    public int $myOwnConfig = 1000;', // Adds this line
    'public bool $CSPEnabled = false;'     // After this line
);

addLineBefore(string $file, string $line, string $after): bool

版本 4.3.0 中的新增功能。

在包含特定字符串 $after 的行之前添加 $line

<?php

use CodeIgniter\Publisher\Publisher;

$source    = service('autoloader')->getNamespace('CodeIgniter\\Shield')[0];
$publisher = new Publisher($source, APPPATH);

$file = APPPATH . 'Config/App.php';

$publisher->addLineBefore(
    $file,
    '    public int $myOwnConfig = 1000;', // Add this line
    'public bool $CSPEnabled = false;'     // Before this line
);