Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Visitando alguns fóruns pela internet eu vi que várias pessoas têm dificuldades com paginação. Eu resolvi, então, criar um tutorial para que algumas pessoas tivessem menos problemas com isso.
Para isso vamos simular um sistema de catálogo de produtos. Nosso sistema busca o nome do produto e exibe logo abaixo do nome a descrição deste.
Atenção: Eu fiz a classe e o tuto para mostrar mais ou menos como fazer a sua, mas se você preferir utilizá-la como está, lembre-se de copiar todas as funções para dentro da classe.
Vamos começar pela tabela do nosso produto:
create table produtos(id tinyint not null auto_increment,nome varchar(30) not null,descricao tinytext not null,primary key(id)) type=MyISAM auto_increment=1;Nosso arquivo de conexão:
<?php$bd = "meu_banco";$server = "localhost";$user = "eu";$senha = "senha";$conn = mysql_connect($server,$user,$senha) or die("erro ao conectar!");mysql_select_db($bd);?>**Para mantermos um texto mais limpo, vou colocar as url de referência no final do tutorial, e darei a elas apelidos que serão utilizados no desenvolver do texto, no lugar das url's inteiras.
Bom, agora podemos começar o que realmente interessa: a paginação.
Uma paginação tem, geralmente, os mesmos atributos: números de registros por página, número de páginas, url da página que chamou a paginação, e mais alguns que nem sempre há nas paginações. Para nossa paginação, iremos utilizar o paradigma da orientação a objetos (ver em POO, e PHPOO). Se você não sabe o que siginifica orientação a objetos, leia o POO, depois o PHPOO, mas se estiver muito apressado e não quer aprender orientação a objetos, você pode continuar a ler que conseguirá entender a lógica do negócio. ;).
Nossa paginação deverá trabalhar com banco de dados, ou com qualquer outra fonte de dados, por isso, a conexão com o BD e as queries não estarão nesta classe.
Nossa paginação irá retornar um menu comun, ou um menu com limitação de resultados, e a opção de ter um identificador de localização nos registros, como no google (reg.: 1 a 10 de 50).
Para darmos mais dinamismo a nossa paginação, podemos ter a opção de escolher qual o separador entre os links de navegação, e qual os "botões" de avançar e de retroceder.
Então vamos lá!
Para que você consiga fazer sua paginação, você precisa saber quantos registros irá usar. Tendo em mãos o total de registros, você precisa definir qual será o limite de registros a ser selecionado no banco de dados, baseado na quantidade de registros que quer por página.
Vamos então mostrar como será o "esqueleto" de nossa classe:
class paginacao{ var $regs; // número de registros por páginas var $exNum;//número de páginas a ser exibida no menu ex.: mostra "1 2 3 4 5" apesar de termos 10 páginas var $url;//url da página que chamou nossa classe var $totReg;//número de registros contidos no banco de dados var $totPag;//número total de páginas var $pg;//página em que estamos var $separador;//separador entre os links do menu ex. separador = "|" menu = 1|2|3|4|5 var $tag_pagin;//tag na qual nossa paginação estará inserida var $url_separator;//separador de url, para o caso de precisarmos passar outros argumentos pelo get, que não seja apenas a página /** método construtor da nossa classe / function paginacao($regs,$url,$totReg,$pg,$exNum="",$separador=' | ',$tag_pagin="") { } /** método que nos retornará uma string com o valor a ser passado na cláusula limit do sql @return string limit pronto pra sql */ function limit() { } /** métod que cria nossa paginação @return string menu formatado */ function paginar($primeiro="<<",$ultimo=">>",$prev="<",$next=">") { } /** método que cria uma paginação limitada @access private @return string menu quase pronto / function paginar_limitado($exNum,$totalPg) { } /** método que nos retornará aquele lembrete de localização nos registros como no google ex. reg.: 1 a 5 de 10 @return lembrete de registros */ function paginar_inteligente($template="") { }}Bom, agora vamos colocar alguns métodos para trabalhar, mas antes vamos entender com irá funcionar nossa classe:Você instancia a classe paginação, que tem um método construtor (paginacao()) que irá definir os valores dos atributos da classe. Então você, caso precise, chamará o método limit() para que possa obter o limit que precisa na sua query. Feito isso, você mostra o conteúdo de sua página, e então chama o método paginar() que irá retornar o menu com a paginação pronta. Como nossa classe é uma classe democrática, você tem o direito de escolher se quer uma paginação comun, ou uma paginação limitada, apenas definindo um valor inteiro ou nada para a variável $exNum. Dai, se você quiser aquele avisinho como há no google, você chama o método paginar_inteligente().
Vamos aos métodos:
Paginação vai definir valores de atributos da classe. Note que aqueles parâmetros que queremos ter como opcionais, nós colocamos com um valor já "setado":
function paginacao($regs,$url,$totReg,$pg,$exNum="",$separador=' | ',$tag_pagin="") { $this->regs = $regs; $this->exNum = $exNum; $this->url = $url; $this->totReg = $totReg; $totPag = ($totReg <= $regs) ? 1 : ceil($totReg/$regs);//se você está acostumado com o operador ternário, você sabe o que isso siginifica, senão, leia TERNÁRIO $this->totPag = $totPag; $this->pg = ($pg != "") ? $pg : 1; $this->separador = $separador != "" ? $separador : " | "; $this->tag_pagin = $tag_pagin; $this->url_separator = (strpos($url,"?") !== false) ? "&" : "?"; }Note que no url_separator nós conferimos se há o "?" na url (o que indica que você quer passar outro parâmetro junto com a página pela url) e damos um separador de strings adaptado a situação encontrada.
Agora vamos para o método limit(). Ele faz uma continha para descobrir qual o registro inicial e que o total de registros buscados para você usar na sua query:
function limit() { $inicio = ($this->pg * $this->regs) - $this->regs;//o primeiro registro a ser puxado do BD $final = $this->regs;//o total de registros a ser puxado do BD $limit = $inicio.",".$final;//limit formatado para ser utilizado na sua query return $limit; }Façamos um teste para ver se nosso limite está certo: se estivermos na página um, devemos começar pelo registro 0,digamos que queremos 5 registros por página, então -> 1*5 - 5 = 0. Agora se estivermos na página 2? 2*5 - 5 = 5. Perfeito, nosso cálculo está funcionando como queremos.
Vamos agora fazer o método paginar(), que receberá o formato dos botões de avançar e retroceder para que possamos utilizar um formato diferente de vez em quando (nossa classe é democrática, lembra-se?). Preste bastante atenção nesse método, pois a paginação é um emaranhado de condicionais com uma enorme facilidade de nos deixar perdidos.
function paginar($primeiro="<<",$ultimo=">>",$prev="<",$next=">") { //vamos pegar os valores dos atributos da classe e usar como variáveis locais ao método $pg = $this->pg; $totPag = $this->totPag; $url = $this->url; $tag_pagin = $this->tag_pagin; $url_separator = $this->url_separator; //caso nada seja passado como parâmtro para a função, iremos dar um valor default $primeiro = $primeiro == "" ? "<<" : $primeiro; $ultimo = $ultimo == "" ? ">>" : $ultimo; $prev = $prev == "" ? "<" : $prev; $next = $next == "" ? ">" : $next; //primeira condição. Se tivermos o número de páginas igual á página atual, estaremos na última página, ou temos apenas //uma página, mas nesse caso não exibiremos a paginação (com css, display: none) //já que estamos na última página, não precisaremos de links nos botoes proximo e última if($totPag == $pg) { //a página utilizada nos links dos botoes de voltar $pg = $this->pg - 1; $proximo = $next." "; $ultima = $ultimo; //precisaremos de botoes anterior e primeira, a não ser que tenhamos apenas uma página, mas nesse caso esconderemos //nossa paginação $anterior = "<a href=\"".$url.$url_separator."pg=$pg\"> ".$prev."</a> "; $primeira = "<a href=\"".$url.$url_separator."pg=1\">".$primeiro."</a>"; }elseif($pg == 1) { //nosso segundo condicional. se estivermos na página 1, não precisamos de botões de voltar, certo? //a página utilizada nos links dos botões de avançar $pg = $this->pg + 1; $proximo = "<a href=\"".$url.$url_separator."pg=$pg\">".$next." </a>"; $anterior = " ".$prev." "; $primeira = $primeiro; $ultima = "<a href=\"".$url.$url_separator."pg=$totPag\">".$ultimo."</a>"; }else { //significa que não estamos nem na última nem na primeira, e os botões podem ter os links normais //número de página usado no botão de avançar $pg_p = $pg + 1; //número de página usado no botão de voltar $pg_a = $pg - 1; //botões $proximo = "<a href=\"".$url.$url_separator."pg=$pg_p\">".$next." </a>"; $anterior = "<a href=\"".$url.$url_separator."pg=$pg_a\"> ".$prev."</a> "; $primeira = "<a href=\"".$url.$url_separator."pg=1\">".$primeiro."<a/>"; $ultima = "<a href=\"".$url.$url_separator."pg=$totPag\">".$ultimo."</a>"; } //montamos nossa primeira seção de botões, os de voltar $pagina = "<p>".$primeira.$anterior; //se tivermos o $exNum definido, é porque teremos uma paginação limitada... if($this->exNum != "") { $exNum = $this->exNum; $pagina .= $this->paginar_limitado($exNum,$totPag); }else { //...senão, podemos paginar normalmente //esse laço vai nos dar os números do menu da paginação for($i=1;$i<=$totPag;$i++) { //se nosso link for o mesmo da página em que estamos, devemos mostrá-lo em negrito, e sem links... if($i == $this->pg) { //nossa variável página será concatenada com os valores de i dentro do loop, aqui e dentro do else log abaixo $pagina .= "<b>".$i."</b> | "; } else { //...senão, mostramos ele normalmente, com os links. Note a utilização do url_separator aqui também. $pagina .= "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a> | "; } }//close for } //mais uma concatenação, agora para terminarmos nosso menu, com os botões de ir e vir, e com os números internos $pagina .= $proximo.$ultima."</p>"; //aqui nós definiremos se o menu deve aparecer ou não (caso tenha apenas uma página) if($totPag == 1) { $result = "<span style=\"visibility: hidden\">".$pagina."</span>"; }elseif ($tag_pagin != "") { //se tivermos uma tag definida, devemos colocar nosso menu dentro dela, correto? $result = str_replace("><",">".$pagina."<",$tag_pagin); }else { $result = $pagina; } //retornamos o nosso menu completo, pronto para ser mostrado na nossa página return $result; }Como eu disse, preste bastante atenção nos condicionais, é neles que você se baseia para gerar uma paginação sem erros.
Agora vamos para a grande geradora de dúvidas: a paginação com limite de exibição no menu. Se você se perdeu um pouco nos condicionais do método paginar(), agora você deve ter o dobro de atenção, senão não entenderá como o resultado final foi obtido.
function paginar_limitado($exNum,$totalPg) { //vamos pegar os valores dos atributos da classe e usar como variáveis locais ao método $url = $this->url; $url_separator = $this->url_separator; $separador = $this->separador; $pg = $this->pg; //aqui teremos duas variáveis importantíssimas: //div nos mostra a o próximo inteiro mais próximo da metade do valor de exNum. esse valor será utilizado em cálculos futuros $div = (is_int($exNum/2)) ? $exNum/2 : floor($exNum/2); //centro é o centro da paginação. se tivermos 5 como exNum, teremos 3 com sendo o centro. Isso é para uma apresentação estética mais agradável $centro = (is_int($exNum/2)) ? $exNum/2 : ceil($exNum/2); //1° condicional: nossa página é igual ao total de páginas(estamos na última página)... if($pg == $totalPg) { //...testamos se nosso total de paginas é maior que o numero de links a ser exibido no menu... //...se sim, nosso menu ultrapassa os limites de exibição de links, e deve começar por um número maior que 1. if($totalPg >= $exNum) { //variável que indica o final do menu $termina = $totalPg; //variável que indica o começo do menu. Precisamos subtrair do final, o exNum. E adicionar 1, senão teremos um link a mais no nosso menu $comeco = $termina - $exNum +1; //começaremos nosso loop com o valor do primeiro link do menu $i = $comeco; //para exibirmos os links do menu, temos que ter um loop, e nesse loop precisamos testar se nosso número tem link ou não. Fazemos isso com um operador ternário, que é mais enxuto: while($i >= $comeco and $i<=$termina) { //Note o uso da variável $separador, como separador de números do menu $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } }else { //...senão, nosso menu não ultrapassa os limites de exibição de links. //nosso menu começa...do começo...rsrs $comeco = 1; //e termina na nossa última página $termina = $totalPg; //daqui para baixo, os loops serão os mesmos, então considere os comentários do loop de cimam ^ $i = $comeco; while($i>=$comeco and $i<=$termina) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } }elseif ($pg == 1) { //2° condicional: estamos na primeira página //nosso menu começa...do começo...rsrs $comeco = 1; //para sabermos onde termina, precisamos saber se nosso menu ultrapassa os limites de links, se sim, nosso menu termina exNum números depois do começo menos 1. Assim fazemos nosso menu ter apenas exNum links, e não exNum + 1. $termina = $totalPg >= $exNum ? ($comeco + $exNum) -1 : $totalPg; $i = $comeco; while($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } }elseif ($pg < $totalPg - $exNum) { //3° condicional: estamos num página cujo número é menor que o total de páginas menos o exNum. ou seja, não está entre os "exNum"s links do nosso menu if($pg > $exNum) { //estamos num página que é maior que os "exNum" primeiros números do nosso menu //nosso menu começa "div" números antes da nossa página atual. ou seja, nossa página ficará no centro do menu $comeco = $pg - $div; //o nosso sistema de fazer o menu terminar com exNum links $termina = $comeco + $exNum - 1; $i = $comeco; while ($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } }elseif ($pg < $exNum) { //estamos entre os "exNum"s primeiros números do menu, devemos saber se estamos no centro, ou se estamos antes dele if($pg > $centro) { //se estamos além do centro, nosso menu começa "div" números antes de nossa página atual, ou seja, nossa página ficará no centro do menu $comeco = $pg - $div; $termina = $comeco + $exNum - 1; $i = $comeco; while($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } }else { //se estamos antes ou no centro, nossa página ficará na posição normal, sem deslocamento para centrarmos ao menu $comeco = 1; $termina = $comeco + $exNum - 1; $i = $comeco; while($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } }elseif ($pg == $exNum) { //estamos no limite de exibição de links //jogaremos, então, nossa página no centro do menu, com esse cálculo, onde tiramos div do exNum, o que nos dará o centro atual do menu: $comeco = $exNum - $div; $termina = $comeco + $exNum -1; $i = $comeco; while ($i >= $comeco and $i <= $termina) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } }elseif ($pg > $totalPg - $exNum) { //4° condicional: testamos se nossa página está entre as exNum últimas do menu if($totalPg > $exNum) { //temos um total de paginas que ultrapassa o limite de links/página if($pg <= $totalPg - ($exNum - $centro)) { //nossa página está antes ou é o centro atual do menu, então jogamos ela para o centro, quando já não o é $comeco = $pg - $div; $termina = $comeco + $exNum - 1; $i = $comeco; while ($i >= $comeco and $i <= $termina) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } }else { //nossa página está além do centro, então teremos que manter seu lugar normal, sem deslocá-la para o centro, senão teremos mais links do que realmente precisamos $comeco = $totalPg - $exNum + 1; $termina = $comeco + $exNum - 1; $i = $comeco; while ($i >= $comeco and $i <= $termina) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } }elseif ($totalPg <= $exNum) { //nosso total de páginas não é maior que noss menu $comeco = 1; $termina = $totalPg; $i = $comeco; while ($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } }elseif ($pg == $totalPg - $exNum) { //5° condicional: se nossa página for igual ao total de páginas menos o limite de exibição por menu, ela ainda não foi atingida por nenhum condicional acima, então teremos um só pra ela... :D $termina = $totalPg - ($exNum - $div); $comeco = $termina - $exNum + 1; $i = $comeco; while ($i <= $termina and $i >= $comeco) { $menu .= ($i == $pg) ? "<b>".$i."</b>$separador" : "<a href=\"".$url.$url_separator."pg=".$i."\">".$i."</a>$separador"; $i++; } } //die($menu);//essa é uma boa pratica: testar o seu resultado retornado antes de utilizar a classe no seu site;) //aqui nós iremos retirar o ultimo separador da nossa string, pois não precisamos dele entre os números e os botões. $menu = rtrim($menu,$separador)." "; return $menu; }Meio grande né? Pois bem, você deve ler com atenção os comentários para entender o que cada um está fazendo. Basicamente, nós estamos testando várias condições que farão com que nosso menu fique errado, e corrigimos esses problemas. Procure escrever as condições que testamos em língua falada (caso não tenha entendido o que está escrito) ou tente diminuir os condicionais, o máximo possível, caso tenha entendido. Não se preocupe, esse tutorial vai estar disponível por muito tempo, então pode brincar a vontade com nossos condicionais, até que você aprenda o que acontece dentro deles.
Agora vamos para o método paginar_inteligente(), que nos retorna aquele lembrete como temos no google. Lembra-se da democracia de nossa classe? Aqui ela também é democrática, e nos deixa escolher uma forma diferente de exibirmos o lebrete. Usaremos como padrão a frase "itens # a # de #":
Editado: Correção do bug que fazia escrever algo como "registros 1 a 10 de 0 encontrados". Obrigado: dammy.
Editado2: Correção do bug que calcula errado os registros exibidos. Obrigado: dammy.
function paginar_inteligente($template="") { //valor que será apresentado como o valor inicial $de = $this->pg * $this->exNum - $this->exNum + 1; //valor final que se está exibindo $a = $this->pg * $this->exNum; //total de registros exibidos $total = $this->totReg; //correção de um bug bem feio ($total - $a) < $this->exNum ? $a = $total : ""; //testamos se não tem nada no parametro da função, para então retornarmos o nosso default, ou o template passado. if(!empty($template)) { //temos aqui uma aglomeração de funções do php para manipulação de strings, procure entender cada uma delas no manual. //veja SUBSTR_REPLACE e STRPOS $tpl = substr_replace(substr_replace(substr_replace($template,$total,strpos($template,"#3"),2),$a,strpos($template,"#2"),2),$de,strpos($template,"#1"),2); $menu = $tpl; }else { $menu = "Itens ".$de." a ".$a." de ".$total; } return $menu; }Você agora só precisa de um exemplo, num é? Então vamos tentar um, no qual exibiremos os nossos produtos, dos quais falei no começo do tutorial (lembra?)Bom, esse é o exibir.php:
<?php$sql = "select count(*) as total from produtos";$query = mysql_query($sql);$result = mysql_result($query,0,"total");$pagin = new paginacao(5,$_SERVER['PHP_SELF'],$result,$_GET['pg'],5);$limit = $pagin->limit();$sql = "select nome, descricao from produtos order by nome limit ".$limit;$query = mysql_query($sql);while($array = mysql_fetch_array($query)){ echo "Produto: ".$array[0]."<br />Descrição: ".$array[1]."<br /><br /><br />";}$paginar = $pagin->paginar("primeiro","último","anterior","próximo");echo $paginar;$lembrete = $pagin->paginar_inteligente("Exibindo registro #1 a #2 de #3 encontrados");echo "<br />".$lembrete;?>Se tivermos mais de 25 registros no banco conseguiremos ver algo como:
"primeiro anterior 1 2 3 4 5 próximo último
Exibindo registro 16 a 20 de 30 encontrados"
Bom, era isso então, espero que esse tutorial ajude você a construir sua própria classe de paginação. Qualquer dúvida você pode postar no forum do imasters ou me mandar um e-mail. ;)
REFERÊNCIAS
PHPOO - http://br2.php.net/manual/pt_BR/language.oop.php
POO - http://www.dca.fee.unicamp.br/cursos/POOCPP/node3.html
TERNÁRIO - http://br2.php.net/manual/pt_BR/language.expressions.php
CEIL - http://br2.php.net/manual/pt_BR/function.ceil.php
SUBSTR_REPLACE - http://br2.php.net/manual/pt_BR/function.substr_replace.php
STRPOS - http://br2.php.net/manual/pt_BR/function.strpos.php
FLOOR - http://br2.php.net/manual/pt_BR/function.floor.php
RTRIM - http://br2.php.net/manual/pt_BR/function.rtrim.php
STR_REPLACE - http://br2.php.net/manual/pt_BR/function.str_replace.php
FORUM_IMASTERS - http://forum.imasters.com.br/index.php?showforum=3
Você gostou? Dê uma palavra de força. Isso as vezes vale mais do que dinheiro.
Você me encontra em charlesschaefer(no)gmail(ponto)com, ou em theschaefer.blogger.com ou em forum.imasters.com.
Você encontra um exemplo sem banco de dados no endereço: http://schaefer.myfreewebs.net/classes/testeSP.php
Não me culpe pelos erros de português, por favor, eram 3:20 da matina quando terminei esse tuto, então não poderia mesmo fazê-lo sem nem um errinho. :D
Carregando comentários...