diff --git a/build/db-sync.phar b/build/db-sync.phar index c2b422f..f93ac2b 100755 Binary files a/build/db-sync.phar and b/build/db-sync.phar differ diff --git a/composer.json b/composer.json index 4d81ec1..bf7572e 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "description": "Command component based on laravel's", "autoload": { "psr-0": { - "DbSync": "src" + "DbSync": "src", + "Dbal": "src" }, "files" : ["src/functions.php"] }, @@ -17,7 +18,6 @@ "minimum-stability": "dev", "require" : { "mrjgreen/php-cli" : "1.*", - "mrjgreen/dbal" : "1.*", "psr/log" : "dev-master" } } diff --git a/composer.lock b/composer.lock index 9d17def..955fa1e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,61 +4,20 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "bbd4e8534793a2bbec5381add5b32f01", + "hash": "061acc2d1135014b38a16c4b4945961b", "packages": [ { - "name": "joegreen0991/dbal", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/joegreen0991/dbal.git", - "reference": "8fd2913a6e6148b90ff02bf86766f59f6f25b282" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/joegreen0991/dbal/zipball/8fd2913a6e6148b90ff02bf86766f59f6f25b282", - "reference": "8fd2913a6e6148b90ff02bf86766f59f6f25b282", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Dbal": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joe Green", - "email": "joe.green0991@gmail.com" - } - ], - "description": "Database Abstraction Layer for laravel four", - "keywords": [ - "Abstraction Layer", - "database", - "dbal" - ], - "time": "2014-07-23 15:04:40" - }, - { - "name": "joegreen0991/php-cli", - "version": "dev-master", + "name": "mrjgreen/php-cli", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/joegreen0991/php-cli.git", - "reference": "ed20427f9b37fd02eb853b3c1b38c495a94327ac" + "url": "https://github.com/mrjgreen/php-cli.git", + "reference": "37f341959d94a347aa2db852d1c7b686f9746c29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/joegreen0991/php-cli/zipball/ed20427f9b37fd02eb853b3c1b38c495a94327ac", - "reference": "ed20427f9b37fd02eb853b3c1b38c495a94327ac", + "url": "https://api.github.com/repos/mrjgreen/php-cli/zipball/37f341959d94a347aa2db852d1c7b686f9746c29", + "reference": "37f341959d94a347aa2db852d1c7b686f9746c29", "shasum": "" }, "type": "library", @@ -69,7 +28,7 @@ }, "notification-url": "https://packagist.org/downloads/", "description": "Command component based on laravel's", - "time": "2014-02-22 10:20:37" + "time": "2014-10-20 08:36:15" }, { "name": "psr/log", @@ -115,22 +74,13 @@ "time": "2014-01-18 15:33:09" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "packages-dev": [], + "aliases": [], "minimum-stability": "dev", "stability-flags": { - "joegreen0991/php-cli": 20, - "joegreen0991/dbal": 20, "psr/log": 20 }, - "platform": [ - - ], - "platform-dev": [ - - ] + "prefer-stable": false, + "platform": [], + "platform-dev": [] } diff --git a/src/Dbal/Db.php b/src/Dbal/Db.php new file mode 100644 index 0000000..d5ec83b --- /dev/null +++ b/src/Dbal/Db.php @@ -0,0 +1,570 @@ +_connection = $connection; + + $this->profile($profile); + + } + + public function profile($profile = true) + { + $this->_profile = (bool) $profile; + } + + /** + * Get the PDO connection or fetch the default one if it doesn't exist + * @return \PDO + */ + function getConnection() { + return $this->_connection; + } + + /** + * Get the PDO connection or fetch the default one if it doesn't exist + * @return \PDO + */ + function setConnection(PDO $connection) { + $this->_connection = $connection; + } + + /** + * Prpare and execute a full query using place holders and bound paramters. Return the executed PDOStatement + * + * @param string $sql + * @param mixed $bind + * @return \PDOStatement + */ + public function query($sql, $bind = array()) { + + is_array($bind) or $bind = array($bind); + + $stmt = $this->getConnection()->prepare($sql); + + $start = microtime(true); + + try { + $stmt->execute($bind); + }catch(\PDOException $e){ + throw new \PDOException($e->getMessage() . "\n QUERY: " . $sql . "\n BIND: " . var_export($bind,1)); + } + + $time = microtime(true) - $start; + + $this->_profile and $this->_queries[] = compact('sql','bind','time','stmt'); + + return $stmt; + } + + /** + * Queries the database to find the columns in the given table(s) + * + * @param mixed $table A string table name, or an array of string tablenames + * @return array An array containing the column names from the given table(s) + */ + public function getColumnNames($table) { + if (is_array($table)) { + $columnNames = array(); + foreach ($table as $t) { + $columnNames = array_merge($columnNames, $this->getColumnNames($t)); + } + return $columnNames; + } + + return array_keys($this->getColumnInfo($table)); + } + + /** + * Queries the database to find the columns in the given table(s) + * + * @param mixed $table A string table name, or an array of string tablenames + * @return array An array containing the column names from the given table(s) + */ + public function getColumnInfo($table) { + if (!array_key_exists($table, $this->_tableCache)) { + $cols = array(); + foreach ($this->query('SHOW COLUMNS FROM ' . $table) as $col) { + isset($col['Field']) and $cols[$col['Field']] = $col; + } + $this->_tableCache[$table] = $cols; + } + return $this->_tableCache[$table]; + } + + public function showTables($database = null) + { + $from = $database ? ' FROM ' . $database : ''; + + return array_map(function($obj){ + return $obj[0]; + }, $this->query('SHOW TABLES' . $from)->fetchAll(PDO::FETCH_NUM)); + } + + public function showPrimaryKey($table) + { + $fields = array_filter($this->getColumnInfo($table), function($item){ + return $item['Key'] === 'PRI'; + }); + + return array_keys($fields); + } + + /** + * Escape a value or array of values and bind them into an sql statement + * + * @param string $string The sql statement + * @param array|mixed $bind A single value to be bound or an array of values + * @param string $quotedString The quoted string + */ + public function quoteInto($sql, $bind = array()) { + is_array($bind) or $bind = array($bind); + + foreach ($bind as $key => $value) { + $replace = (is_numeric($key) ? '?' : ':' . $key); + $sql = substr_replace($sql, $this->quote($value), strpos($sql, $replace), strlen($replace)); + } + return $sql; + } + + /** + * Escape a value ready to be inserted into the database + * + * @param string $string The unquoted string + * @param string $quotedString The quoted string + */ + public function quote($string) { + + $connection = $this->getConnection(); + + if(is_array($string)){ + foreach($string as $k => $value){ + $string[$k] = $connection->quote($value); + } + return $string; + } + + return $connection->quote($string); + } + + /** + * Remove all keys that don't exist as columns in the given table(s) + * + * @param mixed $table + * @param array $data The input array + * @return array The remaining array key=>values + */ + public function reduceData($table, $data) { + return array_intersect_key($data, array_flip($this->getColumnNames($table))); + } + + /** + * + * @param string $sql + * @param mixed $bind + * @return mixed + */ + public function fetch($sql, $bind = array()) { + return $this->query($sql, $bind)->fetch(); + } + + /** + * + * @param string $sql + * @param mixed $bind + * @return mixed + */ + public function fetchNumeric($sql, $bind = array()) { + return $this->query($sql, $bind)->fetch(PDO::FETCH_NUM); + } + + /** + * + * @param string $sql + * @param mixed $bind + * @return mixed + */ + public function fetchObject($sql, $bind = array(),$object = null) { + $query = $this->query($sql, $bind); + $query->setFetchMode(PDO::FETCH_INTO,is_null($object) ? new \stdClass() : $object); + return $query->fetch(); + } + + /** + * + * @param string $sql + * @param mixed $bind + * @return array + */ + public function fetchOne($sql, $bind = array()) { + return $this->query($sql, $bind)->fetchColumn(); + } + + /** + * + * @param string $sql + * @param mixed $bind + * @return array + */ + public function fetchAll($sql, $bind = array()) { + return $this->query($sql, $bind)->fetchAll(); + } + + /** + * + * @param string $table + * @param array $data + * @return \PDOStatement + */ + public function insert($table, array $data) { + return $this->doInsert($table, $data); + } + + /** + * + * @param string $table + * @param array $data + * @return \PDOStatement + * @throws DbException + */ + public function insertIgnore($table, array $data){ + return $this->doInsert($table, $data, self::INSERT_IGNORE); + } + + /** + * + * @param string $table + * @param array $data + * @return \PDOStatement + * @throws DbException + */ + public function replace($table, array $data){ + return $this->doInsert($table, $data, self::INSERT_REPLACE); + } + + /** + * + * @param string $table + * @param array $data + * @param bool $ignore Run as an insert ignore + * @return \PDOStatement + * @throws DbException + */ + protected function doInsert($table, array $data, $type = self::INSERT){ + + $data = $this->reduceData($table, $data); + + if (!count($data)) { + throw new EmptyDataset('No data in array to insert'); + } + + $cols = array_keys($data); + + $qs = ''; + foreach ($data as $col => $v) { + $qs .= ($v instanceof Expr ? (string)$v : '?') . ','; + if($v instanceof Expr){ + unset($data[$col]); + } + } + + $sql = $type . ' INTO ' . $table . ' (' . implode(',', $cols) . ') VALUES (' . trim($qs, ',') . ')'; + + return $this->query($sql, array_values($data)); + } + + /** + * + * @param string $table + * @param array $data + * @param bool $ignore Run as an insert ignore + * @return \PDOStatement + * @throws DbException + */ + protected function doMultiInsert($table, $data, $type = self::INSERT){ + + $count = $data instanceof \PDOStatement ? $data->rowCount() : count($data); + + if (!$count) { + throw new EmptyDataset('No data in array to insert'); + } + + $cols = null; + $qs = ''; + $bind = array(); + $i = 0; + + foreach($data as $row) + { + $cols or $cols = array_keys($row); + $subq = ''; + foreach ($row as $col => $v) { + $subq .= ($v instanceof Expr ? (string)$v : '?') . ','; + if(!$v instanceof Expr){ + $bind[] = $row[$col]; + } + else { + unset($row[$col]); + } + } + + $qs .= ('(' . rtrim($subq, ',') . ')' . (++$i !== $count ? ',' : '')); + } + + return $this->query($type . ' INTO ' . $table . ' (' . implode(',', $cols) . ') VALUES ' . $qs, $bind); + } + + /** + * + * @param string $table + * @param array $data + * @param bool $ignore Run as an insert ignore + * @return \PDOStatement + * @throws DbException + */ + public function multiInsert($table, $data){ + return $this->doMultiInsert($table, $data); + } + + /** + * + * @param string $table + * @param array $data + * @param bool $ignore Run as an insert ignore + * @return \PDOStatement + * @throws DbException + */ + public function multiInsertIgnore($table, $data){ + return $this->doMultiInsert($table, $data, self::INSERT_IGNORE); + } + + /** + * + * @param string $table + * @param array $data + * @param bool $ignore Run as an insert ignore + * @return \PDOStatement + * @throws DbException + */ + public function multiReplace($table, $data){ + return $this->doMultiInsert($table, $data, self::INSERT_REPLACE); + } + + /** + * + * @param string $table + * @param array $data + * @return \PDOStatement + * @throws DbException + */ + public function multiInsertOnDuplicateKeyUpdate($table, $data) { + $count = $data instanceof \PDOStatement ? $data->rowCount() : count($data); + + if (!$count) { + throw new EmptyDataset('No data in array to insert'); + } + + $cols = null; + $qs = ''; + $bind = array(); + + foreach($data as $row) + { $qs .= '('; + foreach ($row as $col => $v) { + $qs .= ($v instanceof Expr ? (string)$v : '?') . ','; + if(!$v instanceof Expr){ + $bind[] = $row[$col]; + } + else { + unset($row[$col]); + } + } + $cols or $cols = array_keys($row); + $qs = trim($qs, ',') . '),'; + } + + $colsValues = array_map(function($col){ + return $col . ' = VALUES(' . $col . ')'; + }, $cols); + + // Build the statement + $sql = 'INSERT INTO ' . $table . ' + (' . implode(', ', $cols) . ') VALUES ' . trim($qs, ',') . ' + ON DUPLICATE KEY UPDATE ' . implode(', ', $colsValues); + + return $this->query($sql, $bind); + } + + /** + * + * @param string $table + * @param array $data + * @return \PDOStatement + * @throws DbException + */ + public function insertOnDuplicateKeyUpdate($table, array $data) { + $data = $this->reduceData($table, $data); + + if (!count($data)) { + throw new EmptyDataset('No data in array to update'); + } + + // Extract column names from the array keys + $update = $vals = $cols = array(); + foreach ($data as $col => $v) { + $cols[] = $col; + $vals[] = $v instanceof Expr ? (string)$v : '?'; + $update[] = $col . ' = ' . ($v instanceof Expr ? (string)$v : '?'); + + if($v instanceof Expr){ + unset($data[$col]); + } + } + + // Build the statement + $sql = 'INSERT INTO ' . $table . ' + (' . implode(', ', $cols) . ') VALUES (' . implode(', ', $vals) . ') + ON DUPLICATE KEY UPDATE ' . implode(', ', $update); + + return $this->query($sql, array_merge(array_values($data), array_values($data))); + } + + /** + * + * @param string $table + * @param array $data + * @param string $where + * @param mixed $bind + * @return \PDOStatement + */ + public function update($table, array $data, $where = '0', $bind = array()) { + + $data = $this->reduceData($table, $data); + + if (!count($data)) { + throw new EmptyDataset('No data in array to update'); + } + + $str = ''; + foreach ($data as $col => $v) { + $val = $v instanceof Expr ? (string)$v : '?'; + $str .= $col . '=' . $val . ','; + + if($v instanceof Expr){ + unset($data[$col]); + } + } + + $sql = 'UPDATE ' . $table . ' SET ' . trim($str, ',') . ' WHERE ' . $where; + + $bind = array_merge($data, is_array($bind) ? $bind : array($bind)); + + return $this->query($sql, array_values($bind)); + } + + /** + * + * @param string $table + * @param string $where + * @param mixed|array $bind + * @return \PDOStatement + */ + public function delete($table, $where = 0, $bind = array()) { + return $this->query('DELETE FROM ' . $table . ' WHERE ' . $where, $bind); + } + + /** + * Get the "auto increment id" of the last inserted row. + * + * @return int + */ + public function lastInsertId() { + return $this->getConnection()->lastInsertId(); + } + + + /** + * Execute a Closure within a transaction. + * + * @param Closure $callback + * @return mixed + */ + public function transaction(\Closure $callback){ + + $this->getConnection()->beginTransaction(); + + $result = $callback($this); + + $this->getConnection()->commit(); + + return $result; + } + + /** + * Get the profile data of queries ran so far + * + * @return array + */ + public function getProfile(){ + return $this->_queries; + } + + public static function make(array $config){ + + extract($config); + + $db = isset($database) ? ";dbname={$database}" : ''; + + $defaults = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ); + + $options = isset($options) ? array_replace($defaults, $options) : $defaults; + + $pdo = new PDO("mysql:host={$host}$db", $user, $password, $options); + + isset($charset) and $pdo->prepare("SET NAMES '{$charset}'")->execute(); + + return new static($pdo); + } + +} diff --git a/src/Dbal/Expr.php b/src/Dbal/Expr.php new file mode 100644 index 0000000..cb5da70 --- /dev/null +++ b/src/Dbal/Expr.php @@ -0,0 +1,13 @@ +expr = $expr; + } + + public function __toString() { + return $this->expr; + } +} \ No newline at end of file