SitePoint Sponsor

User Tag List

Results 1 to 10 of 10

Thread: Mapper Pattern

  1. #1
    SitePoint Enthusiast
    Join Date
    Jun 2007
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Mapper Pattern

    I'm just getting some experiences with the Mapper Pattern, because I didnt like Active Record that much ... But I have one question regarding the implementation: I understand how to fetch ONE result, but what if I wanted to fetch more, maybe even all?

    Some code to emphasize my problem:

    PHP Code:
    class NewsMapper
    {
      private 
    $dbh;
      public function 
    __construct($dbh
      {
        
    $this->dbh $dbh;
      }

      public function 
    getByID($id
      {
        
    //db query, select the id
        
    $row $stmt->fetch();
        return(new 
    News($row));
      }

      public function 
    getAllNews()
      {
        
    //so here I am not sure what to do
        //query all and make something like this:
        
    $news = array();
        foreach(
    $result->fetchAll() as $row) { 
          
    $news[] = new News($row);// I actually dont think this is good?
        
    }
        return(
    $news); 
      }

    Maybe you can give me some advice with that


    (Some other question which I have for a very long time: How can I achieve something like mysql_num_rows with PDO? Any other way than the COUNT(*) way?)

  2. #2
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    There's nothing wrong with your getAllNews, other than it would use a fair bit of memory if you have a lot of results.

    If you're using PDO, you can use the $statement->fetchObject($className); syntax for one row and $statement->fetchAll(PDO::FETCH_CLASS, $className).

    There's no equivalent to mysql_num_rows because PDO uses unbuffered queries.

  3. #3
    SitePoint Enthusiast
    Join Date
    Jun 2007
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So there is no 'more elegant' way of the getAllNews()-Method?
    What about just returning the result and processing it in the View? How do you guys handle it?



    To the second question: If there is no equivalent to mysql_num_rows in pdo, how do you guys do something like this:

    PHP Code:
    $sql "Select * From news";
    //...
    if(mysql_num_rows($result) == 0) echo("No News available"); 

  4. #4
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, you could have getAllNews return an iterator, but I don't have the time to make an example right now.

    As for row counts, if you do a fetchAll into an array, you can just count the size of the array. Otherwise you do a count(*) query.

  5. #5
    SitePoint Addict
    Join Date
    Jan 2005
    Location
    United Kingdom
    Posts
    208
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just to illustrate what 33degrees means with regard to the Iterator:
    PHP Code:
    class NewsMapper
    {
      public function 
    getAllNews()
      {
        
    $recordset $this->db->query$some_sql );
        
    $iterator = new NewsIterator$recordset );
        return 
    $iterator
      }
    }
    class 
    RecordSetIterator
    {
      public function 
    next()
      {
        if( 
    $row $this->recordset->fetch() )
        {
          return 
    $row;
        }
        return 
    false;
    }
    class 
    NewsIterator extends RecordSetIterator
    {
      public function 
    next()
      {
        if( 
    $row parent::next() )
        {
          return new 
    News$row );
        }
        return 
    false;
      }
    }
    $mapper = new NewsMapper;
    $iterator $mapper->fetchAll();
    while( 
    $newsitem $iterator->next() )
    {
      
    // do something with $newsitem

    This can be made a little more elegant by implementing the SPL Iterator interface within ResultSetIterator, which would allow:
    PHP Code:
    $mapper = new NewsMapper;
    foreach( 
    $mapper->getAllNews() as $newsitem )
    {
      
    // do something with $newsitem

    I don't use PDO but I believe a PDOStatement object is foreachable anyway, so if the News object is pretty simple you could probably do away with much of the above. Still it's good to illustrate how it works.

  6. #6
    SitePoint Enthusiast
    Join Date
    Jun 2007
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    looks interesting and much more elegant than my solution. I will have a close look at it now and will try to understand it

    Thanks for this!

  7. #7
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Shrike View Post
    This can be made a little more elegant by implementing the SPL Iterator interface within ResultSetIterator
    What is an SPL Iterator interface?

  8. #8
    SitePoint Addict
    Join Date
    Jan 2005
    Location
    United Kingdom
    Posts
    208
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by allspiritseve View Post
    What is an SPL Iterator interface?
    Defined here.

  9. #9
    SitePoint Enthusiast
    Join Date
    Jul 2005
    Posts
    86
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I got inspired by this and tried to implement a solution to this based on mysqli (should be easy to port to any DB interface):

    PHP Code:
    abstract class DataAccess_Iterator implements Iterator {

        
    /**
         * Current resultset
         *
         * @var mysqli_result
         */
        
    protected $resultSet;

        
    /**
         * The current for as an associative array
         * @var array
         */
        
    private $currentRow;

        
    /**
         * Current line in the iterator
         * @var int
         */
        
    private $currentLine 0;

        
    /**
         * @param mysqli_result $resultSet
         */
        
    public function __construct(mysqli_result $resultSet){
            
    $this->resultSet $resultSet;        
        }

        
    /**
         * Get the current row in the iterator
         * 
         * @return array
         */
        
    public function current(){
            return 
    $this->currentRow;
        }

        
    /**
         * Get the key of the current field in the iterator
         *
         * @return int
         */
        
    public function key(){
            return 
    $this->currentLine;
        }

        
    /**
         * Move the iterator to the next step
         * @return void
         */
        
    public function next() {
            
    $this->currentLine++;
            
    $this->currentRow $this->resultSet->fetch_assoc();
        }

        
    /**
         * Reset the iterator to 0
         *
         * @return boolean
         */
        
    public function rewind(){
            
    $this->currentLine 1;
            return 
    $this->resultSet->data_seek(0);
        }

        
    /**
         * Checks if the current index is valid
         *
         * @return boolean
         */
        
    public function valid(){
            return (
    $this->currentLine <  $this->resultSet->num_rows);
        }

    }

    class 
    DataAccess_Iterator_Object extends DataAccess_Iterator  {

        
    /**
         * Name of the class to create on iteration
         *
         * @var string
         */
        
    private $className;

        
    /**
         * @throws Exception
         * 
         * @param string $className
         * @param mysqli_result $resultSet
         */
        
    public function __construct($classNamemysqli_result $resultSet){
            
            if( !
    class_exists($className) ){
                throw new 
    Exception('Class '.$className.' does not exsist.');
            }
            
            
    parent::__construct($resultSet);
            
    $this->className $className;        
        }
        
        
    /**
         * Returns an instance of the object given in $this->className
         *
         * @return object
         */
        
    public function current(){
            return new 
    $this->className(parent::current());
        }
    }
    ?> 
    This should work as a generic iterator for all classes where the relationship between rows and object is 1:1. If you need more advanced relationships you can just subclass the Iterator class to create specific Iterators.

    To relate this to previous examples, the NewsMapper had to return a iterator like this:

    PHP Code:
    <?php
    try{
      
    $iterator = new DataAccess_Iterator_Object($resultset,'News');
    }
    catch(
    Exception $e){

    }
    return 
    $iterator;
    ?>
    And then one could do like this:
    PHP Code:
    <?php
    $mapper 
    = new NewsMapper();
    $iterator $mapper->fetchAll();
    foreach(
    $iterator as $news){

    }
    ?>

    I have not tested this thoroughly, but initial use seems to work. Any comments to this approach?

    There are some bugs in the code, so consider as proof of concept code. I'll work a little further on this. Send me a message if you'd like an updated version.
    Last edited by stadskle; Nov 13, 2007 at 11:50.

  10. #10
    SitePoint Enthusiast
    Join Date
    Jun 2007
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, I tried to implement something like this too, but I failed to do it with PDO since there is no equivalent to num_rows() and data_seek() ... maybe some more experienced user here can help there?


    And can somebody sum up the pros and cons of the iterator-style? Because I havent seen a model returning an iterator very often before, so I think there must be some kind of disadvantage?


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •