Skip to content

通用概念#

以下部分将详细介绍在开发 XenForo 插件时会遇到的一些通用系统和概念。如果您熟悉 XenForo 1.x 的开发,那么其中许多概念对您来说可能并不陌生,但值得回顾一下,因为有一些优秀的新工具和功能可以帮助您开发插件。

第三方组件#

XF2 不像 XF1 那样由特定框架驱动,但我们使用了一些流行的、经过良好测试的开源包来帮助完成特定任务。例如,我们使用名为 SwiftMailer 的项目来发送电子邮件,并使用名为 Guzzle 的项目作为 HTTP 客户端。所有第三方项目都从 src/vendor 目录加载。

目前,插件开发者无法将自己的依赖项添加到此位置。

集成开发环境 (IDE)#

在开始 XF2 开发之前,您可能需要花一些时间评估您将用于创建和编辑 PHP 文件的实际应用程序。这通常被称为 IDE。有许多可用的选项,从基本的记事本到像 Sublime Text 这样的工具,后者可以通过插件扩展以获得更好的 PHP 支持,再到一个完整的 IDE,例如 PhpStorm。在内部,我们使用 PhpStorm 作为我们首选的 IDE。这是一个商业产品,但可能有免费的替代品。无论如何,没有人能告诉您哪种应用程序最适合您的需求,您应该花一些时间试用多种产品(即使是免费的),并根据这些经验找到您的偏好。

自动加载器#

XF2 使用由 Composer 自动生成的自动加载器。这使得所有 XF 代码、第三方供应商代码以及任何插件开发者的代码都可以在整个项目中自动包含,而无需手动 include/require 您的类。

所有 XF 插件的自动加载根目录是 src/addons 目录。这意味着您的所有类名都将相对于此基本位置。还值得注意的是,XF2 采用了严格的“每个文件一个类”的命名模式。每个文件应只包含一个类,并且该类的名称应标识该类 PHP 文件在文件系统中的确切位置。

例如,如果您想在名为 src/addons/Demo/Setup.php 的文件中创建一个新类(其中 Demo 是您的插件 ID),那么该类将被命名为 Demo\Setup。相反,如果您有一个名为 Demo\Entity\Thing 的类,那么您将知道该类的文件位于路径 src/addons/Demo/Entity/Thing.php 中。

命名空间#

在整个 XF 中,我们使用 命名空间,以便我们可以更简洁地引用同一命名空间中的类。建议所有插件也使用命名空间。在上面的示例中,我们讨论了一个名为 Demo\Setup 的类。使用命名空间时,类实际上将简单地命名为 Setup,但命名空间将设置为 Demo。作为一个更具体的示例,我们上面还讨论了一个名为 Demo\Entity\Thing 的类。让我们看看该类的 PHP 代码是什么样子的:

src/addons/Demo/Entity/Thing.php
<?php

namespace Demo\Entity;

class Thing
{

}

如果在 src/addons/Demo/Entity 目录中有一个名为 AnotherThing 的类,我们可以在 Thing 类中简单地引用该类为 AnotherThing,因为该类位于相同的 Demo\Entity 命名空间中。

短类名#

有时,XF 中引用的类会被缩短。例如,如果您希望调用 User 实体(更多关于实体的内容见下文),您可能会看到类名被简单地引用为 XF:User。短类名的使用及其解析到的完整类名完全取决于上下文。因此,在调用实体的上下文中,短类名将解析为以下完整类名 XF\Entity\UserXF 部分表示文件路径(基于插件 ID),Entity 部分由调用实体暗示,User 部分表示特定实体。同样,当您开始创建自己的类时,您也将使用短类名来引用您自己的类。例如,如果您需要为您的 Demo 插件创建一个新的 Thing 实体,那么您可以编写以下内容:

PHP
\XF::em()->create('Demo:Thing');

这将解析为 Demo\Entity\Thing 类。同样,如果您想访问 Thing 存储库,您可以编写如下内容:

PHP
\XF::repository('Demo:Thing');

请注意,短类名是相同的。存储库调用实际上将解析为 Demo\Repository\Thing

扩展类#

XF2 中的许多类都是可扩展的,这使得开发者可以在不直接编辑核心代码的情况下扩展和覆盖核心代码。如果您熟悉 XF1 开发,您将对以下过程有些熟悉:

  1. 创建一个 Listener PHP 文件
  2. 创建一个最终将扩展原始类的类
  3. 编写一个函数,该函数匹配 load_class 事件之一的预期回调签名,并添加您的扩展类的名称
  4. 在 Admin CP 中添加一个“代码事件监听器”,指定上述函数的 Listener 类和方法名称,并可选地提示正在扩展的类

在 XF2 中,我们移除了这些事件,转而使用一个称为“类扩展”的特定系统。过程如下:

  1. 创建一个最终将扩展原始类的类
  2. 在 Admin CP 中添加一个“类扩展”,指定您正在扩展的类的名称以及扩展它的类的名称

这显然减少了一些扩展类所需的样板代码,并提供了一个专用的 UI 来查看和管理这些扩展。让我们通过扩展公共 Member 控制器并添加一个显示简单消息的新操作来了解这个过程。

首先要做的是创建一个插件。我们之前概述了如何使用 xf-addon:create 命令来执行此操作 此处。对于此示例,我们假设您创建了一个 ID 和标题为“Demo”的插件。

您现在将在以下位置拥有此插件的 addon.json 文件:src/addons/Demo/addon.json

注意

严格来说,您可以将扩展类放在插件目录中的任何位置,但建议将扩展类放在一个易于识别的目录中,该目录可以轻松识别 a) 插件所属的插件 b) 正在扩展的类类型 c) 正在扩展的类的名称。在以下示例中,我们正在扩展公共 XF Member 控制器,因此我们将扩展类放在以下路径中:src/addons/Demo/XF/Pub/Controller/Member.php

扩展类需要在我们将类扩展添加到 Admin CP 之前存在。因此,请按照以下说明操作:

  1. src/addons/Demo 中创建一个名为 XF 的新目录
  2. src/addons/Demo/XF 中创建一个名为 Pub 的新目录
  3. src/addons/Demo/XF/Pub 中创建一个名为 Controller 的新目录
  4. src/addons/Demo/XF/Pub/Controller 中创建一个名为 Member.php 的新文件

您的 PHP 文件的初始内容应如下所示:

src/addons/Demo/XF/Pub/Controller/Member.php
<?php

namespace Demo\XF\Pub\Controller;

class Member extends XFCP_Member
{

}

如果您熟悉扩展 PHP 类但不熟悉 XF,上面的示例最初可能会让您感到困惑。原因是您可能期望直接扩展 XF\Pub\Controller\Member 类,而不是 XFCP_Member。在 XF 中,我们使用“XenForo 类代理”系统(简称 XFCP)来构建“继承链”,最终允许单个类被多个插件扩展。约定是引用一个虚拟扩展类,该虚拟扩展类是当前类名 Member,并为其添加前缀 XFCP_

现在类已创建,我们可以在 Admin CP > 开发 > 类扩展 > 添加类扩展页面上创建类扩展。

您需要做的就是在第一个字段中输入基类名称(XF\Pub\Controller\Member),在第二个字段中输入扩展类名称(您刚刚创建的类)(Demo\XF\Pub\Controller\Member),然后单击“保存”按钮。

您的类扩展现在应该已激活,但目前还没有做任何事情。为了使某些事情发生,我们需要通过创建与现有方法同名的方法来覆盖此类中的现有方法,或者完全添加一个新方法。让我们做后者:

src/addons/Demo/XF/Pub/Controller/Member.php
<?php

namespace Demo\XF\Pub\Controller;

class Member extends XFCP_Member
{
    public function actionHelloWorld()
    {
        return $this->message('Hello world!');
    }
}

我们在 控制器基础 页面中更多地讨论控制器、操作和回复,所以现在不必特别担心理解这一点。

现在我们已经向扩展控制器添加了一些代码,让我们看看它的实际效果。只需输入以下 URL(相对于您的论坛 URL):index.php?members/hello-world。您现在应该会看到一个“Hello world!”消息显示!

如前所述,也可以覆盖类中的现有方法。例如,如果我们将 actionHelloWorld() 更改为 actionIndex(),那么您将不再有“Notable members”列表,而是会显示“Hello world!”消息!这并不是扩展现有控制器操作(或任何类方法)的正确方式,但我们在 修改控制器操作回复(正确的方式) 部分中更详细地讨论了这一点。

类型提示#

XF 中的许多对象都是通过工厂方法实例化的。例如,如果我们想实例化一个特定的存储库,我们可以编写以下内容:

PHP
$repo = \XF::repository('Demo:Thing');

这是一种非常方便且一致的实例化对象的方式。我们只需查看它,就知道将实例化什么对象。该方法中的结果代码知道如何返回我们请求的正确对象。

不幸的是,您的 IDE 可能(至少默认情况下)对此一无所知。就 IDE 而言,此方法将返回 XF\Mvc\Entity\Repository 的对象实例。这在某种程度上是有用的,但特定 Demo\Repository\Thing 对象中可能有许多方法,您的 IDE 并不知道。这最终意味着当您尝试在代码中使用 $repo 对象时,您的 IDE 将无法建议或自动完成方法名称及其所需的参数。

这就是类型提示变得有用的地方,语法应该得到大多数 IDE 和一些“PHP 感知”文本编辑器的标准支持。我们只需将存储库调用更改如下:

PHP
/** @var \Demo\Repository\Thing $repo */
$repo = \XF::repository('Demo:Thing');

存储库调用上方的类型提示现在告诉 IDE,$repo 与由 Demo\Repository\Thing 类表示的对象相关,而不是它最初自动推断的对象。

类型提示在扩展类时也特别有用。我们的类扩展方法的一个潜在问题是,本质上您的类并不扩展您想要扩展的原始类,而是通过一个实际上不存在的类进行代理,例如 XFCP_Member,如 上面的示例 所示。

为了解决这个问题,我们自动生成一个名为 extension_hint.php 的文件,并将其存储在您的 _output 目录中。

这添加了一个 IDE 可以读取但 PHP 无法读取的引用,以便 IDE 现在理解,当我们在扩展类中的任何方法中使用 $this 时,它可以建议和自动完成 Member 控制器或其父类中可用的方法和属性。