跳至主要内容
版本:5.1

在组件中实现类别

在自定义组件中实现类别非常简单。以下是您需要执行的操作摘要。

在您的组件数据库表中指定类别 ID 列

所有类别数据都存储在 Categories 表中;您只需指定一个指向相应 Categories 记录的外键,因此您需要在组件表中创建一个额外的列来保存该键。通常将您的列命名为“catid”,但您不必这样做。它与 Categories 表的“id”字段匹配。

添加一个字段来捕获类别

在允许用户添加新记录或编辑现有记录的地方,您将希望扩展表单以捕获关联的类别。您可以将类似于以下内容添加到用于添加或编辑组件记录的 XML 文件中

<field
name="catid"
type="category"
extension="com_yourcomponent"
class="inputbox"
default=""
label="JCATEGORY"
required="true"
>
<option value="0">JOPTION_SELECT_CATEGORY</option>
</field>

只要您将字段的name命名为与数据库列名称相同,Joomla 就会将来自提交表单的数据映射到数据库表中。

在管理子菜单中添加条目

您应该在组件子菜单中添加一个条目,以允许管理员管理类别数据。您可以在组件清单 XML 文件中的管理员<submenu>标签内完成此操作

<administration>
<submenu>
<menu link="option=com_categories&amp;extension=com_yourcomponent">JCATEGORIES</menu>
</submenu>
</administration>

为类别指定 ACL

您需要在 access.xml 中访问一些条目,例如

<?xml version="1.0" encoding="utf-8" ?>
<access component="com_yourcomponent">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN"/>
<action name="core.manage" title="JACTION_MANAGE"/>
<action name="core.create" title="JACTION_CREATE"/>
<action name="core.delete" title="JACTION_DELETE"/>
<action name="core.edit" title="JACTION_EDIT"/>
<action name="core.edit.state" title="JACTION_EDITSTATE"/>
</section>
<section name="category">
<action name="core.create" title="JACTION_CREATE"/>
<action name="core.delete" title="JACTION_DELETE"/>
<action name="core.edit" title="JACTION_EDIT"/>
<action name="core.edit.state" title="JACTION_EDITSTATE"/>
</section>
</access>

按发布状态划分的类别摘要

在管理员类别表单中,通常会为每个类别记录显示具有已发布/未发布/已存档/已删除状态的组件记录数。要实现此功能,com_categories 会启动您的组件并调用您扩展类实例上的countItems()函数(在 Joomla\CMS\Categories\CategoryServiceInterface 中定义)。您可以直接提供countItems()函数,或者(如果您的类别字段名为catid)使用 Joomla\CMS\Categories\CategoryServiceTrait 并覆盖

  • getTableNameForSection – 返回您的组件的表名,
  • getStateColumnForSection – 返回保存状态的列(例如“published”)。

com_categories 然后将在您的组件表本身执行查询。

创建未分类类别

您可能已经注意到,当您首次安装 Joomla 时,每个使用类别的 Joomla 核心组件都有一个未分类类别记录。并且他们数据库表中的catid字段不允许为 NULL - 每个组件的项目都必须有一个关联的类别记录。

您应该遵循这种模式,在安装脚本中设置组件的未分类类别记录。否则,您可能会在某些区域遇到困难(例如多语言关联),因为 Joomla 库代码有时会假设关联的类别不是可选的。

其他更改

您可能希望进行各种其他更改,具体取决于组件的功能,例如

  • 如果您有一个显示组件项及其属性的表单,您可能希望扩展它以包含类别
  • 还将类别包含在此表单的过滤器字段列表中
  • 与类别相关的批量操作

获取 CategoryFactory

如果您计划在组件中支持 SEF 路由,那么您将需要一个自定义路由器,这将涉及使用类别 API 来构建和解析 SEF 路由的类别段。您可能还想出于其他原因使用类别 API,例如在组件的类别视图中显示类别详细信息。

获取 Categories 对象的最佳方法是通过从 CategoryFactory 对象实例化它,并通过依赖注入容器 (DIC) 获取该 CategoryFactory 对象。为此,您可以使用以下方法。

下面的 services/provider.php 文件用于组件com_example,命名空间为Mycompany\Component\Example(如 xml 清单文件中定义)。与获取 CategoryFactory 相关的行已添加注释以说明正在发生的事情。

<?php

defined('_JEXEC') or die;

use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory as ComponentDispatcherFactoryServiceProvider;
use Joomla\CMS\Extension\Service\Provider\CategoryFactory as CategoryFactorServiceProvider;
use Joomla\CMS\Extension\Service\Provider\MVCFactory as MVCFactoryServiceProvider;
use Joomla\CMS\Extension\Service\Provider\RouterFactory as RouterFactoryServiceProvider;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\CMS\Categories\CategoryFactoryInterface;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Mycompany\Component\Example\Administrator\Extension\ExampleComponent;
use Joomla\Database\DatabaseInterface;


return new class implements ServiceProviderInterface {

public function register(Container $container): void
{

/* The line below will call register() in libraries/src/Extension/Service/Provider/CategoryFactory.php
* That function will create an entry in our component's child DIC with:
* key = 'Joomla\CMS\Categories\CategoryFactoryInterface'
* value = a function which will
* 1. create an instance of CategoryFactory, instantiated with the namespace string passed in
* 2. call setDatabase() on that CategoryFactory instance, so that it's got access to the database object
* 3. return the CategoryFactory instance
*/
$container->registerServiceProvider(new CategoryFactorServiceProvider('\\Mycompany\\Component\\Example'));
$container->registerServiceProvider(new MVCFactoryServiceProvider('\\Mycompany\\Component\\Example'));
$container->registerServiceProvider(new ComponentDispatcherFactoryServiceProvider('\\Mycompany\\Component\\Example'));
$container->registerServiceProvider(new RouterFactoryServiceProvider('\\Mycompany\\Component\\Example'));
$container->set(
ComponentInterface::class,
function (Container $container) {
// The next line creates an instance of our com_example component Extension class
$component = new ExampleComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
/* The line below will get from the DIC the entry with key 'Joomla\CMS\Categories\CategoryFactoryInterface'
* The CategoryFactory instance will be returned, and we'll save a reference to it in our component by
* calling setCategoryFactory(), passing it in as the parameter
*/
$component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
$component->setDatabase($container->get(DatabaseInterface::class));

return $component;
}
);
}
};

为了匹配这一点,您需要在扩展类中包含一些关键行

<?php

namespace Mycompany\Component\Example\Administrator\Extension;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\Database\DatabaseAwareTrait;


class ExampleComponent extends MVCComponent implements
CategoryServiceInterface, RouterServiceInterface, BootableExtensionInterface
{
use CategoryServiceTrait;
use RouterServiceTrait;
use DatabaseAwareTrait;

// Use a static variable to store the Categories instance
public static $categories;

public function boot(ContainerInterface $container)
{
self::$categories = $this->categoryFactory->createCategory();
}
// ...

CategoryServiceTrait 用于包含函数setCategoryFactory,该函数用于在 services/provider.php 中保存返回的 CategoryFactory。该函数将其保存在实例变量$categoryFactory中。

每当 Joomla 要执行组件时,它都会运行 services/provider.php 文件(它创建扩展实例),然后调用该扩展对象上的boot()。上面的boot()中的代码访问存储的 CategoryFactory 并调用其上的createCategory()来创建Categories实例。该结果存储在静态变量中,可以通过以下方式在组件的任何地方访问

`$categories = ExampleComponent::$categories;``

CategoryFactory createCategory()代码将使用传递给它的命名空间(最初在上面的 services/provider.php 中)并尝试实例化一个类<namespace>\Site\Service\Category,如果该类不存在,则引发异常。因此,您需要在站点区域 src/Service/Category.php 中提供一个类似于以下内容的文件

<?php

namespace Mycompany\Component\Example\Site\Service;

use Joomla\CMS\Categories\Categories;

\defined('_JEXEC') or die;

class Category extends Categories
{

public function __construct($options = array())
{
$options['table'] = '#__example';
$options['extension'] = 'com_example';

parent::__construct($options);
}
}

通过这种方式,定义了类别 API的选项数组。(您可以将这些选项作为参数传递给createCategory()调用,但您仍然必须提供 Category.php 类文件)。

多个类别字段

您可以将多种不同类型的类别与组件关联。例如,如果您有一个组件 com_product,您就可以有

  • 一个尺寸类别 - 可能包括小、中、大等,以及
  • 一个价格类别 - 可能包括便宜、中等、昂贵等。

为了支持这一点,Joomla 允许您在组件在 Categories 表中的分区内创建分区,并且该表中的组件条目将具有设置为“com_product.size”或“com_product.price”的extension字段。

要在您的组件中实现此功能,您显然需要在组件的数据库表中添加 2 个类别列,例如 catid1、catid2。

您还需要实现一些其他更改。

管理员子菜单

您需要添加类似于以下内容

<menu link="option=com_categories&amp;extension=com_product.size">Size categories</menu>
<menu link="option=com_categories&amp;extension=com_product.price">Price categories</menu>

在组件的管理员子菜单中创建 2 个类别菜单项。

管理员编辑项目

在管理员编辑产品 xml 表单中(您在其中定义每个产品及其关联的尺寸和价格类别),用于捕获类别的字段必须区分它们,例如

<field name=catid1 type="category" extension="com_product.size" />
<field name=catid2 type="category" extension="com_product.price" />

对于filter_products.xml中的任何过滤器字段也是如此,这些字段用于过滤管理员产品表单中显示的产品列表。

类别摘要。

在扩展类中,您需要提供countItems()函数,该函数位于 trait Joomla\CMS\Categories\CategoryServiceTrait中。com_categories将调用该函数并传入类别section,如果您使用ContentHelper::countRelations()来提供摘要,那么您需要为每个部分设置相应的类别 ID 字段 - 例如,对于部分“size”为“catid1”,对于部分“price”为“catid2”。以下是一个示例。

public function countItems(array $items, string $section)
{
$config = (object) [
'related_tbl' => $this->getTableNameForSection($section),
'state_col' => $this->getStateColumnForSection($section),
'group_col' => $section == "size" ? 'catid1' : 'catid2',
'relation_type' => 'category_or_group',
];

ContentHelper::countRelations($items, $config);
}