diff options
Diffstat (limited to 'includes/vendor/aura/sql/src/ExtendedPdo.php')
| -rw-r--r-- | includes/vendor/aura/sql/src/ExtendedPdo.php | 1138 |
1 files changed, 1138 insertions, 0 deletions
diff --git a/includes/vendor/aura/sql/src/ExtendedPdo.php b/includes/vendor/aura/sql/src/ExtendedPdo.php new file mode 100644 index 0000000..d3668ac --- /dev/null +++ b/includes/vendor/aura/sql/src/ExtendedPdo.php @@ -0,0 +1,1138 @@ +<?php +/** + * + * This file is part of Aura for PHP. + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + */ +namespace Aura\Sql; + +use Aura\Sql\Exception; +use PDO; +use PDOStatement; + +/** + * + * This extended decorator for PDO provides lazy connection, array quoting, a + * new `perform()` method, and new `fetch*()` methods. + * + * @package Aura.Sql + * + */ +class ExtendedPdo extends PDO implements ExtendedPdoInterface +{ + /** + * + * The PDO connection itself. + * + * @var PDO + * + */ + protected $pdo; + + /** + * + * The attributes for a lazy connection. + * + * @var array + * + */ + protected $attributes = array( + self::ATTR_ERRMODE => self::ERRMODE_EXCEPTION, + ); + + /** + * + * Was the PDO connection injected at construction time? + * + * @var PDO + * + */ + protected $injected = false; + + /** + * + * The DSN for a lazy connection. + * + * @var string + * + */ + protected $dsn; + + /** + * + * PDO options for a lazy connection. + * + * @var array + * + */ + protected $options = array(); + + /** + * + * The password for a lazy connection. + * + * @var string + * + */ + protected $password; + + /** + * + * The current profile information. + * + * @var array + * + */ + protected $profile = array(); + + /** + * + * A query profiler. + * + * @var ProfilerInterface + * + */ + protected $profiler; + + /** + * + * The username for a lazy connection. + * + * @var string + * + */ + protected $username; + + /** + * + * A specialized statement preparer. + * + * @var Rebuilder + * + */ + protected $rebuilder; + + /** + * + * This constructor is pseudo-polymorphic. You may pass a normal set of PDO + * constructor parameters, and ExtendedPdo will use them for a lazy + * connection. Alternatively, if the `$dsn` parameter is an existing PDO + * instance, that instance will be decorated by ExtendedPdo; the remaining + * parameters will be ignored. + * + * @param PDO|string $dsn The data source name for a lazy PDO connection, + * or an existing instance of PDO. If the latter, the remaining params are + * ignored. + * + * @param string $username The username for a lazy connection. + * + * @param string $password The password for a lazy connection. + * + * @param array $options Driver-specific options for a lazy connection. + * + * @param array $attributes Attributes to set after a lazy connection. + * + * @see http://php.net/manual/en/pdo.construct.php + * + */ + public function __construct( + $dsn, + $username = null, + $password = null, + array $options = array(), + array $attributes = array() + ) { + if ($dsn instanceof PDO) { + $this->pdo = $dsn; + $this->injected = true; + } else { + $this->dsn = $dsn; + $this->username = $username; + $this->password = $password; + $this->options = $options; + $this->attributes = array_replace($this->attributes, $attributes); + } + } + + /** + * + * Begins a transaction and turns off autocommit mode. + * + * @return bool True on success, false on failure. + * + * @see http://php.net/manual/en/pdo.begintransaction.php + * + */ + public function beginTransaction() + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $result = $this->pdo->beginTransaction(); + $this->endProfile(); + return $result; + } + + /** + * + * Commits the existing transaction and restores autocommit mode. + * + * @return bool True on success, false on failure. + * + * @see http://php.net/manual/en/pdo.commit.php + * + */ + public function commit() + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $result = $this->pdo->commit(); + $this->endProfile(); + return $result; + } + + /** + * + * Connects to the database and sets PDO attributes. + * + * @return null + * + * @throws \PDOException if the connection fails. + * + */ + public function connect() + { + // don't connect twice + if ($this->pdo) { + return; + } + + // connect to the database + $this->beginProfile(__FUNCTION__); + $this->pdo = new PDO( + $this->dsn, + $this->username, + $this->password, + $this->options + ); + $this->endProfile(); + + // set attributes + foreach ($this->attributes as $attribute => $value) { + $this->setAttribute($attribute, $value); + } + } + + /** + * + * Explicitly disconnect by unsetting the PDO instance; does not prevent + * later reconnection, whether implicit or explicit. + * + * @return null + * + * @throws Exception\CannotDisconnect when the PDO instance was injected + * for decoration; manage the lifecycle of that PDO instance elsewhere. + * + */ + public function disconnect() + { + if ($this->injected) { + $message = "Cannot disconnect an injected PDO instance."; + throw new Exception\CannotDisconnect($message); + } + $this->pdo = null; + } + + /** + * + * Gets the most recent error code. + * + * @return mixed + * + */ + public function errorCode() + { + $this->connect(); + return $this->pdo->errorCode(); + } + + /** + * + * Gets the most recent error info. + * + * @return array + * + */ + public function errorInfo() + { + $this->connect(); + return $this->pdo->errorInfo(); + } + + /** + * + * Executes an SQL statement and returns the number of affected rows. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @return int The number of affected rows. + * + * @see http://php.net/manual/en/pdo.exec.php + * + */ + public function exec($statement) + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $affected_rows = $this->pdo->exec($statement); + $this->endProfile($statement); + return $affected_rows; + } + + /** + * + * Performs a statement and returns the number of affected rows. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return int + * + */ + public function fetchAffected($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return $sth->rowCount(); + } + + /** + * + * Fetches a sequential array of rows from the database; the rows + * are returned as associative arrays. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param callable $callable A callable to be applied to each of the rows + * to be returned. + * + * @return array + * + */ + public function fetchAll( + $statement, + array $values = array(), + $callable = null + ) { + return $this->fetchAllWithCallable( + self::FETCH_ASSOC, + $statement, + $values, + $callable + ); + } + + /** + * + * Support for fetchAll() and fetchCol(). + * + * @param string $fetch_type A PDO FETCH_* constant. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param callable $callable A callable to be applied to each of the rows + * to be returned. + * + * @return array + * + */ + protected function fetchAllWithCallable( + $fetch_type, + $statement, + array $values = array(), + $callable = null + ) { + $sth = $this->perform($statement, $values); + if ($fetch_type == self::FETCH_COLUMN) { + $data = $sth->fetchAll($fetch_type, 0); + } else { + $data = $sth->fetchAll($fetch_type); + } + return $this->applyCallableToFetchAll($callable, $data); + } + + /** + * + * Applies a callable to a data set. + * + * @param callable|null $callable The callable to apply, if any. + * + * @param array $data The data set. + * + * @return array + * + */ + protected function applyCallableToFetchAll($callable, $data) + { + if ($callable) { + foreach ($data as $key => $row) { + $data[$key] = call_user_func($callable, $row); + } + } + return $data; + } + + /** + * + * Fetches an associative array of rows from the database; the rows + * are returned as associative arrays, and the array of rows is keyed + * on the first column of each row. + * + * N.b.: If multiple rows have the same first column value, the last + * row with that value will override earlier rows. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param callable $callable A callable to be applied to each of the rows + * to be returned. + * + * @return array + * + */ + public function fetchAssoc( + $statement, + array $values = array(), + $callable = null + ) { + $sth = $this->perform($statement, $values); + + if (! $callable) { + $callable = function ($row) { return $row; }; + } + + $data = array(); + while ($row = $sth->fetch(self::FETCH_ASSOC)) { + $key = current($row); + $data[$key] = call_user_func($callable, $row); + } + + return $data; + } + + /** + * + * Fetches the first column of rows as a sequential array. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param callable $callable A callable to be applied to each of the rows + * to be returned. + * + * @return array + * + */ + public function fetchCol( + $statement, + array $values = array(), + $callable = null + ) { + return $this->fetchAllWithCallable( + self::FETCH_COLUMN, + $statement, + $values, + $callable + ); + } + + /** + * + * Fetches one row from the database as an object where the column values + * are mapped to object properties. + * + * Warning: PDO "injects property-values BEFORE invoking the constructor - + * in other words, if your class initializes property-values to defaults + * in the constructor, you will be overwriting the values injected by + * fetchObject() !" + * <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744> + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param string $class_name The name of the class to create. + * + * @param array $ctor_args Arguments to pass to the object constructor. + * + * @return object|false + * + */ + public function fetchObject( + $statement, + array $values = array(), + $class_name = 'StdClass', + array $ctor_args = array() + ) { + $sth = $this->perform($statement, $values); + + if ($ctor_args) { + return $sth->fetchObject($class_name, $ctor_args); + } + + return $sth->fetchObject($class_name); + } + + /** + * + * Fetches a sequential array of rows from the database; the rows + * are returned as objects where the column values are mapped to + * object properties. + * + * Warning: PDO "injects property-values BEFORE invoking the constructor - + * in other words, if your class initializes property-values to defaults + * in the constructor, you will be overwriting the values injected by + * fetchObject() !" + * <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744> + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param string $class_name The name of the class to create from each + * row. + * + * @param array $ctor_args Arguments to pass to each object constructor. + * + * @return array + * + */ + public function fetchObjects( + $statement, + array $values = array(), + $class_name = 'StdClass', + array $ctor_args = array() + ) { + $sth = $this->perform($statement, $values); + + if ($ctor_args) { + return $sth->fetchAll(self::FETCH_CLASS, $class_name, $ctor_args); + } + + return $sth->fetchAll(self::FETCH_CLASS, $class_name); + } + + /** + * + * Fetches one row from the database as an associative array. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return array|false + * + */ + public function fetchOne($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return $sth->fetch(self::FETCH_ASSOC); + } + + /** + * + * Fetches an associative array of rows as key-value pairs (first + * column is the key, second column is the value). + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param callable $callable A callable to be applied to each of the rows + * to be returned. + * + * @return array + * + */ + public function fetchPairs( + $statement, + array $values = array(), + $callable = null + ) { + $sth = $this->perform($statement, $values); + if ($callable) { + $data = array(); + while ($row = $sth->fetch(self::FETCH_NUM)) { + // apply the callback first so the key can be modified + $row = call_user_func($callable, $row); + // now retain the data + $data[$row[0]] = $row[1]; + } + } else { + $data = $sth->fetchAll(self::FETCH_KEY_PAIR); + } + return $data; + } + + /** + * + * Fetches the very first value (i.e., first column of the first row). + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return mixed + * + */ + public function fetchValue($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return $sth->fetchColumn(0); + } + + /** + * + * Fetches multiple from the database as an associative array. + * The first column will be the index + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param int $style a fetch style defaults to PDO::FETCH_COLUMN for single + * values, use PDO::FETCH_NAMED when fetching a multiple columns + * + * @return array + * + */ + public function fetchGroup( + $statement, + array $values = array(), + $style = self::FETCH_COLUMN + ) { + $sth = $this->perform($statement, $values); + return $sth->fetchAll(self::FETCH_GROUP | $style); + } + + /** + * + * Gets a PDO attribute value. + * + * @param mixed $attribute The PDO::ATTR_* constant. + * + * @return mixed The value for the attribute. + * + */ + public function getAttribute($attribute) + { + $this->connect(); + return $this->pdo->getAttribute($attribute); + } + + /** + * + * Returns the DSN for a lazy connection; if the underlying PDO instance + * was injected at construction time, this will be null. + * + * @return string|null + * + */ + public function getDsn() + { + return $this->dsn; + } + + /** + * + * Returns the underlying PDO connection object. + * + * @return PDO + * + */ + public function getPdo() + { + $this->connect(); + return $this->pdo; + } + + /** + * + * Returns the profiler object. + * + * @return ProfilerInterface + * + */ + public function getProfiler() + { + return $this->profiler; + } + + /** + * + * Is a transaction currently active? + * + * @return bool + * + * @see http://php.net/manual/en/pdo.intransaction.php + * + */ + public function inTransaction() + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $result = $this->pdo->inTransaction(); + $this->endProfile(); + return $result; + } + + /** + * + * Is this instance connected to a database? + * + * @return bool + * + */ + public function isConnected() + { + return isset($this->pdo); + } + + /** + * + * Returns the last inserted autoincrement sequence value. + * + * @param string $name The name of the sequence to check; typically needed + * only for PostgreSQL, where it takes the form of `<table>_<column>_seq`. + * + * @return int + * + * @see http://php.net/manual/en/pdo.lastinsertid.php + * + */ + public function lastInsertId($name = null) + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $result = $this->pdo->lastInsertId($name); + $this->endProfile(); + return $result; + } + + /** + * + * Performs a query with bound values and returns the resulting + * PDOStatement; array values will be passed through `quote()` and their + * respective placeholders will be replaced in the query string. + * + * @param string $statement The SQL statement to perform. + * + * @param array $values Values to bind to the query + * + * @return PDOStatement + * + * @see quote() + * + */ + public function perform($statement, array $values = array()) + { + $sth = $this->prepareWithValues($statement, $values); + $this->beginProfile(__FUNCTION__); + $sth->execute(); + $this->endProfile($statement, $values); + return $sth; + } + + /** + * + * Prepares an SQL statement for execution. + * + * @param string $statement The SQL statement to prepare for execution. + * + * @param array $options Set these attributes on the returned + * PDOStatement. + * + * @return PDOStatement + * + * @see http://php.net/manual/en/pdo.prepare.php + * + */ + public function prepare($statement, $options = array()) + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $sth = $this->pdo->prepare($statement, $options); + $this->endProfile($statement, $options); + return $sth; + } + + /** + * + * Queries the database and returns a PDOStatement. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param int $fetch_mode The `PDO::FETCH_*` type to set on the returned + * `PDOStatement::setFetchMode()`. + * + * @param mixed $fetch_arg1 The first additional argument to send to + * `PDOStatement::setFetchMode()`. + * + * @param mixed $fetch_arg2 The second additional argument to send to + * `PDOStatement::setFetchMode()`. + * + * @return PDOStatement + * + * @see http://php.net/manual/en/pdo.query.php + * + */ + public function query($statement) + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + + // remove empty constructor params list if it exists + $args = func_get_args(); + if (count($args) === 4 && $args[3] === array()) { + unset($args[3]); + } + + $sth = call_user_func_array(array($this->pdo, 'query'), $args); + + $this->endProfile($sth->queryString); + return $sth; + } + + /** + * + * Quotes a value for use in an SQL statement. + * + * This differs from `PDO::quote()` in that it will convert an array into + * a string of comma-separated quoted values. + * + * @param mixed $value The value to quote. + * + * @param int $parameter_type A data type hint for the database driver. + * + * @return mixed The quoted value. + * + * @see http://php.net/manual/en/pdo.quote.php + * + */ + public function quote($value, $parameter_type = self::PARAM_STR) + { + $this->connect(); + + // non-array quoting + if (! is_array($value)) { + return $this->pdo->quote($value, $parameter_type); + } + + // quote array values, not keys, then combine with commas + foreach ($value as $k => $v) { + $value[$k] = $this->pdo->quote($v, $parameter_type); + } + return implode(', ', $value); + } + + /** + * + * Rolls back the current transaction, and restores autocommit mode. + * + * @return bool True on success, false on failure. + * + * @see http://php.net/manual/en/pdo.rollback.php + * + */ + public function rollBack() + { + $this->connect(); + $this->beginProfile(__FUNCTION__); + $result = $this->pdo->rollBack(); + $this->endProfile(); + + return $result; + } + + /** + * + * Sets a PDO attribute value. + * + * @param mixed $attribute The PDO::ATTR_* constant. + * + * @param mixed $value The value for the attribute. + * + * @return bool True on success, false on failure. Note that if PDO has not + * not connected, all calls will be treated as successful. + * + */ + public function setAttribute($attribute, $value) + { + if ($this->pdo) { + return $this->pdo->setAttribute($attribute, $value); + } + + $this->attributes[$attribute] = $value; + return true; + } + + /** + * + * Sets the profiler object. + * + * @param ProfilerInterface $profiler + * + * @return null + * + */ + public function setProfiler(ProfilerInterface $profiler) + { + $this->profiler = $profiler; + } + + /** + * + * Begins a profile entry. + * + * @param string $function The function starting the profile entry. + * + * @return null + * + */ + protected function beginProfile($function) + { + // if there's no profiler, can't profile + if (! $this->profiler) { + return; + } + + // retain starting profile info + $this->profile['time'] = microtime(true); + $this->profile['function'] = $function; + } + + /** + * + * Ends and records a profile entry. + * + * @param string $statement The statement being profiled, if any. + * + * @param array $values The values bound to the statement, if any. + * + * @return null + * + */ + protected function endProfile($statement = null, array $values = array()) + { + // is there a profiler in place? + if ($this->profiler) { + // add an entry to the profiler + $this->profiler->addProfile( + microtime(true) - $this->profile['time'], + $this->profile['function'], + $statement, + $values + ); + } + + // clear the starting profile info + $this->profile = array(); + } + + /** + * + * Prepares an SQL statement with bound values. + * + * This method only binds values that have placeholders in the + * statement, thereby avoiding errors from PDO regarding too many bound + * values. It also binds all sequential (question-mark) placeholders. + * + * If a placeholder value is an array, the array is converted to a string + * of comma-separated quoted values; e.g., for an `IN (...)` condition. + * The quoted string is replaced directly into the statement instead of + * using `PDOStatement::bindValue()` proper. + * + * @param string $statement The SQL statement to prepare for execution. + * + * @param array $values The values to bind to the statement, if any. + * + * @return PDOStatement + * + * @see http://php.net/manual/en/pdo.prepare.php + * + */ + public function prepareWithValues($statement, array $values = array()) + { + // if there are no values to bind ... + if (! $values) { + // ... use the normal preparation + return $this->prepare($statement); + } + + // rebuild the statement and values + $rebuilder = new Rebuilder($this); + list($statement, $values) = $rebuilder->__invoke($statement, $values); + + // prepare the statement + $sth = $this->prepare($statement); + + // for the placeholders we found, bind the corresponding data values + foreach ($values as $key => $val) { + $this->bindValue($sth, $key, $val); + } + + // done + return $sth; + } + + /** + * + * Yields rows from the database. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return Iterator\AllIterator + * + */ + public function yieldAll($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return new Iterator\AllIterator($sth); + } + + /** + * + * Yields rows from the database keyed on the first column of each row. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return Iterator\AssocIterator + * + */ + public function yieldAssoc($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return new Iterator\AssocIterator($sth); + } + + /** + * + * Yields the first column of each row. + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return Iterator\ColIterator + * + */ + public function yieldCol($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return new Iterator\ColIterator($sth); + } + + /** + * + * Yields objects where the column values are mapped to object properties. + * + * Warning: PDO "injects property-values BEFORE invoking the constructor - + * in other words, if your class initializes property-values to defaults + * in the constructor, you will be overwriting the values injected by + * fetchObject() !" + * <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744> + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @param string $class_name The name of the class to create from each + * row. + * + * @param array $ctor_args Arguments to pass to each object constructor. + * + * @return Iterator\ObjectsIterator + * + */ + public function yieldObjects( + $statement, + array $values = array(), + $class_name = 'stdClass', + array $ctor_args = array() + ) { + $sth = $this->perform($statement, $values); + return new Iterator\ObjectsIterator($sth, $class_name, $ctor_args); + } + + /** + * + * Yields key-value pairs (first column is the key, second column is the + * value). + * + * @param string $statement The SQL statement to prepare and execute. + * + * @param array $values Values to bind to the query. + * + * @return Iterator\PairsIterator + * + */ + public function yieldPairs($statement, array $values = array()) + { + $sth = $this->perform($statement, $values); + return new Iterator\PairsIterator($sth); + } + + /** + * + * Bind a value using the proper PDO::PARAM_* type. + * + * @param PDOStatement $sth The statement to bind to. + * + * @param mixed $key The placeholder key. + * + * @param mixed $val The value to bind to the statement. + * + * @return null + * + * @throws Exception\CannotBindValue when the value to be bound is not + * bindable (e.g., array, object, or resource). + * + */ + protected function bindValue(PDOStatement $sth, $key, $val) + { + if (is_int($val)) { + return $sth->bindValue($key, $val, self::PARAM_INT); + } + + if (is_bool($val)) { + // bind booleans as string '1' or string '0'. + // cf. https://bugs.php.net/bug.php?id=49255 + $val = $val ? '1' : '0'; + return $sth->bindValue($key, $val, self::PARAM_BOOL); + } + + if (is_null($val)) { + return $sth->bindValue($key, $val, self::PARAM_NULL); + } + + if (! is_scalar($val)) { + $type = gettype($val); + throw new Exception\CannotBindValue( + "Cannot bind value of type '{$type}' to placeholder '{$key}'" + ); + } + + $sth->bindValue($key, $val); + } +} |