PHP - Simple Database ORM

DataContext is a simple ORM class that can be used to manage CRUD operations on a database. Standard PHP objects or arrays are used to insert/update data.

DataContext

The DataContext class is an abstract class and cannot be instantiated directly, it must be inherited from. Once inherited, you must overwrite the initialize() method of the DataContext class and add tables to the context using the addSet() method. A class that inherits from the DataContext class will inherit methods that can then be used to insert,update and query a database. The DataContext class and it's related DbSet class can be found at the end of this article.

Usage

  1. class BlogContext extends DataContext {  
  2.     /*  
  3.      * BlogContext is a concrete class.   
  4.      * DbSets must be added to the context using the addSet() method.  
  5.      */  
  6.     protected function initialize(){  
  7.         $this->addSet('users', 'user_id');  
  8.         $this->addSet('posts', 'post_id');  
  9.     }  
  10. } 
The addSet() method is used to add a table to the context and accepts two parameters. The first, is the database table name and the second is the table primarykey. In the above code sample, a new concrete class BlogContext has been defined with two datasets that have been added to the context. The following code examples, show how to connect to a MySQL database and perform insert,update and query operations using the BlogContext

Create a new instance of the BlogContext.
  1. $db = new BlogContext();  
or
  1. $db = new BlogContext("driver=mysql;host=localhost;dbname=blog;uid=root;pwd=")
By default, a parameterless constructor will attempt to connect to a local MySQL server. The name of the database is the lowercase concrete class name (blogcontext). Alternatively a connection string can be supplied to the constructor.
The following code sample shows how to add a user to the context. Note that the context exposes a magic users property. This property only exists because a DbSet 'users' was added to the context in the initialize() method.
  1. $user = new stdClass();  
  2. $user->username = 'John';  
  3. $user->password = 'pass123';  
  4. $user->email = 'j.smith@mailxyz.com';  
  5.  
  6. $db = new BlogContext();  
  7. $db->users->insert($user);  
  8. $db->saveChanges()

Multiple users can be added to the 'users' DbSet then saved at once.
  1. $db->users  
  2.     ->insert($user1);  
  3.     ->insert($user2);  
  4.  
  5. $db->saveChanges()

The following code sample below shows how to update a user that exists in the users table. Note that the $user object has a 'user_id' property with a value. This value is used to update the record.
  1. $user = new stdClass();  
  2. $user->user_id = 1;  
  3. $user->username = "John";  
  4. $user->password = "pass43211";  
  5. $user->email = "john.smith@mailxyz.com";  
  6.  
  7. $db = new BlogContext();  
  8. $db->users->update($user);  
  9. $db->saveChanges()

The BlogContext provides a simple association for inserting and updating data entities. The example code below shows how to create a new user and a new blog post using association.
  1. $user = new stdClass();  
  2. $user->first_name = "Linda";  
  3. $user->last_name = "Baker";  
  4. $user->email = "l.baker@mailxyz.com";  
  5.  
  6. $post = new stdClass();  
  7. $post->user_id = $user;  
  8. $post->title = "My First Post";  
  9. $post->body = "This is my first blog post";  
  10. $post->created_date = date("Y-d-m");  
  11.  
  12. $db = new BlogContext();  
  13. $db->users->insert($user);  
  14. $db->posts->insert($post);  
  15. $db->saveChanges()

Notice that the $post object has a 'user_id' property which has been assigned the $user object. The BlogContext internally assigns the last insert id from the $user object to the user_id property of the $post object.

The DataContext and it's related DbSet class is shown below.

DataContext

  1. <?php  
  2.    
  3. abstract class DataContext {  
  4.       
  5.     /*  
  6.      * Database connection settings  
  7.      */  
  8.     protected $dbConfig;   
  9.       
  10.     /*  
  11.      * PDo instance  
  12.      */  
  13.     protected $pdo;  
  14.       
  15.     /*  
  16.      * Collection of datasets  
  17.      */  
  18.     protected $dbSets = array();  
  19.       
  20.     /*  
  21.      * Stores a collection of persisted entities for lookup  
  22.      */  
  23.     protected $entities = array();  
  24.  
  25.     /*  
  26.      * A parameterless constructor will attempt to connect on localhost with  
  27.      * default local settings.  
  28.      */  
  29.     public function __construct($connectionString = null){  
  30.           
  31.         /*  
  32.          * Example connection string  
  33.          * driver:mysql;host=localhost;uid=root;pwd=password  
  34.          */  
  35.         if($connectionString){  
  36.             $pairs = explode(';', $connectionString);  
  37.  
  38.             foreach($pairs as $pair){  
  39.                 if(strpos($pair, '=')){  
  40.                     list($name, $value) = explode('=', $pair);  
  41.                     $this->dbConfig[$name] = $value;  
  42.                 }  
  43.             }  
  44.         }else{  
  45.             $this->dbConfig['driver'] = 'mysql';  
  46.             $this->dbConfig['host'] = 'localhost';  
  47.             $this->dbConfig['dbname'] = strtolower(get_called_class());  
  48.             $this->dbConfig['uid'] = 'root';  
  49.         }  
  50.  
  51.         $dsn = sprintf('%s:host=%s;dbname=%s;',   
  52.             $this->getDbConfig('driver'),  
  53.             $this->getDbConfig('host'),  
  54.             $this->getDbConfig('dbname')       
  55.         );  
  56.  
  57.         $this->pdo = new Pdo($dsn,   
  58.             $this->getDbConfig('uid'),   
  59.             $this->getDbConfig('pwd')  
  60.         );  
  61.           
  62.         /*  
  63.          * Throw exceptions instead of warnings  
  64.          */  
  65.         $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);  
  66.           
  67.         $this->initialize();  
  68.     }  
  69.       
  70.     private function getDbConfig($name){  
  71.         if(isset($this->dbConfig[$name])){  
  72.             return $this->dbConfig[$name];  
  73.         }  
  74.     }  
  75.       
  76.     /*  
  77.      * This method is intended to be overwritten in a base class.  
  78.      * It is used to add DbSets.  
  79.      */  
  80.     protected function initialize(){}  
  81.       
  82.     /*  
  83.      * Adds a DbSet to the Context. The setName is the table name.  
  84.      */  
  85.     protected function addSet($setName, $primaryKey){  
  86.         $dbSet = new DbSet($this, $setName, $primaryKey);  
  87.         $this->dbSets[$setName] = $dbSet;  
  88.     }  
  89.       
  90.     /*  
  91.      * Returns a DbSet.  
  92.      */  
  93.     public function getDbSet($setName){  
  94.         if(isset($this->dbSets[$setName])){  
  95.             return $this->dbSets[$setName];  
  96.         }  
  97.     }  
  98.       
  99.     /*  
  100.      * Magic method alias to getDbSet.  
  101.      */  
  102.     public function __get($setName){  
  103.         return $this->getDbSet($setName);  
  104.     }  
  105.       
  106.     /*  
  107.      * Issues an SQL statement and returns a PDOStatement instance  
  108.      */  
  109.     public function query($sql, array $params = array()){  
  110.         $stm = $this->pdo->prepare($sql);  
  111.         $stm->execute($params);  
  112.         return $stm;  
  113.     }  
  114.       
  115.     /*  
  116.      * Issues an SQL statement used to fetch a single table row.  
  117.      */  
  118.     public function fetch($sql, $params = array(), $fetchSytle = \PDO::FETCH_OBJ){  
  119.         $stm = $this->query($sql, $params);  
  120.         return $stm->fetch($fetchSytle);  
  121.     }  
  122.       
  123.     /*  
  124.      * Issues an SQL statement used to fetch table rows.  
  125.      */  
  126.     public function fetchAll($sql, $params = array(), $fetchSytle = \PDO::FETCH_OBJ){  
  127.         $stm = $this->query($sql, $params);  
  128.         return $stm->fetchAll($fetchSytle);  
  129.     }  
  130.       
  131.     /*  
  132.      * Issues an SQL statement used to fetch a single row column.  
  133.      */  
  134.     public function fetchOne($sql, $params = array()){  
  135.         $stm = $this->query($sql, $params);  
  136.         return $stm->fetchColumn();  
  137.     }  
  138.       
  139.     /*  
  140.      * Inserts data into a table. $data can be either an array or an object.  
  141.      */  
  142.     public function insert($tableName, $data){  
  143.         if(is_object($data)){  
  144.             $data = get_object_vars($data);  
  145.         }  
  146.  
  147.         $placeHolders = trim(str_repeat('?,', count($data)), ',');  
  148.         $sql = 'INSERT INTO ' . $tableName . ' (' . implode(',', array_keys($data));  
  149.         $sql .= ') VALUES (' . $placeHolders . ')';  
  150.  
  151.         $stm = $this->query($sql, array_values($data));  
  152.  
  153.         if($stm->rowCount() > 0){  
  154.             return true;  
  155.         }  
  156.         return false;  
  157.     }  
  158.       
  159.     /*  
  160.      * Updates data in a table. $data can be either an array or an object.  
  161.      * $conditions is the search criteria used to update the record.  
  162.      */  
  163.     public function update($tableName, $data, array $conditions){  
  164.         if(is_object($data)){  
  165.             $data = get_object_vars($data);  
  166.         }  
  167.  
  168.         $params = array();  
  169.         $sql = 'UPDATE ' . $tableName . ' SET ';  
  170.  
  171.         foreach($data as $field=>$value){  
  172.             $sql.= $field.'=:'.$field.',';  
  173.             $params[':'.$field] = $value;  
  174.         }  
  175.  
  176.         $sql = rtrim($sql, ',') . ' WHERE ';  
  177.  
  178.         $idx=0;  
  179.         foreach($conditions as $field=>$value){  
  180.             if($idx > 0){  
  181.                 $sql.= ' AND ';  
  182.             }  
  183.  
  184.             $sql.= $field.'=:c_'.$field;  
  185.             $params[':c_'.$field] = $value;  
  186.             ++$idx;  
  187.         }  
  188.  
  189.         $stm = $this->query($sql, $params);  
  190.  
  191.         if($stm->rowCount() > 0){  
  192.             return true;  
  193.         }  
  194.         return false;  
  195.     }  
  196.  
  197.     /*  
  198.      * Returns the last insert id.  
  199.      */  
  200.     public function getInsertId($field = null){  
  201.         return $this->pdo->lastInsertId($field);  
  202.     }  
  203.       
  204.     /*  
  205.      * Persists, all data objects to the database.  
  206.      */  
  207.     public function saveChanges(){  
  208.         foreach($this->dbSets as $dbSetName=>$dbSet){  
  209.             $data = $dbSet->getData();  
  210.               
  211.             if(isset($data['INSERT'])){  
  212.                 $insertDataSets = $data['INSERT'];  
  213.  
  214.                 foreach($insertDataSets as $entity){  
  215.                       
  216.                     foreach($entity as $propertyName=>$value){  
  217.                         if(is_object($value)){  
  218.                             $hash = spl_object_hash($value);  
  219.                               
  220.                             if(isset($this->entities[$hash])){  
  221.                                 $assocDataEntry = $this->entities[$hash];  
  222.                                 $assocDbSet = $this->getDbSet($assocDataEntry['setName']);  
  223.                                 $assocPrimaryKey = $assocDbSet->getPrimaryKey();  
  224.                                 $assocData = $assocDataEntry['entity'];  
  225.                                 $value = $assocData->{$assocPrimaryKey};  
  226.                                   
  227.                                 $entity->{$propertyName} = $value;  
  228.                             }  
  229.                         }  
  230.                     }  
  231.                       
  232.                     $insert = $this->insert($dbSetName, $entity);  
  233.                       
  234.                     if($insert){  
  235.                         $primaryKey = $dbSet->getPrimaryKey();  
  236.                         $insertId = $this->getInsertId($primaryKey);  
  237.                         $entity->{$primaryKey} = $insertId;  
  238.                           
  239.                         $hash = spl_object_hash($entity);  
  240.                         $this->entities[$hash] = array(  
  241.                             'setName' => $dbSetName,   
  242.                             'entity' => $entity  
  243.                         );  
  244.                     }  
  245.                 }  
  246.             }  
  247.  
  248.             if(isset($data['UPDATE'])){  
  249.                 $insertDataSets = $data['UPDATE'];  
  250.  
  251.                 foreach($insertDataSets as $entity){  
  252.                       
  253.                     foreach($entity as $propertyName=>$value){  
  254.                         if(is_object($value)){  
  255.                             $hash = spl_object_hash($value);  
  256.                               
  257.                             if(isset($this->entities[$hash])){  
  258.                                 $assocDataEntry = $this->entities[$hash];  
  259.                                 $assocDbSet = $this->getDbSet($assocDataEntry['setName']);  
  260.                                 $assocPrimaryKey = $assocDbSet->getPrimaryKey();  
  261.                                 $assocData = $assocDataEntry['entity'];  
  262.                                 $value = $assocData->{$assocPrimaryKey};  
  263.                                   
  264.                                 $entity->{$propertyName} = $value;  
  265.                             }  
  266.                         }  
  267.                     }  
  268.                       
  269.                     $primaryKey = $dbSet->getPrimaryKey();  
  270.                       
  271.                     if(isset($entity->{$primaryKey})){  
  272.                         $value = $entity->{$primaryKey};  
  273.                         $this->update($dbSetName, $entity, array($primaryKey => $value));  
  274.                     }else{  
  275.                         throw new Exception(sprintf("Unable to update entity '%s'. Primarykey '%s' is not present in entity data.", $dbSetName, $primaryKey));  
  276.                     }  
  277.                 }  
  278.             }  
  279.         }  
  280.     }  
  281. }  
  282.  
  283.  
  284. /*  
  285.  * Class that represents a table and stores a list of data to persist.  
  286.  */  
  287. class DbSet {  
  288.     protected $dbContext;  
  289.     protected $dbSetName;  
  290.     protected $primaryKey;  
  291.     protected $data = array();  
  292.       
  293.     public function __construct($dbContext, $dbSetName, $primaryKey){  
  294.         $this->dbContext = $dbContext;  
  295.         $this->dbSetName = $dbSetName;  
  296.         $this->primaryKey = $primaryKey;  
  297.     }  
  298.       
  299.     public function getPrimaryKey(){  
  300.         return $this->primaryKey;  
  301.     }  
  302.       
  303.     public function insert($data){  
  304.         $this->data['INSERT'][] = $data;  
  305.         return $this;  
  306.     }  
  307.       
  308.     public function update($data){  
  309.         $this->data['UPDATE'][] = $data;  
  310.         return $this;  
  311.     }  
  312.       
  313.     public function find($value){  
  314.         return $this->dbContext->fetch(  
  315.             "SELECT * FROM "$this->dbSetName . " WHERE " . $this->primaryKey . "=:value",   
  316.             array(":value"=>$value)  
  317.         );  
  318.     }  
  319.       
  320.     public function getData(){  
  321.         return $this->data;  
  322.     }  
  323. }  
  324.  
  325. ?> 

No comments:

Post a Comment