使用类别 API
访问类别类
要访问 com_content
的类别集,您可以使用
use Joomla\CMS\Categories\Categories;
...
$extension = "content"; // Note that you don't have the usual "com_" prefix in the extension.
$categories = Categories::getInstance($extension);
(这实际上使用了已弃用的 API 作为访问类别类的方法,因为它没有通过依赖注入容器。但是,它要简单得多,并且稍后将描述使用 DIC 的另一种方法)。您可以提供一个关联数组作为选项作为 Categories 静态 getInstance()
方法的第二个参数。此数组的键为
- "access" – 如果为
true
(或等于 PHPtrue
的某些值),则仅返回当前用户有权查看的类别。如果为false
,则将返回所有类别,无论当前用户是否有权查看它们。默认为true
。 - "published" – 如果为 1(整数一),则仅返回发布状态为 1(即“已发布”)的类别。否则,将返回任何状态的类别。默认为 1,这意味着仅返回“已发布”的类别。
- "countitems" – 如果为 1(整数一),则当返回类别时,Joomla 将为每个类别记录确定与该类别关联的扩展记录的数量(默认是不计算项目)。为此,Joomla 将对扩展表执行 SQL JOIN,其中该表中的类别 ID 字段匹配,并计算该表中“键”字段的实例。Joomla 需要知道这些表和字段的名称,这些名称可以在下面的数组选项中提供。
- "table" – 扩展表的名称。没有默认值,因此如果您使用“countitems”,则必须提供此值。
- "field" – 扩展表中保存类别 ID 的字段的名称(默认“catid”)
- "key" – 扩展表中执行 SQL COUNT 的键字段的名称(默认“id”)。
- "statefield" – 扩展表中保存记录发布状态的字段的名称(默认“state”)。如果 Joomla 仅返回已发布的类别(如上面描述的“published”选项所确定的),那么它也将仅计算扩展表中已发布的项目。(请注意,对于“access”不会执行等效操作 - 扩展表中的任何 Access 字段都会被忽略)。
示例
$categories = Categories::getInstance("content", array("access" => false, "published" => 0));
返回的类别节点不会受访问权限或发布状态的限制。
$categories = Categories::getInstance("helloworld", array("countitems" => 1, "table" => "helloworld", "statefield" => "published"));
"helloworld" 组件的类别节点将包括“helloworld”表中关联记录的数量,其中发布状态保存在名为“published”的字段中。
类别 get()
获得 $categories
实例后,您可以使用例如获取 CategoryNode
实例
$categoryNodes = $categories->get(12); // returns the category node for category with id=12
Categories get()
方法将其第一个参数作为要从数据库中读取的类别记录的 id
,并将相应的 CategoryNode
对象返回给调用方。如果没有给出 id
,则该方法将返回与数据库中类别树顶部的系统 ROOT
记录相关的 CategoryNode
对象。
get()
功能实际上从数据库中读取请求的类别记录到类别 root
记录的所有上级(这使得下面描述的类别路径能够确定),以及请求的类别记录的所有后代。然后,它在本地存储这些内容,以便后续调用获取任何这些子项的数据可以通过返回此缓存中的数据而不是对数据库执行另一个查询来提供服务。
但是,如果第二个参数 $forceload
设置为 true
,则它将通过再次查询数据库来服务请求,而不是使用缓存的记录。
在多语言站点中,返回的类别将仅限于当前语言或语言 *(对于所有语言)。类似地,扩展表中任何项目的计数都将仅限于当前语言或语言 *,并且 Joomla 将假设语言存储在名为“language”的字段中(您无法覆盖此语言字段的名称)。
CategoryNode 属性
对 Categories get($id)
方法的调用的对象将是与 id 为 $id
的类别相关的 CategoryNode
对象。如果您使用“root”的默认值调用了 get()
,那么您必须随后调用 getChildren()
以获取您想要的扩展的 CategoryNode
对象数组,例如
use Joomla\CMS\Categories\Categories;
use Joomla\CMS\Categories\CategoryNode;
$categories = Categories::getInstance("content");
$rootNode = $categories->get();
$categoryNodes = $rootNode->getChildren();
获得 CategoryNode
对象后,您可以访问此对象的属性,如 API 定义中所定义,并且这些属性大多与 Joomla 管理表单中可见的类别属性密切相关。下面没有描述含义非常清楚的属性,但下面列出了那些含义可能不完全清楚的属性。
- asset_id – 管理员已为单个类别定义 ACL 时,这是资产表中存储这些 ACL 的记录的 id。
- parent_id、lft、rgt、level – 这些字段与类别在类别树中的位置相关,该位置使用嵌套集模型实现。
lft
和rgt
值按模型的要求实现,parent_id
是类别表中父记录的 id(不是资产表 - 这是 Joomla 代码中的错别字),level
为根节点为 0,其父节点为根节点的记录为 1,下一级后代为 2,依此类推。 - extension – 这次存在“com_”前缀
- numitems – 与此类别关联的扩展表中的记录数
- childrennumitems – 此项未设置 - 请勿尝试使用它!
- slug – 这是 Joomla 在显示 SEF URL 时传统上显示为 URL 部分的内容。它采用
id:alias
的形式,例如“3:uncategorised". - assets – 此项未设置 - 请勿尝试使用它!
CategoryNode get 方法
一些 CategoryNode get
方法使您能够访问树中的其他 CategoryNode 对象
getChildren(boolean $recursive = false)
返回直接子项的数组,或者如果$recursive
=true
则返回所有后代的数组。getParent()
返回父 CategoryNodegetSibling(boolean $right = true)
返回右侧的同级 CategoryNode,或者如果$right
=false
则返回左侧的同级 CategoryNode。
一些 get
方法返回 CategoryNode 对象的属性
getAuthor(boolean $modified_user = false)
返回与创建类别的用户或如果$modified_user
=true
则返回上次修改用户的用户关联的用户对象getMetadata()
返回包含类别元数据(json 结构)的 Joomla 注册表对象getParams()
返回包含类别参数(json 结构)的 Joomla 注册表对象getNumItems(boolean $recursive = false)
返回与该类别相关的扩展表中的记录数。如果$recursive
=true
,则它是与此类别及其所有后代关联的记录总数。getPath()
返回从类别树的根到此类别的树向下遍历的片段数组。例如,如果此类别的 id 为 9 且别名为“dog”,其父类别的 id 为 6 且别名为“mammal”,祖父母(根节点正下方)的 id 为 3 且别名为“animal”,则getPath()
将返回array(3 => "3:animal", 6 => "6:mammal", 9 => "9:dog"
)。
请注意,hasParent()
包括父节点为根节点,因此除非您在根节点上调用此方法,否则此方法始终返回 true
。
CategoryNode set 方法
CategoryNode API 有许多 set
方法,但这些方法主要由 Joomla 类别功能用于基于从数据库检索的数据设置 CategoryNode 对象。调用其中一个不会将数据持久化到数据库,例如,您不能使用 setParent()
将类别记录重新置于数据库中另一个类别的父节点下。
模块代码示例
以下是您可以安装和运行的简单 Joomla 模块代码,用于演示分类和 CategoryNode API 功能的使用。如果您不确定如何开发和安装 Joomla 模块,请参考 创建简单模块 教程。 (请注意,以下代码未遵循模块设计最佳实践指南,而是为了简化展示分类 API 而进行了简化)。
在文件夹 mod_sample_categories
中创建以下 2 个文件
mod_sample_categories.xml
<?xml version="1.0" encoding="utf-8"?>
<extension type="module" version="3.1" client="site" method="upgrade">
<name>Categories demo</name>
<version>1.0.1</version>
<description>Code demonstrating use of Joomla APIs related to Categories</description>
<files>
<filename module="mod_sample_categories">mod_sample_categories.php</filename>
</files>
</extension>
mod_sample_categories.php
<?php
defined('_JEXEC') or die('Restricted Access');
use Joomla\CMS\Factory;
use Joomla\CMS\Categories\Categories;
use Joomla\CMS\Categories\CategoryNode;
$app = Factory::getApplication();
$input = $app->input;
$ext = $input->get('categoryextension', "Content", "STRING");
$tab = $input->get('categorytable', "Content", "STRING");
echo "Getting {$ext} categories and using {$tab} table<br>";
echo "-------------<br>";
$categories = Categories::getInstance($ext, array("table" => "Content", "countItems" => 1, "access" => false));
$cat0 = $categories->get('root');
$cats = $cat0->getChildren(true);
foreach ($cats as $cat)
{
echo "Category {$cat->id}, title: {$cat->title}<br>";
$parent = $cat->getParent();
echo "Level: {$cat->level}, parent id: {$cat->parent_id}, title: {$parent->title}<br>";
echo "Numitems {$cat->getNumitems()}, including descendants: {$cat->getNumitems(true)}<br>";
var_dump($cat->getPath());
echo "-------------<br>";
}
$ext2 = $input->get('option', "", "STRING");
$catid = $input->get('catid', 0, "INT");
$view = $input->get('view', "", "STRING");
$id = $input->get('id', 0, "INT");
if ($ext2 && (strtolower(substr($ext2, 0, 4)) == "com_") && ($catid || (strtolower($view) == "category" && $id)))
{
$ext2 = substr($ext2, 4);
$categories2 = Categories::getInstance($ext2, array("access" => false));
$categoryId = $catid ? $catid : $id;
echo "<br>Getting $ext2 category $categoryId<br>";
$cat2 = $categories2->get($categoryId);
if ($cat2)
{
echo "Category {$cat2->id}, title: {$cat2->title}<br>";
}
}
将 mod_sample_categories 目录压缩成 mod_sample_categories.zip
文件。
在您的 Joomla 后台,进入“安装扩展”,通过“上传安装包”选项卡选择此 zip 文件安装此示例分类模块。
通过编辑此模块使其可见(在“模块”页面中点击它),然后
- 将其状态设置为“已发布”
- 选择页面上显示模块的位置
- 在“菜单分配”选项卡上指定模块应显示的页面
当您访问网站页面时,您应该会在您选择的位置看到该模块,并且它应该会显示数据库中所有 com_content 分类,以及每个分类的
- ID 和标题
- 在分类树中的层级,以及父分类的 ID 和标题
- 具有此分类的文章数量,以及具有此分类或其任何子分类的文章数量
- 分类
getPath()
返回值的var_dump
。
您可以通过向 URL 添加参数 categoryextension
和 categorytable
来获取其他组件的分类,例如 ...&categoryextension=contact&categorytable=contact_details
获取 com_contact
分类。请注意,如果您尝试获取非 Joomla 核心组件的组件分类,则可能需要在 options
中提供组件字段名称等信息给 Categories::getInstance()
调用,如上所述。
代码还尝试猜测显示的页面是否与某个分类相关,方法是检查 URL 中是否存在 catid
参数,或者 view
参数是否设置为“category”。在这种情况下,它会显示关联分类的 ID 和标题。显然,这可能并非在所有情况下都能正常工作。
通过依赖注入容器获取分类
通常,访问 Categories
类首选的方式是通过 DI 容器,特别是使用通过 DI 容器获取的 CategoryFactory
类。获取 CategoryFactory
后,您可以在该实例上调用 createCategory($options)
并接收 Categories
对象;$options
数组是在本页顶部描述的一组选项。但是,CategoryFactory
对象必须使用您想要访问其分类的扩展进行实例化,因为 createCategory()
将尝试访问 \\<namespace prefix>\\Site\\Service\\Category
类以查找扩展的数据库表等。
以下是在 services/provider.php 文件中访问 com_content
分类的一种方法。
<?php
defined('_JEXEC') or die;
use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
use Joomla\CMS\Extension\Service\Provider\HelperFactory;
use Joomla\CMS\Extension\Service\Provider\Module;
use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
use Joomla\CMS\Dispatcher\ModuleDispatcherFactoryInterface;
use Joomla\CMS\Extension\ModuleInterface;
use Joomla\CMS\Helper\HelperFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Mycompany\Module\CategoriesDemo\Site\Extension\CategoriesDemoModule;
use Joomla\CMS\Categories\CategoryFactoryInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container)
{
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new ModuleDispatcherFactory('\\Mycompany\\Module\\CategoriesDemo'));
$container->registerServiceProvider(new HelperFactory('\\Mycompany\\Module\\CategoriesDemo\\Site\\Helper'));
//$container->registerServiceProvider(new Module());
$container->set(
ModuleInterface::class,
function (Container $container) {
$module = new CategoriesDemoModule(
$container->get(ModuleDispatcherFactoryInterface::class),
$container->has(HelperFactoryInterface::class) ? $container->get(HelperFactoryInterface::class) : null
);
$module->setCategoryFactory($container->get(CategoryFactoryInterface::class));
return $module;
}
);
}
};
代码行
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
将在 libraries/src/Extension/Service/Provider/CategoryFactory.php 中运行 register()
函数。这将在 DI 容器中创建一个键为“Joomla\CMS\Categories\CategoryFactoryInterface”的条目,其值为一个函数,该函数将返回一个使用 com_content
的命名空间前缀实例化的 CategoryFactory。
代码行
$module->setCategoryFactory($container->get(CategoryFactoryInterface::class));
将从 DI 容器中获取此条目(因此将获取 CategoryFactory 实例),并通过 setCategoryFactory
调用将其存储到我们的扩展 $module
中。
以上假设您在模块清单文件中设置了自己的模块的命名空间前缀
<namespace path="src">Mycompany\Module\CategoriesDemo</namespace>
然后在您的扩展文件(模块的 src/Extension/CategoriesDemoModule.php 文件中)
<?php
namespace Mycompany\Module\CategoriesDemo\Site\Extension;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Extension\Module;
class CategoriesDemoModule extends Module implements CategoryServiceInterface, BootableExtensionInterface
{
use CategoryServiceTrait;
public static $categories;
public function boot(ContainerInterface $container)
{
self::$categories = $this->categoryFactory->createCategory();
}
public static function getCategories()
{
return self::$categories;
}
}
这是在 services/provider.php 文件中创建并作为 $module
返回的扩展对象。在那里使用的函数 setCategoryFactory
位于 CategoryServiceTrait
中,该函数将 CategoryFactory 存储为局部变量,可以通过 $this->categoryFactory
访问,或者您可以使用
self::$categories = $this->getCategory();
当您的模块运行时,Joomla 将调用您的 boot()
函数,这将设置一个保存 Categories 实例的静态变量。然后,您可以通过此静态变量访问它,例如
use Mycompany\Module\CategoriesDemo\Site\Extension\CategoriesDemoModule;
// ...
$categories = CategoriesDemoModule::$categories;
但是请注意
-
如果(如示例模块代码)您以动态方式确定要访问其分类的扩展,则问题会更复杂,因为这会影响您的代码行
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
当您将条目加载到 DI 容器中时
-
如果您希望在模块中访问多个组件的分类,则此方法不起作用,因为如果您尝试通过以下方式加载
com_content
和com_contact
CategoryFactory 类$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Contact'));
这是因为第二个条目将覆盖第一个条目,因为它们都在 DI 容器中使用相同的键。
并非所有关于迁移到使用 DI 容器的问题都已得到圆满解决!