fbpx

Laravel: paginación, búsqueda y listado por backend

Cuando un listado o una tabla, tiene pocos registros, librerías jQuery como Datatables, te proporcionan una manera fácil y rápida de paginar resultados, ordenar por contenido de columna o incluso hacer una búsqueda de resultados, ya que tiene todos los registros cargados y almacenados, por lo que acceder a ellos es muy ágil

Pero, ¿qué sucede cuando ese listado empieza a tener cientos de resultados?. Estas librerías empiezan a no poder soportar la carga de realizar todas esas acciones masivamente a nivel front-end.

Es aquí cuando es necesario buscar otra solución: la paginación de back-end.

Laravel, proporciona una paginación muy fácil de implementar con el método paginate(), pero esto no sirve si además se quiere ordenar, buscar o filtrar resultados de una manera personalizada

Si además, parte de esos datos filtrables, se encuentran encriptados en la base de datos, es necesario hacer uso del modelo correspondiente para poder desencriptarlos mediante accessors.

Métodos


public function filtering($filters, $keyword){
 $filters = array_map('strtolower', $filters);

 foreach($filters as $filter){
   if(strpos($filter, $keyword) !== false){
     return true;
     break;
   }
 }

 return false;
}

Esta función es muy simple, lo único que hace es comprobar que la keyword proporcionada esté total o parcialmente en el array de parámetros también proporcionado. La complejidad de esta búsqueda/filtrado, reside en el controlador, ya que es donde está la colección de elementos, el cual veremos después.


public function ordering($collection, $sorting, $sortBy, $order){


 if ($order === "asc") {
   $collection = $collection->sortBy(function ($item) use ($sortBy, $sorting) {
     return $this->sorting($item, $sortBy, $sorting);
   });
 } else {
   $collection = $collection->sortByDesc(function ($item) use ($sortBy, $sorting) {
     return $this->sorting($item, $sortBy, $sorting);
   });
 }


 return $collection;
}

Esta función, recibe como parámetro la colección a ser ordenada, el nombre del atributo por que se desea ordenar y el tipo de orden (ascendente o  descendente). Simplemente se hace uso de un par de métodos de ordenado de colecciones de Laravel, las cuales esperan que se les pase como parámetro una función callback que devuelva el valor del atributo por el que se quiere ordenar. Y para ello está la siguiente función.


public function sorting($item, $sortBy, $sorting) {

 $steps = explode("->", $sorting[$sortBy]);
 $attribute = array_pop($steps);

 $last_relation_result = "";
 foreach($steps as $key => $step){
   if ($key === 0) {
     $last_relation_result = $step;
   } else {
     $last_relation_result = $last_relation_result->$step;
   }
 }

 if ($last_relation_result === "") {
   $result = $item->$attribute;
 } else {
   $result = optional($item->$last_relation_result)->$attribute;
 }

 return $result;
}

Este método se encarga de interpretar los parámetros sortBy (nombre del atributo a ordenar) y sorting (mapa de relaciones), para obtener el valor del atributo correspondiente y devolverlo al método anterior, que hará uso de él. Cuando veamos el controlador, esta parte quedará más clara.


public function pagination($collection, $per_page = 10){
 $request = request();
 $total=count($collection);
 $current_page = $request->input("page") ?? 1;

 $starting_point = ($current_page * $per_page) - $per_page;

 $array = $collection->slice($starting_point, $per_page);

 $array = new Paginator($array, $total, $per_page, $current_page, [
   'path' => $request->url(),
   'query' => $request->query(),
 ]);

 return $array;

}

La función encargada de paginar, solo recibe la colección por la que se quiere ordenar y el número de elementos por página. Dentro además, se hace uso de la variable page que genera por defecto Laravel al hacer uso del objeto Paginator.

Controlador

Ahora vamos a usar todas estos métodos en el controlador correspondiente en el que sepamos que los resultados sean masivos filtrables y por lo tanto se necesite una paginación de back.


public function index(Request $request)
{

 $users = user::with('user', 'principal_contact', 'captation', 'projects', 'status')->orderByDesc('id')->get();

 if($keyword = $request->search){
   $keyword = strtolower($keyword);
   //busqueda
   $users = $users->filter(function($user) use ($keyword){
     $filters = [$user->code, $user->identifier_code, $user->user->name, $user->user->surname1, $user->user->surname2, optional($user->principal_contact)->email, optional($user->principal_contact)->phone, implode(",",$user->projects->pluck('name')->toArray())];

     return $this->filtering($filters, $keyword);
   });
 }


 if($sortBy = $request->sort) {
   $sorting = ["code" => "code", "user" => "full_name", "email" => "principal_contact->email", "mobile_phone" => "principal_contact->mobile_phone", "captated_by" => "captation->name", "status" => "status->name" ];
   $users = $this->ordering($users, $sorting, $sortBy, $request->order);
 }

 $users = $this->pagination($users);
 $statusables = Statusable::fromUsers()->get();

 return view('user.users', compact('users', 'statusables'));
}

Vamos a analizar el método del controlador línea por línea.

Lo primero que vamos a hacer es obtener la colección entera, con el orden por defecto y con todas las relaciones que se vayan a usar. Esto último es importante, si se usa una relación y no se incluye en el with(), el tiempo de espera es considerablemente mayor ya que hará la consulta por cada elemento de la colección, en lugar de una sola vez al principio para todos.

Comprobamos que la variable URL, en este caso search, contenga el valor por el que se quiere buscar. En este caso, quiero que las mayúsculas y minúsculas no afecten a las búsquedas, así que lo convierto todo a minúsculas a la hora de comparar.

Aquí es donde viene lo importante. Hacemos uso de la función de laravel filter(), la cuál espera como parámetro una función callback que devuelva true si el elemento debe permanecer o un false si debe eliminarse de la colección.

Dentro de este callback, es donde generamos el array de filters que luego recibe el método filtering() , es decir, donde se obtienen e indican los valores buscables, y el mapa de relaciones necesarios para obtener ese valor en este caso específico. Estas pueden ser relaciones anidadas o incluso accessors.

Por ejemplo, si en la variable URL, indica que hay que buscar por email, gracias a este array podemos observar que para obtener el email es necesario usar la relación principal_contact(). Esto depende de la colección de modelos que sea. Si fuera una colección de modelos de usuarios, no haría falta hacer uso de ninguna relación, ya que el email sería un atributo directo.

Si observas el último de los filtros, es algo más complejo. Esto es porque lo que devuelve la relación projects(), es una colección, mientras que lo que espera filtering() es que sea un string. Por ello, hago uso de la función pluck() y toArray() de laravel, así como implode() de php, para que todos los nombres de los proyectos de la colección, los ponga en un string.

Y hasta aquí sería la parte complicada. Ahora dentro de este callback simplemente usamos la función filtering(), pasándole el array de filters que hemos generado, para que devuelva true o false según corresponda y la función filter() se encargue de todo.

Por último, en el caso de que también venga la variable para ordenar, en este caso sort, hacemos uso del método ordering() y terminamos con el método pagination() para que a la vista solo lleguen los resultados de la página actual, que al fin y al cabo, es a lo que hemos venido.

Después en la vista, se hace uso del método links() de Laravel, que se encarga de generar todas las URLs de las páginas y renderizar el HTML necesario para poder usarlo. Esto es posible gracias a que ahora en el ejemplo, $users es de tipo Paginator, en lugar de una simple colección de modelos.

Menú

Utilizamos cookies propias y de terceros para recopilar información que ayuda a optimizar su visita. Las cookies no se utilizan para recoger información de carácter personal. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies