alshenetsky / easyadmin-breadcrumbs
一个允许您向EasyAdmin添加面包屑的包
Requires
- php: ^8.0
- easycorp/easyadmin-bundle: ^4.5
- symfony/config: ^5.4|^6.0|^7.0
- symfony/dependency-injection: ^5.4|^6.0|^7.0
- symfony/http-kernel: ^5.4|^6.0|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.14
- phpstan/phpstan: ^1.10
README
一个允许您向EasyAdmin添加面包屑的包
安装
此包需要EasyAdmin 4.5或更高版本,PHP 8.0或更高版本,以及Symfony 5.4或更高版本。在您的应用程序中安装它,请运行以下命令
$ composer require alshenetsky/easyadmin-breadcrumbs
文档
概念
众所周知,EasyAdmin没有在管理页面上放置面包屑的功能。管理区域的导航基于GET请求数据,封装在名为AdminContext的类中。控制器方法之间的转换是通过生成所需CRUD的URL实现的,如果需要,可以对其应用过滤器。因此,构建面包屑树变得很困难,因为您需要以某种方式存储控制器层次结构,而不会丢失过滤器和控制器之间的连接。
此包允许您重新创建这样的层次结构。您创建一个面包屑类,然后它又创建对父面包屑的引用,依此类推。
创建面包屑层次结构
此类必须实现BreadcrumbInterface。最简单的方法是继承AbstractBreadcrumb类,该类已经实现了此接口并包含有用的方法,减少了样板代码的数量
<?php namespace App\Controller\Admin\Breadcrumb; use Alshenetsky\EasyAdminBreadcrumbs\Breadcrumb\AbstractBreadcrumb; use Alshenetsky\EasyAdminBreadcrumbs\Breadcrumb\BreadcrumbType; use App\Entity\User; class UserIndexBreadcrumb extends AbstractBreadcrumb { public function getType(): BreadcrumbType { return BreadcrumbType::INDEX; } public function getEntityFqdn(): string { return User::class; } public function getName(): string { return 'Users'; } }
每个面包屑类将通过getType()
和getEntityFqdn()
方法与当前URL进行匹配。因此,此示例中的面包屑将出现在UserController::index
页面上。
方法getType()
返回与EasyAdmin包中Crud::PAGE_*常量完美匹配的BreadcrumbType
枚举。
此外,如果您需要更复杂的逻辑来决定是否在页面上显示面包屑,您还可以实现supports()
方法
public function supports(AdminContext $context): bool { return isset($context->getRequest()->get('filters')['parent']['value']); }
让我们更深入地了解导航树。在用户列表中,很可能会有一个用户编辑。让我们创建一个二级面包屑
<?php namespace App\Controller\Admin\Breadcrumb; use Alshenetsky\EasyAdminBreadcrumbs\Breadcrumb\AbstractBreadcrumb; use Alshenetsky\EasyAdminBreadcrumbs\Breadcrumb\BreadcrumbData; use Alshenetsky\EasyAdminBreadcrumbs\Breadcrumb\BreadcrumbType; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; class UserEditBreadcrumb extends AbstractBreadcrumb { public function getType(): BreadcrumbType { return BreadcrumbType::EDIT; } public function getEntityFqdn(): string { return User::class; } public function getParent(): ?string { return UserIndexBreadcrumb::class; } public function gather(AdminContext $context): BreadcrumbData { return parent::gather($context) ->set('userId', $context->getEntity()->getPrimaryKeyValue()) ; } public function configure(BreadcrumbData $gatheredData): void { /** @var User $user */ $user = $this->getEntityManager() ->getReference( User::class, $gatheredData->get('userId') ) ; $this ->setName(sprintf('Editing user %s', $user->getName())) ->setEntityId($user->getId()) ; } }
您可能会看到一些之前不熟悉的方法。
- 第一个是
getParent()
。它建立了子面包屑和父面包屑之间的链接。 - 第二个是
gather()
。它从当前查询(只有当给定的面包屑定义为当前时)收集数据,并将其存储在BreadcrumbData类中,该类作为数据存储库。 - 第三个是
configure()
。它接收BreadcrumbData
对象(我们在gather()
方法中生成的)。基于这些数据,我们可以配置此面包屑的名称和URL。这里的setEntityId()
调用是一个辅助方法调用,允许我们将entityId
键添加到URL生成器,并使用默认的URL生成逻辑。但我们可以通过调用setUrl()
来完全覆盖此机制->setName(sprintf('Editing user %s', $user->getName())) ->setUrl( $this ->getDefaultUrl() // returns EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator instance with controller and action already provided ->setEntityId($user->getId()) ->set('foo', 'bar') )
就是这样。现在我们有了两个级别的面包屑导航
用户 -> 编辑用户John Doe
现在更深入。假设在用户的编辑页面上有一个链接到用户的订单,这些订单在OrdersController中显示。假设在用户的编辑页面上有一个链接到用户的订单,这些订单在OrdersController中显示。您很可能在用户的编辑页面上创建一个自定义操作,并为其创建一个链接,该链接指向OrdersController::index,并设置一个过滤器('user' => ['comparison' => '=', 'value' => $user->getId()]]
)。很容易为下一级嵌套添加面包屑
用户 -> 编辑用户John Doe -> 用户订单
<?php class OrdersIndexBreadcrumb extends AbstractBreadcrumb { public function getType(): BreadcrumbType { return BreadcrumbType::INDEX; } public function getEntityFqdn(): string { return Order::class; } public function getParent(): ?string { return UserEditBreadcrumb::class; } public function gather(AdminContext $context): BreadcrumbData { // gather userId from request filters: return parent::gather($context) ->set('userId', $context->getRequest()->get('filters')['userId']['value']) ; } public function provide(BreadcrumbData $gatheredData): BreadcrumbData { // provide UserEditBreadcrumb with userId we gathered: return parent::provide($gatheredData) ->set('userId', $gatheredData->get('userId')) ; } public function configure(BreadcrumbData $gatheredData): void { $this ->setName('User orders') ->setFilters(['userId' => ['comparison' => '=', 'value' => $gatheredData->get('userId')]]) ; } }
您应该了解的最后一种方法是 provide()
。它也接收在 gather()
中收集的 BreadcrumbData,但它返回另一个 BreadcrumbData
类,以便用其填充父级面包屑。您应该提供与父级面包屑需要的完全相同的键。
您看,在另一个页面(不同的上下文)中,只会对当前面包屑调用 gather()
方法,而父级面包屑通过 provide()
方法从链中获取其 BreadcrumbData
。这就是面包屑层次结构形成的方式。您可以形成任意多的嵌套级别。
摘要
- 使用
configure()
方法设置面包屑的 URL 和名称,来自BreadcrumbData
- 使用
gather()
方法从当前上下文(仅对当前面包屑)收集BreadcrumbData
- 使用
provide()
方法将BreadcrumbData
发送到父级面包屑,使用与父级面包屑需要的完全相同的键。
异常处理
当显示子项列表时,使用 EasyAdmin 过滤器时,您可能会发现当您重置过滤器时,您会超出面包屑结构。为了避免遇到 500 错误,在任何 configure
、gather
或 provide
方法中抛出 BreadcrumbNotApplicableException
。这个错误将被正确处理,面包屑将不会渲染。
use \Alshenetsky\EasyAdminBreadcrumbs\Exception\BreadcrumbNotApplicableException; public function gather(AdminContext $context): BreadcrumbData { return parent::gather($context) ->set( 'userId', $context->getRequest()->get('filters')['userId']['value'] ?? throw new BreadcrumbNotApplicableException() // the absence of a filter value means that the list of ALL orders is now displayed, not just the user's orders. In this case, breadcrumbs are not applicable. ) ; }
在页面上放置面包屑
- 通过创建文件
templates/bundles/EasyAdminBundle/layout.html.twig
来覆盖 EadyAdmin 的layout.html
twig 模板 - 例如,在
content_header_wrapper
块中放置{{ breadcrumbs() }}
{% extends '@!EasyAdmin/layout.html.twig' %} {% block content_header_wrapper %} {{ breadcrumbs()}} {{ parent() }} {% endblock %}
贡献
欢迎贡献!
待办事项列表
- 添加测试
- 添加静态分析工具
- 配置 CI
许可证
本软件在 MIT 许可证 下发布