Laravel pagination pretty URL
Is there a way to get a pagination pretty URL in Laravel 4?
For example, by default:
http://example.com/something/?page=3
And what I would like to get:
http://example.com/something/page/3
Also, the pagination should render this way, and appending to the pagination should appear in this way.
Solution 1:
Here's a hacky workaround. I am using Laravel v4.1.23. It assumes page number is the last bit of your url. Haven't tested it deeply so I'm interested in any bugs people can find. I'm even more interested in a better solution :-)
Route:
Route::get('/articles/page/{page_number?}', function($page_number=1){
$per_page = 1;
Articles::resolveConnection()->getPaginator()->setCurrentPage($page_number);
$articles = Articles::orderBy('created_at', 'desc')->paginate($per_page);
return View::make('pages/articles')->with('articles', $articles);
});
View:
<?php
$links = $articles->links();
$patterns = array();
$patterns[] = '/'.$articles->getCurrentPage().'\?page=/';
$replacements = array();
$replacements[] = '';
echo preg_replace($patterns, $replacements, $links);
?>
Model:
<?php
class Articles extends Eloquent {
protected $table = 'articles';
}
Migration:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticlesTable extends Migration {
public function up()
{
Schema::create('articles', function($table){
$table->increments('id');
$table->string('slug');
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
public function down()
{
Schema::drop('articles');
}
}
Solution 2:
It's possible but you need to code a bit.
First you need to change in app/config/app.php
pagination service provider - you need to write your own.
Comment:
// 'Illuminate\Pagination\PaginationServiceProvider',
and add
'Providers\PaginationServiceProvider',
in providers section.
Now you need to create your PaginationServiceProvider to use custom pagination factory:
model/Providers/PaginationServiceProvider.php
file:
<?php
namespace Providers;
use Illuminate\Support\ServiceProvider;
class PaginationServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bindShared('paginator', function ($app) {
$paginator = new PaginationFactory($app['request'], $app['view'],
$app['translator']);
$paginator->setViewName($app['config']['view.pagination']);
$app->refresh('request', $paginator, 'setRequest');
return $paginator;
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('paginator');
}
}
Above you create Providers\PaginationFactory
object, so now we need to create this file:
model/providers/PaginationFactory.php
file:
<?php
namespace Providers;
use Illuminate\Pagination\Factory;
class PaginationFactory extends Factory {
/**
* Get a new paginator instance.
*
* @param array $items
* @param int $total
* @param int|null $perPage
* @return \Illuminate\Pagination\Paginator
*/
public function make(array $items, $total, $perPage = null)
{
$paginator = new \Utils\Paginator($this, $items, $total, $perPage);
return $paginator->setupPaginationContext();
}
}
Here you create only \Utils\Paginator
object so now let's create it:
model/Utils/Paginator.php
file:
<?php
namespace Utils;
class Paginator extends \Illuminate\Pagination\Paginator {
/**
* Get a URL for a given page number.
*
* @param int $page
* @return string
*/
public function getUrl($page)
{
$routeParameters = array();
if ($page > 1) { // if $page == 1 don't add it to url
$routeParameters[$this->factory->getPageName()] = $page;
}
return \URL::route($this->factory->getCurrentUrl(), $routeParameters);
}
}
In this file we finally override default method for creating pagination urls.
Let's assume you have route defined this way:
Route::get('/categories/{page?}',
['as' => 'categories',
'uses' => 'CategoryController@displayList'
])->where('page', '[1-9]+[0-9]*');
As you see we defined here route name using as
(it's important because of Paginator implementation above - but you can do it of course in different way).
Now in method displayList
of CategoryController
class you can do:
public function displayList($categories, $page = 1) // default 1 is needed here
{
Paginator::setCurrentPage($page);
Paginator::setBaseUrl('categories'); // use here route name and not the url
Paginator::setPageName('page');
$categories = Category::paginate(15);
return View::make('admin.category')->with(
['categories' => $categories]
);
}
When in your view you add:
<?php echo $categories->links(); ?>
you will get generated urls this way:
http://localhost/categories
http://localhost/categories/2
http://localhost/categories/3
http://localhost/categories/4
http://localhost/categories/5
without ? in query string
However in my opinion something like this should be added by default or at least it should be enough to extend one class and not to create 3 classes just to implement one method.
Solution 3:
hope this is helpful for someone, I've made a trait to be used in models. The idea is that this custom method can detect current route and adjust links to use correct segment position for {page} parameter:
https://gist.github.com/zofe/ced0054e6ac6eff1ea95