{"id":510,"date":"2013-10-10T08:54:12","date_gmt":"2013-10-10T08:54:12","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=510"},"modified":"2024-09-30T08:00:32","modified_gmt":"2024-09-30T08:00:32","slug":"simple-symfony2-3-pagination-with-both-nativequery-and-querybuilder-doctrine","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/simple-symfony2-3-pagination-with-both-nativequery-and-querybuilder-doctrine\/","title":{"rendered":"Simple Symfony2.3 Pagination With Both Nativequery And Querybuilder (Doctrine)"},"content":{"rendered":"<h2>Symfony2.3 pagination<\/h2>\n<p>I&#8217;ve allocated quite a reasonable amount of time to find a good bundle to achieve pagination AND use NativeQuery in the same time. I didn&#8217;t. Maybe I should have searched more&#8230;<!--more--><\/p>\n<p>Anyway, here it is in a nutshell, until I find some time to turn this into a bundle.<\/p>\n<blockquote><p>Goal: paginate records that are obtained both with NativeQuery and QueryBuilder in Doctrine.<\/p>\n<p>What do we have:<\/p>\n<ul>\n<li>entity called Process<\/li>\n<\/ul>\n<p>What should we achieve at the end of this post:<\/p>\n<ul>\n<li>one listing page with records resulted from a NativeQuery &#8211; paginated<\/li>\n<li>one listing page with records resulted from QueryBuilder &#8211; paginated<\/li>\n<\/ul>\n<\/blockquote>\n<p>First, let&#8217;s take care of Controller.<\/p>\n<p>File\/class called ProcessController should have an indexNativeAction where we will display a list of Process entities using NativeQuery. We&#8217;ll go for a simple approach:<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">\/\/ Ibw\/BlogBundle\/Controller\/ProcessController.php\r\n\r\nuse IbwBlogBundleLibPaginator;\r\n\r\n...\r\n\r\npublic function indexNativeAction($filter)\r\n{\r\n    \/\/ get the Process repository - this is where you'll store your queries, etc\r\n    $repository = $this-&gt;getDoctrine()-&gt;getRepository('IbwBlogBundle:Process');\r\n\r\n    \/\/ our custom paginator\r\n    $paginator = new Paginator();\r\n\r\n    \/\/ this is the query for listing\r\n    $queryProcesses = $repository-&gt;getProcessesNativeQuery();\r\n\r\n    \/\/ paginating \r\n    $pagination = $paginator-&gt;paginate($queryProcesses,\r\n        \/\/ page, default 1\r\n        $this-&gt;get('request')-&gt;query-&gt;get('page', 1),\r\n        \/\/ how many results per page, taken from parameters (so we won't hardcode)\r\n        $this-&gt;container-&gt;getParameter('pagination_limit_page')); \r\n\r\n    \/\/ finally, preparing the view \r\n    return $this-&gt;render('IbwBlogBundle:Process:indexNative.html.twig', array( \r\n        'pagination' =&gt; $pagination, \r\n        'paginator' =&gt; $paginator, \r\n    )); \r\n}<\/pre>\n<p>Then, to fill all the gaps, we&#8217;ll continue with creating the Paginator custom class, needed for doing almost all the work. Go ahead and create a Paginator.php file somewhere (I prefer a &#8220;Lib&#8221; folder inside the bundle).<\/p>\n<p>This class will contain the most important method : paginate() but also a bunch of getters for the class params like count of all records in query, current page #, how many pages are in total, etc.<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">&lt;?php\r\n\/\/ Ibw\/BlogBundle\/Lib\/Paginator.php \r\n\r\nnamespace IbwBlogBundleLib;\r\n\r\nuse DoctrineORMQueryResultSetMappingBuilder;\r\nuse DoctrineORMToolsPaginationPaginator as DoctrinePaginator;\r\n\r\nclass Paginator\r\n{\r\n    private $count;\r\n    private $currentPage;\r\n    private $totalPages;\r\n\r\n    \/**\r\n    * paginate results\r\n    *\r\n    * @param $query - naming is a bit off as it can be a NativeQuery OR QueryBuilder, we'll survive eventually\r\n    * @param int $page\r\n    * @param $limit\r\n    * @return array\r\n    *\/\r\n    public function paginate($query, $page = 1, $limit)\r\n    {\r\n        \/\/ setting current page \r\n        $this-&gt;currentPage = $page;\r\n        \/\/ set the limit \r\n        $limit = (int)$limit;\r\n\r\n        \/\/ this covers the NativeQuery case\r\n        if (is_a($query, 'DoctrineORMNativeQuery'))\r\n        {\r\n            \/\/ do a count for all query, create a separate NativeQuery only for that\r\n            $sqlInitial = $query-&gt;getSQL();\r\n\r\n            $rsm = new ResultSetMappingBuilder($query-&gt;getEntityManager());\r\n            $rsm-&gt;addScalarResult('count', 'count');\r\n\r\n            $sqlCount = 'select count(*) as count from (' . $sqlInitial . ') as item';\r\n            $qCount = $query-&gt;getEntityManager()-&gt;createNativeQuery($sqlCount, $rsm);\r\n            $qCount-&gt;setParameters($query-&gt;getParameters());\r\n\r\n            $resultCount = (int)$qCount-&gt;getSingleScalarResult();\r\n            $this-&gt;count = $resultCount;\r\n\r\n            \/\/ then, add the limit - paginate for current page\r\n            $query-&gt;setSQL($query-&gt;getSQL() . ' limit ' . (($page - 1) * $limit) . ', ' . $limit);\r\n        } \r\n        \/\/ this covers the QueryBuilder case, turning it into Query\r\n        elseif(is_a($query, 'DoctrineORMQueryBuilder'))\r\n        {\r\n            \/\/ set limit and offset, getting the query out of queryBuilder\r\n            $query = $query-&gt;setFirstResult(($page -1) * $limit)-&gt;setMaxResults($limit)-&gt;getQuery();\r\n\r\n            \/\/ using already build Doctrine paginator to get a count\r\n            \/\/ for all records. Saves load.\r\n            $paginator = new DoctrinePaginator($query, $fetchJoinCollection = true);\r\n            $this-&gt;count = count($paginator);\r\n        }\r\n\r\n        \/\/ set total pages\r\n        $this-&gt;totalPages = ceil($this-&gt;count \/ $limit);\r\n\r\n        return $query-&gt;getResult();\r\n    }\r\n\r\n    \/**\r\n    * get current page\r\n    *\r\n    * @return int\r\n    *\/\r\n    public function getCurrentPage()\r\n    {\r\n        return $this-&gt;currentPage;\r\n    }\r\n\r\n    \/**\r\n    * get total pages\r\n    *\r\n    * @return int\r\n    *\/\r\n    public function getTotalPages()\r\n   {\r\n       return $this-&gt;totalPages;\r\n   }\r\n\r\n   \/**\r\n   * get total result count\r\n   *\r\n   * @return int\r\n   *\/\r\n   public function getCount()\r\n   {\r\n   return $this-&gt;count;\r\n   }\r\n}<\/pre>\n<p>Now, let&#8217;s build the result in Repository. We&#8217;re going to create method getProcessesNativeQuery() that returns the NativeQuery which will collect whatever we&#8217;re interested in. In this case : all the Process entities (you can be more creative):<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">\/\/ Ibw\/BlogBundle\/Entity\/ProcessRepository.php\r\n\r\n\/**\r\n* query for all processes\r\n*\r\n* @return DoctrineORMNativeQuery\r\n*\/\r\npublic function getProcessesNativeQuery()\r\n{\r\n    $rsm = new ResultSetMappingBuilder($this-&gt;getEntityManager());\r\n    $rsm-&gt;addRootEntityFromClassMetadata('IbwBlogBundleEntityProcess', 'p');\r\n\r\n    $q = $this-&gt;getEntityManager()\r\n        -&gt;createNativeQuery('select * from Process p order by p.created_at desc', $rsm);\r\n\r\n    return $q;\r\n}<\/pre>\n<p>I think we have all setup for the final step: view . This is where all takes shape.<\/p>\n<p>Let&#8217;s create the actual listing page, indexNative.html.twig.<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">{# Ibw\/BlogBundle\/Resources\/views\/Process\/indexNative.html.twig #}\r\n\r\n{% block content %} {# or whatever your content block is called #}\r\n\r\n{# throw some title #}\r\n&lt;h1&gt;Processes&lt;\/h1&gt;\r\n\r\n{# this is an attempt to make a header - replace with your own #}\r\n&lt;div class=\"row-fluid\"&gt;\r\n&lt;div class=\"span8 text-left\"&gt;&lt;strong&gt;Process&lt;\/strong&gt;&lt;\/div&gt;\r\n&lt;div class=\"span2 text-center\"&gt;&lt;strong&gt;Approved?&lt;\/strong&gt;&lt;\/div&gt;\r\n&lt;div class=\"span2 text-right\"&gt;&lt;strong&gt;Manage&lt;\/strong&gt;&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n\r\n{# the actual Process listing - this is where Pagination kicks in #}\r\n{% for process in pagination %}\r\n    {# we have a separate template for Process listing #}\r\n    {{ include('IbwPtoolBundle:Process:_item_listing.html.twig', {'process': process}) }}\r\n{% endfor %}\r\n\r\n{# display navigation for pages - paginator_navigator #}\r\n{% if pagination|length &gt; 0 and paginator.getTotalPages() &gt; 1 %}\r\n    {{ include('IbwBlogBundle:Paginator:paginator_navigator.html.twig', {'paginator' : paginator}) }}\r\n{% endif %}\r\n\r\n{% endblock %}<\/pre>\n<p>Let&#8217;s see how the template for one Process entity listing looks like:<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">{# Ibw\/BlogBundle\/Resources\/views\/Process\/_item_listing.html.twig #}\r\n\r\n&lt;div class=\"row-fluid\"&gt;\r\n    &lt;div class=\"span8\"&gt;\r\n        {{ process.name }}\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"span2 text-center\"&gt;\r\n        {{ process.isApproved }}    \r\n    &lt;\/div&gt;\r\n    &lt;div class=\"span2\"&gt;\r\n        &lt;a href=\"#\" title=\"Edit\"&gt;Edit process&lt;\/a&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>And the pagination navigator:<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">{# Ibw\/BlogBundle\/Resources\/views\/Paginator\/paginator_navigator.html.twig #}\r\n\r\n{% set currentPath = path(app.request.get('_route'), app.request.get('_route_params')) %}\r\n{% set currentPage = paginator.getCurrentPage() %}\r\n{% set totalPages = paginator.getTotalPages() %}\r\n\r\n&lt;div class=\"pagination pagination-centered\"&gt;\r\n&lt;ul&gt;\r\n{% if currentPage &gt; 1 %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page=1\"&gt;&amp;laquo;&lt;\/a&gt;&lt;\/li&gt;\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage &gt; 1 ? currentPage - 1 : 1 }}\"&gt;&lt;&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n\r\n{% if (currentPage - 3) &gt; 0 %}\r\n    &lt;li&gt;&lt;a href=\"#\"&gt;...&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n{% if (currentPage - 2) &gt; 0 %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage - 2 }}\"&gt;{{ currentPage - 2 }}&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n{% if (currentPage - 1) &gt; 0 %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage - 1 }}\"&gt;{{ currentPage - 1 }}&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n&lt;li class=\"active\"&gt;&lt;a href=\"#\"&gt;{{ currentPage }}&lt;\/a&gt;&lt;\/li&gt;\r\n{% if (currentPage + 1) &lt;= totalPages %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage + 1 }}\"&gt;{{ currentPage + 1 }}&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n{% if (currentPage + 2) &lt;= totalPages %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage + 2 }}\"&gt;{{ currentPage + 2 }}&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n{% if (currentPage + 2) &lt; totalPages %}\r\n    &lt;li&gt;&lt;a href=\"#\"&gt;...&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n\r\n{% if currentPage &lt; totalPages %}\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ currentPage &lt; totalPages ? currentPage + 1 : currentPage }}\"&gt;&gt;&lt;\/a&gt;&lt;\/li&gt;\r\n    &lt;li&gt;&lt;a href=\"{{ currentPath }}?page={{ totalPages }}\"&gt;&amp;raquo;&lt;\/a&gt;&lt;\/li&gt;\r\n{% endif %}\r\n\r\n&lt;\/ul&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>All you need to do is setup the number of items per page (the &#8220;limit&#8221;) as parameter and all this should work just fine. We&#8217;ll set it to 10 by adding it in paramaters.yml file<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\"># Ibw\/BlogBundle\/Resources\/config\/parameters.yml\r\n\r\nparameters:\r\n    pagination_limit_page: 10<\/pre>\n<p>At this point, you have everything working with NativeQuery.<\/p>\n<h3>What about QueryBuilder?<\/h3>\n<p>Basically it&#8217;s all the same, except one thing:\u00a0repository should return a QueryBuilder (instead of NativeQuery).<\/p>\n<p>For that, create a new method in repository called getProcessesQueryBuilder(), like this:<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">\/\/ Ibw\/BlogBundle\/Entity\/ProcessRepository.php\r\n\r\n\/**\r\n* query for all processes\r\n*\r\n* @return DoctrineORMQueryBuilder\r\n*\/\r\npublic function getProcessesQueryBuilder()\r\n{\r\n    $q = $this-&gt;createQueryBuilder('p')\r\n                -&gt;select('p')\r\n                -&gt;orderBy('p.createdAt', 'desc');\r\n\r\n    return $q;\r\n}<\/pre>\n<p>After that, of course you need to adjust the controller to call this for getting the results. Change the controller as follows:<\/p>\n<pre class=\"theme:tomorrow-night lang:default decode:true\">\/\/ \/Ibw\/BlogBundle\/Controller\/ProcessController.php\r\n\r\npublic function indexNativeAction($filter)\r\n{\r\n    ...\r\n\r\n    \/\/ this is the query for listing\r\n    $queryProcesses = $repository-&gt;getProcessesQueryBuilder();\r\n\r\n    ...\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>This should be enough for you application to handle pagination for both NativeQueries and DQL in Doctrine.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Symfony2.3 pagination I&#8217;ve allocated quite a reasonable amount of time to find a good bundle to achieve pagination AND use [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[123,124,177,190,195,238,240],"yst_prominent_words":[6,320,334,798,1133,1395,1398],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/510"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=510"}],"version-history":[{"count":7,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/510\/revisions"}],"predecessor-version":[{"id":133249,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/510\/revisions\/133249"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=510"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=510"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=510"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=510"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}