├── library └── Gnix │ └── Db │ ├── Exception.php │ ├── Connection │ ├── Slave.php │ ├── Master.php │ └── Abstract.php │ ├── Util.php │ ├── Literal.php │ ├── Criteria │ ├── Index.php │ ├── OrderBy.php │ ├── Limit.php │ └── Where.php │ ├── Connection.php │ ├── Query │ └── Resolver.php │ ├── Criteria.php │ ├── Row.php │ └── Query.php └── README.md /library/Gnix/Db/Exception.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Gnix_Db_Exception extends Exception 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /library/Gnix/Db/Connection/Slave.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Connection_Slave extends Gnix_Db_Connection_Abstract 12 | { 13 | protected static $_defaultAttributes = array(); 14 | protected static $_infos = array(); 15 | protected static $_connections = array(); 16 | } 17 | -------------------------------------------------------------------------------- /library/Gnix/Db/Connection/Master.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Connection_Master extends Gnix_Db_Connection_Abstract 12 | { 13 | protected static $_defaultAttributes = array(); 14 | protected static $_infos = array(); 15 | protected static $_connections = array(); 16 | } 17 | -------------------------------------------------------------------------------- /library/Gnix/Db/Util.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Util 12 | { 13 | public static function uncamelize($str) 14 | { 15 | $str = lcfirst($str); 16 | $str = str_replace('_', '-', $str); 17 | $str = preg_replace('/([A-Z])/', '_$1', $str); 18 | return strtolower($str); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/Gnix/Db/Literal.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Literal 12 | { 13 | private $_literal; 14 | 15 | public function __construct($literal) 16 | { 17 | // TODO: Exception if $data is not a scalar value. 18 | $this->_literal = $literal; 19 | } 20 | 21 | public function toString() 22 | { 23 | return (string) $this->_literal; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /library/Gnix/Db/Criteria/Index.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Criteria_Index 12 | { 13 | private $_hint = ''; 14 | 15 | public function indexUse($index) 16 | { 17 | $this->_hint = ' USE INDEX (' . $index . ')'; 18 | } 19 | 20 | public function indexForce($index) 21 | { 22 | $this->_hint = ' FORCE INDEX (' . $index . ')'; 23 | } 24 | 25 | public function toString() 26 | { 27 | return $this->_hint; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/Gnix/Db/Criteria/OrderBy.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Criteria_OrderBy 12 | { 13 | private $_columns = array(); 14 | 15 | public function orderBy($column) 16 | { 17 | $this->_columns[] = $column; 18 | } 19 | 20 | public function orderByDesc($column) 21 | { 22 | $this->orderBy($column . ' DESC'); 23 | } 24 | 25 | public function toString() 26 | { 27 | if ($this->_columns) { 28 | return ' ORDER BY ' . implode(', ', $this->_columns); 29 | } 30 | 31 | return ''; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/Gnix/Db/Connection.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Connection 12 | { 13 | public static function setDefaultAttributes(array $attributes) 14 | { 15 | Gnix_Db_Connection_Master::setDefaultAttributes($attributes); 16 | Gnix_Db_Connection_Slave::setDefaultAttributes($attributes); 17 | } 18 | 19 | public static function setInfo($key, array $info) 20 | { 21 | Gnix_Db_Connection_Master::setInfo($key, $info); 22 | Gnix_Db_Connection_Slave::setInfo($key, $info); 23 | } 24 | 25 | public static function disconnect($key) 26 | { 27 | Gnix_Db_Connection_Master::disconnect($key); 28 | Gnix_Db_Connection_Slave::disconnect($key); 29 | } 30 | 31 | public static function disconnectAll() 32 | { 33 | Gnix_Db_Connection_Master::disconnectAll(); 34 | Gnix_Db_Connection_Slave::disconnectAll(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /library/Gnix/Db/Query/Resolver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Query_Resolver 12 | { 13 | private $_rowClass; 14 | private $_connectionName; 15 | private $_table; 16 | 17 | public function __construct($calledClass, $connectionName, $table) 18 | { 19 | if (!preg_match('/^((\w+)_(\w+))_Query$/', $calledClass, $matches)) { 20 | throw new Gnix_Db_Exception("Somthing wrong with the Query class '$calledClass'"); 21 | } 22 | 23 | $this->_rowClass = $matches[1]; 24 | $this->_connectionName = $connectionName ? $connectionName : Gnix_Db_Util::uncamelize($matches[2]); 25 | $this->_table = $table ? $table : Gnix_Db_Util::uncamelize($matches[3]); 26 | } 27 | 28 | public function getRowClass() 29 | { 30 | return $this->_rowClass; 31 | } 32 | 33 | public function getConnectionName() 34 | { 35 | return $this->_connectionName; 36 | } 37 | 38 | public function getTable() 39 | { 40 | return $this->_table; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/Gnix/Db/Criteria/Limit.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Criteria_Limit 12 | { 13 | private $_limit; 14 | private $_offset; 15 | 16 | public function limit($limit) 17 | { 18 | $this->_limit = $limit; 19 | } 20 | 21 | public function offset($offset) 22 | { 23 | if ($this->_limit === null) { 24 | throw new Gnix_Db_Exception("Can't set 'offset' before you set 'limit'"); 25 | } 26 | 27 | $this->_offset = $offset; 28 | } 29 | 30 | public function page($page) 31 | { 32 | if ($this->_limit === null) { 33 | throw new Gnix_Db_Exception("Can't set 'offset' before you set 'limit'"); 34 | } 35 | 36 | $page = (int) $page; 37 | if ($page <= 0) { 38 | $page = 1; 39 | } 40 | 41 | $this->_offset = ($page - 1) * $this->_limit; 42 | } 43 | 44 | public function toString() 45 | { 46 | if ($this->_limit === null) { 47 | return ''; 48 | } 49 | 50 | if ($this->_offset) { 51 | return ' LIMIT ' . (int) $this->_offset . ', ' . (int) $this->_limit; 52 | } 53 | 54 | return ' LIMIT ' . (int) $this->_limit; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/Gnix/Db/Criteria.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Criteria 12 | { 13 | private $_where; 14 | private $_orderBy; 15 | private $_limit; 16 | private $_index; 17 | 18 | public static function self() 19 | { 20 | trigger_error('Gnix_Db_Criteria::self() is deprecated. Use self::_getCriteria() in *_Query class instead', E_USER_DEPRECATED); 21 | return new self(); 22 | } 23 | 24 | public function __construct() 25 | { 26 | $this->_where = new Gnix_Db_Criteria_Where(); 27 | $this->_orderBy = new Gnix_Db_Criteria_OrderBy(); 28 | $this->_limit = new Gnix_Db_Criteria_Limit(); 29 | $this->_index = new Gnix_Db_Criteria_Index(); 30 | } 31 | 32 | public function __call($name, array $arguments) 33 | { 34 | switch ($name) { 35 | case 'where': 36 | case 'whereEqual': 37 | case 'whereNotEqual': 38 | case 'whereGreater': 39 | case 'whereGreaterEqual': 40 | case 'whereLess': 41 | case 'whereLessEqual': 42 | case 'whereIsNull': 43 | case 'whereIsNotNull': 44 | case 'whereLike': 45 | case 'whereNotLike': 46 | case 'whereBetween': 47 | case 'whereIn': 48 | case 'whereNotIn': 49 | call_user_func_array(array($this->_where, $name), $arguments); 50 | return $this; 51 | case 'orderBy': 52 | case 'orderByDesc': 53 | call_user_func_array(array($this->_orderBy, $name), $arguments); 54 | return $this; 55 | case 'limit': 56 | case 'offset': 57 | case 'page': 58 | call_user_func_array(array($this->_limit, $name), $arguments); 59 | return $this; 60 | case 'indexUse': 61 | case 'indexForce': 62 | call_user_func_array(array($this->_index, $name), $arguments); 63 | return $this; 64 | } 65 | 66 | throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $name . '()'); 67 | } 68 | 69 | public function assemble() 70 | { 71 | return $this->_index->toString() . $this->_where->toString() . $this->_orderBy->toString() . $this->_limit->toString(); 72 | } 73 | 74 | public function getParams() 75 | { 76 | return $this->_where->getParams(); 77 | } 78 | 79 | public function debug() 80 | { 81 | ob_start(); 82 | echo 'SQL:' . $this->assemble() . PHP_EOL; 83 | echo ' -- ' . PHP_EOL; 84 | echo 'PARAMS: '; 85 | var_dump($this->_where->getParams()); 86 | return ob_get_clean(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/Gnix/Db/Criteria/Where.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class Gnix_Db_Criteria_Where 12 | { 13 | private $_columns = array(); 14 | private $_params = array(); 15 | 16 | public function where($column, $params = null) 17 | { 18 | $this->_columns[] = $column; 19 | 20 | foreach ((array) $params as $param) { 21 | $this->_params[] = $param; 22 | } 23 | } 24 | 25 | public function whereEqual($column, $param) 26 | { 27 | $this->where($column . ' = ?', $param); 28 | } 29 | 30 | public function whereNotEqual($column, $param) 31 | { 32 | $this->where($column . ' != ?', $param); 33 | } 34 | 35 | public function whereGreater($column, $param) 36 | { 37 | $this->where($column . ' > ?', $param); 38 | } 39 | 40 | public function whereGreaterEqual($column, $param) 41 | { 42 | $this->where($column . ' >= ?', $param); 43 | } 44 | 45 | public function whereLess($column, $param) 46 | { 47 | $this->where($column . ' < ?', $param); 48 | } 49 | 50 | public function whereLessEqual($column, $param) 51 | { 52 | $this->where($column . ' <= ?', $param); 53 | } 54 | 55 | public function whereIsNull($column) 56 | { 57 | $this->where($column . ' IS NULL'); 58 | } 59 | 60 | public function whereIsNotNull($column) 61 | { 62 | $this->where($column . ' IS NOT NULL'); 63 | } 64 | 65 | public function whereLike($column, $param) 66 | { 67 | $this->where($column . ' LIKE ?', $param); 68 | } 69 | 70 | public function whereNotLike($column, $param) 71 | { 72 | $this->where($column . ' NOT LIKE ?', $param); 73 | } 74 | 75 | public function whereBetween($column, $paramFrom, $paramTo) 76 | { 77 | $this->where($column . ' BETWEEN ? AND ?', array($paramFrom, $paramTo)); 78 | } 79 | 80 | public function whereIn($column, array $params) 81 | { 82 | $this->where($column . ' IN (' . $this->_getHolderString(count($params)) . ')', $params); 83 | } 84 | 85 | public function whereNotIn($column, array $params) 86 | { 87 | $this->where($column . ' NOT IN (' . $this->_getHolderString(count($params)) . ')', $params); 88 | } 89 | 90 | public function toString() 91 | { 92 | if ($this->_columns) { 93 | return ' WHERE ' . implode(' AND ', $this->_columns); 94 | } 95 | return ''; 96 | } 97 | 98 | public function getParams() 99 | { 100 | return $this->_params; 101 | } 102 | 103 | private function _getHolderString($num) 104 | { 105 | return implode(', ', array_fill(0, $num, '?')); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /library/Gnix/Db/Connection/Abstract.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | abstract class Gnix_Db_Connection_Abstract 12 | { 13 | public static function setDefaultAttributes(array $defaultAttributes) 14 | { 15 | static::$_defaultAttributes = $defaultAttributes; 16 | } 17 | 18 | public static function setInfo($key, array $info) 19 | { 20 | if (!array_key_exists('host', $info)) { 21 | throw new Gnix_Db_Exception("'host' parameter is requisite for '$key'."); 22 | } 23 | 24 | if (!array_key_exists('port', $info)) { 25 | $info['port'] = ''; 26 | } 27 | 28 | if (!array_key_exists('dbname', $info)) { 29 | throw new Gnix_Db_Exception("'dbname' parameter is requisite for '$key'."); 30 | } 31 | 32 | if (!array_key_exists('user', $info)) { 33 | throw new Gnix_Db_Exception("'user' parameter is requisite for '$key'."); 34 | } 35 | 36 | if (!array_key_exists('pass', $info)) { 37 | throw new Gnix_Db_Exception("'pass' parameter is requisite for '$key'."); 38 | } 39 | 40 | if (!array_key_exists('attributes', $info)) { 41 | $info['attributes'] = static::$_defaultAttributes; 42 | } 43 | 44 | static::$_infos[$key] = $info; 45 | } 46 | 47 | public static function getInfo($key) 48 | { 49 | if (array_key_exists($key, static::$_infos)) { 50 | return static::$_infos[$key]; 51 | } 52 | 53 | throw new Gnix_Db_Exception("Information for '$key' is not set yet at " . get_called_class() . '.'); 54 | } 55 | 56 | public static function get($key) 57 | { 58 | if (array_key_exists($key, static::$_connections)) { 59 | return static::$_connections[$key]; 60 | } 61 | 62 | if (Gnix_Db_Connection_Master::getInfo($key) === Gnix_Db_Connection_Slave::getInfo($key)) { 63 | if (get_called_class() === 'Gnix_Db_Connection_Slave') { 64 | return Gnix_Db_Connection_Master::get($key); 65 | } 66 | } 67 | 68 | static::$_connections[$key] = self::_getConnection(self::getInfo($key)); 69 | return static::$_connections[$key]; 70 | } 71 | 72 | private static function _getConnection($info) 73 | { 74 | $connection = new PDO(sprintf('mysql:host=%s;port=%s;dbname=%s', $info['host'], $info['port'], $info['dbname']), $info['user'], $info['pass']); 75 | foreach ($info['attributes'] as $key => $value) { 76 | $connection->setAttribute($key, $value); 77 | } 78 | return $connection; 79 | } 80 | 81 | public static function disconnect($key) 82 | { 83 | unset(static::$_connections[$key]); 84 | } 85 | 86 | public static function disconnectAll() 87 | { 88 | static::$_connections = array(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/Gnix/Db/Row.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | abstract class Gnix_Db_Row 12 | { 13 | private static $_cache = array(); 14 | private $_row = array(); 15 | private $_connectionName; 16 | 17 | public function __construct($connectionName = null) 18 | { 19 | $this->_connectionName = $connectionName; 20 | } 21 | 22 | public function row(array $row) 23 | { 24 | $this->_row = $row; 25 | } 26 | 27 | public function __call($method, array $arguments) 28 | { 29 | if (!array_key_exists($method, self::$_cache)) { 30 | self::$_cache[$method] = self::_parse($method); 31 | } 32 | 33 | $prefix = self::$_cache[$method]['prefix']; 34 | $column = self::$_cache[$method]['column']; 35 | 36 | switch ($prefix) { 37 | case 'get': 38 | if (array_key_exists($column, $this->_row)) { 39 | return $this->_row[$column]; 40 | } 41 | break; 42 | case 'set': 43 | if (array_key_exists(0, $arguments)) { 44 | $this->_row[$column] = $arguments[0]; 45 | return; 46 | } 47 | } 48 | 49 | throw new Gnix_Db_Exception('Call to undefined method ' . get_class($this) . '::' . $method . '()'); 50 | } 51 | 52 | private static function _parse($method) 53 | { 54 | if (preg_match('/^(get|set)([A-Z]\w*)$/', $method, $matches)) { 55 | return array( 56 | 'prefix' => $matches[1], 57 | 'column' => Gnix_Db_Util::uncamelize($matches[2]), 58 | ); 59 | } 60 | 61 | throw new Gnix_Db_Exception('Call to undefined method ' . get_class($this) . '::' . $method . '()'); 62 | } 63 | 64 | public function save($findAfterCreate = true) 65 | { 66 | $queryClass = $this->_getQueryClass(); 67 | 68 | $keyName = $queryClass::getKeyName(); 69 | 70 | // UPDATE if there is Primary Key data. 71 | if (array_key_exists($keyName, $this->_row)) { 72 | $queryClass::updateByKey($this->_row, $this->_row[$keyName], $this->_connectionName); 73 | return; 74 | } 75 | 76 | // INSERT if there is NOT Primary Key data. 77 | $key = $queryClass::create($this->_row, $this->_connectionName); 78 | if ($findAfterCreate) { 79 | $rowObject = $queryClass::findByKeyOnMaster($key, array('*'), $this->_connectionName); 80 | if (!isset($rowObject->_row)) { 81 | // Couldn't get data just after inserting it. This couldn't be possible! 82 | throw new Gnix_Db_Exception("Can't get data 'PRIMARY KEY = $key' via $queryClass on master db."); 83 | } 84 | $this->_row = $rowObject->_row; 85 | } 86 | return $key; 87 | } 88 | 89 | // TODO: DRY! DRY! DRY! 90 | public function upsert($findAfterCreate = true) 91 | { 92 | $queryClass = $this->_getQueryClass(); 93 | 94 | // REPLACE 95 | $key = $queryClass::upsert($this->_row, $this->_connectionName); 96 | if ($findAfterCreate) { 97 | $rowObject = $queryClass::findByKeyOnMaster($key, array('*'), $this->_connectionName); 98 | if (!isset($rowObject->_row)) { 99 | // Couldn't get data just after inserting it. This couldn't be possible! 100 | throw new Gnix_Db_Exception("Can't get data 'PRIMARY KEY = $key' via $queryClass on master db."); 101 | } 102 | $this->_row = $rowObject->_row; 103 | } 104 | return $key; 105 | } 106 | 107 | public function delete() 108 | { 109 | $queryClass = $this->_getQueryClass(); 110 | 111 | $keyName = $queryClass::getKeyName(); 112 | $queryClass::deleteByKey($this->_row[$keyName], $this->_connectionName); 113 | } 114 | 115 | private function _getQueryClass() 116 | { 117 | return get_class($this) . '_Query'; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /library/Gnix/Db/Query.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | abstract class Gnix_Db_Query 12 | { 13 | protected static $_connectionName; 14 | protected static $_table; 15 | protected static $_key = 'id'; 16 | 17 | protected static function _getCriteria() 18 | { 19 | return new Gnix_Db_Criteria(); 20 | } 21 | 22 | /** 23 | * CREATE method 24 | */ 25 | public static function create(array $data, $connectionName = null) 26 | { 27 | $columns = array(); 28 | $holders = array(); 29 | $params = array(); 30 | foreach ($data as $key => $value) { 31 | $columns[] = $key; 32 | if ($value instanceof Gnix_Db_Literal) { 33 | $holders[] = $value->toString(); 34 | } else { 35 | $holders[] = '?'; 36 | $params[] = $value; 37 | } 38 | } 39 | 40 | $resolver = self::_getResolver(); 41 | $sql = 'INSERT INTO ' . $resolver->getTable() . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $holders) . ')'; 42 | 43 | $dbh = Gnix_Db_Connection_Master::get($connectionName ?: $resolver->getConnectionName()); 44 | $sth = $dbh->prepare($sql); 45 | $sth->execute($params); 46 | return $dbh->lastInsertId(); 47 | } 48 | 49 | /** 50 | * TODO: Code duplication! Keep it DRY!! 51 | */ 52 | public static function upsert(array $data, $connectionName = null) 53 | { 54 | $columns = array(); 55 | $holders = array(); 56 | $params = array(); 57 | foreach ($data as $key => $value) { 58 | $columns[] = $key; 59 | if ($value instanceof Gnix_Db_Literal) { 60 | $holders[] = $value->toString(); 61 | } else { 62 | $holders[] = '?'; 63 | $params[] = $value; 64 | } 65 | } 66 | 67 | $resolver = self::_getResolver(); 68 | $sql = 'REPLACE INTO ' . $resolver->getTable() . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $holders) . ')'; 69 | 70 | $dbh = Gnix_Db_Connection_Master::get($connectionName ?: $resolver->getConnectionName()); 71 | $sth = $dbh->prepare($sql); 72 | $sth->execute($params); 73 | return $dbh->lastInsertId(); 74 | } 75 | 76 | /** 77 | * FIND methods 78 | */ 79 | public static function findAll(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 80 | { 81 | $resolver = self::_getResolver(); 82 | $sql = 'SELECT ' . implode(', ', $columns) . ' FROM ' . $resolver->getTable() . $criteria->assemble(); 83 | 84 | $dbh = Gnix_Db_Connection_Slave::get($connectionName ?: $resolver->getConnectionName()); 85 | $sth = $dbh->prepare($sql); 86 | $sth->execute($criteria->getParams()); 87 | return self::_hydrate($resolver->getRowClass(), $sth->fetchAll(), $connectionName); 88 | } 89 | 90 | public static function find(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 91 | { 92 | $rowObjects = self::findAll($criteria, $columns, $connectionName); 93 | return isset($rowObjects[0]) ? $rowObjects[0] : null; 94 | } 95 | 96 | public static function findByKey($key, array $columns = array('*'), $connectionName = null) 97 | { 98 | return self::find(self::_getCriteriaByKey($key), $columns, $connectionName); 99 | } 100 | 101 | public static function count(Gnix_Db_Criteria $criteria, $connectionName = null) 102 | { 103 | $rowObject = self::find($criteria, array('COUNT(*) AS count'), $connectionName); 104 | return (int) $rowObject->getCount(); 105 | } 106 | 107 | /** 108 | * FIND on Master methods 109 | * TODO: Code duplication! Keep it DRY!! 110 | */ 111 | public static function findAllOnMaster(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 112 | { 113 | $resolver = self::_getResolver(); 114 | $sql = 'SELECT ' . implode(', ', $columns) . ' FROM ' . $resolver->getTable() . $criteria->assemble(); 115 | 116 | $dbh = Gnix_Db_Connection_Master::get($connectionName ?: $resolver->getConnectionName()); 117 | $sth = $dbh->prepare($sql); 118 | $sth->execute($criteria->getParams()); 119 | return self::_hydrate($resolver->getRowClass(), $sth->fetchAll(), $connectionName); 120 | } 121 | 122 | public static function findOnMaster(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 123 | { 124 | $rowObjects = self::findAllOnMaster($criteria, $columns, $connectionName); 125 | return isset($rowObjects[0]) ? $rowObjects[0] : null; 126 | } 127 | 128 | public static function findByKeyOnMaster($key, array $columns = array('*'), $connectionName = null) 129 | { 130 | return self::findOnMaster(self::_getCriteriaByKey($key), $columns, $connectionName); 131 | } 132 | 133 | public static function countOnMaster(Gnix_Db_Criteria $criteria, $connectionName = null) 134 | { 135 | $rowObject = self::findOnMaster($criteria, array('COUNT(*) AS count'), $connectionName); 136 | return (int) $rowObject->getCount(); 137 | } 138 | 139 | /** 140 | * UPDATE methods 141 | */ 142 | public static function update(array $data, Gnix_Db_Criteria $criteria, $connectionName = null) 143 | { 144 | $holders = array(); 145 | $params = array(); 146 | foreach ($data as $key => $value) { 147 | if ($value instanceof Gnix_Db_Literal) { 148 | $holders[] = $key . ' = ' . $value->toString(); 149 | } else { 150 | $holders[] = $key . ' = ?'; 151 | $params[] = $value; 152 | } 153 | } 154 | 155 | $resolver = self::_getResolver(); 156 | $sql = 'UPDATE ' . $resolver->getTable() . ' SET ' . implode(', ', $holders) . $criteria->assemble(); 157 | 158 | $dbh = Gnix_Db_Connection_Master::get($connectionName ?: $resolver->getConnectionName()); 159 | $sth = $dbh->prepare($sql); 160 | $sth->execute(array_merge($params, $criteria->getParams())); 161 | return $sth->rowCount(); 162 | } 163 | 164 | public static function updateByKey(array $data, $key, $connectionName = null) 165 | { 166 | return self::update($data, self::_getCriteriaByKey($key), $connectionName); 167 | } 168 | 169 | /** 170 | * DELETE methods 171 | */ 172 | public static function delete(Gnix_Db_Criteria $criteria, $connectionName = null) 173 | { 174 | $resolver = self::_getResolver(); 175 | $sql = 'DELETE FROM ' . $resolver->getTable() . $criteria->assemble(); 176 | 177 | $dbh = Gnix_Db_Connection_Master::get($connectionName ?: $resolver->getConnectionName()); 178 | $sth = $dbh->prepare($sql); 179 | $sth->execute($criteria->getParams()); 180 | return $sth->rowCount(); 181 | } 182 | 183 | public static function deleteByKey($key, $connectionName = null) 184 | { 185 | return self::delete(self::_getCriteriaByKey($key), $connectionName); 186 | } 187 | 188 | /** 189 | * Other methods 190 | */ 191 | private static function _getResolver() 192 | { 193 | return new Gnix_Db_Query_Resolver(get_called_class(), static::$_connectionName, static::$_table); 194 | } 195 | 196 | private static function _getCriteriaByKey($key) 197 | { 198 | return self::_getCriteria() 199 | ->whereEqual(static::$_key, $key) 200 | ; 201 | } 202 | 203 | private static function _hydrate($rowClass, array $rows, $connectionName) 204 | { 205 | $rowObjects = array(); 206 | foreach ($rows as $row) { 207 | $rowObject = new $rowClass($connectionName); 208 | $rowObject->row($row); 209 | $rowObjects[] = $rowObject; 210 | } 211 | return $rowObjects; 212 | } 213 | 214 | public static function getKeyName() 215 | { 216 | return static::$_key; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gnix_Db 2 | ====== 3 | 4 | Gnix_Db(ニックス・ディービー)はPHP5.3/MySQL専用のORマッピングツールのプロトタイプです。データ規模、アクセス規模が大きく、複雑なJOINやサブクエリーを利用していないシステムに向いています。 5 | 6 | ※プロトタイプとはいえ、このORMは数ヶ月間実際に運用されており、Webサーバー1台あたり約2億PV/monthを、Apache処理時間100msec/req程度で処理しています。 7 | 8 | 9 | ## 経緯 10 | 11 | あるORMを利用しようと思ったところ、そのマニュアルが数百ページにも及んでいました。私はともかくチーム全員にそのORMをマスターしてもらうには気が引けました。我々はただ簡単なSELECTやUPDATEがしたかっただけなのです。 12 | 13 | ORMは元々リレーショナル技術とオブジェクト技術の概念の差異([インピーダンス・ミスマッチ](http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch))を埋めるべく考案されたものです。しかし、そのORM技術自体が成熟し、肥大化し、今度はORM技術と技術者の間に新たなミスマッチが生まれている気がします。 14 | 15 | のような経緯から自社システムのために作成したのですが、自社システム特有のコードがなかったので(絶対に誰も使わないであろうことを承知で)公開しました。 16 | 17 | 18 | ## 必要なもの 19 | 20 | - PHP >= 5.3 21 | - MySQL 22 | 23 | PHP5.3の機能、[遅延静的束縛](http://php.net/manual/ja/language.oop5.late-static-bindings.php)を利用しているため、PHP5.2以前のバージョンでは動作しません。また、[ZendFrameworkコーディング規約](http://framework.zend.com/manual/ja/coding-standard.overview.html)に則っていますので、Zend_Loaderを利用すればクラスの自動ロードが可能になり非常に楽です。 24 | 25 | 26 | ## 特徴 27 | 28 | [Propel](http://www.propelorm.org/)と[Zend_Db_Table](http://framework.zend.com/manual/ja/zend.db.table.html)から影響を受けています。クライアント側のコードはPropelを利用した場合とよく似ています。 29 | 30 | 以下のものは**不要**です: 31 | 32 | - XML、yaml、json等の定義ファイル (データ定義の管理の責任をデータベースにもたせました) 33 | - データベースやテーブル定義の変更 34 | - コマンド (自動生成しなくてはいけないコード自体不要です) 35 | - 結果データのプロパティや連想配列としての操作 (演算やMAX()、DATE()関数等を利用した場合も含めて、結果は全てメソッドでの操作になります) 36 | - DESCRIBE TABLE等の開発者が意図しないクエリー 37 | - SQL 38 | 39 | 出来ないこと: 40 | 41 | - JOINやサブクエリー (できない事もないですが、PHP側で処理する方がよいです) 42 | - アソシエーション機能や連鎖更新、連鎖削除のエミュレート (知ってました? もしやりたいのであればInnoDBでできます!) 43 | - データやクエリーの自動キャッシュ (ご自身でキャッシュしてください) 44 | 45 | 出来ること: 46 | 47 | - 更新=マスター/参照=スレーブの自動切換え (また特定の参照クエリーをマスターに向けることも可能です) 48 | - DBへの遅延接続 (初めてクエリーが発行される際に初めて接続します) 49 | - 変則的なクエリー (Tritonnや自作プラグイン、ストアド関数、MySQL特有の日時関数等を自由に記述できます) 50 | - MySQL専用の構文 (現在のところREPLACE構文や USE INDEX、FORCE INDEXに対応しています) 51 | - 取得するカラムの指定 (デフォルトは「*」による全カラム取得ですが、必要なカラムをクエリー単位に指定できます) 52 | - 接続先のカスタマイズ (クラスのディレクトリー[データベース]単位、クラス[テーブル]単位、メソッド[クエリー]単位で設定のオーバーライド可能) 53 | - トランザクション (専用のメソッドはありませんがPDOオブジェクトを取得できるため自由に行えます) 54 | 55 | 問題点: 56 | 57 | このコードはリファクタリングが必要です。重複行があります。なによりもその前にテストコードが必要です。ここまで来るとは思ってなかった。。現在の行数はコメントを含めて900行程度です。もともとシンプルなORM、勉強会の必要のないORMを目指していました。1000行以内は死守せねばなりません。 58 | 59 | 60 | ## 利用方法 61 | 62 | ### 1. インストール 63 | 64 | 1. このページ上部の「Downloads(ダウンロード)」ボタンよりデータを取得・展開します。 65 | 2. もしZendFrameworkを利用している場合は以下のコードで、自動ロードが可能です。そうでない場合は、エラーメッセージの通り、クラス(PHPファイル)をrequire_onceしてください。 66 | 67 | 自動ロードの設定例 68 | 69 | set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/gnix-db/library'); 70 | 71 | require_once 'Zend/Loader/Autoloader.php'; 72 | $autoloader = Zend_Loader_Autoloader::getInstance(); 73 | $autoloader->setFallbackAutoloader(true); 74 | 75 | 76 | ### 2. DB接続設定 77 | 78 | 以下は、twitterデータベースのマスターDBへの設定例です。第一引数は「接続名」です。接続名は自由に決められますが、データベース名と合わせると便利です(理由は後述)。attributesは[PDO属性](http://php.net/manual/ja/pdo.setattribute.php)です。 79 | 80 | Gnix_Db_Connection_Master::setInfo( 81 | 'twitter', 82 | array ( 83 | 'host' => '192.168.0.1', 84 | 'port' => '3306', 85 | 'dbname' => 'twitter', 86 | 'user' => 'username', 87 | 'pass' => 'password', 88 | 'attributes' => array( 89 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 90 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 91 | ) 92 | ) 93 | ); 94 | 95 | 続いて、twitterデータベースのスレーブDBへの設定例です。 96 | 97 | Gnix_Db_Connection_Slave::setInfo( 98 | 'twitter', 99 | array ( 100 | : 101 | 102 | 上記の情報を確認するには getInfo() メソッドを利用します。 103 | 104 | var_dump(Gnix_Db_Connection_Master::getInfo('twitter')); 105 | var_dump(Gnix_Db_Connection_Slave::getInfo('twitter')); 106 | 107 | 単一サーバーで、マスター/スレーブ構成でない場合は、同じ設定を2度行うか、Gnix_Db_Connectionクラスを利用します。なおマスター/スレーブの設定情報がPDO属性を含めて完全に同じ場合は、内部的に同じ接続(PDOインスタンス)が利用されます。よってスレーブレスなDB設計(理想的!)の場合でもコネクションの無駄はありません。 108 | 109 | Gnix_Db_Connection::setInfo( 110 | 'twitter', 111 | array ( 112 | : 113 | 114 | 多くの場合、全ての環境のPDO属性は共通のものを利用すると思います。その場合、上記の接続設定よりも先にデフォルト値を設定します。 115 | 116 | Gnix_Db_Connection::setDefaultAttributes(array( 117 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 118 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 119 | )); 120 | 121 | これらの設定は変数に保存されるだけで実際にはMySQLに接続しません。実際に接続されるのは、初めてのクエリーが発行される時です(遅延接続)。よって、もし百台のMySQLサーバーをお持ちであれば、百台の設定コードを書いても問題ありません。それによるオーバーヘッドはほとんどありません。 122 | 123 | なお、遅延接続は設定を間違えてもエラーになりません(設定時に接続しないため)。接続を確認するには、get() を利用します。正しければPDOオブジェクトを返却します、間違えていればPDOレベルのエラーとなります。 124 | 125 | var_dump(Gnix_Db_Connection_Master::get('twitter')); 126 | var_dump(Gnix_Db_Connection_Slave::get('twitter')); 127 | 128 | 上記のコードは新しいサーバーを追加した時に、一度確認のために必要になるだけです。 129 | 130 | また、通常は不要ですが、広告の取得等、時間のかかる処理の前に接続を破棄したい場合等は以下のようにします。 131 | 132 | Gnix_Db_Connection_Master::disconnect('twitter'); 133 | Gnix_Db_Connection_Slave::disconnect('twitter'); 134 | 135 | // 上記のコードを1行で書く 136 | Gnix_Db_Connection::disconnect('twitter'); 137 | 138 | // 全ての接続を破棄 139 | Gnix_Db_Connection::disconnectAll(); 140 | 141 | ### 3. クラスを作る 142 | 143 | [Table Data Gatewayパターン](http://martinfowler.com/eaaCatalog/tableDataGateway.html)を採用しているため、一つのテーブルにつき二つのクラスが必要です。 144 | 145 | クエリーの発行を担当するクエリークラスを作成します。**Gnix_Db_Query**を継承します。中身は空です。下記は、接続名が「twitter」のtweetテーブルに接続するためのクラスです。 146 | 147 | setScreenName('chikaram'); // screen_nameカラムに値を設定 185 | $tweet->setText('Good Morning!'); // textカラムに値を設定 186 | $tweet->save(); // INSERTを発行 187 | echo $tweet->getId(); // idカラムの値を表示 188 | echo $tweet->getCreatedAt(); // created_atカラムの値を表示 189 | 190 | setしていないidカラムやcreated_atカラムが取得できる理由は、save() 直後に取得したLAST_INSERT_IDで再度SELECTし、その結果を自分自身のデータと置き換えているためです。また、このSELECTはスレーブDBの遅延も考慮してマスターDB上で行われます。このORMの特徴に「開発者が意図しないクエリー」と書きましたが、この1点だけは例外です。主キーでの取得は非常に高速なため、ほとんどの場合は問題ないと思いますが、マスターDBに[BLACKHOLE ストレージエンジン](http://dev.mysql.com/doc/refman/5.1/ja/blackhole-storage-engine.html)(もしくはMyISAM)を採用している等の特殊な理由で、このSELECT処理が不要な場合は、save(false) のようにfalseを渡すか、後述の createメソッドを利用してください。 191 | 192 | また、save() メソッドはオブジェクトが主キー値を保持している場合はUPDATEメソッドを発行します。主キーがAUTO_INCREMENT等のデフォルト値を持たず、手動で主キーを設定した場合は、upsert() メソッドを利用します。 193 | 194 | ※なお、行クラスはコンストラクターに接続名を指定できます。twitter_backupという接続情報がある場合、TwitterBackup_Tweet\*クラスを作成してもよいですが、同じテーブル定義であれば、Twitter_Tweet\*を使いまわすことが可能です。そのような場合は、`$tweet = new Twitter_Tweet('twitter_backup');` というように接続名を指定できます。 195 | 196 | 上記とは別のプロセスでSELECTする場合、以下のようにします。 197 | 198 | $tweet = Twitter_Tweet_Query::findByKey(1); // 主キーが「1」のものを取得 199 | echo $tweet->getId(); // idカラムの値を表示 200 | echo $tweet->getScreenName(); // screen_nameのカラム値を表示 201 | echo $tweet->getText(); // textのカラム値を表示 202 | echo $tweet->getCreatedAt(); // created_atのカラム値を表示 203 | 204 | find系メソッドの第二引数は取得するカラム名の配列です。もし演算や関数を用いる場合は必ずASキーワードでエイリアス名をつけて下さい。 205 | 206 | $tweet = Twitter_Tweet_Query::findByKey(1, array('(id + 100) AS foo', 'DATE(created_at) AS bar')); 207 | echo $tweet->getFoo(); // id + 100 の値を表示 208 | echo $tweet->getBar(); // DATE(created_at) の値を表示 209 | 210 | 続けてUPDATEを行います。 211 | 212 | $tweet->setText('Hello!'); // textカラムに値を設定 213 | $tweet->setCreatedAt(new Gnix_Db_Literal('NOW()')); // created_atカラムに値を設定 214 | $tweet->save(); // UPDATEを発行 215 | 216 | ※MySQLの関数等エスケープ不要な値を設定するには、Gnix_Db_Literalクラスを利用します。setCreatedAt('NOW()') とすると、プリペアドステートメントのプレースホルダーに 'NOW()' という文字列を代入してしまいます。 217 | 218 | 続けてDELETEを行います。 219 | 220 | $tweet->delete(); // DELETEを発行 221 | 222 | 上記のような1行をSELECT後にUPDATE/DELETEする方法以外に、一括UPDATE、DELETEも可能です。ページ下部の「メソッド一覧」をご覧ください。 223 | 224 | なお、getScreenName() から screen_nameカラムを取得するしくみは、マジックメソッドの [__call](http://www.php.net/manual/ja/language.oop5.overloading.php#language.oop5.overloading.methods) およびメソッド名の反キャメライズ化(アンキャメライズ?)処理によるものです。__call は通常のメソッドより若干オーバーヘッドがありますが、当社の環境で1コールあたり約15マイクロ秒で、仮に50行のデータの10カラムを表示するような画面の場合でも7、8ミリ秒におさまります(普通の画面なら1、2ミリ秒)。一方アンキャメライズは1処理で約50マイクロ秒かかり、大量のデータを表示する画面では無視できない処理時間(約2、30ミリ秒)になりますが、こちらはstatic変数にキャッシュすることにより、2行目以降のデータの表示にはほとんど処理時間がかかりません。 225 | 226 | ※Apache処理時間等も含めて目標を達成しないと給料が下がるんです。現に一回下がった! 227 | 228 | 229 | #### 複雑なパターン 230 | 231 | 複雑なWHERE句の生成は、[Query Object](http://martinfowler.com/eaaCatalog/queryObject.html)に相当するGnix_Db_Criteriaクラスを使用します。このクラスのインスタンスは、クエリークラス(上記の場合Twitter_Tweet_Query)でしか取得できません。こうしてクエリーの責任をクエリークラスに強制することにより、MVCパターンでいうところのコントローラーが肥大化するのを防ぎます。 232 | 233 | まず、適当なメソッドを作ります。 234 | 235 | class Twitter_Tweet_Query extends Gnix_Db_Query 236 | { 237 | public static function findFooBar() 238 | { 239 | } 240 | } 241 | 242 | 以下、findFooBar() 内のコードです。[流暢なインターフェース](http://www.martinfowler.com/bliki/FluentInterface.html)を利用して以下のように書きます。 243 | 244 | $criteria = self::_getCriteria() // Criteriaオブジェクト取得 245 | ->whereLike('text', '%あ%') // 'text LIKE ?' を生成 246 | ->orderByDesc('id') // 'ORDER BY id DESC' を生成 247 | ->limit(15) // 'LIMIT 15' を生成 248 | ->page(3) // 3ページ目(31件目以降)のoffset値を自動計算 249 | ; 250 | 251 | 生成したCriteriaはdebug() メソッドで確認できます。 252 | 253 | echo $criteria->debug(); 254 | 255 | // 下記を出力 256 | SQL: WHERE text LIKE ? ORDER BY id DESC LIMIT 30, 15 257 | -- 258 | PARAMS: array(1) { 259 | [0]=> 260 | string(5) "%あ%" 261 | } 262 | 263 | findAll() メソッドにCriteriaを渡せばデータを取得できます。完成したメソッドは以下の通りです。 264 | 265 | public static function findFooBar($keyword, $page) 266 | { 267 | $criteria = self::_getCriteria() 268 | ->whereLike('text', '%' . $keyword . '%') 269 | ->orderByDesc('id') 270 | ->limit(15) 271 | ->page($page) 272 | ; 273 | return self::findAll($criteria); 274 | } 275 | 276 | コントローラーから以下のように呼び出します。 277 | 278 | $tweets = Twitter_Tweet_Query::findFooBar('あ', 3); 279 | 280 | 複数行の取得は、行オブジェクトの配列ですので、テンプレートは以下のようになります。(以下はZend_Viewの場合の例ですが、Smarty等でも構いません。) 281 | 282 | tweets as $tweet): ?> 283 |

@getScreenName() ?>: getText() ?>

284 | 285 | 286 | もし、上記のような表示パターンを何度も利用するのであれば、以下のようなメソッドを行クラスに作成します。 287 | 288 | class Twitter_Tweet extends Gnix_Db_Row 289 | { 290 | public function getDisplayTweet() 291 | { 292 | return '@' . $this->getScreenName() . ': ' . $this->getText(); 293 | } 294 | } 295 | 296 | そうすればテンプレートが簡潔になります。 297 | 298 | tweets as $tweet): ?> 299 |

getDisplayTweet() ?>

300 | 301 | 302 | 303 | ## メソッド一覧 304 | 305 | ### Gnix_Db_Criteria 306 | 307 | #### WHERE系 308 | 309 | 1. whereEqual('column', 'value') 310 | 2. whereNotEqual('column', 'value') 311 | 3. whereGreater('column', 'value') 312 | 4. whereGreaterEqual('column', 'value') 313 | 5. whereLess('column', 'value') 314 | 6. whereLessEqual('column', 'value') 315 | 7. whereIsNull('column', 'value') 316 | 8. whereIsNotNull('column', 'value') 317 | 9. whereNotLike('column', 'value') 318 | 10. whereBetween('column', 'from_value', 'to_value') 319 | 11. whereIn('column', array('value1', 'value2', 'value3', ...)) 320 | 12. whereNotIn('column', array('value1', 'value2', 'value3', ...)) 321 | 13. where('string' [, 'value' OR array('value1', 'value2', 'value3', ...)]) 322 | 323 | whereメソッドの例) 324 | 325 | - where('column1 = ? OR column2 = ?', array(10, 20)) 326 | - where('updated_at < (CURRENT_TIMESTAMP - INTERVAL 15 SECOND)') 327 | - where('MATCH (text) AGAINST (? IN BOOLEAN MODE)', '*D+ ' . $keyword) // Tritonnならこんな感じ 328 | - where('id IN (SELECT foo FROM bar WHERE baz = ?)', 3) // JOINやサブクエリーをどうしても使いたい場合 329 | 330 | #### GROUP BY系 331 | 332 | 実装予定 333 | 334 | #### HAVING系 335 | 336 | 実装予定 337 | 338 | #### ORDER BY系 339 | 340 | 1. orderBy('column') 341 | 2. orderByDesc('column') 342 | 343 | 下記の例では、ORDER BY aaa, bbb DESC, ccc, ddd DESC を生成します。 344 | 345 | $criteria = self::_getCriteria() 346 | ->orderBy('aaa') 347 | ->orderByDesc('bbb') 348 | ->orderBy('ccc') 349 | ->orderByDesc('ddd') 350 | ; 351 | 352 | #### LIMIT系 353 | 354 | 1. limit(int) 355 | 2. offset(int) 356 | 3. page(int) // offset値の自動計算 357 | 358 | #### INDEX系 (MySQL専用) 359 | 360 | 1. indexUse('index_name') // USE INDEX (index_name) を発行 361 | 2. indexForce('index_name') // FORCE INDEX (index_name) を発行 362 | 363 | IGNORE INDEX や USE/FORCE/IGNORE FOR ~ 等の高度な構文には対応していません。 364 | 365 | 366 | ### Gnix_Db_Query 367 | 368 | #### SELECT系 369 | 370 | 1. array(Gnix_Db_Row) = findAll(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 371 | 2. Gnix_Db_Row = find(Gnix_Db_Criteria $criteria, array $columns = array('*'), $connectionName = null) 372 | 3. Gnix_Db_Row = findByKey($key, array $columns = array('*'), $connectionName = null) 373 | 4. int $count = count(Gnix_Db_Criteria $criteria, $connectionName = null) 374 | 375 | 最終パラメータはGnix_Db_Connectionで設定した接続名です。デフォルトはクラス名が "Twitter_Tweet_Query" なら先頭の twitter が利用されます。 376 | 377 | また、各メソッド名に接尾辞'OnMaster'を付けると、マスターDBでSELECTします(戻り値・引数は同じ)。メソッド名が冗長ですが、ほとんど使うことはないでしょう。 378 | 379 | 1. findAllOnMaster() 380 | 2. findOnMaster() 381 | 3. findByKeyOnMaster() 382 | 4. countOnMaster() 383 | 384 | またデータの取得結果は以下になります。 385 | 386 | - 複数行取得(findAll)で結果あり: array(行オブジェクト, 行オブジェクト, 行オブジェクト...) 387 | - 複数行取得(findAll)で結果なし: array() 388 | - 単数行取得(find)で結果あり: 行オブジェクト 389 | - 単数行取得(find)で結果なし: null 390 | 391 | #### INSERT系 392 | 393 | 1. int $lastInsertId = create(array $data, $connectionName = null) 394 | 395 | $dataは挿入するデータの連想配列です。エスケープ不要な命令文は Gnix_Db_Literal でラップしてから渡します。 396 | 397 | $lastInsertId = Twitter_Tweet_Query::create(array( 398 | 'screen_name' => 'chikaram', 399 | 'text' => 'Good Morning!', 400 | 'created_at' => new Gnix_Db_Literal('NOW()'), 401 | )); 402 | 403 | 少し冗長ですが、これらのメソッドは一括更新やバッチ処理でしか利用しません。通常は、行オブジェクトのsave() やdelete() メソッドを利用します。 404 | 405 | #### UPDATE系 406 | 407 | 1. int $rowCount = update(array $data, Gnix_Db_Criteria $criteria, $connectionName = null) 408 | 2. int $rowCount = updateByKey(array $data, $key, $connectionName = null) 409 | 410 | #### DELETE系 411 | 412 | 1. int $rowCount = delete(Gnix_Db_Criteria $criteria, $connectionName = null) 413 | 2. int $rowCount = deleteByKey($key, $connectionName = null) 414 | 415 | #### REPLACE系 416 | 417 | 1. int $lastInsertId = upsert(array $data, $connectionName = null) 418 | 419 | 「REPLACE」というキーワードは文字列関数を連想させるので、[upsert](http://en.wikipedia.org/wiki/Upsert)というメソッド名を採用しました(update or insertの意)。 420 | 421 | なお、更新系クエリーは常にマスターDB上で行われます。スレーブDBでの更新メソッドはありません。 422 | 423 | 424 | ## License 425 | 426 | [The MIT License](http://www.gmo-media.jp/licence/mit.html) 427 | --------------------------------------------------------------------------------