Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ttskch/TtskchPaginatorBundle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

95 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TtskchPaginatorBundle

codecov Latest Stable Version Total Downloads

The most thin, simple and customizable paginator bundle for Symfony.

Features

  • So light weight
  • Well typed (PHPStan level max)
  • Depends on nothing other than Symfony and Twig
  • But also easy to use with Doctrine ORM
  • Of course can paginate everything
  • Customizable twig-templated views
  • Very easy-to-use sortable link feature
  • Easy to use with your own search form
  • Preset beautiful Bootstrap4/5 theme

Requirements

  • PHP: ^8.0
  • Symfony: ^5.0|^6.0|^7.0

Demo

👉 Live demo is here

You can also see a sample code on demo/ directory.

Installation

$ composer require ttskch/paginator-bundle
// config/bundles.php

return [
    // ...
    Ttskch\PaginatorBundle\TtskchPaginatorBundle::class => ['all' => true],
];

Basic usages

With Doctrine ORM

// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter;
use Ttskch\PaginatorBundle\Criteria\Criteria;
use Ttskch\PaginatorBundle\Paginator;
use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer;

/**
 * @param Paginator<\Traversable<array-key, Foo>, Criteria> $paginator
 */
public function index(FooRepository $fooRepository, Paginator $paginator): Response
{
    $qb = $fooRepository->createQueryBuilder('f');
    $paginator->initialize(new QueryBuilderSlicer($qb), new QueryBuilderCounter($qb), new Criteria('id'));

    return $this->render('index.html.twig', [
        'foos' => $paginator->getSlice(),
    ]);
}
{# index.html.twig #}

<table>
  <thead>
  <tr>
    <th>{{ ttskch_paginator_sortable('id', 'Id') }}</th>
    <th>{{ ttskch_paginator_sortable('name', 'Name') }}</th>
    <th>{{ ttskch_paginator_sortable('email', 'Email') }}</th>
  </tr>
  </thead>
  <tbody>
  {% for foo in foos %}
    <tr>
      <td>{{ foo.id }}</td>
      <td>{{ foo.name }}</td>
      <td>{{ foo.email }}</td>
    </tr>
  {% endfor %}
  </tbody>
</table>

{{ ttskch_paginator_pager() }}

See src/Twig/TtskchPaginatorExtension.php to learn more about twig functions.

Sort with property of joined entity

Just do like as following.

{# index.html.twig #}

{# ... #}

<th>{{ ttskch_paginator_sortable('id', 'Id') }}</th>
<th>{{ ttskch_paginator_sortable('name', 'Name') }}</th>
<th>{{ ttskch_paginator_sortable('email', 'Email') }}</th>
<th>{{ ttskch_paginator_sortable('bar.id', 'Bar') }}</th>
<th>{{ ttskch_paginator_sortable('bar.baz.id', 'Baz') }}</th>

{# ... #}

With array

// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Counter\ArrayCounter;
use Ttskch\PaginatorBundle\Criteria\Criteria;
use Ttskch\PaginatorBundle\Paginator;
use Ttskch\PaginatorBundle\Slicer\ArraySlicer;

/**
 * @param Paginator<array<array{id: int, name: string, email: string}>, Criteria> $paginator
 */
public function index(Paginator $paginator): Response
{
    $array = [
        ['id' => 1, 'name' => 'Tommy Yount', 'email' => '[email protected]'],
        ['id' => 2, 'name' => 'Hye Panter', 'email' => '[email protected]'],
        ['id' => 3, 'name' => 'Vi Yohe', 'email' => '[email protected]'],
        ['id' => 4, 'name' => 'Keva Bandy', 'email' => '[email protected]'],
        ['id' => 5, 'name' => 'Hannelore Corning', 'email' => '[email protected]'],
        ['id' => 6, 'name' => 'Delorse Whitcher', 'email' => '[email protected]'],
        ['id' => 7, 'name' => 'Katharyn Marrinan', 'email' => '[email protected]'],
        ['id' => 8, 'name' => 'Jeannine Tope', 'email' => '[email protected]'],
        ['id' => 9, 'name' => 'Jamila Braggs', 'email' => '[email protected]'],
        ['id' => 10, 'name' => 'Eden Cunniff', 'email' => '[email protected]'],
        // ...
        ['id' => 299, 'name' => 'Deshawn Kennedy', 'email' => '[email protected]'],
        ['id' => 300, 'name' => 'Elenore Evens', 'email' => '[email protected]'],
    ];

    $paginator->initialize(
        new ArraySlicer($array),
        new ArrayCounter($array),
        new Criteria('id'),
    );

    return $this->render('index.html.twig', [
        'foos' => $paginator->getSlice(),
    ]);
}

With something other data

Implement slicer and counter by yourself like as following.

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Counter\CallbackCounter;
use Ttskch\PaginatorBundle\Criteria\Criteria;
use Ttskch\PaginatorBundle\Paginator;
use Ttskch\PaginatorBundle\Slicer\CallbackSlicer;

/**
 * @param Paginator<TypeOfYourOwnSlice>, Criteria> $paginator
 */
public function index(Paginator $paginator): Response
{
    $yourOwnData = /* ... */;

    $paginator->initialize(
        new CallbackSlicer(function (Criteria $criteria) use ($yourOwnData) {
            /* ... */
            return $yourOwnSlice;
        }),
        new CallbackCounter(function (Criteria $criteria) use ($yourOwnData) {
            /* ... */
            return $totalItemsCount;
        }),
        new Criteria('default_sort_key'),
    );

    return $this->render('index.html.twig', [
        'yourOwnSlice' => $paginator->getSlice(),
    ]);
}

Configuring

$ bin/console config:dump-reference ttskch_paginator
# Default configuration for extension with alias: "ttskch_paginator"
ttskch_paginator:
    page:
        name:                 page
        range:                5
    limit:
        name:                 limit
        default:              10
    sort:
        key:
            name:                 sort
        direction:
            name:                 direction

            # "asc" or "desc"
            default:              asc
    template:
        pager:                '@TtskchPaginator/pager/default.html.twig'
        sortable:             '@TtskchPaginator/sortable/default.html.twig'

Customizing views

Using preset Bootstrap4/5 theme

Just configure bundle like below.

# config/packages/ttskch_paginator.yaml

ttskch_paginator:
  template:
    pager: '@TtskchPaginator/pager/bootstrap5.html.twig'
#   pager: '@TtskchPaginator/pager/bootstrap4.html.twig'

Using your own theme

Create your own templates and configure bundle like below.

# config/packages/ttskch_paginator.yaml

ttskch_paginator:
  template:
    pager: 'your/own/pager.html.twig'
    sortable: 'your/own/sortable.html.twig'

Using with search form

// FooCriteria.php

use Ttskch\PaginatorBundle\Criteria\AbstractCriteria;

class FooCriteria extends AbstractCriteria
{
    public ?string $query = null;

    public function __construct(string $sort)
    {
        parent::__construct($sort);
    }

    public function getFormTypeClass(): string
    {
        return FooSearchType::class;
    }
}
// FooSearchType.php

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Ttskch\PaginatorBundle\Form\CriteriaType;

class FooSearchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('query', SearchType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => FooCriteria::class,
            // if your app depends on symfony/security-csrf adding below is recommended
            // 'csrf_protection' => false,
        ]);
    }

    public function getParent(): string
    {
        return CriteriaType::class;
    }
}
// FooRepository.php

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter;
use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer;

/**
 * @extends ServiceEntityRepository<Foo>
 */
class FooRepository extends ServiceEntityRepository
{
    // ...

    /**
     * @return \Traversable<array-key, Foo>
     */
    public function sliceByCriteria(FooCriteria $criteria): \Traversable
    {
        $qb = $this->createQueryBuilderFromCriteria($criteria);
        $slicer = new QueryBuilderSlicer($qb);
    
        return $slicer->slice($criteria);
    }
    
    public function countByCriteria(FooCriteria $criteria): int
    {
        $qb = $this->createQueryBuilderFromCriteria($criteria);
        $counter = new QueryBuilderCounter($qb);
    
        return $counter->count($criteria);
    }
    
    private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder
    {
        return $this->createQueryBuilder('f')
            ->orWhere('f.name like :query')
            ->orWhere('f.email like :query')
            ->setParameter('query', '%'.str_replace('%', '\%', $criteria->query).'%')
        ;
    }
}
// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;

/**
 * @param Paginator<\Traversable<array-key, Foo>, FooCriteria> $paginator
 */
public function index(FooRepository $fooRepository, Paginator $paginator): Response
{
    $paginator->initialize(
        $fooRepository->sliceByCriteria(...),
        $fooRepository->countByCriteria(...),
        // or if PHP < 8.1
        // \Closure::fromCallable([$fooRepository, 'sliceByCriteria']),
        // \Closure::fromCallable([$fooRepository, 'countByCriteria']),
        new FooCriteria('id'),
    );

    return $this->render('index.html.twig', [
        'foos' => $paginator->getSlice(),
        'form' => $paginator->getForm()->createView(),
    ]);
}
{# index.html.twig #}

{{ form(form, {action: path('foo_index'), method: 'get'}) }}

<table>
    <thead>
    <tr>
        <th>{{ ttskch_paginator_sortable('id', 'Id') }}</th>
        <th>{{ ttskch_paginator_sortable('name', 'Name') }}</th>
        <th>{{ ttskch_paginator_sortable('email', 'Email') }}</th>
    </tr>
    </thead>
    <tbody>
    {% for foo in foos %}
        <tr>
            <td>{{ foo.id }}</td>
            <td>{{ foo.name }}</td>
            <td>{{ foo.email }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

{{ ttskch_paginator_pager() }}

Using with joined query

// FooRepository.php

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter;
use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer;

/**
 * @extends ServiceEntityRepository<Foo>
 */
class FooRepository extends ServiceEntityRepository
{
    // ...

    /**
     * @return \Traversable<array-key, Foo>
     */
    public function sliceByCriteria(FooCriteria $criteria): \Traversable
    {
        $qb = $this->createQueryBuilderFromCriteria($criteria);
        $slicer = new QueryBuilderSlicer($qb, alreadyJoined: true); // **PAY ATTENTION HERE**
    
        return $slicer->slice($criteria);
    }
    
    public function countByCriteria(FooCriteria $criteria): int
    {
        $qb = $this->createQueryBuilderFromCriteria($criteria);
        $counter = new QueryBuilderCounter($qb);
    
        return $counter->count($criteria);
    }
    
    private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder
    {
        return $this->createQueryBuilder('f')
            ->leftJoin('f.bar', 'bar')
            ->leftJoin('bar.baz', 'baz')
            ->orWhere('f.name like :query')
            ->orWhere('f.email like :query')
            ->orWhere('bar.name like :query')
            ->orWhere('baz.name like :query')
            ->setParameter('query', '%'.str_replace('%', '\%', $criteria->query).'%')
        ;
    }
}
// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;

/**
 * @param Paginator<\Traversable<array-key, Foo>, FooCriteria> $paginator
 */
public function index(FooRepository $fooRepository, Paginator $paginator): Response
{
    $paginator->initialize(
        $fooRepository->sliceByCriteria(...),
        $fooRepository->countByCriteria(...),
        // or if PHP < 8.1
        // \Closure::fromCallable([$fooRepository, 'sliceByCriteria']),
        // \Closure::fromCallable([$fooRepository, 'countByCriteria']),
        new FooCriteria('f.id')
    );

    return $this->render('index.html.twig', [
        'foos' => $paginator->getSlice(),
        'form' => $paginator->getForm()->createView(),
    ]);
}
{# index.html.twig #}

{{ form(form, {action: path('foo_index'), method: 'get'}) }}

<table>
    <thead>
    <tr>
        <th>{{ ttskch_paginator_sortable('f.id', 'Id') }}</th>
        <th>{{ ttskch_paginator_sortable('f.name', 'Name') }}</th>
        <th>{{ ttskch_paginator_sortable('f.email', 'Email') }}</th>
        <th>{{ ttskch_paginator_sortable('bar.name', 'Bar') }}</th>
        <th>{{ ttskch_paginator_sortable('baz.name', 'Baz') }}</th>
    </tr>
    </thead>
    <tbody>
    {% for foo in foos %}
        <tr>
            <td>{{ foo.id }}</td>
            <td>{{ foo.name }}</td>
            <td>{{ foo.email }}</td>
            <td>{{ foo.bar.name }}</td>
            <td>{{ foo.bar.baz.name }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

{{ ttskch_paginator_pager() }}

Getting involved

$ composer install

# Develop...

$ composer tests

About

The most thin, simple and customizable paginator bundle for Symfony.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •