mimmi20/mezzio-navigation

为 Mezzio 提供导航功能。

3.0.7 2024-09-10 07:39 UTC

README

Latest Stable Version Latest Unstable Version License

代码状态

codecov Test Coverage Average time to resolve an issue Percentage of issues still open Mutation testing badge Maintainability

简介

该组件提供了一个用于管理网页指针树的组件。简单来说:它可以用于创建菜单、面包屑、链接和网站地图,或作为其他导航相关用途的模型。

laminas-navigation 不同,此库提供了一个必须用于准备导航的中介。

安装

您可以使用 Composer 安装 mezzio-navigation 库。

composer require mimmi20/mezzio-navigation

页面和容器

在 mezzio-navigation 中有两个主要概念:页面和容器。

页面

在 mezzio-navigation 中,页面在最基本的形式下是一个包含指向网页指针的对象。除了指针本身之外,页面对象还包含许多通常与导航相关的属性,例如 labeltitle 等。

有关页面的更多信息,请参阅页面部分。

容器

导航容器包含页面。它具有添加、检索、删除和迭代页面的方法。它实现了 SPL 接口 RecursiveIteratorCountable,因此可以使用如 RecursiveIteratorIterator 之类的 SPL 迭代器进行迭代。

有关容器的更多信息,请参阅容器部分。

页面是容器

Mezzio\Navigation\PageInterface 扩展 Mezzio\Navigation\ContainerInterface,这意味着页面可以有子页面。

视图助手

数据和渲染分离

mezzio-navigation 命名空间中的类不处理导航元素的渲染。渲染是通过导航视图助手完成的。然而,页面包含视图助手在渲染时使用的信息,例如 labelclass(CSS)、titlelastmodpriority 属性用于网站地图等。

我们提供了一个渲染库。

该渲染器可以通过 Composer 安装。

composer require mimmi20/mezzio-navigation-laminasviewrenderer

容器

容器具有添加、检索、删除和迭代页面的方法。容器实现了 SPL 接口 RecursiveIteratorCountable,这意味着可以使用 SPL RecursiveIteratorIterator 类迭代容器。

创建容器

Mezzio\Navigation\ContainerInterface 不能直接实例化。如果您想实例化一个容器,请使用 Mezzio\Navigation\Navigation

Mezzio\Navigation\Navigation 可以完全为空构造,或者接受一个包含要放入容器中的页面的数组或 Traversable 对象。通过选项提供的每个页面最终都会传递给容器类的 addPage() 方法,这意味着选项中的每个元素也可以是数组、Traversable 对象或 Mezzio\Navigation\Page\PageInterface 实例。

使用数组创建容器

Laminas Navigation不同,无法直接将数组配置添加到Mezzio Navigation中。将数组配置转换为页面对象列表是在导航工厂中完成的。

use Mimmi20\Mezzio\Navigation\Navigation;
use Mimmi20\Mezzio\Navigation\Config\NavigationConfigInterface;

/*
 * Create a container from an array
 */
$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages([
    [
        'label' => 'Page 1',
        'id' => 'home-link',
        'uri' => '/',
    ],
    [
        'label' => 'Laminas',
        'uri' => 'http://www.laminas-project.com/',
        'order' => 100,
    ],
    [
        'label' => 'Page 2',
        'route' => 'page2',
    ],
]);
$container = $serviceLocator->get(Navigation::class);

添加页面

可以使用addPage()addPages()setPages()方法向容器中添加页面。下面是示例,以解释说明。

Laminas Navigation不同,只允许实现PageInterface的对象。可以使用PageFactory来使用数组配置。

use Laminas\Config\Config;
use Mimmi20\Mezzio\Navigation\Navigation;
use Mimmi20\Mezzio\Navigation\Page\PageFactory;

// create container
$container = new Navigation();

// add page by giving a page instance
$container->addPage(new Class implements PageInterface{});

// add page by giving a page instance using the PageFactory
$container->addPage((new PageFactory())->factory([
    'uri' => 'http://www.example.com/',
]));

$pages = [
    new Class implements PageInterface{},
    new Class implements PageInterface{},
];

// add two pages
$container->addPages($pages);

// remove existing pages and add the given pages
$container->setPages($pages);

移除页面

可以使用removePage()removePages()方法移除页面。removePage()接受一个页面实例或一个整数。整数参数对应于页面的order。使用removePages()将移除容器中的所有页面。

use Mimmi20\Mezzio\Navigation\Navigation;

$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages([
    [
        'label'  => 'Page 1',
        'action' => 'page1',
    ],
    [
        'label'  => 'Page 2',
        'action' => 'page2',
        'order'  => 200,
    ],
    [
        'label'  => 'Page 3',
        'action' => 'page3',
    ],
]);
$container = $serviceLocator->get(Navigation::class);

// remove page by implicit page order
$container->removePage(0);      // removes Page 1

// remove page by instance
$page3 = $container->findOneByAction('page3');
$container->removePage($page3); // removes Page 3

// remove page by explicit page order
$container->removePage(200);    // removes Page 2

// remove all pages
$container->removePages();      // removes all pages

递归移除页面

可以通过removePage()方法的第二个参数递归地移除页面,该参数期望一个布尔值。

use Mimmi20\Mezzio\Navigation\Navigation;

$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages(
    [
        [
            'label' => 'Page 1',
            'route' => 'page1',
            'pages' => [
                [
                    'label' => 'Page 1.1',
                    'route' => 'page1/page1-1',
                    'pages' => [
                        [
                            'label' => 'Page 1.1.1',
                            'route' => 'page1/page1-1/page1-1-1',
                        ],
                    ],
                ],
            ],
        ],
    ]
);
$container = $serviceLocator->get(Navigation::class);

// Removes Page 1.1.1
$container->removePage(
    $container->findOneBy('route', 'page1/page1-1/page1-1-1'),
    true
);

查找页面

容器有两个查找方法用于检索页面。它们都会递归地搜索容器,检查具有与提供的值匹配的属性。

  • findOneBy($property, $value) : PageInterface|null:返回第一个符合标准的页面,如果没有找到则返回null
  • findAllBy($property, $value) : array:返回一个符合标准的页面实例数组。

Laminas Navigation不同,不可用findBy

查找方法还可以通过在findByfindOneByfindAllBy后附加属性名来神奇地使用。例如,findOneByLabel('Home')将返回第一个标签为'Home'的匹配页面。

其他组合包括findByLabel(...)findOneByTitle(...)findAllByController(...)等。查找方法也适用于自定义属性,如findByFoo('bar')

use Mimmi20\Mezzio\Navigation\Navigation;

$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages([
    [
        'label' => 'Page 1',
        'uri'   => 'page-1',
        'foo'   => 'bar',
        'pages' => [
            [
                'label' => 'Page 1.1',
                'uri'   => 'page-1.1',
                'foo'   => 'bar',
            ],
            [
                'label' => 'Page 1.2',
                'uri'   => 'page-1.2',
                'class' => 'my-class',
            ],
            [
                'type'   => 'uri',
                'label'  => 'Page 1.3',
                'uri'    => 'page-1.3',
                'action' => 'about',
            ],
        ],
    ],
    [
        'label'      => 'Page 2',
        'id'         => 'page_2_and_3',
        'class'      => 'my-class',
        'module'     => 'page2',
        'controller' => 'index',
        'action'     => 'page1',
    ],
    [
        'label'      => 'Page 3',
        'id'         => 'page_2_and_3',
        'module'     => 'page3',
        'controller' => 'index',
    ],
]);
$container = $serviceLocator->get(Navigation::class);

// The 'id' is not required to be unique, but be aware that
// having two pages with the same id will render the same id attribute
// in menus and breadcrumbs.

// Returns "Page 2":
$found = $container->findBy('id', 'page_2_and_3');

// Returns "Page 2":
$found = $container->findOneBy('id', 'page_2_and_3');

// Returns "Page 2":
$found = $container->findOneById('page_2_and_3');

// Returns "Page 2" AND "Page 3":
$found = $container->findAllById('page_2_and_3');

// Find all pages matching the CSS class "my-class":
// Returns "Page 1.2" and "Page 2":
$found = $container->findAllBy('class', 'my-class');
$found = $container->findAllByClass('my-class');

// Find first page matching CSS class "my-class":
// Returns "Page 1.2":
$found = $container->findOneByClass('my-class');

// Find all pages matching the CSS class "non-existent":
// Returns an empty array.
$found = $container->findAllByClass('non-existent');

// Find first page matching the CSS class "non-existent":
// Returns null.
$found = $container->findOneByClass('non-existent');

// Find all pages with custom property 'foo' = 'bar':
// Returns "Page 1" and "Page 1.1":
$found = $container->findAllBy('foo', 'bar');

// To achieve the same magically, 'foo' must be in lowercase.
// This is because 'foo' is a custom property, and thus the
// property name is not normalized to 'Foo':
$found = $container->findAllByfoo('bar');

// Find all with controller = 'index':
// Returns "Page 2" and "Page 3":
$found = $container->findAllByController('index');

迭代容器

Mezzio\Navigation\ContainerInterface扩展了RecursiveIterator。要递归迭代容器,请使用RecursiveIteratorIterator类。

use RecursiveIteratorIterator;
use Mimmi20\Mezzio\Navigation\Navigation;

/*
 * Create a container from an array
 */
$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages([
    [
        'label' => 'Page 1',
        'uri'   => '#',
    ],
    [
        'label' => 'Page 2',
        'uri'   => '#',
        'pages' => [
            [
                'label' => 'Page 2.1',
                'uri'   => '#',
            ],
            [
                'label' => 'Page 2.2',
                'uri'   => '#',
            ],
        ],
    ],
    [
        'label' => 'Page 3',
        'uri'   => '#',
    ],
]);
$container = $serviceLocator->get(Navigation::class);

// Iterate flat using regular foreach:
// Output: Page 1, Page 2, Page 3
foreach ($container as $page) {
    echo $page->label;
}

// Iterate recursively using RecursiveIteratorIterator
$it = new RecursiveIteratorIterator(
    $container,
    RecursiveIteratorIterator::SELF_FIRST
);

// Output: Page 1, Page 2, Page 2.1, Page 2.2, Page 3
foreach ($it as $page) {
    echo $page->label;
}

其他操作

hasPage

hasPage(PageInterface $page) : bool

检查容器是否具有指定的页面。

hasPages

hasPages() : bool

检查容器中是否有页面,等效于count($container) > 0

toArray

toArray() : array

将容器及其中的页面转换为(嵌套)数组。这在序列化和调试时可能很有用。

use Mimmi20\Mezzio\Navigation\Navigation;

$navigationConfig = $serviceLocator->get(NavigationConfigInterface::class);
$navigationConfig->setPages([
    [
        'label' => 'Page 1',
        'uri'   => '#',
    ],
    [
        'label' => 'Page 2',
        'uri'   => '#',
        'pages' => [
            [
                'label' => 'Page 2.1',
                'uri'   => '#',
            ],
            [
                'label' => 'Page 2.2',
                'uri'   => '#',
            ],
        ],
    ],
]);
$container = $serviceLocator->get(Navigation::class);

var_dump($container->toArray());

/* Output:
array(2) {
  [0]=> array(15) {
    ["label"]=> string(6) "Page 1"
    ["id"]=> NULL
    ["class"]=> NULL
    ["title"]=> NULL
    ["target"]=> NULL
    ["rel"]=> array(0) {
    }
    ["rev"]=> array(0) {
    }
    ["order"]=> NULL
    ["resource"]=> NULL
    ["privilege"]=> NULL
    ["active"]=> bool(false)
    ["visible"]=> bool(true)
    ["type"]=> string(23) "Mezzio\Navigation\Page\Uri"
    ["pages"]=> array(0) {
    }
    ["uri"]=> string(1) "#"
  }
  [1]=> array(15) {
    ["label"]=> string(6) "Page 2"
    ["id"]=> NULL
    ["class"]=> NULL
    ["title"]=> NULL
    ["target"]=> NULL
    ["rel"]=> array(0) {
    }
    ["rev"]=> array(0) {
    }
    ["order"]=> NULL
    ["resource"]=> NULL
    ["privilege"]=> NULL
    ["active"]=> bool(false)
    ["visible"]=> bool(true)
    ["type"]=> string(23) "Mezzio\Navigation\Page\Uri"
    ["pages"]=> array(2) {
      [0]=> array(15) {
        ["label"]=> string(8) "Page 2.1"
        ["id"]=> NULL
        ["class"]=> NULL
        ["title"]=> NULL
        ["target"]=> NULL
        ["rel"]=> array(0) {
        }
        ["rev"]=> array(0) {
        }
        ["order"]=> NULL
        ["resource"]=> NULL
        ["privilege"]=> NULL
        ["active"]=> bool(false)
        ["visible"]=> bool(true)
        ["type"]=> string(23) "Mezzio\Navigation\Page\Uri"
        ["pages"]=> array(0) {
        }
        ["uri"]=> string(1) "#"
      }
      [1]=>
      array(15) {
        ["label"]=> string(8) "Page 2.2"
        ["id"]=> NULL
        ["class"]=> NULL
        ["title"]=> NULL
        ["target"]=> NULL
        ["rel"]=> array(0) {
        }
        ["rev"]=> array(0) {
        }
        ["order"]=> NULL
        ["resource"]=> NULL
        ["privilege"]=> NULL
        ["active"]=> bool(false)
        ["visible"]=> bool(true)
        ["type"]=> string(23) "Mezzio\Navigation\Page\Uri"
        ["pages"]=> array(0) {
        }
        ["uri"]=> string(1) "#"
      }
    }
    ["uri"]=> string(1) "#"
  }
}
*/

页面

mezzio-navigation提供了两种页面类型

路由页面链接到站内网页,并使用路由参数(routeparams)定义。URI页面通过单个属性uri定义,这为您提供了完全的灵活性来链接站外页面或对生成的链接进行其他操作(例如,将URI转换为<a href="#">foo<a>)。

路由页面替换了来自Laminas Navigation的MVC页面。

Laminas Navigation不同,不支持controlleraction选项。

常见页面功能

所有页面类都必须扩展Mezzio\Navigation\Page\PageInterface,因此将共享一组共同的功能和属性。最值得注意的是,它们共享下表中的选项和相同的初始化过程。

选项键映射到set*()方法。这意味着选项order映射到方法setOrder(),而reset_params映射到方法setResetParams()。如果没有为选项提供setter方法,它将作为页面的自定义属性设置。

在“创建自定义页面类型”部分阅读更多关于扩展 Mezzio\Navigation\Page\PageInterface 的内容。

常见页面选项

自定义属性

所有页面都支持使用魔术方法 __set($name, $value)__get($name)__isset($name)__unset($name) 来设置和检索自定义属性。自定义属性可以具有任何值,并且会被包含在从 $page->toArray() 返回的数组中,这意味着即使页面包含不在页面类中本地的属性,页面也可以成功地进行序列化和反序列化。

可以使用 $page->set($name, $value)$page->get($name) 或通过使用魔术方法来设置原生和自定义属性。

以下示例演示了自定义属性

$page = new Mimmi20\Mezzio\Navigation\Page\Route();
$page->foo     = 'bar';
$page->meaning = 42;

echo $page->foo;

if ($page->meaning != 42) {
    // action should be taken
}

路由页面

可以使用路由页面与路由一起使用。如果一个页面有路由,这个路由将在 getHref() 中使用来生成 href 属性,并且 isActive() 方法将比较 Mezzio\Router\RouteResult 参数与页面的参数以确定页面是否活跃。

useRouteMatch 标志

如果你想在生成链接时重用任何匹配的路由参数,可以通过 useRouteMatch 标志来实现。这在创建包含当前选定语言或区域设置的初始段落的段路由时特别有用,因为它确保生成的所有链接都包含匹配的值。

路由页面选项

isActive() 决定页面是否活跃

此示例演示了路由页面通过使用路由匹配对象中找到的参数来确定是否活跃。

use Mimmi20\Mezzio\Navigation\Page;

/**
 * Dispatched request:
 * - route:     index
 */
$page1 = new Page\Route([
    'route' => 'index',
]);

$page2 = new Page\Route([
    'route' => 'edit',
]);

$page1->isActive(); // returns true
$page2->isActive(); // returns false

/**
 * Dispatched request:
 * - route:      edit
 * - id:         1337
 */
$page = new Page\Route([
    'route'  => 'edit',
    'params' => ['id' => 1337],
]);

// returns true, because request has the same route
$page->isActive();

/**
 * Dispatched request:
 * - route:     edit
 */
$page = new Page\Route([
    'route'  => 'edit',
    'params' => ['id' => null],
]);

// returns false, because page requires the id param to be set in the request
$page->isActive(); // returns false

URI 页面

类型为 Mezzio\Navigation\Page\Uri 的页面可以用于链接到其他域名或站点的页面,或者实现页面的自定义逻辑。除了常见的页面选项外,URI 页面只需要一个额外的选项,即 uri。当调用 $page->getHref() 时将返回 uri,它可以是 stringnull

不自动确定活跃状态

Mezzio\Navigation\Page\Uri 不会尝试在调用 $page->isActive() 时确定它是否应该活跃;它只是返回当前设置的内容。为了使 URI 页面活跃,必须手动调用 $page->setActive() 或在实例化时指定 active 作为页面选项。

URI 页面选项

创建自定义页面类型

当实现 Mezzio\Navigation\Page\PageInterface 并使用 Mezzio\Navigation\Page\PageTrait 时,通常不需要重写构造函数或 setOptions() 方法。页面构造函数接受单个参数,一个 iterable,然后将其传递给 setOptions()。该方法将根据提供的选项调用相应的 set*() 方法,这些方法再将选项映射到原生或自定义属性。如果提供了 internal_id 选项,该方法将首先寻找名为 setInternalId() 的方法,如果该方法存在,则将选项传递给该方法。如果该方法不存在,选项将被设置为页面的自定义属性,并通过 $internalId = $page->internal_id;$internalId = $page->get('internal_id'); 访问。

基本自定义页面示例

自定义页面类需要实现的方法只有 getHref()

namespace My;

use Mimmi20\Mezzio\Navigation\Page\PageInterface;
use Mimmi20\Mezzio\Navigation\Page\PageTrait;

class Page implements PageInterface
{
    use PageTrait;
    public function getHref(): string
    {
        return 'something-completely-different';
    }
}

具有属性的自定义页面

在向扩展的页面添加属性时,不需要重写或修改 setOptions()

namespace My\Navigation;

use Mimmi20\Mezzio\Navigation\Page\PageInterface;

class Page implements PageInterface
{
    protected $foo;
    protected $fooBar;

    public function setFoo($foo)
    {
        $this->foo = $foo;
    }

    public function getFoo()
    {
        return $this->foo;
    }

    public function setFooBar($fooBar)
    {
        $this->fooBar = $fooBar;
    }

    public function getFooBar()
    {
        return $this->fooBar;
    }

    public function getHref(): string
    {
        return sprintf('%s/%s', $this->foo, $this->fooBar);
    }
}

// Instantiation:
$page = new Page([
    'label'   => 'Property names are mapped to setters',
    'foo'     => 'bar',
    'foo_bar' => 'baz',
]);

使用页面工厂创建页面

所有页面(包括自定义类),都可以使用页面工厂Mezzio\Navigation\Page\PageFactory创建。工厂接受一个iterable选项集合。选项中的每个键对应一个页面选项,如前所述。如果提供了uri选项而没有提供路由选项(route),将创建一个URI页面。如果提供了任何路由选项,将创建一个路由页面。

如果提供了type,工厂将假设该值是要创建的类的名称。如果值是routeuri,则分别创建路由或URI页面。

使用页面工厂创建路由页面

use Mimmi20\Mezzio\Navigation\Page\PageInterface;

// Route page, as "route" is defined
$page = (new PageFactory())->factory([
    'label' => 'Home',
    'route' => 'home',
]);

// Route page, as "type" is "route"
$page = (new PageFactory())->factory([
    'type'   => 'route',
    'label'  => 'My Route page',
]);

使用页面工厂创建URI页面

use Mimmi20\Mezzio\Navigation\Page\PageInterface;

// URI page, as "uri" is present, with now MVC options
$page = (new PageFactory())->factory([
    'label' => 'My URI page',
    'uri'   => 'http://www.example.com/',
]);

// URI page, as "uri" is present
$page = (new PageFactory())->factory([
    'label'  => 'Search',
    'uri'    => 'http://www.example.com/search',
    'active' => true,
]);

// URI page, as "uri" is present
$page = (new PageFactory())->factory([
    'label' => 'My URI page',
    'uri'   => '#',
]);

// URI page, as "type" is "uri"
$page = (new PageFactory())->factory([
    'type'  => 'uri',
    'label' => 'My URI page',
]);

使用页面工厂创建自定义页面类型

要使用工厂创建自定义页面类型,请使用选项type指定要实例化的类名。

namespace My\Navigation;

use Mimmi20\Mezzio\Navigation\Page\PageInterface;

class Page extends PageInterface
{
    protected $fooBar = 'ok';

    public function setFooBar($fooBar)
    {
        $this->fooBar = $fooBar;
    }
}

// Creates Page instance, as "type" refers to its class.
$page = (new PageFactory())->factory([
    'type'    => Page::class,
    'label'   => 'My custom page',
    'foo_bar' => 'foo bar',
]);

快速入门

在mezzio应用程序中使用

开始使用mezzio-navigation的最快方法是

  • 将mezzio-navigation注册为模块。
  • 在您的应用程序配置中,在顶级navigation键下定义导航容器配置。
  • 在视图脚本中使用导航视图助手渲染您的容器。

将mezzio-navigation注册为模块

编辑应用程序配置文件config/application.config.php

<?php
return [
    'modules' => [
        'Mezzio\Router',
        'Mezzio\Navigation', // <-- Add this line
        // ...
    ],
];

将NavigationMiddleware添加到管道

<?php
return [
    'middleware' => [
        'Mezzio\Authentication\AuthenticationMiddleware', // <-- not required
        'Mimmi20\Mezzio\Navigation\NavigationMiddleware', // <-- Add this line
        // ...
    ],
];

如果您需要在布局中使用导航,并且布局也用于404页面,您必须在路由之前将中间件添加到管道中。

    $app->pipe(\Mimmi20\Mezzio\Navigation\NavigationMiddleware::class);

    // Register the routing middleware in the middleware pipeline.
    // This middleware registers the Mezzio\Router\RouteResult request attribute.
    $app->pipe(RouteMiddleware::class);

导航容器配置

将容器定义添加到您的配置文件中,例如config/autoload/global.php

<?php
return [
    // ...

    'navigation' => [
        'default' => [
            [
                'label' => 'Home',
                'route' => 'home',
            ],
            [
                'label' => 'Page #1',
                'route' => 'page-1',
                'pages' => [
                    [
                        'label' => 'Child #1',
                        'route' => 'page-1-child',
                    ],
                ],
            ],
            [
                'label' => 'Page #2',
                'route' => 'page-2',
            ],
        ],
    ],
    // ...
];

渲染导航

Mezzio支持多种视图渲染。

有一个渲染器可用

许可证

此包使用MIT许可证授权。

请参阅LICENSE.md