[PHP] Ein Basis-OR-Mapper für PDO

[PHP] Ein Basis-OR-Mapper für PDO

Diese Klasse ist lediglich inline-dokumentiert. Im Beitrag Prepared Statements Teil 3 wird die Klasse erklärt und man wird auch Beispiele für die Anwendung dort finden.

PHP:
<?php
/**
 * ORM.php
 * 
 * - Provides a basic class for accessing database using ORM technology.
 * 
 * @author saftmeister
 * 
 */
class ORMException extends Exception {}

/**
 * The OR-Mapper class
 *
 * Provides the basic database access functionality
 */
class ORM
{
  private static $dbhost = '127.0.0.1';
  private static $dbuser = 'test';
  private static $dbpass = '';
  private static $dbname = 'test';
  protected $table;
  private $pkCol = null;
  private $db;
  
  /**
   * Set the credentials for the database access
   * 
   * @param string $dbhost          
   * @param string $dbuser          
   * @param string $dbpass          
   * @param string $dbname          
   */
  public static function setCredentials($dbhost, $dbuser, $dbpass, $dbname)
  {
    self::$dbhost = $dbhost;
    self::$dbname = $dbname;
    self::$dbpass = $dbpass;
    self::$dbuser = $dbuser;
  }
  
  /**
   * Create a new instance of our ORM mapper
   *
   * @param string $table
   *          The database table name
   *          If not given, we use the class name of entity class in lower case
   * @param PDO $db
   *          A valid database connection to fulfil a dependency injection request
   *          If not given, we create a connection inside for you
   *          
   * @throws ORMException
   */
  public function __construct($table = null, PDO $db = null)
  {
    try
    {
      $this->db = $db;
      $this->table = $table;
      
      $this->init ();
      $this->initMeta ();
    }
    catch ( Exception $ex )
    {
      if ($ex instanceof ORMException)
      {
        throw $ex;
      }
      throw new ORMException ( $ex->getMessage (), $ex->getCode (), $ex );
    }
  }
  
  /**
   * Initialization of OR mapper
   * 
   * @throws ORMException
   */
  private function init()
  {
    if ($this->db)
      return;
    
    try
    {
      $this->db = new PDO ( sprintf ( "mysql:dbname=%s;host=%s", self::$dbname, self::$dbhost ), self::$dbuser, self::$dbpass );
      $this->db->setAttribute ( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
    }
    catch(Exception $ex)
    {
      throw new ORMException($ex->getMessage(), $ex->getCode(), $ex);
    }
  }
  
  /**
   * Initialize entity meta data
   *
   * @throws ORMException
   */
  private function initMeta()
  {
    $this->init ();
    
    if ($this->table == null)
    {
      $this->table = strtolower ( get_called_class () );
    }
    
    $metaStatement = $this->db->query ( sprintf('SELECT * FROM %s LIMIT 0', $this->table) );
    
    for($i = 0; $i < $metaStatement->columnCount (); $i ++)
    {
      $meta = $metaStatement->getColumnMeta ( $i );
      
      if (count ( $meta ['flags'] ))
      {
        if (array_search ( 'primary_key', $meta ['flags'] ))
        {
          $this->pkCol = $meta ['name'];
          break;
        }
      }
    }
    
    if (! $this->pkCol)
    {
      throw new ORMException ( 'Could not find column which contains primary key!' );
    }
  }
  
  /**
   * Retrieve the name of column which contains the table primary key.
   *
   * @throws ORMException
   * @return string
   */
  protected function getPrimaryKeyColumn()
  {
    if (! $this->pkCol)
      $this->initMeta ();
    
    return $this->pkCol;
  }
  
  /**
   * Retrieve the value of primary key for this entity
   *
   * @throws ORMException
   * @return mixed
   */
  protected function getPrimaryKeyValue()
  {
    $pk = $this->getPrimaryKeyColumn ();
    
    return $this->getProperty ( $pk );
  }
  
  /**
   * Evil hack to retrieve the properties from the derived class
   *
   * This method uses a hack to get class properties without the
   * use of reflection to minimize the dependencies to php functionality.
   *
   * First a object dump is retrieved.
   * Then we try to find all property names from the object dump by regular
   * expression.
   * Then we retrieve the object vars from the base class using get_object_class().
   * Due to limitation the method get_object_class will return only properties
   * of class ORM, which is the base class.
   * We walk over all derived class properties to find match in base class. If
   * found, we skip that property.
   * After all walks we have only properties, which exists in the derived class.
   *
   * @return array All properties known in the derived class.
   */
  private function getObjectProperties()
  {
    $s = var_export ( $this, true );
    
    $propertyNames = array ();
    $matches = array ();
    
    preg_match_all ( '/\'([^\']+?)\' => .*/m', $s, $matches );
    if (count ( $matches ) > 1)
    {
      array_shift ( $matches );
      
      $baseClassProperties = get_object_vars ( $this );
      
      foreach ( $matches [0] as $match )
      {
        if (! array_key_exists ( $match, $baseClassProperties ))
          $propertyNames [] = $match;
      }
    }
    return $propertyNames;
  }
  
  /**
   * Retrieve a particular property value by calling getter of property
   *
   * @param string $propertyName          
   *
   * @throws ORMException
   * @return mixed
   */
  private function getProperty($propertyName)
  {
    $getter = "get" . ucfirst ( $propertyName );
    
    if (! method_exists ( $this, $getter ))
    {
      throw new ORMException ( "Could not find getter for property $propertyName" );
    }
    
    return $this->$getter ();
  }
  
  /**
   * Set the given property to a particular value
   * 
   * @param string $propertyName
   * @param mixed $value
   * 
   * @throws ORMException
   */
  private function setProperty($propertyName, $value)
  {
    $setter = "set" . ucfirst ( $propertyName );
    
    if (! method_exists ( $this, $setter ))
    {
      throw new ORMException ( "Could not find setter for property $propertyName" );
    }
    
    $this->$setter ( $value );
  }
  
  /**
   * Create an ORM instance for internal use
   *
   * @throws ORMException
   * @return ORM
   */
  private static function create()
  {
    $concrete = get_called_class ();
    
    $orm = new self ( strtolower ( $concrete ) );
    
    return $orm;
  }
  
  /**
   * Retrieve a particular entry by given id
   *
   * @throws ORMException
   * @param mixed $id          
   * @return mixed
   */
  public static function get($id)
  {
    try
    {
      $orm = self::create ();
      $query = sprintf("SELECT * FROM %s WHERE %s = :pk", $orm->table, $orm->getPrimaryKeyColumn ());
      $statement = $orm->db->prepare ( $query );
      $statement->setFetchMode ( PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, $concrete );
      $statement->bindValue ( ":pk", $id );
      $statement->execute ();
      $result = $statement->fetch ( PDO::FETCH_CLASS );
      if ($result)
      {
        $result->table = $orm->table;
        $result->pkCol = $orm->getPrimaryKeyColumn ();
        $result->db = $orm->db;
      }
    }
    catch ( Exception $ex )
    {
      if ($ex instanceof ORMException)
      {
        throw $ex;
      }
      throw new ORMException ( $ex->getMessage (), $ex->getCode (), $ex );
    }
    return $result;
  }
  
  /**
   * Find a particular entry by passing specific criteria
   *
   * @param array $criteria          
   * @throws ORMException
   * @return Ambigous <boolean, unknown, mixed>
   */
  public static function find(array $criteria)
  {
    $orm = self::create ();
    
    try
    {
      $concrete = get_called_class ();
      
      $wheres = "";
      $values = array ();
      if (count ( $criteria ))
      {
        foreach ( $criteria as $column => $value )
        {
          $wheres .= ($wheres ? ' AND ' : ' WHERE ') . "$column = ?";
          $values [] = $value;
        }
      }
      $query = sprintf("SELECT * FROM %s %s", $orm->table, $wheres);
      
      $statement = $orm->db->prepare ( $query );
      $statement->setFetchMode ( PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, $concrete );
      $statement->execute ( $values );
      
      $results = false;
      while ( $result = $statement->fetch ( PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE ) )
      {
        $results [] = $result;
      }
      
      if ($results === false)
      {
        throw new ORMException ( "No element found!" );
      }
      for($i = 0; $i < count ( $results ); $i ++)
      {
        $results [$i]->pkCol = $orm->getPrimaryKeyColumn ();
        $results [$i]->table = $orm->table;
        $results [$i]->db = $orm->db;
      }
      if (count ( $results ) == 1)
      {
        $results = $results [0];
      }
    }
    catch ( Exception $ex )
    {
      if ($ex instanceof ORMException)
      {
        throw $ex;
      }
      throw new ORMException ( $ex->getMessage (), $ex->getCode (), $ex );
    }
    return $results;
  }
  
  /**
   * Save the current entity
   *
   * If entity does not exist, it will be inserted.
   * Otherwise it will be updated.
   *
   * @throws ORMException
   */
  public function persist()
  {
    try
    {
      $pk = $this->getPrimaryKeyColumn ();
      $where = "";
      $query = "";
      
      if ($pk)
      {
        if ($this->getProperty ( $pk ))
        {
          $where = "WHERE " . $pk . " = :pk";
          $query = "UPDATE " . $this->table . " SET ";
          $values [':pk'] = $this->getPrimaryKeyValue ();
        }
        else
        {
          $query = "INSERT INTO " . $this->table . " SET ";
        }
      }
      else
      {
        throw new ORMException ( "Did not find any primary key identifier for updating!" );
      }
      
      $sets = "";
      
      foreach ( $this->getObjectProperties () as $propertyName )
      {
        $sets .= ($sets ? ", " : "") . $propertyName . " = :" . $propertyName . " ";
        $values [':' . $propertyName] = $this->getProperty ( $propertyName );
      }
      
      $query .= $sets . $where;
      
      $statement = $this->db->prepare ( $query );
      $statement->execute ( $values );
      if (! $this->getProperty ( $pk ))
      {
        $this->setProperty ( $pk, $this->db->lastInsertId () );
      }
    }
    catch ( Exception $ex )
    {
      if ($ex instanceof ORMException)
      {
        throw $ex;
      }
      throw new ORMException ( $ex->getMessage (), $ex->getCode (), $ex );
    }
  }
  
  /**
   * Remove particular entry by primary key
   *
   * @throws ORMException
   */
  public function remove()
  {
    try
    {
      $pk = $this->getPrimaryKeyColumn ();
    
      $query = sprintf("DELETE FROM %s WHERE %s = :%s", $this->table, $pk, $pk);
      
      $statement = $this->db->prepare ( $query );
      $statement->bindValue ( ':' . $pk, $this->getProperty ( $pk ) );
      $statement->execute ();
    }
    catch ( Exception $ex )
    {
      if($ex instanceof ORMException)
      {
        throw $ex;
      }
      throw new ORMException ( $ex->getMessage (), $ex->getCode (), $ex );
    }
  }
}
Autor
saftmeister
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen

More resources from saftmeister