SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 28
  1. #1
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    DAO within relational schemes

    First of all let me apologize for the code to come. Its fairly large and serves only as an example. I wrote this in like an hour or so and I didn't test it to see if it worked ._.

    I was attempting to implement a small DAO and hit a bit of a snag. Most of the examples that I've seen hard code the SQL which is a problem for me. I don't enjoy having to write and/or maintain SQL, so I wanted a solution that would avoid this. To avoid having to hard code the SQL I ended up forcing my DTO retain additional information on how the the data relates. My DTO ended up being a model of table relations and became far to complex to be a DTO... But here is the code that spewed out. Any input would be greatly appreciated.

    PHP Code:
    <?php

        
    interface DBInterface
        
    {
            public function 
    getName();
            public function 
    getValue();
             public function 
    setName();
            public function 
    setValue();
            public function 
    clear();        
        }
        
        interface 
    DBAdapterInterface
        
    {
            public function 
    insert($table$columns);
            public function 
    update($table$columns);
            public function 
    delete($table$id);
        }
        
        class 
    Zend_Db_Wrapper implements DBAdapterInterface
        
    {
             public function 
    __construct($connection)
             {
                
    $this->_db $connection;
            }
            
            public function 
    insert($table,$columns)
            {
                return 
    $this->_db->insert($table$columns);
            }
            
            public function 
    update($table,$columns)
            {
                return 
    $this->_db->update($table$columns);
            }
            
            public function 
    delete($table,$id)
            {
                return 
    $this->_db->delete($table$id);
            }
        }
        
        abstract class 
    DBAbstract implements DBInterface
        
    {
            protected 
    $_name null;
            protected 
    $_value =  null;
            
            public function 
    getName()
            {
                return 
    $this->_name;
            }
            
            public function 
    getValue()
            {
                return 
    $this->_value;
            }
            
            public function 
    setName($name)
            {
                
    $this->_name $name;
                return 
    true;
            }
            
            public function 
    setValue($value)
            {
                
    $this->_value $value;
                return 
    true;
            }
            
            public function 
    clear()
            {
                 unset(
    $this->_name);
                unset(
    $this->_value);
            }
        }

        class 
    DBColumn extends DBAbstract
        
    {
            public function 
    __construct($name$value null)
            {
                
    $this->setName($name);
                
    $this->setValue($value);
            }
            
            static public function 
    getColumn($name$value)
            {
                return new 
    DBColumn($name$value);
            }
        }
        
        class 
    DBTable extends DBAbstract
        
    {
             protected 
    $_primaryKey =  null;
            public function 
    __contruct($name$columns null)
            {
                
    $this->setName($name);
                if(!empty(
    $columns))
                {
                     
    $columns = (array) $columns;
                    
    $this->setValue($columns);
                }
            }
            
            static public function 
    getTable($name, array $columns)
            {
                return new 
    DBTable($name$columns);
            }
            
            public function 
    setPrimaryKey($name)
            {
                
    $this->_primaryKey $name;
            }
            
            public function 
    getPrimaryKey()
            {
                return 
    $this->_primaryKey;
            }
            
            public function 
    getColumnsAsArray()
            {
                 
    $columns = array();
                foreach(
    $this->_columns as $c)
                {
                     
    $name $c->getName();
                     
    $value $c->getValue();
                    
    $columns[$name] = $value;
                }
                return 
    $columns;                
            }
            
            public function 
    __set($column$value)
            {
                return 
    $this->set($column$value)
            }
            
            public function 
    set($column$value)
            {
                foreach(
    $this->_columns as $c)
                    if(
    $c->getName() == $column)
                        return 
    $c->setValue($value);
            }
        
            public function 
    __get($column)
            {
                return 
    $this->get($column);
            }
            
            public function 
    get($column)
            {
                foreach(
    $this->_columns as $c)
                    if(
    $c->getName() == $column)
                        return 
    $c->getValue();
            }
        }

        abstract class 
    DBScheme
        
    {
            protected 
    $_tables = array();
            protected 
    $_defaultTable null;
            
            public function 
    addTable($table)
            {
                 if(
    $table instanceof DBTable)
                    
    $this->_tables[] = $table;
                else
                    
    $this->_tables[] = new DBTable($table)
                return 
    $this;
            }
            
            public function 
    addTables($tables)
            {
                foreach(
    $tables as $table)
                    
    $this->addTable($table);
                return 
    $this;
            }
            
            public function &
    getTable($table)
            {
                foreach(
    $this->_tables as $t)
                    if(
    $t->getName() == $table)
                        return 
    $t;
                return 
    false;
            }
            
            public function 
    getTables()
            {
                return 
    $this->_tables;
            }
            
            public function 
    getDefaultTable()
            {
                
    $table $this->_defaultTable;
                return 
    $this->getTable($table);
            }
            
            public function 
    setTables($tables)
            {
                
    $this->clear();
                
    $this->addTables($tables);
            }
            
            public function 
    clear()
            {
                unset(
    $this->_tables);
                unset(
    $this->_defaultTable);
            }
            
            public function 
    isDefaultTable($table)
            {
                return 
    $table == $this->_defaultTable;
            }
            
            
    pulic function hasPrimaryKey()
            {
                return 
    $this->getDefaultTable()
                            ->
    get($default->getPrimaryKey())
                            ->
    getValue();
            }
        }
        
        class 
    UsersAccount extends DBScheme
        
    {
            public function 
    __construct()
            {
                
    $user = new DBTable('Users');
                
    $user->addColumn('Uid')
                     ->
    addColumn('Uusername')
                     ->
    addColumn('Upassword')
                     ->
    addColumn('Uemail')
                     ->
    setPrimary('Uid');
                
                
    $profile = new DBTable('Profiles');
                
    $profile->addColumn('Uid')
                        ->
    addColumn('Pid')
                        ->
    addColumn('Pfirstname')
                        ->
    addColumn('Plastname')
                        ->
    addColumn('Page')
                        ->
    setPrimary('Pid');
                
                
    $this->addTables(array($user$profile));
                
    $this->setDefaultTable('Users');
            }
        }

        abstract class 
    DOA
        
    {
             protected 
    $_db null;
             protected 
    $_dto null
         
             
    public function __construct(DBAdapterInterface$dbDBScheme $dto null)
             {
                
    $this->_db $db;
            }
            
            public function 
    save()
            {
                 
    $dto $this->_dto;
                if(
    $dto->hasPrimaryKey())
                    return 
    $this->create($dto);
                else
                    return 
    $this->update($dto);
            }
            
            public function 
    create()
            {
                 
    $db $this->_db;
                 
    $dto $this->_dto;
                 
    $id null;
                 
    $pk null;
                 
                 if(!
    $id)
                 {
                      
    $default $dto->getDefaultTable();
                      
    $table $default->getName();
                    
    $columns $default->getColumnsAsArray();
                    
    $result $db->insiert($table$columns);
                    
    $id    $result->lastInsertId();
                    
    $pk $default->getPrimaryKey();
                }
                 else
                {
                     
    $tables $dto->getTables();
                    foreach(
    $tables as $t)
                    {
                         if(!
    $t->isDefaultTable())
                         {
                             
    $t->$pk $id;
                             
    $table $t->getName();
                             
    $columns $t->getColumnsAsArray();
                             
    $result $db->insert($table$columns);
                        }
                    }
                }                
            }
            
            public function 
    update()
            {
                
    $db $this->_db;
                 
    $dto $this->_dto;
                 
    $tables $dto->getTables();
                 
    forach($tables as t)
                 {
                      
    $table $t->getName();
                    
    $columns $t->getColumnsAsArray();
                    
    $result $db->update($table$columns);
                }
            }
        }    
        
        class 
    UserAccountDAO extends DAO
        
    {}
        
        
    $userAccount = new UserAccount();
        
    $user = &$userAccount->getTable('user');    
        
    $user->Uusername 'isausername';
        
    $user->Upassword 'isapassword';
        
    $user->Uemail 'isa@email.com';
        
        
    $profile = &$userAccount->getTable('profile');
        
    $profile->Pfirstname 'isafirstname';
        
    $profile->Plastname 'isalastname';
        
    $profile->Page 'isaage';
        
        
    $dao = new UserAccountDAO($dbConn$userAccount);
        
    $dao->save();
    ?>

  2. #2
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Wow, great timing. I'm currently working on an open-source project to solve the same problem. I saw Ruby DataMapper and got inspired to make a sort of "translation" of that project to PHP. I would love for you to help me out with it.

    One of the main things it does is force you to define table structure in the model, with the goal of enabling auto migrations. I see your code here is essentially doing that same thing - creating the table structure. That's a key missing element right now to my project that I was planning on working on next. Here's what my table definitions will look like from the Getting Started page:

    PHP Code:
    // Post
    class PostMapper extends PHPDataMapper_Model {
        
    // Specify the database table
        
    protected $table "blog_posts";
     
        
    // Define your fields
        
    protected $fields = array(
            
    'id' => array('type' => 'int''primary' => true),
            
    'title' => array('type' => 'string''required' => true),
            
    'body' => array('type' => 'text''required' => true),
            
    'status' => array('type' => 'string''default' => 'draft'),
            
    'date_created' => array('type' => 'datetime')
        );

    So I was planning to make a function something like this in my database-specific adapters:

    PHP Code:
    public function migrate($table$fields) {
       
    // Loop over all fields, create SQL, execute, return true/false

    Seeing as you've already waded into the water here, I thought it was worth asking if you wanted to help by contributing to an already more complete solution that works?

    The project is PHP DataMapper
    Google Code: http://code.google.com/p/phpdatamapper/

  3. #3
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PHP Code:
    // Define your fields
        
    protected $fields = array(
            
    'id' => array('type' => 'int''primary' => true),
            
    'title' => array('type' => 'string''required' => true),
            
    'body' => array('type' => 'text''required' => true),
            
    'status' => array('type' => 'string''default' => 'draft'),
            
    'date_created' => array('type' => 'datetime')
        ); 
    This is IMHO tight coupling and you have only one choice for field source. What about setFields method with code for reading from some config file (XML, YAML etc.)

    imaginethis:
    PHP Code:
        class UsersAccount extends DBScheme
        
    {
            public function 
    __construct()
            {
                
    $user = new DBTable('Users');
                
    $user->addColumn('Uid')
                     ->
    addColumn('Uusername')
                     ... 
    Same objection. How would you allow user update of table structure in this scenario?

  4. #4
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is IMHO tight coupling and you have only one choice for field source. What about setFields method with code for reading from some config file (XML, YAML etc.)
    I personally hate configuration in separate files, and that won't be the path this project takes - ever. The exact point of making one mapper per table IS to create a tight coupling. The model needs to know about all the fields and what type of data they contain. The best place to define the fields and their types is therefore directly in the model itself without jumping through any additional hoops. Updating table structure would be as easy as adding a new field to the array and calling the migrate() function.

  5. #5
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Czaries View Post
    The model needs to know about all the fields and what type of data they contain.
    No doubt about it. But external config makes no harm to this principle.

    Quote Originally Posted by Czaries View Post
    Updating table structure would be as easy as adding a new field to the array and calling the migrate() function.
    So you have to replace file with code. From developer point of view - OK. From user point of view - ?

  6. #6
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Mastodont View Post
    PHP Code:
    // Define your fields
        
    protected $fields = array(
            
    'id' => array('type' => 'int''primary' => true),
            
    'title' => array('type' => 'string''required' => true),
            
    'body' => array('type' => 'text''required' => true),
            
    'status' => array('type' => 'string''default' => 'draft'),
            
    'date_created' => array('type' => 'datetime')
        ); 
    This is IMHO tight coupling and you have only one choice for field source. What about setFields method with code for reading from some config file (XML, YAML etc.)

    imaginethis:
    PHP Code:
        class UsersAccount extends DBScheme
        
    {
            public function 
    __construct()
            {
                
    $user = new DBTable('Users');
                
    $user->addColumn('Uid')
                     ->
    addColumn('Uusername')
                     ... 
    Same objection. How would you allow user update of table structure in this scenario?
    You mean other than typing? I'm not exactly sure what your talking about? If your objection is that the code is tightly coupled to PHP? I think I'd have to object to your objection... In any case, the code was written in an hour and by no means was I expecting it to be used by anyone else.

    Czaries, thanks for the idea. I think I was looking for a Mapper rather a simple DAO. I'll revisit my example and see if I can make some clarifications.

  7. #7
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Mastodont View Post
    So you have to replace file with code. From developer point of view - OK. From user point of view - ?
    Why would you EVER want users to mess with your table structure? I think you're misunderstanding the purpose here. The PHP DataMapper project is just meant to drive the model portion of an application. Developers are the only ones who will ever go near the model. Users don't even know what a model IS, and they shouldn't. I still fail to see any advantage at all to going towards a configuration system based on separate external files, when the data is already exactly where it needs to be, already right with the model that's talking to the table?

  8. #8
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Mastodont View Post
    No doubt about it. But external config makes no harm to this principle.



    So you have to replace file with code. From developer point of view - OK. From user point of view - ?
    What developer in there right mind would be dumb enough to let a user modify business logic? That's like giving a suicidal person a gun and asking them politely not to shoot themselves... Developers, if they are hell bent on using configuration files of any sort, probably have a configuration scheme that they are already using and can "develop" a method for getting configured data to configure an object >.>

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    I think hard coding the sql is a necessary evil that promotes readability, flexibility and maintainability of the code. I'm currently working a application that uses this logic and hard coding the sql yields ease in extendability and modification. There is never a need to jump through generalization hoops. The logic reflects the objective not a generalization which makes my life easier and code readability straight forward.

    Code:
    /******************************/
    
    class User
    
    class UserDAO {  }
    
    class UserInspector { }
    
    class UserCollection { }
    
    class UserDisplay { }
    
    /******************************/
    Here is small sample of ever developing interface:

    Code:
    class UserDAO extends DAO {
    
    	public function insert(User $pUser) {
    	}
    	
    	public function findByNameAndPwd($pName,$pPwd) {
    	}
    	
    	public function findByNameAndPassAnswer($pName,$pPassAnswer) {
    	}
    	
    }
    Even though, there are going to to a large number of classes the logic remains simple and each function/class holds a small number of responsibilities.

    I'm not saying you should change your stand point. I'm just suggesting my reasoning to hard code rather then generalize. Generalization involves assumptions. The more assumptions you can avoid the simpler your code and logic will be.

    Heres the dao currently:

    Code:
    class DAO {
    	
    	protected $_db;
    	
    	public function __construct(Database $pDatabase) {
    		$this->_db = $pDatabase;
    	}
    	
    	protected function _bindParams(PDOStatement &$pStmt,$pValues,$pTypes) {
    		
    		for($i=0;$i<count($pValues);$i++) {
    			$pStmt->bind($i+1,$pValues[$i],$pTypes[$i]);
    		}
    		
    	}
    	
    }
    This is all just draft code at the moment.

  10. #10
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    This is probably a better example.

    Code:
    class BidDAO extends DAO {
    
    	private function _bindColumns(PDOStatement &$pStmt,&$pProjectId,&$pUserId,&$pAmount,&$pTimeline,&$pTerms,&$pCreated) {
    		
    		$pStmt->bindColumn('project_id',$pProjectId);
    		$pStmt->bindColumn('user_id',$pUserId);
    		$pStmt->bindColumn('amount',$pAmount);
    		$pStmt->bindColumn('timeline',$pTimeline);
    		$pStmt->bindColumn('terms',$pTerms);
    		$pStmt->bindColumn('created',$pCreated);
    		
    	}
    	
    	public function insert(Bid &$pBid) {
    	
    		$sql = 'INSERT INTO bids (project_id,user_id,amount,timeline,terms,created) VALUES (?,?,?,?,?,?)';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    			
    			$this->bindParams(
    				$stmt
    				,array(
    					$pBid->getProjectId()
    					,$pBid->getUserId()
    					,$pBid->getAmount()
    					,$pBid->getTimeline()
    					,$pBid->getTerms()
    					,$pBid->getCreated()
    				)
    				,array(
    					PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_STR
    					,PDO::PARAM_INT
    				)
    			);
    			
    			
    			if($stmt->execute()) {
    				return true;
    			}
    			
    		}
    	
    	}
    	
    	public function findByProjectIdAndUserId($pProjectId,$pUserId) {
    		
    		$sql = 'SELECT project_id,user_id,amount,timeline,terms,created FROM bids WHERE project_id = ? AND user_id = ? LIMIT 1';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pProjectId,$pUserId),array(PDO::PARAM_INT,PDO::PARAM_INT));
    			
    			if($stmt->execute()) {
    			
    				$this->_bindColumns($stmt,$project_id,$user_id,$amount,$timeline,$terms,$created);
    				
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    					return new Bid(
    						$project_id
    						,$user_id
    						,$amount
    						,$timeline
    						,$terms
    						,$created
    					);
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	public function findByProjectId($pProjectId) {
    	
    		$sql = 'SELECT project_id,user_id,amount,timeline,terms,created FROM bids WHERE project_id = ?';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pProjectId),array(PDO::PARAM_INT));
    			
    			if($stmt->execute()) {
    			
    				$this->_bindColumns($stmt,$project_id,$user_id,$amount,$timeline,$terms,$created);
    				
    				$collection = new BidCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Bid(
    							$project_id
    							,$user_id
    							,$amount
    							,$timeline
    							,$terms
    							,$created
    						)
    					);
    				}			
    				return $collection;
    			
    			}
    			
    		}
    	
    	}
    	
    	public function findByUserId($pUserId) {
    	
    		$sql = 'SELECT project_id,user_id,amount,timeline,terms,created FROM bids WHERE user_id = ?';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pUserId),array(PDO::PARAM_INT));
    			
    			if($stmt->execute()) {
    			
    				$this->_bindColumns($stmt,$project_id,$user_id,$amount,$timeline,$terms,$created);
    				
    				$collection = new BidCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Bid(
    							$project_id
    							,$user_id
    							,$amount
    							,$timeline
    							,$terms
    							,$created
    						)
    					);
    				}			
    				return $collection;
    			
    			}
    			
    		}
    	
    	}
    	
    }

  11. #11
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    On small application, hard coding isn't such a problem. Unfortunately, many of the applications I'm asked to write at work range between 1000 and 10,000 lines of code. Having to maintain the SQL for some of these applications proves to be a challenge especially when a client changes there mind mind development, an unavoidable evil.

    I would be interested in seeing your implementation so as to get a better understanding of the differences between Mappers and DAOs. My attempts thus far to find information using google has been less than informative ._.

  12. #12
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    I think hard coding the sql is a necessary evil that promotes readability, flexibility and maintainability of the code. I'm currently working a application that uses this logic and hard coding the sql yields ease in extendability and modification. There is never a need to jump through generalization hoops.
    I kindly disagree. I almost always prefer a more general interface, because there are less specific functions to learn, and once you learn them, they can be applied across your entire code base.

    Quote Originally Posted by oddz View Post
    Here is small sample of ever developing interface:

    Code:
    class UserDAO extends DAO {
    
    	public function insert(User $pUser) {
    	}
    	
    	public function findByNameAndPwd($pName,$pPwd) {
    	}
    	
    	public function findByNameAndPassAnswer($pName,$pPassAnswer) {
    	}
    	
    }
    With PHP DataMapper, the usage would look like this instead:

    PHP Code:
    #=> insert()
    $userMapper->save($user);

    #=> findByNameAndPwd()
    $user $userMapper->first(array('name' => $pName'password' => $pPwd));


    #=> findByNameAndPassAnswer()
    $user $userMapper->first(array('name' => $pName'password_answer' => $pPassAnswer)); 
    I would rather avoid writing custom SQL queries at all costs wherever possible, because they tie the application to a specific database and take up development time that can easily be removed by using a more generic implementation. You get automatic prepared statements with bound parameters and you can use native PHP code with no SQL at all.

  13. #13
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    However, exceptions are not kindly welcome. I see what your saying, but exceptions and circumstances that you didn't plan for are always inevitable with generalizations. Then in the time spent to modify the existing implementation to conform to that exception in a general sense ultimately results in more time then hard coding. At least that's normally the flaws I run into with generalized implementations that I rather avoid. I guess it depends on the circumstance. I have a full understanding of my applications working schema so I rather it be a direct reflection of it then a generalized one. If something changes then I change the code. No biggie especially because the implementation is straight forward and simple avoiding the hoops to create generalization.

    The main problem is that the mapper only accounts for certain circumstances. If those circumstances change then all the code needs to change. This will essentially effect the entire application. However, by creating small methods that with single responsibilites the functionality is contained to those methods only. If one method doesn't yield the expected results then the damage is contained to that method which makes debugging easier. Its much easier and readable to code for the objective rather then accounting for every single circumstance which is pretty much impossible. With generalization you loose flexibility. With hard coding for the objective you accomplish exactly what needs to be accomplished and it is staright forward so it can be easily changed without bubbling through the rest of the application.

    if the method findByProjectId() yields unexpected results in my implementation then I can fix that method. However, in your mapper I have to figure out what is wrong while avoiding destroying the other parts of the application that are dependent on that mapper. I don't think this is a very proficient. I shouldn't need to worry about affecting all parts of the application that rely on that mapper just to debug one circumstance. That circumstance should be contained to a single method.

  14. #14
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It might be more time than hard coding but when you have a highly reusable piece of code that you can utilize cross multiple projects and circumstances, you will spend less time coding. The initial work required in developing is outweighed by the benefits that generalized code provides. The same argument can be used for frameworks. Sure you can probably spend less time programing without developing a framework, but down the line you'll end up repeating yourself in one way or another. You end up working for code rather than having code work for you.

  15. #15
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    This is a fairly large one and it will probably increase as time goes on, but you should be able to able to grasp the idea from the readability of the code.

    Code:
    class ProjectDAO extends DAO {
    
    	private function _bindColumns(
    		,PDOStatement $pStmt
    		,$pTitle
    		,$pMinBudget
    		,$pMaxBudget
    		,$pDescription
    		,$pDeadline
    		,$pOpen
    		,$pClose
    		,$pPlacement
    		,$pStatus
    		,$pId
    	) {
    	
    		$pStmt->bindColumn('title',$pTitle);
    		$pStmt->bindColumn('min_budget',$pMinBudget);
    		$pStmt->bindColumn('max_budget',$pMaxBudget);
    		$pStmt->bindColumn('description',$pDescription);
    		$pStmt->bindColumn('deadline',$pDeadline);
    		$pStmt->bindColumn('open',$pOpen);
    		$pStmt->bindColumn('close',$pClose);
    		$pStmt->bindColumn('placement',$pPlacement);
    		$pStmt->bindColumn('status',$pStatus);
    		$pStmt->bindColumn('id',$pId);
    	
    	}
    	
    	public function insert(Project $pProject) {
    		
    		$sql = 'INSERT INTO projects (title,min_budget,max_budget,description,deadline,open,close,placement,status) VALUES (?,?,?,?,?,?,?,?,?)';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindColumns(
    				$stmt
    				,array(
    					$pProject->getTitle()
    					,$pProject->getMinBudget()
    					,$pProject->getMaxBudget()
    					,$pProject->getDescription()
    					,$pProject->getDeadline()
    					,$pProject->getOpen()
    					,$pProject->getClose()
    					,$pproject->getPlacement()
    					,$pProject->getStatus()
    				)
    				,array(
    					,PDO::PARAM_STR
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_STR
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    				)
    			);
    			
    			if($stmt->execute()) {
    				return true;
    			}
    			
    		}
    		
    	}
    
    	public function findById($pId) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE id = ? LIMIT 1';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    			
    			$this->_bindParams($stmt,array($pId),array(PDO::PARAM_INT));
    			
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    					return new Project(
    						,$title
    						,$min_budget
    						,$max_budget
    						,$description
    						,$deadline
    						,$open
    						,$close
    						,$placement
    						,$status
    						,$id
    					);
    						
    				}
    			
    			}
    			
    		}
    	
    	}
    	
    	public function findOpenWithLimitOrderByOpenAscending($pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpen($pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	public function findOpenFilterBudgetWithLimitOrderByOpenAscending($pBudget,$pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE ? between min_budget AND max_budget AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pBudget),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpenWithBudgetFilter($pBudget,$pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE ? between min_budget AND max_budget AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pBudget),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	
    	public function findOpenFilterOwnerWithLimitOrderByOpenAscending($pOwner,$pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE owner = ? AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpenWithOwnerFilter($pOwner,$pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE owner = ? AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	public function findByOwnerWithLimitOrderByOpenAscending($pOwner,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE owner = ? ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllWithOwnerFilter($pOwner) {
    	
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE owner = ?';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    	
    	}
    
    }
    Here is a look at the interface.

    Code:
    class ProjectDAO extends DAO {
    
    	private function _bindColumns(
    		,PDOStatement $pStmt
    		,$pTitle
    		,$pMinBudget
    		,$pMaxBudget
    		,$pDescription
    		,$pDeadline
    		,$pOpen
    		,$pClose
    		,$pPlacement
    		,$pStatus
    		,$pId
    	) {
    	
    	}
    	
    	public function insert(Project $pProject) {    }
    
    	public function findById($pId) {  }
    	
    	public function findOpenWithLimitOrderByOpenAscending($pCurrentTime,$pFrom,$pTo) { }
    	
    	public function countAllThatAreOpen($pCurrentTime) {   }
    	
    	public function findOpenFilterBudgetWithLimitOrderByOpenAscending($pBudget,$pCurrentTime,$pFrom,$pTo) {  }
    	
    	public function countAllThatAreOpenWithBudgetFilter($pBudget,$pCurrentTime) {   }
    	
    	public function findOpenFilterOwnerWithLimitOrderByOpenAscending($pOwner,$pCurrentTime,$pFrom,$pTo) {   }
    	
    	public function countAllThatAreOpenWithOwnerFilter($pOwner,$pCurrentTime) {   }
    	
    	public function findByOwnerWithLimitOrderByOpenAscending($pOwner,$pFrom,$pTo) {   }
    	
    	public function countAllWithOwnerFilter($pOwner) {  }
    
    }
    The largest advantage is that without breaking the existing application I can add new methods because all public methods are independent of each other. What that further means is that if one method doesn't yield the correct results then the damage is within that method. Anything that is changed in that method will never resonate throughout the rest of the application. All logic is simple and straight forward. You want to find all open projects you call the method, no need to pass a array for that circumstance because the methods reflect a distinct outcome rather then a abstract one.

  16. #16
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    I've actually been a practitioner of generalized code and frameworks for quit some time. However, I've been reading this book called Clean Code and it has really made me question and ultimately change the way I program for the better.

  17. #17
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    The largest advantage is that without breaking the existing application I can add new methods because all public methods are independent of each other. What that further means is that if one method doesn't yield the correct results then the damage is within that method. Anything that is changed in that method will never resonate throughout the rest of the application. All logic is simple and straight forward. You want to find all open projects you call the method, no need to pass a array for that circumstance because the methods reflect a distinct outcome rather then a abstract one.
    Well, the entire point of my project is to generalize and provide the most commonly needed functionality built-in. Most of the time, people just want to create/edit/delete rows and retrieve records based on simple conditions. They may also have related tables, etc. so the mapper provides all that built-in. It's kind of like the 80/20 rule (pareto principle) - I give 80% of what's needed out of the box, and in most cases that's enough.

    For the other 20%, I provide other methods and ways to get the data you need that require a little more manual work. You would never have to hack a solution together. PHP DataMapper provides an easy function for running your own queries with raw SQL (query()), and a query builder object if you prefer not to write the actual SQL to keep it more database independent.

    The result is that you can still rely on the mapper for all the mundane insert/update/delete operations, while customizing your select queries for special cases. You can still define your own custom functions in the mapper (you extend from the base mapper) to pass the parameters you need, run your queries, and return the results just the same as you have done above, but still with less work and less code because the parameter binding is automated.

    So you really get the best of both worlds with an intelligently designed generic interface.

  18. #18
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    This is a fairly large one and it will probably increase as time goes on, but you should be able to able to grasp the idea from the readability of the code.

    Code:
    class ProjectDAO extends DAO {
    
    	private function _bindColumns(
    		,PDOStatement $pStmt
    		,$pTitle
    		,$pMinBudget
    		,$pMaxBudget
    		,$pDescription
    		,$pDeadline
    		,$pOpen
    		,$pClose
    		,$pPlacement
    		,$pStatus
    		,$pId
    	) {
    	
    		$pStmt->bindColumn('title',$pTitle);
    		$pStmt->bindColumn('min_budget',$pMinBudget);
    		$pStmt->bindColumn('max_budget',$pMaxBudget);
    		$pStmt->bindColumn('description',$pDescription);
    		$pStmt->bindColumn('deadline',$pDeadline);
    		$pStmt->bindColumn('open',$pOpen);
    		$pStmt->bindColumn('close',$pClose);
    		$pStmt->bindColumn('placement',$pPlacement);
    		$pStmt->bindColumn('status',$pStatus);
    		$pStmt->bindColumn('id',$pId);
    	
    	}
    	
    	public function insert(Project $pProject) {
    		
    		$sql = 'INSERT INTO projects (title,min_budget,max_budget,description,deadline,open,close,placement,status) VALUES (?,?,?,?,?,?,?,?,?)';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindColumns(
    				$stmt
    				,array(
    					$pProject->getTitle()
    					,$pProject->getMinBudget()
    					,$pProject->getMaxBudget()
    					,$pProject->getDescription()
    					,$pProject->getDeadline()
    					,$pProject->getOpen()
    					,$pProject->getClose()
    					,$pproject->getPlacement()
    					,$pProject->getStatus()
    				)
    				,array(
    					,PDO::PARAM_STR
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_STR
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    					,PDO::PARAM_INT
    				)
    			);
    			
    			if($stmt->execute()) {
    				return true;
    			}
    			
    		}
    		
    	}
    
    	public function findById($pId) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE id = ? LIMIT 1';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    			
    			$this->_bindParams($stmt,array($pId),array(PDO::PARAM_INT));
    			
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    					return new Project(
    						,$title
    						,$min_budget
    						,$max_budget
    						,$description
    						,$deadline
    						,$open
    						,$close
    						,$placement
    						,$status
    						,$id
    					);
    						
    				}
    			
    			}
    			
    		}
    	
    	}
    	
    	public function findOpenWithLimitOrderByOpenAscending($pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpen($pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	public function findOpenFilterBudgetWithLimitOrderByOpenAscending($pBudget,$pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE ? between min_budget AND max_budget AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pBudget),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpenWithBudgetFilter($pBudget,$pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE ? between min_budget AND max_budget AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pBudget),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	
    	public function findOpenFilterOwnerWithLimitOrderByOpenAscending($pOwner,$pCurrentTime,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE owner = ? AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).') ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllThatAreOpenWithOwnerFilter($pOwner,$pCurrentTime) {
    		
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE owner = ? AND status = 1 AND close < FROM_UNIXTIME('.strtotime($pCurrentTime).')';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    		
    	}
    	
    	public function findByOwnerWithLimitOrderByOpenAscending($pOwner,$pFrom,$pTo) {
    	
    		$sql = 'SELECT id,title,min_budget,max_budget,description,deadline,open,close,placement,status FROM projects WHERE owner = ? ORDER BY open ASC LIMIT '.$pFrom.','.$pTo;
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$this->_bindColumns(
    					$stmt
    					,$title
    					,$min_budget
    					,$max_budget
    					,$description
    					,$deadline
    					,$open
    					,$close
    					,$placement
    					,$status
    					,$id
    				);
    				
    				$collection = new ProjectCollection();
    				while($stmt->fetch(PDO::FETCH_BOUND)) {
    					$collection->add(
    						new Project(
    							,$title
    							,$min_budget
    							,$max_budget
    							,$description
    							,$deadline
    							,$open
    							,$close
    							,$placement
    							,$status
    							,$id
    						)
    					);
    						
    				}
    			
    			}
    		
    		}
    	
    	}
    	
    	public function countAllWithOwnerFilter($pOwner) {
    	
    		$sql = 'SELECT COUNT() AS total FROM projects WHERE owner = ?';
    		
    		if($stmt = $this->_db->prepare($sql)) {
    		
    			$this->_bindParams($stmt,array($pOwner),array(PDO::PARAM_INT));
    		
    			if($stmt->execute()) {
    			
    				$stmt->bindColumn('total',$total);
    			
    				if($stmt->fetch(PDO::FETCH_BOUND)) {
    				
    					return $total;
    				
    				}
    			
    			}
    			
    		}
    	
    	}
    
    }
    Here is a look at the interface.

    Code:
    class ProjectDAO extends DAO {
    
    	private function _bindColumns(
    		,PDOStatement $pStmt
    		,$pTitle
    		,$pMinBudget
    		,$pMaxBudget
    		,$pDescription
    		,$pDeadline
    		,$pOpen
    		,$pClose
    		,$pPlacement
    		,$pStatus
    		,$pId
    	) {
    	
    	}
    	
    	public function insert(Project $pProject) {    }
    
    	public function findById($pId) {  }
    	
    	public function findOpenWithLimitOrderByOpenAscending($pCurrentTime,$pFrom,$pTo) { }
    	
    	public function countAllThatAreOpen($pCurrentTime) {   }
    	
    	public function findOpenFilterBudgetWithLimitOrderByOpenAscending($pBudget,$pCurrentTime,$pFrom,$pTo) {  }
    	
    	public function countAllThatAreOpenWithBudgetFilter($pBudget,$pCurrentTime) {   }
    	
    	public function findOpenFilterOwnerWithLimitOrderByOpenAscending($pOwner,$pCurrentTime,$pFrom,$pTo) {   }
    	
    	public function countAllThatAreOpenWithOwnerFilter($pOwner,$pCurrentTime) {   }
    	
    	public function findByOwnerWithLimitOrderByOpenAscending($pOwner,$pFrom,$pTo) {   }
    	
    	public function countAllWithOwnerFilter($pOwner) {  }
    
    }
    The largest advantage is that without breaking the existing application I can add new methods because all public methods are independent of each other. What that further means is that if one method doesn't yield the correct results then the damage is within that method. Anything that is changed in that method will never resonate throughout the rest of the application. All logic is simple and straight forward. You want to find all open projects you call the method, no need to pass a array for that circumstance because the methods reflect a distinct outcome rather then a abstract one.
    How is this not a prime example of code bloat?
    PHP Code:
    public function findById($pId) {  }
        
    public function 
    findOpenWithLimitOrderByOpenAscending($pCurrentTime,$pFrom,$pTo) { }
        
    public function 
    countAllThatAreOpen($pCurrentTime) {   }
        
    public function 
    findOpenFilterBudgetWithLimitOrderByOpenAscending($pBudget,$pCurrentTime,$pFrom,$pTo) {  }
        
    public function 
    countAllThatAreOpenWithBudgetFilter($pBudget,$pCurrentTime) {   }
        
    public function 
    findOpenFilterOwnerWithLimitOrderByOpenAscending($pOwner,$pCurrentTime,$pFrom,$pTo) {   }
        
    public function 
    countAllThatAreOpenWithOwnerFilter($pOwner,$pCurrentTime) {   }
        
    public function 
    findByOwnerWithLimitOrderByOpenAscending($pOwner,$pFrom,$pTo) {   }
        
    public function 
    countAllWithOwnerFilter($pOwner) {  } 
    It seems that you can you can have two functions count and find that takes parameters that would give you all the code for those 9 functions. Not to mention the less than appealing naming convention. While your code should be self documenting, there's nothing wrong with an occasional /* comment */ here and there to clarify something. Creating an entirely new function where a comment would suffice seems brutish and lacks elegance. I would think that such code, in the long run, would take far or time than concise general code. The initial development time might be drastically lessened, however maintenance will be a b**ch, pardon my french. I really don't see how this is better.

  19. #19
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by imaginethis View Post
    What developer in there right mind would be dumb enough to let a user modify business logic?
    E. g. in Drupal you - as user - can develop your own content type. They call it CCK.

  20. #20
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    Perhaps, but how would I go about joining or creating highly complex sql statements in your mapper? I doubt it lends itself to all the circumstances that could arise. However, by assigning specific tasks to individual methods that do not rely on others in the public interface a complex and/or unexpected filer,group,etc is easy to add. You just essentially need to write the method that's all. I don't think this is a example of code bloat because all methods are used by the application and there is very little unused code if any. Yes, I am repeating certain tasks, but that lends itself to more readable and contained code. The names are long because a long descriptive name is better then a short generic one any day in my opinion. Comments should be used with caution. If you code with readability in mind you won't need them.

  21. #21
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Perhaps, but how would I go about joining or creating highly complex sql statements in your mapper? I doubt it lends itself to all the circumstances that could arise. However, by assigning specific tasks to individual methods that do not rely on others in the public interface a complex and/or unexpected filer,group,etc is easy to add. You just essentially need to write the method that's all. I don't think this is a example of code bloat because all methods are used by the application and there is very little unused code if any. Yes, I am repeating certain tasks, but that lends itself to more readable and contained code. The names are long because a long descriptive name is better then a short generic one any day in my opinion. Comments should be used with caution. If you code with readability in mind you won't need them.

    My code wasn't designed to be a mapper initially, I'm re-writing in now for that purpose. I don't use complex joins. I have never come across a problem, thus far, that I couldn't interpret simply. I'm not saying that there aren't problems out there that require complex joins, I just haven't come across one. I tend toward simple solutions but I suppose if I came across a problem that required such a problem I would probably create an additional function for that specific task while relying on the code I had already written. But not every action requires its own function.

    I totally disagree with you on your comment on comments . Long descriptive names do not replace comments. Example:

    class User
    {
    public function createNewUserSoUserCanLogIntoTheAdmnistrationSystem(){}
    }

    versus

    class User
    {
    // Creates a new User
    public function create(){}
    }

    Granted a bit more elaborate than your example, but you get my point. Function names need not be sentences. I think of writing applications like write papers, short sweet and to the point. Long descriptive names of functions just take longer to get to the point.

  22. #22
    SitePoint Guru
    Join Date
    Jan 2005
    Location
    heaven
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Mastodont View Post
    E. g. in Drupal you - as user - can develop your own content type. They call it CCK.
    If your modifying Business Logic, your not a user. "Users are dumb" were some of the first words of my C.S. Professor and after countless run ins with dumb users I'm obliged to agree. The rights and responsibility of a users are consistently stupid. Would you want a bank teller with no programming experience modifying business logic that determine how much money was in your account? I would think not.

    I have no experience with Drupal and I'm not a user, but if they are allowing a user to modify SQL or modify classes then they are stupid and asking for trouble because this is were business logic is handled. I'm fairly certain however, that you are using words without understanding what they actually mean which may be the cause of confusion. For instance "as user - can develop" users don't develop. Users use.

  23. #23
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by imaginethis View Post
    I'm fairly certain however, that you are using words without understanding what they actually mean which may be the cause of confusion.
    Yes, it is possible. With regards to any existing CMS (not only Drupal), I rate myself always only as user - with ordinary or admin rights. But surely I am not programmer of this system.

  24. #24
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,184
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    Well if your application doesn't call for exceptions then more power to you. I may take some what you said to heart and refactor afew generic methods into the DAO. The only problem that occurs though is that the return object can not be defined. The only way to essentially figure out what records and collection that the DAO is dependent on would either be to hard code it or use get_class and create some algorithm to calculate it. We'll see… I don't really think the repetition is a problem though. My current method is proving relatively easy to use because literally all the business logic has been contained. I think its nice not have to worry about passing arrays,ect with the find and filter params. Just call the function with the appropriate aguments and be done with it – simple. To each their own.

  25. #25
    SitePoint Wizard
    Join Date
    Feb 2009
    Posts
    1,006
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sorry for this late reply I just like to catch up a little here:
    More then 1 Year later:
    Oddz do you still use a DAO per table or you have a generic DAO?


    Regards,
    Márcio


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
  •