插件

CakePHP 允许您设置控制器、模型和视图的组合,并将它们作为预打包的应用程序插件发布,其他人可以在他们的 CakePHP 应用程序中使用它。如果您在某个应用程序中创建了出色的用户管理、简单的博客或 Web 服务适配器,为什么不将其打包为 CakePHP 插件呢?这样,您就可以在其他应用程序中重复使用它,并与社区分享!

CakePHP 插件独立于主机应用程序本身,通常提供一些定义明确的功能,这些功能可以整齐地打包,并在其他应用程序中轻松重复使用。应用程序和插件在各自的独立空间中运行,但共享应用程序的配置数据(例如,数据库连接、电子邮件传输)。

插件应该定义自己的顶级命名空间。例如:DebugKit。按照惯例,插件使用其包名称作为其命名空间。如果您想使用不同的命名空间,您可以在加载插件时配置插件命名空间。

使用 Composer 安装插件

许多插件都可以在 Packagist 上找到,可以使用 Composer 安装。要安装 DebugKit,您需要执行以下操作

php composer.phar require cakephp/debug_kit

这将安装 DebugKit 的最新版本,并更新您的 composer.jsoncomposer.lock 文件,更新 vendor/cakephp-plugins.php,并更新您的自动加载器。

手动安装插件

如果您要安装的插件在 packagist.org 上不可用,您可以将插件代码克隆或复制到您的 plugins 目录中。假设您要安装一个名为“ContactManager”的插件,您应该在 plugins 中有一个名为“ContactManager”的文件夹。在这个目录中是插件的 src、tests 和任何其他目录。

手动加载插件类

如果您通过 composerbake 安装插件,则无需为插件配置类自动加载。

如果您在 plugins 文件夹下手动创建插件,则需要告诉 composer 刷新其自动加载缓存

php composer.phar dumpautoload

如果您正在为插件使用供应商命名空间,则必须在运行上述 composer 命令之前将命名空间添加到路径映射到 composer.json 中,类似于以下内容

{
    "autoload": {
        "psr-4": {
            "AcmeCorp\\Users\\": "plugins/AcmeCorp/Users/src/",
        }
    },
    "autoload-dev": {
        "psr-4": {
            "AcmeCorp\\Users\\Test\\": "plugins/AcmeCorp/Users/tests/"
        }
    }
}

加载插件

如果您想使用插件的路由、控制台命令、中间件、事件监听器、模板或 webroot 资源,则需要加载插件。

如果您只想从插件中使用辅助函数、行为或组件,则无需显式加载插件,但建议始终这样做。

还有一个方便的控制台命令来加载插件。执行以下行

bin/cake plugin load ContactManager

这将使用类似于 'ContactManager' => [] 的条目更新您的应用程序的 config/plugins.php 中的数组。

插件挂钩配置

插件提供多个挂钩,允许插件将自身注入到应用程序的适当部分。挂钩是

  • bootstrap 用于加载插件默认配置文件、定义常量和其他全局函数。 bootstrap 方法传递了当前 Application 实例,让您可以广泛访问 DI 容器和配置。

  • routes 用于加载插件的路由。在加载应用程序路由后触发。

  • middleware 用于将插件中间件添加到应用程序的中间件队列。

  • console 用于将控制台命令添加到应用程序的命令集合。

  • services 用于注册应用程序容器服务。这是一个很好的机会来设置需要访问容器的附加对象。

默认情况下,所有插件挂钩都已启用。您可以使用 plugin load 命令的相关选项来禁用挂钩

bin/cake plugin load ContactManager --no-routes

这将使用类似于 'ContactManager' => ['routes' => false] 的条目更新您的应用程序的 config/plugins.php 中的数组。

插件加载选项

除了插件挂钩的选项之外, plugin load 命令还有以下选项来控制插件加载

  • --only-debug 仅在调试模式启用时加载插件。

  • --only-cli 仅针对 CLI 加载插件。

  • --optional 如果插件不可用,则不会引发错误。

通过 Application::bootstrap() 加载插件

除了 config/plugins.php 中的配置数组外,还可以在应用程序的 bootstrap() 方法中加载插件

// In src/Application.php
use Cake\Http\BaseApplication;
use ContactManager\ContactManagerPlugin;

class Application extends BaseApplication
{
    public function bootstrap()
    {
        parent::bootstrap();

        // Load the contact manager plugin by class name
        $this->addPlugin(ContactManagerPlugin::class);

        // Load a plugin with a vendor namespace by 'short name' with options
        $this->addPlugin('AcmeCorp/ContactManager', ['console' => false]);

        // Load a dev dependency that will not exist in production builds.
        $this->addOptionalPlugin('AcmeCorp/ContactManager');
    }
}

您可以使用数组选项或插件类提供的方法配置挂钩

// In Application::bootstrap()
use ContactManager\ContactManagerPlugin;

// Use the disable/enable to configure hooks.
$plugin = new ContactManagerPlugin();

$plugin->disable('bootstrap');
$plugin->enable('routes');
$this->addPlugin($plugin);

插件类也知道其名称和路径信息

$plugin = new ContactManagerPlugin();

// Get the plugin name.
$name = $plugin->getName();

// Path to the plugin root, and other paths.
$path = $plugin->getPath();
$path = $plugin->getConfigPath();
$path = $plugin->getClassPath();

使用插件类

您可以通过添加插件名称前缀来引用插件的控制器、模型、组件、行为和辅助函数。

例如,假设您想使用 ContactManager 插件的 ContactInfoHelper 在其中一个视图中输出格式化的联系信息。在您的控制器中,使用 addHelper() 可能看起来像这样

$this->viewBuilder()->addHelper('ContactManager.ContactInfo');

注意

这个用点分隔的类名被称为 插件语法

然后您就可以像在视图中使用任何其他辅助函数一样访问 ContactInfoHelper,例如

echo $this->ContactInfo->address($contact);

如果需要,插件可以使用应用程序或其他插件提供的模型、组件、行为和辅助函数

// Use an application component
$this->loadComponent('AppFlash');

// Use another plugin's behavior
$this->addBehavior('OtherPlugin.AuditLog');

创建你自己的插件

作为工作示例,让我们开始创建上面提到的 ContactManager 插件。首先,我们将设置插件的基本目录结构。它应该看起来像这样

/src
/plugins
    /ContactManager
        /config
        /src
            /ContactManagerPlugin.php
            /Controller
                /Component
            /Model
                /Table
                /Entity
                /Behavior
            /View
                /Helper
        /templates
            /layout
        /tests
            /TestCase
            /Fixture
        /webroot

请注意插件文件夹的名称“**ContactManager**”。重要的是,这个文件夹的名称与插件的名称相同。

在插件文件夹中,你会注意到它看起来很像一个 CakePHP 应用程序,实际上它基本上也是一个。只是它没有 Application.php 文件,而有一个 ContactManagerPlugin.php 文件。你不必包含任何你没有用到的文件夹。有些插件可能只定义了一个组件和一个行为,在这种情况下,它们可以完全省略 “templates” 目录。

插件也可以包含应用程序中的任何其他目录,例如 Config、Console、webroot 等。

使用 Bake 创建插件

使用 bake 可以大大简化插件的创建过程。

为了烘焙一个插件,请使用以下命令

bin/cake bake plugin ContactManager

Bake 可以用于在你的插件中创建类。例如,要生成一个插件控制器,你可以运行

bin/cake bake controller --plugin ContactManager Contacts

如果你在使用命令行时遇到任何问题,请参考 使用 Bake 生成代码 章节。在创建完插件后,一定要重新生成你的自动加载器。

php composer.phar dumpautoload

插件类

插件类允许插件作者定义设置逻辑、定义默认钩子、加载路由、中间件和控制台命令。插件类位于 **src/{PluginName}Plugin.php** 中。对于我们的 ContactManager 插件,我们的插件类可能看起来像这样

namespace ContactManager;

use Cake\Core\BasePlugin;
use Cake\Core\ContainerInterface;
use Cake\Core\PluginApplicationInterface;
use Cake\Console\CommandCollection;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;

class ContactManagerPlugin extends BasePlugin
{
    /**
     * @inheritDoc
     */
    public function middleware(MiddlewareQueue $middleware): MiddlewareQueue
    {
        // Add middleware here.
        $middleware = parent::middleware($middleware);

        return $middleware;
    }

    /**
     * @inheritDoc
     */
    public function console(CommandCollection $commands): CommandCollection
    {
        // Add console commands here.
        $commands = parent::console($commands);

        return $commands;
    }

    /**
     * @inheritDoc
     */
    public function bootstrap(PluginApplicationInterface $app): void
    {
        // Add constants, load configuration defaults.
        // By default will load `config/bootstrap.php` in the plugin.
        parent::bootstrap($app);
    }

    /**
     * @inheritDoc
     */
    public function routes(RouteBuilder $routes): void
    {
        // Add routes.
        // By default will load `config/routes.php` in the plugin.
        parent::routes($routes);
    }

    /**
     * Register application container services.
     *
     * @param \Cake\Core\ContainerInterface $container The Container to update.
     * @return void
     * @link https://book.cakephp.com.cn/5/en/development/dependency-injection.html#dependency-injection
     */
    public function services(ContainerInterface $container): void
    {
        // Add your services here
    }
}

插件路由

插件可以提供包含其路由的路由文件。每个插件都可以包含一个 **config/routes.php** 文件。当插件被添加时,或者在应用程序的路由文件中,这个路由文件可以被加载。要创建 ContactManager 插件路由,请将以下内容放入 **plugins/ContactManager/config/routes.php** 中

<?php
use Cake\Routing\Route\DashedRoute;

$routes->plugin(
    'ContactManager',
    ['path' => '/contact-manager'],
    function ($routes) {
        $routes->setRouteClass(DashedRoute::class);

        $routes->get('/contacts', ['controller' => 'Contacts']);
        $routes->get('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'view']);
        $routes->put('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'update']);
    }
);

以上将连接你插件的默认路由。你以后可以使用更具体的路由来定制这个文件。

你也可以在你的应用程序的路由列表中加载插件路由。这样做可以让你更有效地控制插件路由的加载方式,并允许你在其他作用域或前缀中包装插件路由。

$routes->scope('/', function ($routes) {
    // Connect other routes.
    $routes->scope('/backend', function ($routes) {
        $routes->loadPlugin('ContactManager');
    });
});

以上将导致像 /backend/contact-manager/contacts 这样的 URL。

插件控制器

我们 ContactManager 插件的控制器将存储在 **plugins/ContactManager/src/Controller/** 中。由于我们主要做的事情是管理联系人,所以我们需要一个 ContactsController 来管理这个插件。

所以,我们将新的 ContactsController 放入 **plugins/ContactManager/src/Controller** 中,它看起来像这样

// plugins/ContactManager/src/Controller/ContactsController.php
namespace ContactManager\Controller;

use ContactManager\Controller\AppController;

class ContactsController extends AppController
{
    public function index()
    {
        //...
    }
}

如果你还没有 AppController,也请创建它。

// plugins/ContactManager/src/Controller/AppController.php
namespace ContactManager\Controller;

use App\Controller\AppController as BaseController;

class AppController extends BaseController
{
}

插件的 AppController 可以保存插件中所有控制器通用的控制器逻辑,但如果你不想使用它,它不是必需的。

如果你想访问我们目前已经完成的工作,请访问 /contact-manager/contacts。你应该得到一个 “Missing Model” 错误,因为我们还没有定义 Contact 模型。

如果你的应用程序包含 CakePHP 提供的默认路由,你将可以使用类似于以下的 URL 来访问你的插件控制器。

// Access the index route of a plugin controller.
/contact-manager/contacts

// Any action on a plugin controller.
/contact-manager/contacts/view/1

如果你的应用程序定义了路由前缀,CakePHP 的默认路由也会连接使用以下模式的路由

/{prefix}/{plugin}/{controller}
/{prefix}/{plugin}/{controller}/{action}

有关如何加载插件特定路由文件的更多信息,请参阅有关 插件钩子配置 的部分。

插件模型

插件的模型存储在 **plugins/ContactManager/src/Model** 中。我们已经为这个插件定义了一个 ContactsController,所以让我们为这个控制器创建表和实体。

// plugins/ContactManager/src/Model/Entity/Contact.php:
namespace ContactManager\Model\Entity;

use Cake\ORM\Entity;

class Contact extends Entity
{
}

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
}

如果你需要在构建关联或定义实体类时引用插件中的模型,你需要使用插件名称和类名称,用点隔开。例如

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('ContactManager.AltName');
    }
}

如果你希望关联的数组键不带插件前缀,请使用以下备用语法

// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;

use Cake\ORM\Table;

class ContactsTable extends Table
{
    public function initialize(array $config): void
    {
        $this->hasMany('AltName', [
            'className' => 'ContactManager.AltName',
        ]);
    }
}

你可以使用 Cake\ORM\Locator\LocatorAwareTrait 使用熟悉的 插件语法 加载你的插件表。

// Controllers already use LocatorAwareTrait, so you don't need this.
use Cake\ORM\Locator\LocatorAwareTrait;

$contacts = $this->fetchTable('ContactManager.Contacts');

插件模板

视图的行为与普通应用程序中的行为完全相同。只需将它们放在 plugins/[PluginName]/templates/ 文件夹中的正确文件夹中。对于我们的 ContactManager 插件,我们需要一个视图来显示我们的 ContactsController::index() 操作,所以让我们也将其包含进来

// plugins/ContactManager/templates/Contacts/index.php:
<h1>Contacts</h1>
<p>Following is a sortable list of your contacts</p>
<!-- A sortable list of contacts would go here....-->

插件可以提供自己的布局。要添加插件布局,请将你的模板文件放在 plugins/[PluginName]/templates/layout 中。要在你的控制器中使用插件布局,你可以执行以下操作

$this->viewBuilder()->setLayout('ContactManager.admin');

如果省略插件前缀,布局/视图文件将正常定位。

注意

有关如何使用插件中的元素的信息,请查看 元素

从你的应用程序内部覆盖插件模板

你可以使用特殊的路径从你的应用程序内部覆盖任何插件视图。如果你有一个名为 “ContactManager” 的插件,你可以通过使用以下模板创建文件来用应用程序特定的视图逻辑覆盖插件的模板文件 **templates/plugin/[Plugin]/[Controller]/[view].php**。对于 Contacts 控制器,你可以创建以下文件

templates/plugin/ContactManager/Contacts/index.php

创建这个文件将允许你覆盖 **plugins/ContactManager/templates/Contacts/index.php**。

如果你的插件是 composer 依赖项(例如 “Company/ContactManager”),则 Contacts 控制器的 “index” 视图的路径将是

templates/plugin/TheVendor/ThePlugin/Custom/index.php

创建这个文件将允许你覆盖 **vendor/thevendor/theplugin/templates/Custom/index.php**。

如果插件实现了路由前缀,则必须在你的应用程序模板覆盖中包含路由前缀。例如,如果 “ContactManager” 插件实现了 “Admin” 前缀,则覆盖路径将是

templates/plugin/ContactManager/Admin/ContactManager/index.php

插件资源

插件的 web 资源(但不是 PHP 文件)可以通过插件的 webroot 目录提供服务,就像主应用程序的资源一样

/plugins/ContactManager/webroot/
                               css/
                               js/
                               img/
                               flash/
                               pdf/

你可以在任何目录中放置任何类型的文件,就像一个普通的 webroot 一样。

警告

通过 Dispatcher 处理静态资源(如图像、JavaScript 和 CSS 文件)效率非常低下。有关更多信息,请参阅 提高应用程序的性能

在插件中链接到资源

在使用 插件语法 链接到插件资源时,可以使用 Cake\View\Helper\HtmlHelper 的 script、image 或 css 方法

// Generates a URL of /contact_manager/css/styles.css
echo $this->Html->css('ContactManager.styles');

// Generates a URL of /contact_manager/js/widget.js
echo $this->Html->script('ContactManager.widget');

// Generates a URL of /contact_manager/img/logo.jpg
echo $this->Html->image('ContactManager.logo');

插件资源默认由 AssetMiddleware 中间件提供服务。这仅推荐用于开发。在生产环境中,你应该 将插件资源软链接 以提高性能。

如果你没有使用助手,你可以将 /plugin-name/ 添加到该插件中资源 URL 的开头来提供服务。链接到 ‘/contact_manager/js/some_file.js’ 将提供服务 **plugins/ContactManager/webroot/js/some_file.js**。

组件、助手和行为

插件可以像 CakePHP 应用程序一样拥有组件、助手和行为。你甚至可以创建只包含组件、助手或行为的插件,这是一种构建可重用组件的好方法,这些组件可以被放到任何项目中。

构建这些组件与在普通应用程序中构建组件完全相同,没有特殊的命名约定。

从插件内部或外部引用你的组件只需要在组件名称之前添加插件名称作为前缀。例如

// Component defined in 'ContactManager' plugin
namespace ContactManager\Controller\Component;

use Cake\Controller\Component;

class ExampleComponent extends Component
{
}

// Within your controllers
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('ContactManager.Example');
}

相同的技巧也适用于助手和行为。

命令

插件可以在 console() 钩子中注册它们的命令。默认情况下,插件中的所有控制台命令都会被自动发现并添加到应用程序的命令列表中。插件命令以插件名称为前缀。例如,ContactManager 插件提供的 UserCommand 将被注册为 contact_manager.useruser。只有当应用程序或其他插件没有使用这个未加前缀的名称时,插件才会使用它。

你可以在你的插件中定义每个命令来定制命令名称。

public function console($commands)
{
    // Create nested commands
    $commands->add('bake model', ModelCommand::class);
    $commands->add('bake controller', ControllerCommand::class);

    return $commands;
}

测试你的插件

如果你正在测试控制器或生成 URL,请确保你的插件在 tests/bootstrap.php 中连接了路由。

有关更多信息,请参阅 测试插件 页面。

发布你的插件

CakePHP 插件应该发布到 packagist 上。这样其他人就可以将其作为 composer 依赖项使用。你也可以将你的插件提议给 awesome-cakephp 列表

为包名选择一个语义上有意义的名字。理想情况下,它应该以依赖项为前缀,在本例中为“cakephp”,作为框架。供应商名称通常是您的 GitHub 用户名。不要使用 CakePHP 命名空间(cakephp),因为这是为 CakePHP 拥有的插件保留的。约定是使用小写字母和破折号作为分隔符。

因此,如果您使用 GitHub 帐户“FooBar”创建了一个名为“Logging”的插件,那么一个好的名称将是 foo-bar/cakephp-logging。CakePHP 拥有的“Localized”插件分别位于 cakephp/localized 下。

插件映射文件

通过 Composer 安装插件时,您可能会注意到 vendor/cakephp-plugins.php 被创建。此配置文件包含插件名称及其在文件系统上的路径的映射。它使得插件可以安装到标准供应商目录中,该目录位于正常的搜索路径之外。Plugin 类将使用此文件来定位插件,当它们使用 addPlugin() 加载时。通常您不需要手动编辑此文件,因为 Composer 和 plugin-installer 包将为您管理它。

使用 Mixer 管理您的插件

另一种在您的 CakePHP 应用程序中发现和管理插件的方法是 Mixer。它是一个 CakePHP 插件,可以帮助您从 Packagist 安装插件。它还有助于您管理现有的插件。

注意

重要提示:不要在生产环境中使用它。