Symfony doctrine pager for two tables 14

A while ago I wanted to display contents of two tables in one pager. Records from the second table should be displayed after records from the first one and combine on one page if necessary. This was the case when i needed to display at first the subcategories of a given category and then the products that were in that category.

I tried building some complex queries but Doctrine had problem with unions (at the time). So i figured out a way to display records from two tables and this is what I came up with.

If you’d like to make some changes feel free to fork me on GitHub.

Basically the whole idea is to have active/inactive queries and manipulate their state depending on the case.

<?php

/**
 * dualTableDoctrinePager
 * a pager for combining the results of two tables
 *
 * @author Karol Sójko <karolsojko@gmail.com>
 */
class dualTableDoctrinePager extends sfDoctrinePager
{
  protected $queries = null;

  public function setQueries($queries)
  {
    $pagerQueries = array();
    foreach($queries as $key => $query)
    {
      $pagerQueries[$key]['query']  = $query;
      $pagerQueries[$key]['active'] = true;
    }

    $this->queries = $pagerQueries;
  }

  public function getCountQueries()
  {
    $queries = array();

    foreach($this->queries as $key => $query)
    {
      $queries[$key]['query'] = clone $query['query'];
      $queries[$key]['query']
        ->offset(0)
        ->limit(0);
    }

    return $queries;
  }

  public function init()
  {
    $this->results = null;

    $countQueries = $this->getCountQueries();
    $count = 0;
    $counts = array();

    // remebering counts for each table
    foreach($countQueries as $countQuery)
    {
      $currentCount = $countQuery['query']->count();
      $counts[] = $currentCount;
      $count += $currentCount;
    }

    $this->setNbResults($count);

    // reseting queries
    foreach($this->queries as &$query)
    {
      $query['query']
        ->offset(0)
        ->limit(0)
      ;
    }

    if (0 == $this->getPage() || 0 == $this->getMaxPerPage() || 0 == $this->getNbResults())
    {
      $this->setLastPage(0);
    }
    else
    {
      $offset = ($this->getPage() - 1) * $this->getMaxPerPage();

      $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));

      // set the queries
      if($counts[0] - $offset >= $this->getMaxPerPage())
      {
        // if offset is in the first table only
        foreach($this->queries as $key => &$query)
        {
          if($key == '0')
          {
            $query['active'] = true;
            $query['query']
              ->offset($offset)
              ->limit($this->getMaxPerPage());
          }
          else
          {
            $query['active'] = false;
          }
        }
      }
      else if($offset > $counts[0])
      {
        // if offset is in the second table only
        foreach($this->queries as $key => &$query)
        {
          if($key == '0')
          {
            $query['active'] = false;
          }
          else
          {
            $query['active'] = true;
            $query['query']
              ->offset($offset - $counts[0])
              ->limit($this->getMaxPerPage());
          }
        }
      }
      else
      {
        // if offset is in the first and second table
        foreach($this->queries as $key => &$query)
        {
          if($key == '0')
          {
            $query['active'] = true;
            $query['query']
              ->offset($offset)
              ->limit($counts[0] - $offset);
          }
          else
          {
            $query['active'] = true;
            $query['query']
              ->offset(0)
              ->limit($this->getMaxPerPage() - ($counts[0] - $offset));
          }
        }
      }
    }
  }

  public function getResults($hydrationMode = null)
  {
    $results = array();

    foreach($this->queries as $key => $query)
    {
      if($query['active'])
      {
        $results[] = $query['query']->execute(array(), $hydrationMode);
      }
      else
      {
        $results[] = array();
      }
    }

    return $results;
  }
}

 

14 thoughts on “Symfony doctrine pager for two tables

  1. Reply Akhor Oct 17, 2011 12:45 pm

    I found a mistake. You must use loop “for” instead “foreach” (lines 59, 81, 99 and 117) because in “foreach” $query is a COPY of element $this->queries, not the elenent of array, like in “for”.

  2. Reply Arkadiusz "Arius" Ostrycharz Sep 21, 2011 1:50 pm

    If I may, I would suggest a small change:

    public function getResults($hydrationMode = null)
      {
        $results = array();
    
        foreach($this->queries as $key => $query)
        {
          if($query['active'])
          {
            foreach ($query['query']->execute(array(), $hydrationMode) As $result) {
              $results[] = $result;
            }
          }
        }
    
        return $results;
      }

    AFAIK one list is much better :)

  3. Reply Arkadiusz "Arius" Ostrycharz Sep 21, 2011 11:53 am

    Ech, znalazłem to dopiero jak napisałem własną wersję, ale i tak – kawał dobrej roboty, szczególnie z poprawką Rene.

  4. Reply vibby May 31, 2011 11:38 am

    Thank you very much ! I started coding thius class but finally found yours by good fortune :) Good suprise !

  5. Reply Rene Oct 6, 2010 11:32 am

    :) … I make some changes in your code (if I could ;) ) … lines between 73 and 134 (your “else” in init() method) i’ve replace by:

                $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));
    
                $indexStart = ($this->getPage() - 1) * $this->getMaxPerPage();
                $indexEnd   = $indexStart + $this->getMaxPerPage();
    
                $sumStart = 0;
                $sumEnd   = 0;
    
                foreach($this->queries as $key => &$query)
                {
                    $sumEnd += $counts[$key];
    
                    if ($sumEnd <= $indexStart || $sumStart >= $indexEnd)
                    {
                        $query['active'] = false;
                    }
                    else
                    {
                        $query['active'] = true;
    
                        $currentOffset = ($indexStart - $sumStart);
                        $currentLimit  = $currentOffset > 0 ? $indexEnd - $currentOffset : $indexEnd - $sumStart;
                        if ($currentLimit > $this->getMaxPerPage()) $currentLimit = $this->getMaxPerPage();
                        
                        if ($indexStart > $sumStart && $currentOffset > 0)
                        {
                            $query['query']->offset($currentOffset);
                        }
                        if ($sumEnd > $indexEnd && $currentLimit > 0)
                        {
                            $query['query']->limit($currentLimit);
                        }
                    }
    
                    $sumStart += $counts[$key];
                }
    

    and now you can named your class “MultiTableDoctrinePager” :)

  6. Reply Rene Oct 5, 2010 9:06 pm

    You’re Great! Thanks for that! Nowhere is no such thing and that is very useful!

Leave a Reply