data-dog/pager-bundle

symfony2和doctrine orm的分页器包,支持通过过滤器和排序器进行自定义

安装次数: 88,343

依赖者: 10

建议者: 0

安全性: 0

星级: 11

关注者: 17

分支: 17

开放问题: 3

类型:symfony-bundle

v0.2.14 2021-10-04 10:02 UTC

README

这个分页器在以下方面有所不同

  • 它只有一个通用的类,大约有300行注释代码。其余的源代码都是特定于symfony2框架、twig辅助工具和模板的。
  • 它允许创建自定义分页过滤器 - 搜索、选择..并根据特定用例修改数据库查询。
  • 它还以传统方式处理排序。
  • 它非常小巧,可以经过修改在其他框架中重用。
  • 它只能分页Doctrine2 ORM QueryBuilder。为了保持库小巧和向后兼容,不会支持其他内容。如果您需要自定义,请分叉或复制源代码。
  • 因为URL查询参数是常数,所以可能每个请求只有一个分页。

演示

要查看功能,最好的方式是查看实际演示。只需克隆包并运行

make

访问http://localhost:8000,查看带有自定义过滤器和排序器的分页假项目。

演示应用程序源代码位于example目录中,它是一个基本的symfony应用程序。

安装

首先,使用composer安装

composer require data-dog/pager-bundle

然后,在您的AppKernel包中添加它。

用法

在您的控制器中的通用用法示例

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\QueryBuilder;
use DataDog\PagerBundle\Pagination;

class ProjectController extends Controller
{
    /**
     * @Method("GET")
     * @Template
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $qb = $this->getDoctrine()->getManager()->getRepository("AppBundle:Project")
            ->createQueryBuilder('p')
            ->addSelect('l')
            ->innerJoin('p.language', 'l');

        $projects = new Pagination($qb, $request);
        return compact('projects');
    }
}

您需要用doctrine查询构建器和请求构建分页。分页对象像数组一样工作,因此您可以将其传递给视图并遍历分页项。

视图

<table class="table table-hover">
<thead>
  <tr>
    <th>#</th>
    <th>{{ sorter_link(projects, "p.code", "Code") }}</th>
    <th>{{ sorter_link(projects, "p.name", "Name") }}</th>
    <th>{{ sorter_link(projects, "p.hoursSpent", "Hours Spent") }}</th>
    <th>{{ sorter_link(projects, "l.code", "Language") }}</th>
  </tr>
</thead>
<tbody>
  {% for project in projects %}
    <tr>
      <td>{{ project.id }}</td>
      <td>{{ project.code }}</td>
      <td>{{ project.name }}</td>
      {% if project.isOverDeadline %}
        <td class="text-danger">{{ project.hoursSpent }}</td>
      {% else %}
        <td class="text-success">{{ project.hoursSpent }}</td>
      {% endif %}
      <td>{{ project.language.code }}</td>
    </tr>
  {% endfor %}
</tbody>
</table>

<div class="panel-footer">
{{ pagination(projects) }}
</div>

这里使用了twig辅助函数

  • sorter_link - 使用twig模板生成带有排序顺序类等的链接。
  • pagination - 创建用于导航页面的分页HTML代码。

这些模板可以通过标准symfony方式进行修改,请参阅配置部分。

过滤器

为了以不同的方式过滤分页结果,您可以扩展代码。在控制器中,提供一些分页选项。

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\QueryBuilder;
use DataDog\PagerBundle\Pagination;

class ProjectController extends Controller
{
    /**
     * Our filter handler function, which allows us to
     * modify the query builder specifically for our filter option
     */
    public function projectFilters(QueryBuilder $qb, $key, $val)
    {
        switch ($key) {
        case 'p.name':
            if ($val) {
                $qb->andWhere($qb->expr()->like('p.name', ':name'));
                $qb->setParameter('name', "%$val%");
            }
            break;
        case 'p.hoursSpent':
            switch ($val) {
            case 'lessThan10':
                $qb->andWhere($qb->expr()->lt('p.hoursSpent', 10));
                break;
            case 'upTo20':
                $qb->andWhere($qb->expr()->lte('p.hoursSpent', 20));
                break;
            case 'moreThan2weeks':
                $qb->andWhere($qb->expr()->gte('p.hoursSpent', 80));
                break;
            case 'overDeadline':
                $qb->andWhere($qb->expr()->gt('p.hoursSpent', 'p.deadline'));
                break;
            }
            break;
        case 'l.code':
            $qb->andWhere($qb->expr()->eq('l.code', ':code'));
            $qb->setParameter('code', $val);
            break;
        default:
            // Do not allow filtering by anything else
            throw new \Exception("filter not allowed");
            // You can also enable automatic filtering
            //$paramName = preg_replace('/[^A-z]/', '_', $key);
            //$qb->andWhere($qb->expr()->eq($key, ":$paramName"));
            //$qb->setParameter($paramName, $val);
        }
    }

    /**
     * @Method("GET")
     * @Template
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $qb = $this->getDoctrine()->getManager()->getRepository("AppBundle:Project")
            ->createQueryBuilder('p')
            ->addSelect('l')
            ->innerJoin('p.language', 'l');

        $options = [
            'sorters' => ['l.code' => 'ASC'], // sorted by language code by default
            'filters' => ['p.hoursSpent' => 'overDeadline'], // we can apply a filter option by default
            'applyFilter' => [$this, 'projectFilters'], // custom filter handling
        ];

        // our language filter options, the key will be used in where statemt by default
        // and the value as title.
        // The $filterAny key is a placeholder to skip the filter, so the any value could be ok.
        $languages = [
            Pagination::$filterAny => 'Any',
            'php' => 'PHP',
            'hs' => 'Haskell',
            'go' => 'Golang',
        ];

        // our spent time filter options, has specific keys so we know how to customize
        $spentTimeGroups = [
            Pagination::$filterAny => 'Any',
            'lessThan10' => 'Less than 10h',
            'upTo20' => 'Up to 20h',
            'moreThan2weeks' => 'More than 2weeks',
            'overDeadline' => 'Over deadline',
        ];

        $projects = new Pagination($qb, $request, $options);
        return compact('projects', 'languages', 'spentTimeGroups');
    }
}

现在我们添加了三个过滤器

$languages$spentTimeGroups将用作filter_select选项。语言选项很简单,它们直接引用值,因此不需要修改WHERE语句。但是花费时间组是自定义的,因此我们使用自定义选项。在这种情况下,我们需要将applyFilter选项设置为可调用的,以便QueryBuilder可以根据我们的自定义选项相应地修改。

注意:如果您管理自定义过滤,请确保使用参数或使用$qb->expr()->literal("string")以防止SQL注入。如果您有自定义过滤器处理程序,您必须管理所有过滤器,默认处理程序将不会激活。

所以视图是如何改变的

<table class="table table-hover">
<thead>
  <tr>
    <th>#</th>
    <th>{{ sorter_link(projects, "p.code", "Code") }}</th>
    <th>{{ sorter_link(projects, "p.name", "Name") }}</th>
    <th>{{ sorter_link(projects, "p.hoursSpent", "Hours Spent") }}</th>
    <th>{{ sorter_link(projects, "l.code", "Language") }}</th>
  </tr>

  <tr role="row" class="filter">
    <td></td>
    <td></td>
    <td>{{ filter_search(projects, "p.name") }}</td>
    <td>{{ filter_select(projects, "p.hoursSpent", spentTimeGroups) }}</td>
    <td>{{ filter_select(projects, "l.code", languages) }}</td>
  </tr>
</thead>
<tbody>
  {% for project in projects %}
    <tr>
      <td>{{ project.id }}</td>
      <td>{{ project.code }}</td>
      <td>{{ project.name }}</td>
      {% if project.isOverDeadline %}
        <td class="text-danger">{{ project.hoursSpent }}</td>
      {% else %}
        <td class="text-success">{{ project.hoursSpent }}</td>
      {% endif %}
      <td>{{ project.language.code }}</td>
    </tr>
  {% endfor %}
</tbody>
</table>

<div class="panel-footer">
{{ pagination(projects) }}
</div>

我们使用了两个新的twig函数来处理过滤器

  • filter_search - 用于按名称搜索项目。
  • filter_select - 用于基本选项过滤器。

这些函数正在渲染我们的过滤器的twig模板。

链接

如果您需要创建链接并保持应用搜索过滤器和排序,请使用 $pagination->query() 函数获取所有必要的 URL 参数,并将其与您的链接参数合并。

示例代码处理了项目的 启用禁用 切换操作,并维护所有分页属性。

配置

通用使用情况下无需进行配置。但为了自定义分页,可能需要在 app.php 中设置全局选项,例如

<?php

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
use DataDog\PagerBundle\Pagination;

$loader = require_once __DIR__.'/../app/autoload.php';
Debug::enable();

Pagination::$defaults = array_merge(Pagination::$defaults, [
    'limit' => 15,
    'range' => 9,
]);
Pagination::$maxPerPage = 200;

require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

模板

默认的过滤器和分页模板基于 twitter bootstrapfontawesome。您可以像其他任何包模板一样进行自定义,例如

  • 分页 - app/Resources/DataDogPagerBundle/views/pagination.html.twig
  • 搜索过滤器 - app/Resources/DataDogPagerBundle/views/filters/search.html.twig

扩展更多过滤器

最佳的自定义过滤器方法是扩展 twig 扩展或创建一个新的扩展。如果我们提供太多选项,最终可能会让人感到困惑,所以我们添加了一些基本模板。在您的包 services.yml 中更新参数

parameters:
  datadog.pager.twig_extension.class: AppBundle\Twig\PaginationExtension

然后创建一个类

<?php

namespace AppBundle\Twig;

use DataDog\PagerBundle\Twig\PaginationExtension as Base;
use DataDog\PagerBundle\Pagination;

class PaginationExtension extends Base
{
    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        $defaults = [
            'is_safe' => ['html'],
            'needs_environment' => true,
        ];

        $funcs = parent::getFunctions();
        $funcs['filter_search_placeholder'] = new \Twig_Function_Method($this, 'filterSearchPlaceholder', $defaults);

        return $funcs;
    }

    public function filterSearchPlaceholder(\Twig_Environment $twig, Pagination $pagination, $key, $placeholder)
    {
        $value = isset($pagination->query()['filters'][$key]) ? $pagination->query()['filters'][$key] : '';
        return $twig->render('AppBundle::filters/search_placeholder.html.twig', compact('key', 'pagination', 'value', 'placeholder'));
    }
}

最后,根据您的需求复制并修改模板

截图

Screenshot

Screenshot

许可证

分页器是免费的,并且根据 MIT 许可证 授权。