SitePoint Sponsor

User Tag List

Results 1 to 10 of 10
  1. #1
    SitePoint Addict fattyjules's Avatar
    Join Date
    Dec 2005
    Posts
    295
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    What's the "correct" way of doing this?

    I've been doing some reading on this forum, and have come across a few references to keeping an object 'pure'. That is, unaware of databases, files, HTTP requests, etc.

    Now suppose I'm working with 'user' objects, which are loaded from a database. Normally, I might write it something like this;

    Code:
    class Car 
    {
      private $dbConn;
      public $name;
      public $age;
      public $address;  
    
      public function __construct($dbConn)
      {
        $this->dbConn = $dbConn;
      }
    
      public function load($userId)
      {
        $this->dbConn->query('select name,age,address from user where user_id = ' . (int)$userId);
    
        $row = $this->dbConn->nextRow();
        $this->name = $row->name;
        $this->age = $row->age;
        $this->address = $row->address;
      }
    }
    Ignore the fact that there's no error checking etc., it's just an example.

    How might such a class be written so that it has no knowledge of the database?

  2. #2
    Theoretical Physics Student bronze trophy Jake Arkinstall's Avatar
    Join Date
    May 2006
    Location
    Lancaster University, UK
    Posts
    7,062
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Generally I allow the database connection to be passed (inside a register object) to all classes which use it and they can use it freely.

    But if you're after a solution which doesn't have a database object inside your class, you could have static database-access classes, so you can fetch user data with UserTable::getUserData($id);

    so, for example:
    PHP Code:
    <?php
    class User{
      public 
    $name;
      public 
    $age;
      public 
    $address;  
      public function 
    __construct($userId){
        
    $data UserTable::get($userId);
        
    $this->name $data->name;
        
    $this->age $data->age;
        
    $this->address $data->address;
      }
    }
    ?>
    An added benefit would be that you can cache results if they are likely to be used multiple times in one load. It also means you can get your data from multiple sources.


    I'd be interested in how other people would go about it though.
    Jake Arkinstall
    "Sometimes you don't need to reinvent the wheel;
    Sometimes its enough to make that wheel more rounded"-Molona

  3. #3
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fattyjules View Post
    How might such a class be written so that it has no knowledge of the database?
    There are different solutions to this. One way could be:
    PHP Code:
    class Car {
      public 
    $name;
      public 
    $age;
      public 
    $address;  

      public function 
    __construct($row) {
        
    $this->name $row->name;
        
    $this->age $row->age;
        
    $this->address $row->address;
      }
    }
    class 
    CarGateway {
      protected 
    $dbConn;

      public function 
    __construct($dbConn) {
        
    $this->dbConn $dbConn;
      }

      public function 
    load($userId) {
        
    $this->dbConn->query('select name,age,address from user where user_id = ' . (int)$userId);
        return new 
    Car($this->dbConn->nextRow());
      }


  4. #4
    SitePoint Addict fattyjules's Avatar
    Join Date
    Dec 2005
    Posts
    295
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    arkinstall: So the user object knows it's getting data from somewhere, but it doesn't know whether that somewhere is a file, database, cache or whatever. I like it.

    kyberfabrikken: So you create an object that acts as a gateway (or bridge) between the 'pure' user object and the database? It certainly keeps the user object pure! Is this a common technique? One drawback would appear to be additional complexity. Are there benefits to using gateways?

    In both examples, how would you change the user's properties then save back to the database?

  5. #5
    SitePoint Guru dagfinn's Avatar
    Join Date
    Jan 2004
    Location
    Oslo, Norway
    Posts
    894
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Martin Fowler calls the principle Data Mapper.

    http://martinfowler.com/eaaCatalog/dataMapper.html

    One advantage of having objects that are unaware of how they're persisted is that they can be re-used in another context that has a different persistence mechanism.
    Dagfinn Reiersøl
    PHP in Action / Blog / Twitter
    "Making the impossible possible, the possible easy,
    and the easy elegant"
    -- Moshe Feldenkrais

  6. #6
    Theoretical Physics Student bronze trophy Jake Arkinstall's Avatar
    Join Date
    May 2006
    Location
    Lancaster University, UK
    Posts
    7,062
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    In both examples, how would you change the user's properties then save back to the database?
    PHP Code:
    <?php
    class User{
      public 
    $id$name$age$address;  
      public function 
    __construct($userId){
        
    $data UserTable::get($userId);
        
    $this->id $userId;
        
    $this->name $data->name;
        
    $this->age $data->age;
        
    $this->address $data->address;
      }
      public function 
    save(){
        
    $data::save($this->id$this->name$this->age$this->address);
      }
    }
    ?>
    But I'd actually prefer KyberFabrikken's method - which to save would probably look like:
    PHP Code:
    <?php
    class Car {
        public 
    $id$name$age$address;
        public function 
    __construct($row) {
            
    $this->name $row->name;
            
    $this->age $row->age;
            
    $this->address $row->address;
            
    $this->id $row->user_id;
        }
    }
    class 
    CarGateway {
        protected 
    $dbConn;
        public function 
    __construct($dbConn) {
            
    $this->dbConn $dbConn;
        }
        public function 
    load($userId) {
            
    $this->dbConn->query('select name,age,address,user_id from user where user_id = ' . (int)$userId);
            return new 
    Car($this->dbConn->nextRow());
        }
        public function 
    save(Car $car){
            
    $this->dbConn->query("UPDATE user SET name = '{$car->name}', age = '{$car->age}', address = '{$car->address}' WHERE user_id = {$car->id}");
        }
    }
    ?>
    Jake Arkinstall
    "Sometimes you don't need to reinvent the wheel;
    Sometimes its enough to make that wheel more rounded"-Molona

  7. #7
    SitePoint Zealot Amenthes's Avatar
    Join Date
    Oct 2006
    Location
    Bucharest, Romania
    Posts
    143
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I don't think kyberfabrikken's method is pure enough, or at least not always.
    That is because the Car object has to know the structure of the object it is given in the constructor. In that particular example the object's properties map to the fields in the DB (unless you alias them in the query) so this is not the best solution out there. A few days ago I started a topic similar to this one and the way I'm doing this is by having mappers that create objects independent of the datasource. So there are 2 main classes in my case. One that fetches (for the moment, later it should update) the data, one that maps the data and one that receives the data. The first one knows how to get data from whatever datasource. The second knows for example what field in the DB maps to what property in an object. The last one implements my business specific interface. A relatively simple example but with more than 2 classes since I want to use multiple datasources:

    PHP Code:
    class Car
    {
        protected 
    $_model;

        protected 
    $_buildYear;

        public function 
    setModel($model)
        {
            
    $this->_model $model;
        }
        
        public function 
    getModel()
        {
            return 
    $this->_model;
        }

        public function 
    setBuildYear($year)
        {
            
    // validate the $year param is indeed a year when car did exist
            // and where manufactured
        
    }
        
        public function 
    getBuildYear()
        {
            return 
    $this->_buildYear;
        }

    PHP Code:
    interface iCarProvider
    {
        public function 
    getMostWantedCars();

    PHP Code:
    class CarProvider
    {
        public static function 
    factory($datasource)
        {
            
    // $datasource could be DB, XML, flat file
            
    switch ($datasurce) {
                case 
    'db':
                    return new 
    DbCarProvider;
                    break;
                case 
    'xml':
                    return new 
    DbCarProvider;
                    break;
                default:
                    throw new 
    InvalidArgumentException;
            }
        }

    PHP Code:
    class DbCarProvider implements iCarProvider
    {
        public function 
    getMostWantedCars()
        {
            
    $cars = array();
        
            
    // make SQL query and return and foreach row in DB map it to the car object
            
    while ($row fetch_row()) {
                
    $cars $this->map($row);
            }
            
            return 
    $cars;
        }

        public function 
    map($rawObject)
        {
            
    $car = new Car;
            
    $car->setModel$rawObject->car_model );
            
    $car->setBuildYear$rawObject->car_build_year );
        }

    PHP Code:
    class XmlCarProvider implements iCarProvider
    {
        public function 
    getMostWantedCars()
        {
            
    $cars = array();

            
    // make SQL query and return and foreach row in DB map it to the car object
            
    while ($row fetch_xml_document()) {
                
    $cars $this->map($row);
            }

            return 
    $cars;
        }

        public function 
    map($rawObject)
        {
            
    $car = new Car;
            
    $car->setModel$rawObject->model );
            
    $car->setBuildYear$rawObject->buildYear );
            return 
    $car;
        }

    As you can see, this approach, while it may not be perfect it provides enough decoupling between the interface (Car) and implementation (Providers) so that different naming schemes for DB or XML fields don't affect my program. Further more, I can put constraints in one single place (Car::setBuildYear()) when later on I would take such a Car object and map it to a database or xml structure in case of inserting/updating records.

    As I said, neither my example should be perfect but this is the result of my experience with one such a project where I have more than 2 datasources, one is a DB the others web services with SOAP or proprietary XML interfaces, and things could get even more interesting if let's say you would want to filter or order these aggregated results. One more thing that I've done in my project is separating the process of request (DB or XML) to the process of mapping data in two separate classes. I also return Iterators instead of array from methods like getMostWantedCars() so that I map objects in a lazy fashion, only when needed.
    I'm under construction | http://igstan.ro/

  8. #8
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Your project must be pretty complex to justify 4 classes and 1 interface for every model!

    Personally, I think it is over-engineered to have a domain model with a number of classes equal to "models times datasources".
    Garcia

  9. #9
    SitePoint Zealot Amenthes's Avatar
    Join Date
    Oct 2006
    Location
    Bucharest, Romania
    Posts
    143
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado View Post
    Your project must be pretty complex to justify 4 classes and 1 interface for every model!

    Personally, I think it is over-engineered to have a domain model with a number of classes equal to "models times datasources".
    It is indeed over-engineered in that the iCarProvider interface and the CarProvider factory class are not needed. I don't even know why I posted them in the example, I don't use any of these in my actual project.
    I'm under construction | http://igstan.ro/

  10. #10
    SitePoint Guru
    Join Date
    Nov 2004
    Location
    Plano
    Posts
    643
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    if i wanted a simply approach, i would definitely go with the registry pattern. a more complex, but a much more powerful way is using a DAO, kinda similar to how arkinstall demonstrated.


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
  •