构建 SEF URL
构建 SEF URL 是通过类似于以下的调用开始的
use Joomla\CMS\Router\Route;
…
$url = Route::_('index.php?option=com_content&view=article&id=21&catid=50&lang=en-GB');
我们在这里提供了调用中的查询参数,路由器的任务是生成 SEF URL,最终可能会得到类似下面的内容
http://localhost/joom/index.php/en/mountains/50-alps/21-mont-blanc
生成此 URL 主要有 2 个阶段
- 一个预处理阶段,用于识别要在此 URL 基础上构建的菜单项,以及
- 一个构建阶段,用于构建各个段
最后还有一个整理步骤。
预处理阶段
预处理阶段属于组件路由器的职责范围。Joomla 站点路由器使用 Route::_()
调用中的 option
参数来识别组件(在本例中为 com_content
)。然后它尝试调用组件路由器的 preprocess
方法
public function preprocess($query)
查询参数以关联数组的形式传递,而不是以字符串的形式传递,例如
$query = array('view' => 'article', 'id' => '21', ...)
组件路由器 preprocess
函数的任务是识别要使用的菜单项,并在 $query
数组中设置它
$query['Itemid'] = 13;
return $query;
核心 Joomla 组件通常不会在它们的组件路由器中明确提供 preprocess()
函数,而是指定 RouterView
配置——这将在下一节中介绍——然后是由站点路由器本身提供 preprocess()
函数,我们将考虑它如何为 com_content
完成此操作。
在本例中,站点路由器找到站点上所有与 com_content
相关的菜单项,并选择其中一个作为 SEF URL 的基础。请注意,菜单项集将包括隐藏的菜单项,但不包括未发布的菜单项。
路由器如何知道选择这些菜单项中的哪一个?实际上,选择菜单项的逻辑在多个版本中发生了变化,导致 Joomla Stack Exchange 上出现了一些关于该主题的问题。作为一般指南,它似乎基于 RouterView
规则中第一个出现的规则。
请注意,您可以在 Route::_()
调用中指定菜单项 ID(即 Itemid
)
$url = Route::_('index.php?option=com_content&view=article&id=21&Itemid=6); ?>
但 **路由器很可能忽略此选项,并基于完全不同的菜单项构建 URL**。
但是,Joomla 的灵活性在于,如果您不喜欢它选择菜单项的方式,您可以编写一个插件来用您自己定义的算法覆盖默认功能,如 系统插件路由规则 所示。
所选菜单项提供 SEF URL 的起始段,基于该菜单项的别名字段。如果该菜单项位于子菜单中,则向上遍历菜单树的父菜单项的别名也会作为段预先附加——这实际上是菜单项的 route
属性,在 菜单和菜单项 中进行了描述。
假设所选菜单项是一个顶级菜单项,指向一篇文章类别列表,并且别名为“mountains”。那么我们的段数组将只有一个元素
$segments = array('mountains')
如果站点上没有与 Route::_()
调用中指定的选项相关的菜单项,会发生什么?
在这种情况下,路由器将基于主菜单项构建 SEF URL(如果是多语言站点,则针对该语言)。然后它会添加指向要使用的组件等的段。例如,如果组件是 com_contact
,您最终可能会得到类似下面的段
/en/component/contact/contact/me?Itemid=1
这是一个糟糕的情况,应该不惜一切代价避免!链接(如果它确实有效!)最终将具有主页格式和模块。
可以通过指定与该组件关联的隐藏菜单项来轻松避免这种情况。但是,扩展开发人员不一定能够控制管理员如何构建菜单结构。
构建阶段
构建阶段很大程度上属于组件路由器的职责范围,站点路由器尝试调用组件路由器的 build
方法
public function build(&$query)
查询参数以关联数组的形式传递,现在应该已设置菜单项 Itemid
,例如
$query = array('Itemid' => '13', 'view' => 'article', 'id' => '21', catid => '50', ...)
组件路由器 build
函数的任务是根据传递给它的查询参数生成 SEF URL 的段数组。它应该返回段数组,并取消设置任何已使用的查询参数
$segments = array('50-alps', '21-mont-blanc');
unset($query['view']);
unset($query['id']);
unset($query['catid']);
return $segments;
如果您没有取消设置一些查询参数,那么它们将被添加到 URL 中(在 URL 的查询部分),例如
http://localhost/joom/index.php/en/mountains/50-alps/21-mont-blanc?view=article&id=21
组件路由器可以自由决定要设置哪些段。它只需要能够在用户单击这些段时解析它们即可。
传统上,Joomla 将段设置为 id:alias
的形式,但可以通过设置(对于 com_content
)全局配置:文章 / 集成 / 路由从 URL 中删除 ID 选项来删除 id
部分(只保留 alias
作为段)。(对于联系人也是如此)。id:alias
格式的优点是,在解析传入 URL 时不需要进行数据库查找。如果您在组件中只使用 alias
格式,那么请确保在数据库中的 alias
字段上有一个索引。
通常,如果菜单项指向单个项目(如单个文章或单个联系人),那么它可以只创建一个段,该段是该项目的 id:alias
(或仅 alias)。
如果菜单项指向项目的类别列表,那么它应该提供类别的 id:alias
,并且通常还包含类别树中父类别的信息。这可以使用 CategoryNode 的 getPath()
方法轻松找到,如 CategoryNode 获取方法 所述。(如果您在段中不使用 ID,请记住,类别别名不必在整个 Joomla 实例中唯一;它们只需要在层次结构中同一级别的类别之间唯一。因此需要类别路径。)
在我们的示例中,我们假设菜单项与类别列表相关联,并且我们提供以下段
- 项目的类别的
id-alias
(“50-alps”)(我们假设该类别位于类别树的顶层) - 项目的
id-alias
(“21-mont-blanc”)。
包括在 preprocess
函数中找到的“mountains”段,我们现在有 3 个段
/mountains/50-alps/21-mont-blanc
URL 的剩余部分
到目前为止,生成 SEF URL 的所有艰苦工作都已经完成。剩下的就是添加语言段(再次由多语言站点上的语言过滤器插件完成),以及必要时的域和入口点 (index.php)。