morrisonlevi

class.ActiveRecord.php

Sep 28th, 2011
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 9.43 KB | None | 0 0
  1. <?php
  2. require_once('interface.ActiveRecord.php');
  3.  
  4. /**
  5.  * ActiveRecordEngine implements ActiveRecord, IteratorAggregate, and
  6.  * ArrayAccess.  This means that all subclasses have CRUD ability, can be used
  7.  * in a foreach loop, and can be indexed as if they were an array.
  8.  *
  9.  * It is important to note that classes which extend this one must have three
  10.  * protected static variables named $columns, $table, and $primaryKey.
  11.  * $columns contains the database columns that you to manage with the class.
  12.  * $table is the name of the database table that is associated with the class.
  13.  * $primaryKey is the column which is the primary key for the table.
  14.  *
  15.  * @author Levi Morrison <[email protected]>
  16.  */
  17. abstract class ActiveRecordEngine implements ActiveRecord, IteratorAggregate, ArrayAccess {
  18.    
  19.     /**
  20.      * @var boolean $createNew
  21.      */
  22.     protected $createNew = true;
  23.    
  24.     /**
  25.      * @var array $properties
  26.      */
  27.     private $properties = array();
  28.    
  29.     /**
  30.      * @var array $changed
  31.      */
  32.     private $changed = array();
  33.    
  34.     /**
  35.      * @var PDO $connection
  36.      */
  37.     private static $connection = null;
  38.    
  39.    
  40.     public function __construct(array $properties = null) {
  41.         $class = get_class($this);
  42.        
  43.         foreach($class::$columns as &$name) {
  44.             $this->properties[$name] = null;
  45.         }
  46.        
  47.         if($properties!=null) {
  48.             foreach($properties as $name=>&$value) {
  49.                 if($this->hasProperty($name) && $name!==$class::$primaryKey)
  50.                     $this->changed[$name] = $value;
  51.             }
  52.         }
  53.        
  54.         //does a case-insensitive sort on the array keys
  55.         uksort($this->properties, "strnatcasecmp");
  56.         uksort($this->changed, "strnatcasecmp");
  57.     }
  58.    
  59.     /**
  60.      * @param int $id
  61.      * @return ActiveRecord A class that implements FslActiveRecord
  62.      */
  63.     public static function find($id) {
  64.         try {
  65.             $PDO = self::connection();
  66.             $class = get_called_class();
  67.            
  68.             $statement = "SELECT " . implode(', ', $class::$columns) . ' FROM ' . $class::$table . ' WHERE '.$class::$primaryKey.'=:'.$class::$primaryKey;
  69.            
  70.             $PDOStatement = $PDO->prepare($statement);
  71.             $PDOStatement->execute(array($class::$primaryKey=>$id));
  72.            
  73.             $obj = new $class();
  74.             $obj->createNew = false;
  75.             $PDOStatement->setFetchMode(PDO::FETCH_INTO, $obj);
  76.            
  77.             $PDOStatement->fetch();
  78.            
  79.             //copy, then clear out the changes made by PDO
  80.             $obj->properties = $obj->changed;
  81.             $obj->changed = array();
  82.            
  83.             return $obj;
  84.            
  85.         } catch(PDOException $error) {
  86.             self::log($error);
  87.             return null;
  88.         }
  89.     }
  90.    
  91.     /**
  92.      * Deletes the record associated with $id from the database.
  93.      * @param int $id
  94.      * @return boolean Returns true if the data was successfully deleted.
  95.      */
  96.     public static function delete($id) {       
  97.         try {
  98.            
  99.             $PDO = self::connection();
  100.            
  101.             $PDO->beginTransaction();
  102.            
  103.             $class = get_called_class();
  104.            
  105.             $statement = 'DELETE FROM '.$class::$table.' WHERE '.$class::$primaryKey.'=:'.$class::$primaryKey;
  106.            
  107.             $PDOStatement = $PDO->prepare($statement);
  108.                        
  109.             $PDOStatement->execute(array($class::$primaryKey=>$id));
  110.            
  111.            
  112.         } catch(PDOException $error) {
  113.             if($PDO instanceof PDO)
  114.                 $PDO->rollBack();
  115.             return self::log($error) && false;
  116.         }
  117.     }
  118.    
  119.    
  120.     /**
  121.      * Saves the object to the database.  If the object is a new instance, it
  122.      * inserts a new record into the database.  Calling farther saves on that
  123.      * object results in updates, not new inserts.
  124.      * If the object is not a new instance, it simply saves the data to the
  125.      * database. For safety, you cannot update the primary key.
  126.      *
  127.      * @return boolean Returns true if the save was successful.
  128.      */
  129.     public function save() {
  130.         return $this->createNew
  131.                 ? $this->insert()
  132.                 : $this->update();
  133.     }
  134.    
  135.     /**
  136.      * @return boolean If the data was inserted successfully, it returns true.
  137.      */
  138.     private function insert() {
  139.         //if there's nothing to insert, this fails.
  140.         if(empty($this->changed))
  141.             return false;
  142.        
  143.         try {
  144.            
  145.             $PDO = self::connection();
  146.            
  147.             $PDO->beginTransaction();
  148.            
  149.             $class = get_class($this);
  150.            
  151.             $statement = 'INSERT INTO ' . $class::$table . ' (' . implode(', ', array_keys($this->changed)) . ') VALUES (';
  152.            
  153.             $i=0;
  154.             foreach($this->changed as $name=>&$value) {
  155.                 if($i++>0) {
  156.                     $statement .= ', ';
  157.                 }
  158.                 $statement .= ":$name";
  159.             }
  160.            
  161.             $statement .= ') WHERE '.$class::$primaryKey."= :".$class::$primaryKey;
  162.            
  163.             $PDOStatement = $PDO->prepare($statement);
  164.            
  165.             $parameters = array_merge(
  166.                     $this->changed,
  167.                     array(
  168.                         $class::$primaryKey=>$this->__get($class::$primaryKey)
  169.                     )
  170.                 );
  171.            
  172.             $PDOStatement->execute($parameters);
  173.            
  174.             echo"<pre>";
  175.                 print_r($class::find($PDO->lastInsertId()));
  176.             echo"</pre>";
  177.            
  178.            
  179.         } catch(PDOException $error) {
  180.             if($PDO instanceof PDO)
  181.                 $PDO->rollBack();
  182.             return self::log($error) && false;
  183.         }
  184.        
  185.     }
  186.    
  187.     /**
  188.      * Saves the object to the database.  This implementation only updates
  189.      * values which have been changed.
  190.      * @return boolean If the data was updated successfully, it returns true.
  191.      */
  192.     private function update() {
  193.         //if there's nothing to update, this worked, right?
  194.         if(empty($this->changed))
  195.             return true;
  196.        
  197.         try {
  198.             $PDO = self::connection();
  199.            
  200.             $PDO->beginTransaction();
  201.            
  202.             $class = get_class($this);
  203.            
  204.             $statement = 'UPDATE ' . $class::$table . ' SET';
  205.            
  206.             $i=0;
  207.             foreach($this->changed as $name=>&$value) {
  208.                 if($i++>0)
  209.                     $statement .= ',';
  210.                 $statement .= " $name=:$name";
  211.             }
  212.            
  213.             $statement .= ' WHERE '.$class::$primaryKey.'= :'.$class::$primaryKey;
  214.            
  215.             $PDOStatment = $PDO->prepare($statement);
  216.            
  217.             $parameters = array_merge(
  218.                     $this->changed,
  219.                     array(
  220.                         $class::$primaryKey=>$this->__get($class::$primaryKey)
  221.                     )
  222.                 );
  223.            
  224.            
  225.             $PDOStatment->execute($parameters);
  226.            
  227.             $PDO->commit();
  228.            
  229.             foreach($this->changed as $name=>&$value) {
  230.                 $this->properties[$name] = $value;
  231.             }
  232.            
  233.             $this->changed = array();
  234.            
  235.            
  236.             return true;
  237.            
  238.         } catch(PDOException $error) {
  239.             if($PDO instanceof PDO)
  240.                 $PDO->rollBack();
  241.            
  242.             return self::log($error) && false;
  243.         }
  244.     }
  245.    
  246.     /**
  247.      * Used to determine whether the class has a given property.  Properties
  248.      * are defined as the column names of the table the class represents.
  249.      * @param string $name The name of the property you want to test for existence.
  250.      * @return boolean Returns true if the property exists.
  251.      */
  252.     protected function hasProperty($name) {
  253.         return array_key_exists($name, $this->properties);
  254.     }
  255.    
  256.     /**
  257.      * @param string $name
  258.      * @return mixed
  259.      * @throws DomainException if you try to get a property that doesn't exist.
  260.      */
  261.     public function __get($name) {
  262.         if(!$this->hasProperty($name))
  263.             throw new DomainException();
  264.        
  265.         return $this->properties[$name];
  266.     }
  267.    
  268.     /**
  269.      * @param string $name
  270.      * @param mixed $value
  271.      * @return void
  272.      * @throws DomainException if you try to set a property that doesn't exist.
  273.      */
  274.     public function __set($name, $value) {
  275.         if(!$this->hasProperty($name))
  276.             throw new DomainException();
  277.        
  278.         $this->changed[$name] = $value;
  279.         //$this->properties will contain the original value.
  280.        
  281.     }
  282.    
  283.     /**
  284.      * @param string $name
  285.      * @return boolean
  286.      */
  287.     public function __isset($name) {
  288.         return isset($this->properties[$name]);
  289.     }
  290.    
  291.     /**
  292.      * You cannot unset properties
  293.      * @param string $name
  294.      * @return void
  295.      * @throws UnsupportedOperationException
  296.      */
  297.     public function __unset($name) {
  298.         throw new UnsupportedOperationException(
  299.             'You cannot unset properties of ' . get_class($this). '.'
  300.         );
  301.     }
  302.    
  303.     /**
  304.      * Allows for easier error logging.
  305.      * @param Exception $error
  306.      * @return boolean
  307.      */
  308.     protected static function log(Exception $error) {
  309.         return error_log(
  310.                 get_class($error) . ': ' .$error->getCode()
  311.                 . ', ' .$error->getMessage()
  312.                 . ' in ' . $error->getFile()
  313.                 . ' on line ' . $error->getLine()
  314.                 . ', stack trace: ' . $error->getTraceAsString()
  315.             );
  316.     }
  317.    
  318.     /**
  319.      * Required for ArrayAccess
  320.      * @param string $name The name of the property you'd like to test for existence.
  321.      * @return boolean
  322.      */
  323.     public function offsetExists($name) {
  324.         return $this->hasProperty($name);
  325.     }
  326.  
  327.     /**
  328.      * Required for ArrayAccess
  329.      * @param string $name The name of the property you'd like to get.
  330.      * @return mixed The value associated with that name.
  331.      */
  332.     public function offsetGet($name) {
  333.         return $this->__get($name);
  334.     }
  335.  
  336.     /**
  337.      * Required for ArrayAccess
  338.      * @param string $name The name of the property you'd like to set.
  339.      * @param mixed $value
  340.      */
  341.     public function offsetSet($name, $value) {
  342.         $this->__set($name, $value);
  343.     }
  344.  
  345.     /**
  346.      * Required for ArrayAccess. Unsupported.
  347.      * @param string $name The name of the property you'd like to unset.
  348.      * @throws UnsupportedOperationException
  349.      */
  350.     public function offsetUnset($name) {
  351.         $this->__unset($name);
  352.     }
  353.  
  354.     /**
  355.      * Required for IteratorAggregate.  Allows you to use any subclass to be
  356.      * used in a foreach loop.
  357.      * @return ArrayIterator
  358.      */
  359.     public function getIterator() {
  360.         return new ArrayIterator($this->properties);
  361.     }
  362.  
  363.    
  364.     /**
  365.      * @return PDO
  366.      */
  367.     protected static function connection() {
  368.         if(!(self::$connection instanceof PDO)) {
  369.                 self::$connection = new PDO(
  370.                     "mysql:host=db;dbname=dbname",
  371.                     'user',
  372.                     'password',
  373.                     array(
  374.                         PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
  375.                         PDO::ATTR_PERSISTENT=>true
  376.                     )
  377.                 );
  378.         }
  379.        
  380.         return self::$connection;
  381.     }
  382.    
  383.    
  384. }
  385.  
  386. ?>
  387.  
Advertisement
Add Comment
Please, Sign In to add comment