Drupal自定义内容类型MyCoolCustomType访问控制实现疑问
问题背景
我在Drupal站点里建了个叫MyCoolCustomType的自定义内容类型,想要实现:只有当用户符合指定角色,且内容自身属性(比如审核状态ModerationState)满足条件时,才允许查看这个类型的内容。
我试着通过继承RouteSubscriberBase类修改entity.node.canonical路由的requirements部分,添加_custom_access来调用专属服务,但对整个实现流程有点懵。
给出的代码:
mymodule.services.yml 配置
content_moderation.route_subscriber: class: Drupal\content_moderation\Routing\ContentModerationRouteSubscriber arguments: ['@entity_type.manager'] tags: - { name: event_subscriber }
mymodule/src/EventSubscriber/SituationProAccessSubscriber.php 代码
<?php namespace Drupal\neocadre_features\EventSubscriber; use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Session\AccountProxyInterface; use Symfony\Component\Routing\RouteCollection; class SituationProAccessSubscriber extends RouteSubscriberBase { /** * @var AccountProxy */ protected $currentUser; public function __construct(AccountProxyInterface $currentUser) { $this->currentUser = $currentUser; } protected function alterRoutes(RouteCollection $collection) { if ($route = $collection->get('entity.node.canonical')) { // Here it is :) maybe :) // Can I filter my content type here ? } } }
我的疑问
- 该在哪里做
MyCoolCustomType的专属过滤?是在路由订阅者里,还是在访问服务里? - 这种修改路由的方式,适合给自定义内容类型做高级访问检查吗?
解答
1. 过滤逻辑的放置位置
别在路由订阅者里直接做内容类型过滤,路由订阅者的职责就是修改路由定义,具体的权限判断逻辑得放到专门的访问服务里。
正确的分工是:
- 在路由订阅者中,给
entity.node.canonical路由加上_custom_access规则,指向你的自定义访问服务。 - 在访问服务里,先判断当前节点是不是
MyCoolCustomType,再结合用户角色、内容审核状态这些条件做最终的权限校验。
2. 这种方式是否适合高级访问检查?
这种方式完全能实现复杂的访问规则,但更推荐用Drupal官方的实体权限控制方式——比如hook_node_access()钩子,或者自定义访问控制处理器(Access Control Handler),这两种方案更贴合Drupal的架构设计,后期维护也更方便。
如果你偏要用路由订阅者+自定义访问服务的方式,下面是修正后的代码示例:
第一步:修正services.yml配置
注意要指向你自己的订阅者类,不是Drupal自带的ContentModerationRouteSubscriber:
mymodule.route_subscriber: class: Drupal\neocadre_features\EventSubscriber\SituationProAccessSubscriber arguments: ['@current_user'] tags: - { name: event_subscriber }
第二步:修改路由订阅者,添加自定义访问规则
<?php namespace Drupal\neocadre_features\EventSubscriber; use Drupal\Core\Routing\RouteSubscriberBase; use Symfony\Component\Routing\RouteCollection; class SituationProAccessSubscriber extends RouteSubscriberBase { protected function alterRoutes(RouteCollection $collection) { if ($route = $collection->get('entity.node.canonical')) { // 指定自定义访问服务,注意服务ID要和后面注册的一致 $route->setRequirement('_custom_access', 'mymodule.my_cool_access_checker::access'); } } }
第三步:创建自定义访问检查服务
在mymodule/src/Access/MyCoolAccessChecker.php中写入:
<?php namespace Drupal\neocadre_features\Access; use Drupal\Core\Access\AccessResult; use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountProxyInterface; use Drupal\node\NodeInterface; use Symfony\Component\Routing\Route; class MyCoolAccessChecker implements AccessInterface { /** * 当前用户对象 * @var AccountProxyInterface */ protected $currentUser; public function __construct(AccountProxyInterface $currentUser) { $this->currentUser = $currentUser; } public function access(Route $route, NodeInterface $node) { // 先判断是不是目标内容类型 if ($node->bundle() !== 'my_cool_custom_type') { // 不是的话,交给默认权限检查 return AccessResult::neutral(); } // 检查用户角色(替换成你需要的角色) $allowed_roles = ['editor', 'administrator']; if (!array_intersect($allowed_roles, $this->currentUser->getRoles())) { return AccessResult::forbidden()->addCacheContexts(['user.roles']); } // 检查内容审核状态(替换成你允许的状态) $moderation_state = $node->get('moderation_state')->value; if (!in_array($moderation_state, ['published', 'reviewed'])) { return AccessResult::forbidden()->addCacheableDependency($node); } // 所有条件都满足,允许访问 return AccessResult::allowed() ->addCacheContexts(['user.roles']) ->addCacheableDependency($node); } }
第四步:在services.yml中注册访问检查服务
mymodule.my_cool_access_checker: class: Drupal\neocadre_features\Access\MyCoolAccessChecker arguments: ['@current_user'] tags: - { name: access_check, applies_to: _custom_access }
更简单的官方推荐方案:使用hook_node_access()
如果不需要修改路由,直接在模块的.module文件里实现节点访问钩子更省事:
<?php /** * Implements hook_node_access(). */ function mymodule_node_access(NodeInterface $node, $op, AccountInterface $account) { // 只处理视图权限和目标内容类型 if ($op === 'view' && $node->bundle() === 'my_cool_custom_type') { // 检查用户角色 if (!$account->hasAnyRole('editor', 'administrator')) { return AccessResult::forbidden()->addCacheContexts(['user.roles']); } // 检查内容审核状态 $state = $node->get('moderation_state')->value; if (!in_array($state, ['published', 'reviewed'])) { return AccessResult::forbidden()->addCacheableDependency($node); } // 权限通过 return AccessResult::allowed() ->addCacheContexts(['user.roles']) ->addCacheableDependency($node); } // 其他情况返回中立,使用默认权限逻辑 return AccessResult::neutral(); }
这种方式不用折腾路由和服务,直接写钩子就行,完全符合Drupal的权限系统设计。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

