Overview

Namespaces

  • Alchemy
    • core
      • query
      • schema
    • dialect
    • engine
    • orm
    • tests
    • util
      • promise
  • PHP

Classes

  • Engine
  • ResultSet

Interfaces

  • IEngine
  • IResultSet
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: namespace Alchemy\engine;
  4: use Alchemy\core\query\IQuery;
  5: use Alchemy\dialect\Compiler;
  6: use Alchemy\util\Monad;
  7: use PDO;
  8: 
  9: 
 10: /**
 11:  * Basic Engine implementation using PDO as it's DBAPI layer
 12:  */
 13: class Engine implements IEngine {
 14:     protected $compiler;
 15:     protected $connector;
 16:     protected $echoQueries = false;
 17:     protected $pendingTransaction = false;
 18: 
 19: 
 20:     /**
 21:      * Object constructor. Opens a connection to the database using PDO
 22:      *
 23:      * @param string $dsn See PDO documentation for DSN reference
 24:      * @param string $username
 25:      * @param string $password
 26:      */
 27:     public function __construct(Compiler $compiler, $dsn, $username = '', $password = '') {
 28:         // Get connection
 29:         $this->connector = new PDO($dsn, $username, $password);
 30:         $this->connector->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 31: 
 32:         $this->compiler = $compiler;
 33:     }
 34: 
 35: 
 36:     /**
 37:      * Start an atomic transaction on the database. These should
 38:      * generally not be held open very long in order to prevent
 39:      * deadlocks
 40:      */
 41:     public function beginTransaction() {
 42:         if (!$this->pendingTransaction) {
 43:             $this->connector->beginTransaction();
 44:             $this->pendingTransaction = true;
 45:         }
 46:     }
 47: 
 48: 
 49:     /**
 50:      * Commit a transaction as complete
 51:      */
 52:     public function commitTransaction() {
 53:         if ($this->pendingTransaction) {
 54:             $this->connector->commit();
 55:             $this->pendingTransaction = false;
 56:         }
 57:     }
 58: 
 59: 
 60:     /**
 61:      * Get the PDO bind-parameter type (default PARAM_STR) for an object
 62:      *
 63:      * @param  object $obj
 64:      * @return integer
 65:      */
 66:     protected function getBindParamType($obj) {
 67:         static $map = array(
 68:             'boolean' => PDO::PARAM_BOOL,
 69:             'integer' => PDO::PARAM_INT,
 70:             'null'    => PDO::PARAM_NULL);
 71: 
 72:         $type = $obj->getTag('expr.value');
 73:         return array_key_exists($type, $map) ? $map[$type] : PDO::PARAM_STR;
 74:     }
 75: 
 76: 
 77:     /**
 78:      * Log a SQL statement if echo is enabled
 79:      *
 80:      * @param string $sql
 81:      */
 82:     protected function echoQuery($sql, $params) {
 83:         if (!$this->echoQueries) {
 84:             return;
 85:         }
 86: 
 87:         if (is_callable($this->echoQueries)) {
 88:             return $this->echoQueries($sql, $params);
 89:         }
 90: 
 91:         $sql = preg_replace_callback("/:([\w\d]+)/sui", function($match) use ($params) {
 92:             $v = $params[$match[1]];
 93:             if (is_null($v)) {
 94:                 return 'NULL';
 95:             }
 96: 
 97:             if (is_numeric($v)) {
 98:                 return $v;
 99:             }
100: 
101:             if (mb_strlen($v) > 13) {
102:                $v = mb_substr($v, 0, 10) . '...';
103:             }
104:             return "'{$v}'";
105:         }, $sql);
106: 
107:         echo $sql . "\n";
108:     }
109: 
110: 
111:     /**
112:      * Compile and run a SQL expression on the database
113:      *
114:      * @param IQuery Query to compile
115:      * @return ResultSet
116:      */
117:     public function query(IQuery $query) {
118:         $sql = $this->compiler->compile($query);
119:         $sql = is_array($sql) ? $sql : array($sql);
120:         $params = $query->parameters();
121: 
122:         // Because of the limitations of some RDBMS' (*cough*SQLite)
123:         // some operations can not be performed in a single query.
124:         // When this is the case, the SQL compiler returns an array
125:         // of queries. Execute each of them and return the result set
126:         // of the last one. This behavior is fine because it only currently
127:         // applies to DDL operations, which don't have a useful return value
128:         // anyway
129:         $result = null;
130:         foreach ($sql as $q) {
131:             $result = $this->execute($q, $params);
132:         }
133: 
134:         return $result;
135:     }
136: 
137: 
138:     /**
139:      * Execute raw SQL on the database connection
140:      *
141:      * @param string $sql Statement string
142:      * @param array $params Params to bind to statement
143:      * @return ResultSet
144:      */
145:     public function execute($sql, $params = array()) {
146:         try {
147:             $statement = $this->connector->prepare($sql);
148:         } catch (Exception $e) {
149:             throw new Exception("Could not prepare query: " . $sql);
150:         }
151: 
152:         $paramLog = array();
153:         foreach ($params as $param) {
154:             $type = $this->getBindParamType($param);
155:             $alias = $this->compiler->alias($param);
156:             $value = $param->getValue();
157:             $paramLog[$alias] = $value;
158:             $statement->bindValue($alias, $value, $type);
159:         }
160: 
161:         $this->echoQuery($sql, $paramLog);
162: 
163:         $statement->execute();
164:         return new ResultSet($this->connector, $statement);
165:     }
166: 
167: 
168:     /**
169:      * Revert a pending transaction on the database
170:      */
171:     public function rollbackTransaction() {
172:         if ($this->pendingTransaction) {
173:             $this->connector->rollBack();
174:         }
175:     }
176: 
177: 
178:     /**
179:      * Optionally enable echo'ing of SQL run on the RDBMS.
180:      *
181:      * @param mixed $echoQueries False to disable. True to log to STDOUT. Callable to enable custom logging
182:      */
183:     public function setEcho($echoQueries) {
184:         $this->echoQueries = $echoQueries;
185:     }
186: }
187: 
API documentation generated by ApiGen 2.8.0