扩展 Bake

Bake 具有可扩展的架构,允许您的应用程序或插件修改或添加基本功能。 Bake 使用专门的视图类,该类使用 Twig 模板引擎。

Bake 事件

作为视图类,BakeView 发出与任何其他视图类相同的事件,另外还有一个额外的初始化事件。但是,标准视图类使用事件前缀 “View.”,而 BakeView 使用事件前缀 “Bake.”。

初始化事件可用于进行适用于所有烘焙输出的更改,例如,要向烘焙视图类添加另一个帮助器,可以使用此事件

<?php
use Cake\Event\EventInterface;
use Cake\Event\EventManager;

// in src/Application::bootstrapCli()

EventManager::instance()->on('Bake.initialize', function (EventInterface $event) {
    $view = $event->getSubject();

    // In my bake templates, allow the use of the MySpecial helper
    $view->loadHelper('MySpecial', ['some' => 'config']);

    // And add an $author variable so it's always available
    $view->set('author', 'Andy');
});

Bake 事件对于对现有模板进行微小更改非常有用。例如,要更改烘焙控制器/模板文件时使用的变量名,可以使用一个监听 Bake.beforeRender 的函数来修改烘焙模板中使用的变量

<?php
use Cake\Event\EventInterface;
use Cake\Event\EventManager;

// in src/Application::bootstrapCli()

EventManager::instance()->on('Bake.beforeRender', function (EventInterface $event) {
    $view = $event->getSubject();

    // Use $rows for the main data variable in indexes
    if ($view->get('pluralName')) {
        $view->set('pluralName', 'rows');
    }
    if ($view->get('pluralVar')) {
        $view->set('pluralVar', 'rows');
    }

    // Use $theOne for the main data variable in view/edit
    if ($view->get('singularName')) {
        $view->set('singularName', 'theOne');
    }
    if ($view->get('singularVar')) {
        $view->set('singularVar', 'theOne');
    }
});

您还可以将 Bake.beforeRenderBake.afterRender 事件限定到特定生成的 文件。例如,如果您想在从 **Controller/controller.twig** 文件生成时向您的 UsersController 添加特定操作,您可以使用以下事件

<?php
use Cake\Event\EventInterface;
use Cake\Event\EventManager;
use Cake\Utility\Hash;

// in src/Application::bootstrapCli()

EventManager::instance()->on(
    'Bake.beforeRender.Controller.controller',
    function (EventInterface $event) {
        $view = $event->getSubject();
        if ($view->get('name') === 'Users') {
            // add the login and logout actions to the Users controller
            $view->set('actions', [
                'login',
                'logout',
                'index',
                'view',
                'add',
                'edit',
                'delete',
            ]);
        }
    }
);

通过将事件监听器限定到特定的烘焙模板,您可以简化烘焙相关的事件逻辑,并提供更易于测试的回调。

Bake 模板语法

Bake 模板文件使用 Twig 模板语法。

因此,例如,当这样烘焙一个命令时

bin/cake bake command Foo

使用的模板 (**vendor/cakephp/bake/templates/bake/Command/command.twig**) 如下所示

<?php
declare(strict_types=1);

namespace {{ namespace }}\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

/**
* {{ name }} command.
*/
class {{ name }}Command extends Command
{
    /**
    * Hook method for defining this command's option parser.
    *
    * @see https://book.cakephp.com.cn/5/en/console-commands/commands.html#defining-arguments-and-options
    * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
    * @return \Cake\Console\ConsoleOptionParser The built parser.
    */
    public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser = parent::buildOptionParser($parser);

        return $parser;
    }

    /**
    * Implement this method with your command's logic.
    *
    * @param \Cake\Console\Arguments $args The command arguments.
    * @param \Cake\Console\ConsoleIo $io The console io
    * @return int|null|void The exit code or null for success
    */
    public function execute(Arguments $args, ConsoleIo $io)
    {
    }
}

最终烘焙的类 (**src/Command/FooCommand.php**) 如下所示

<?php
declare(strict_types=1);

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

/**
* Foo command.
*/
class FooCommand extends Command
{
    /**
    * Hook method for defining this command's option parser.
    *
    * @see https://book.cakephp.com.cn/5/en/console-commands/commands.html#defining-arguments-and-options
    * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
    * @return \Cake\Console\ConsoleOptionParser The built parser.
    */
    public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
    {
        $parser = parent::buildOptionParser($parser);

        return $parser;
    }

    /**
    * Implement this method with your command's logic.
    *
    * @param \Cake\Console\Arguments $args The command arguments.
    * @param \Cake\Console\ConsoleIo $io The console io
    * @return int|null|void The exit code or null for success
    */
    public function execute(Arguments $args, ConsoleIo $io)
    {
    }
}

创建 Bake 主题

如果您希望修改“bake” 命令产生的输出,您可以创建自己的 bake “主题”,它允许您替换 bake 使用的一些或所有模板。要创建 bake 主题,请执行以下操作

  1. 烘焙一个新插件。插件的名称就是 bake “主题” 名称。例如 bin/cake bake plugin custom_bake

  2. 创建一个新目录 **plugins/CustomBake/templates/bake**。

  3. 将您要覆盖的任何模板从 **vendor/cakephp/bake/templates/bake** 复制到插件中的匹配文件中。

  4. 运行 bake 时,使用 --theme CustomBake 选项来使用您的 bake 主题。为了避免在每次调用中都必须指定此选项,您还可以将自定义主题设置为默认主题

    <?php
    // in src/Application::bootstrapCli() before loading the 'Bake' plugin.
    Configure::write('Bake.theme', 'MyTheme');
    

应用程序 Bake 模板

如果您只需要自定义几个 bake 模板,或者需要在您的模板中使用应用程序依赖项,您可以在应用程序模板中包含模板覆盖。这些覆盖的工作方式与覆盖其他插件模板类似。

  1. 创建一个新目录 **templates/plugin/Bake/**。

  2. 将您要覆盖的任何模板从 **vendor/cakephp/bake/templates/bake/** 复制到应用程序中的匹配文件中。

使用应用程序模板时,您不需要使用 --theme 选项。

创建新的 Bake 命令选项

通过在您的应用程序或插件中创建命令,可以添加新的 bake 命令选项,或者覆盖 CakePHP 提供的选项。通过扩展 Bake\Command\BakeCommand,bake 将找到您的新命令并将其包含在 bake 中。

例如,我们将创建一个命令来创建一个任意的 foo 类。首先,创建命令文件 **src/Command/Bake/FooCommand.php**。我们现在将扩展 SimpleBakeCommand,因为我们的命令很简单。 SimpleBakeCommand 是抽象的,要求我们定义 3 个方法,这些方法告诉 bake 命令的名称、生成的文件应该放在哪里以及要使用哪个模板。我们的 FooCommand.php 文件应该如下所示

<?php
declare(strict_types=1);

namespace App\Command\Bake;

use Bake\Command\SimpleBakeCommand;

class FooCommand extends SimpleBakeCommand
{
    public $pathFragment = 'FooPath/';

    public function name(): string
    {
        return 'foo';
    }

    public function template(): string
    {
        return 'fooTemplate';
    }

    public function fileName(string $name): string
    {
        return $name . 'FooOut.php';
    }
}

创建此文件后,我们需要创建一个模板,bake 在生成代码时可以使用该模板。创建 **templates/bake/foo_template.twig**。在此文件中,我们将添加以下内容

<?php
namespace {{ namespace }}\FooPath;

/**
 * {{ name }} fooOut
 */
class {{ name }}FooOut
{
    // Add code.
}

您现在应该在 bin/cake bake 的输出中看到您的新命令。您可以通过运行 bin/cake bake foo Example 来运行您的新任务。这将在您的应用程序中生成一个新的 ExampleFooOut 类,位于 **src/FooPath/ExampleFooOut.php** 中。

如果您希望 bake 调用还为您的 ExampleFooOut 类创建一个测试文件,您需要覆盖 FooCommand 类中的 bakeTest() 方法以注册您的自定义命令名称的类后缀和命名空间

use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

public function bakeTest(string $className, Arguments $args, ConsoleIo $io): void
{
    if (!isset($this->Test->classSuffixes[$this->name()])) {
        $this->Test->classSuffixes[$this->name()] = 'Foo';
    }

    $name = ucfirst($this->name());
    if (!isset($this->Test->classTypes[$name])) {
        $this->Test->classTypes[$name] = 'Foo';
    }

    return parent::bakeTest($className);
}
  • 类后缀 将附加到您在 bake 调用中提供的名称。在前面的示例中,它将创建一个 ExampleFooTest.php 文件。

  • 类类型 将是使用的子命名空间,它将导致您的文件(相对于您正在烘焙的应用程序或插件)。在前面的示例中,它将使用命名空间 App\Test\TestCase\Foo 创建您的测试。

配置 BakeView 类

bake 命令使用 BakeView 类来渲染模板。您可以通过监听 Bake.initialize 事件来访问实例。例如,以下是如何加载您自己的帮助器,以便它可以在烘焙模板中使用

<?php
\Cake\Event\EventManager::instance()->on(
    'Bake.initialize',
    function ($event, $view) {
        $view->loadHelper('Foo');
    }
);