mitchellvanw / laravel-doctrine

NO LONGER MAINTAINED! A Doctrine 2 implementation that melts with Laravel
MIT License
187 stars 74 forks source link

Paginator::make #45

Closed glennjacobs closed 10 years ago

glennjacobs commented 10 years ago

Has anyone done anything nice with this package and Paginator::make?

Just coming to paginate results now and wondered if there was a nice way of doing it.

glennjacobs commented 10 years ago

Quickly knocked up this, any thoughts?

use Doctrine\ORM\Query;
use Paginator as LaravelPaginator;
use Input;

/**
 * Doctrine DQL paginator
 */
class Paginator {

    public function make($entityManager, $dql, $perPage = 10)
    {
        $query = $entityManager->createQuery($dql)
                               ->setFirstResult(Input::get('page') - 1)
                               ->setMaxResults($perPage);

        $paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);

        $results = array();

        foreach ($paginator as $entity)
            $results[] = $entity;

        return LaravelPaginator::make($results, $paginator->count(), $perPage);
    }

}
kirkbushell commented 10 years ago

The method arguments here are nothing like the Laravel Paginator::make. You need to ensure that you use the same DSL. laravel-doctrine is basically meant to be a drop-in replacement for eloquent, and shouldn't change how methods are used.

Also I would argue that the paginator should have no knowledge of the database layer at all. If you look at how Laravel utilises it - it passes the data it needs to the paginator. It also has a paginate() method on the eloquent model which instantiates the Paginator instance with the required information. You should be looking to do the same thing.

The paginate method in Laravel which is apart of eloquent(), should probably be on the repository in doctrine.

Then you'd do the following:

$pagination = $entityRepository->paginate();

Which would return the exact same format as Laravel's paginator.

In short - there's no reason to do any sort of custom paginator instance - it's the database abstraction layer you want to play with.

glennjacobs commented 10 years ago

Thanks for the feedback @kirkbushell, I have indeed ended up putting it on a base repository. You're totally correct. Once I've finished playing I'll share what I did, in case it's of use to others.

glennjacobs commented 10 years ago

Something more along the lines of this perhaps? It uses the Doctrine paginator for query efficiency.

<?php namespace App\Repositories;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Input, Paginator;

class BaseRepository extends EntityRepository
{

    public function paginate($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e";

        return $this->makePagination($dql, $perPage);
    }

    protected function makePagination($dql, $perPage = 15)
    {
        $query = $this->_em->createQuery($dql)
                               ->setFirstResult(Input::get('page',1) - 1)
                               ->setMaxResults($perPage);

        $paginator = new DoctrinePaginator($query);

        $results = array();

        foreach ($paginator as $entity)
            $results[] = $entity;

        return Paginator::make($results, $paginator->count(), $perPage);
    }

}
<?php namespace App\Repositories;

class AssetRepository extends BaseRepository
{

}
kirkbushell commented 10 years ago

Yup. One thing though, your methods are confused...

Why not have paginate create the instance and bind the data to that object, and setup a new method which creates and executes the query? Like this.

public function paginate($perPage = 15)
{
    $resultSet = $this->paginationQuery($perPage);

    $results = array();

    foreach ($resultSet as $entity) {
        $results[] = $entity;
    }

    return Paginator::make($results, $resultSet->count(), $perPage);
}

public function paginationQuery($perPage)
{
    $dql = "SELECT e FROM " . $this->getEntityName() . " e";
    $query = $this->_em->createQuery($dql)
                           ->setFirstResult(Input::get('page',1) - 1)
                           ->setMaxResults($perPage);

    return new DoctrinePaginator($query);
}

This way you have two distinct methods and you know exactly what each does. In your example above, neither of them really had distinct behaviour or set of responsibilities. In my example, paginate() is just dealing with creating a new pagination instance, whereas paginationQuery is dealing with the query construction and execution.

glennjacobs commented 10 years ago

Mainly because I wanted to be able to do this... (obviously very simple examples)

<?php namespace App\Repositories;

class AssetRepository extends BaseRepository
{
    public function paginate($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e ORDER BY e.name";

        return $this->makePagination($dql, $perPage);
    }

    public function anotherPaginate($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e ORDER BY e.handle";

        return $this->makePagination($dql, $perPage);
    }
}
kirkbushell commented 10 years ago

Create a query builder. I do not think creating different pagination methods because you want different ordering clauses would be particularly easy to manage, especially as requirements grow.

Have you tried using a queryBuilder instead of DQL? Bit easier to do things like that.

glennjacobs commented 10 years ago

It wasn't a real world example. Not sure at the moment how the query builder works with the Doctrine paginator... still very new to Doctrine! :smiley:

glennjacobs commented 10 years ago

OK! I think this is better, let me know what you think! You can now send in any query and it will handle the rest. So you can use the query builder or DQL etc.

<?php namespace App\Repositories;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Input, Paginator;

abstract class BaseRepository extends EntityRepository
{

    public function paginateAll($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e";
        $query = $this->_em->createQuery($dql);

        return $this->makePagination($query, $perPage);
    }

    protected function makePagination($query, $perPage = 15)
    {
        $query->setFirstResult(Input::get('page',1) - 1)
            ->setMaxResults($perPage);

        $paginator = new DoctrinePaginator($query);

        $results = array();

        foreach ($paginator as $entity)
            $results[] = $entity;

        return Paginator::make($results, $paginator->count(), $perPage);
    }

}
<?php namespace App\Repositories;

class AssetRepository extends BaseRepository
{
    public function paginateExample($perPage = 15)
    {
        $qb = $this->_em->createQueryBuilder();

        $query = $qb->select('e')
           ->from($this->getEntityName(), 'e')
           ->orderBy('e.name', 'ASC')
           ->getQuery();

        return $this->makePagination($query, $perPage);
    }
}
kirkbushell commented 10 years ago

In our repositories at work, we have a custom builder that manages search criteria (record limit, offset.etc. is also considered search criteria). It basically translates arguments into a doctrine-friendly query builder syntax. I would look to be doing the same thing in your example, especially considering pagination is usually part of some sort of search solution.

glennjacobs commented 10 years ago

Yeah, that would be a good addition. Job for another day now :smiley:

BTW, took a look at where you worked... just down the road from an agency I worked at about 10 years ago doing Macromedia Director work! Man I miss Sydney...

alirezarahmani commented 9 years ago

very nice

alirezarahmani commented 9 years ago

i made it correct

 namespace App\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Input, Paginator;
abstract class BaseRepository extends EntityRepository
{
    public function paginateAll($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e";
        $query = $this->_em->createQuery($dql);
        return $this->makePagination($query, $perPage);
    }
    protected function makePagination($query, $perPage = 15)
    {
        $query->setFirstResult((Input::get('page',1) - 1)*$perPage)
            ->setMaxResults($perPage); // this should multiply to $perPage; because pagination will always have 10 results, but with this only show the remain result.
        $paginator = new DoctrinePaginator($query);
        $results = array();
        foreach ($paginator as $entity)
            $results[] = $entity;
        return Paginator::make($results, $paginator->count(), $perPage);
    }
}
 namespace App\Repositories;
class AssetRepository extends BaseRepository
{
    public function paginateExample($perPage = 15)
    {
        $qb = $this->_em->createQueryBuilder();
        $query = $qb->select('e')
           ->from($this->getEntityName(), 'e')
           ->orderBy('e.name', 'ASC')
           ->getQuery();
        return $this->makePagination($query, $perPage);
    }
}
d-ph commented 9 years ago

Hi guys, it would be useful for everyone if someone committed this code as a Trait, that we can use in our Custom Repositories, to get paginateAll() and makePagination(). Both should be public imo. It's very useful to build a laravel paginator for an arbitrary dql query builder.

Cheers.

d-ph commented 9 years ago

If someone needs a trait for it, then here's my code:

<?php
/**
 * Created by PhpStorm.
 * Date: 20/01/2015
 * Time: 10:59
 */

namespace MyCorp\Entity\Traits;

use Doctrine\ORM\Query;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Input;
use Paginator;

/**
 * Class PaginableEntityRepositoryTrait
 *
 * Apply this trait on your entity repository. It provides
 * you with methods to build a Laravel Paginator for a repository.
 *
 * Code stolen from https://github.com/mitchellvanw/laravel-doctrine/issues/45
 * Hopefully someone will create a proper trait in this project someday.
 *
 * @package MyCorp\Entity\Traits
 */
trait PaginableEntityRepositoryTrait
{

    /**
     * @param int $perPage
     * @return \Illuminate\Pagination\Paginator
     */
    public function paginateAll($perPage = 15)
    {
        $dql = "SELECT e FROM " . $this->getEntityName() . " e";
        $query = $this->getEntityManager()->createQuery($dql);

        return $this->makePagination($query, $perPage);
    }

    /**
     * @param Query $query
     * @param int $perPage
     * @return \Illuminate\Pagination\Paginator
     */
    public function makePagination(Query $query, $perPage = 15)
    {
        $query
            ->setFirstResult((Input::get('page', 1) - 1) * $perPage)
            /*
             * This should multiply to $perPage; because pagination
             * will always have 10 results, but with this only show
             * the remain result.
             */
            ->setMaxResults($perPage);

        $paginator = new DoctrinePaginator($query);

        $results = array();

        foreach ($paginator as $entity) {
            $results[] = $entity;
        }

        return Paginator::make($results, $paginator->count(), $perPage);
    }

    /*
     * These abstract methods are the methods
     * we need from Doctrine\Orm\EntityRepository
     */

    /**
     * @return string
     */
    abstract protected function getEntityName();

    /**
     * @return EntityManager
     */
    abstract protected function getEntityManager();
}

Then apply it on your repo:

class MyKewlRepo extends EntityRepository
{
    use PaginableEntityRepositoryTrait;

}

Then query it in your fancy controller:

    public function getListing()
    {
        $myKewlRepoPaginator = Repo::getMyKewlRepo()->paginateAll(2);

        return View::make('admin/kewl-repo/listing')->with(array(
            'myKewlRepoPaginator' => $myKewlRepoPaginator,
        ));
    }

And render it in twig:

{% for myKewlRepoItem in myKewlRepoPaginator %}
      {{ myKewlRepoItem.getId() }}
{% endfor %}

{{ myKewlRepoPaginator.links()|raw }}

Don't mind the Repo::getMyKewlRepo(). It's my custom stuff to get a repo for any entity in my project. And of course don't mind twig. It works with bare php templating as well.

Cheers