diff --git a/.gitignore b/.gitignore index 29263bc2..ac0f57b1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.sqlite docs/_build /phpunit.phar +/vendor +/composer.lock diff --git a/.travis.yml b/.travis.yml index 48e22545..3557c023 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: php php: - - 5.2 - 5.3 - 5.4 + - 5.6 + - 7.0 - hhvm script: "phpunit --colors --coverage-text" diff --git a/README.markdown b/README.markdown index b7f0ac41..4f06f95b 100644 --- a/README.markdown +++ b/README.markdown @@ -75,12 +75,27 @@ foreach ($tweets as $tweet) { Changelog --------- - -#### 1.5.1 - release 2014-06-23 +#### 1.5.2 - released 2016-12-14 + +* Fix autoincremented compound keys inserts [[lrlopez](https://github.com/lrlopez)] - [issue #233](https://github.com/j4mie/idiorm/issues/233) and [pull #235](https://github.com/j4mie/idiorm/pull/235) +* Add @method tags for magic methods [[stellis](https://github.com/stellis)] - [issue #237](https://github.com/j4mie/idiorm/issues/237) +* Ensure `is_dirty()` returns correctly when fed null or an empty string [[tentwofour](https://github.com/tentwofour)] - [issue #268](https://github.com/j4mie/idiorm/issues/268) +* Adding Code Climate badge to the readme file [[e3betht](https://github.com/e3betht)] - [issue #260](https://github.com/j4mie/idiorm/issues/260) +* Typo in navigation [[leongersen](https://github.com/leongersen)] - [issue #257](https://github.com/j4mie/idiorm/issues/257) +* Support named placeholders logging and test [[m92o](https://github.com/m92o)] - [issue #223](https://github.com/j4mie/idiorm/issues/223) +* `having_id_is()` reference undefined variable `$value` [[Treffynnon](https://github.com/treffynnon)] - [issue #224](https://github.com/j4mie/idiorm/issues/224) +* Documentation fix - ORM query output for `where_any_is()` [[uovidiu](https://github.com/uovidiu)] - [issue #306](https://github.com/j4mie/idiorm/issues/306) +* Code style fix preventing nested loops from using the same variable names [[mkkeck](https://github.com/mkkeck)] - [issue #301](https://github.com/j4mie/idiorm/issues/301) +* Document shortcomings of the built in query logger [[Treffynnon](https://github.com/treffynnon)] - [issue #307](https://github.com/j4mie/idiorm/issues/307) +* Add phpunit to dev dependencies, add `composer test` script shortcut and fix PDO mock in test bootstrap [[Treffynnon](https://github.com/treffynnon)] +* New test for multiple raw where clauses [[Treffynnon](https://github.com/treffynnon)] - [issue #236](https://github.com/j4mie/idiorm/issues/236) +* Remove PHP 5.2 from travis-ci containers to test against (**note** Idiorm still supports PHP 5.2 despite this) [[Treffynnon](https://github.com/treffynnon)] + +#### 1.5.1 - released 2014-06-23 * Binding of named parameters was broken [[cainmi](https://github.com/cainmi)] - [issue #221](https://github.com/j4mie/idiorm/pull/221) -#### 1.5.0 - release 2014-06-22 +#### 1.5.0 - released 2014-06-22 * Multiple OR'ed conditions support [[lrlopez](https://github.com/lrlopez)] - [issue #201](https://github.com/j4mie/idiorm/issues/201) * `where_id_in()` for selecting multiple records by primary key [[lrlopez](https://github.com/lrlopez)] - [issue #202](https://github.com/j4mie/idiorm/issues/202) @@ -100,13 +115,13 @@ Changelog * Improve where statement precendence documentation [[thomasahle](https://github.com/thomasahle)] - [issue #190](https://github.com/j4mie/idiorm/issues/190) * Improve testing checks [[charsleysa](https://github.com/charsleysa)] - [issue #173](https://github.com/j4mie/idiorm/issues/173) -#### 1.4.1 - release 2013-12-12 +#### 1.4.1 - released 2013-12-12 **Patch update to remove a broken pull request** - may have consequences for users of 1.4.0 that exploited the "`find_many()` now returns an associative array with the databases primary ID as the array keys" change that was merged in 1.4.0. * Back out pull request/issue [#133](https://github.com/j4mie/idiorm/pull/133) as it breaks backwards compatibility in previously unexpected ways (see [#162](https://github.com/j4mie/idiorm/pull/162), [#156](https://github.com/j4mie/idiorm/issues/156) and [#133](https://github.com/j4mie/idiorm/pull/133#issuecomment-29063108)) - sorry for merging this change into Idiorm - closes [issue 156](https://github.com/j4mie/idiorm/issues/156) -#### 1.4.0 - release 2013-09-05 +#### 1.4.0 - released 2013-09-05 * `find_many()` now returns an associative array with the databases primary ID as the array keys [[Surt](https://github.com/Surt)] - [issue #133](https://github.com/j4mie/idiorm/issues/133) * Calls to `set()` and `set_expr()` return `$this` allowing them to be chained [[Surt](https://github.com/Surt)] @@ -123,7 +138,7 @@ Changelog * Fix docblock [[ulrikjohansson](https://github.com/ulrikjohansson)] - [issue #147](https://github.com/j4mie/idiorm/issues/147) * Fix incorrect variable name in querying documentation [[fridde](https://github.com/fridde)] - [issue #146](https://github.com/j4mie/idiorm/issues/146) -#### 1.3.0 - release 2013-01-31 +#### 1.3.0 - released 2013-01-31 * Documentation moved to [idiorm.rtfd.org](http://idiorm.rtfd.org) and now built using [Sphinx](http://sphinx-doc.org/) * Add support for multiple database connections - closes [issue #15](https://github.com/j4mie/idiorm/issues/15) [[tag](https://github.com/tag)] @@ -143,19 +158,19 @@ Changelog * Fix issue with aggregate functions always returning `int` when is `float` sometimes required - closes [issue #92](https://github.com/j4mie/idiorm/issues/92) * Move testing into PHPUnit to unify method testing and query generation testing -#### 1.2.3 - release 2012-11-28 +#### 1.2.3 - released 2012-11-28 * Fix [issue #78](https://github.com/j4mie/idiorm/issues/78) - remove use of PHP 5.3 static call -#### 1.2.2 - release 2012-11-15 +#### 1.2.2 - released 2012-11-15 * Fix bug where input parameters were sent as part-indexed, part associative -#### 1.2.1 - release 2012-11-15 +#### 1.2.1 - released 2012-11-15 * Fix minor bug caused by IdiormStringException not extending Exception -#### 1.2.0 - release 2012-11-14 +#### 1.2.0 - released 2012-11-14 * Setup composer for installation via packagist (j4mie/idiorm) * Add `order_by_expr` method [[sandermarechal](http://github.com/sandermarechal)] @@ -173,7 +188,7 @@ Changelog * Add `find_array` to get the records as associative arrays [[Surt](https://github.com/Surt)] - closes [issue #17](https://github.com/j4mie/idiorm/issues/17) * Fix bug in `_log_query` with `?` and `%` supplied in raw where statements etc. - closes [issue #57](https://github.com/j4mie/idiorm/issues/57) [[ridgerunner](https://github.com/ridgerunner)] -#### 1.1.1 - release 2011-01-30 +#### 1.1.1 - released 2011-01-30 * Fix bug in quoting column wildcard. j4mie/paris#12 * Small documentation improvements diff --git a/composer.json b/composer.json index 23088737..22009a92 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,12 @@ "role": "Maintainer" } ], + "scripts": { + "test": "vendor/bin/phpunit" + }, + "require-dev": { + "phpunit/phpunit": "^5.6" + }, "license": [ "BSD-2-Clause", "BSD-3-Clause", diff --git a/docs/configuration.rst b/docs/configuration.rst index b40cbe12..fb90dfa0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -254,6 +254,17 @@ the log. ``ORM::get_last_query()`` returns the most recent query executed. ``ORM::get_query_log()`` returns an array of all queries executed. +.. note:: + + The code that does the query log is an approximation of that provided by PDO/the + database (see the Idiorm source code for detail). The actual query isn't even available + to idiorm to log as the database/PDO handles the binding outside of idiorm's reach and + doesn't pass it back. + + This means that you might come across some inconsistencies between what is logged and + what is actually run. In these case you'll need to look at the query log provided by + your database vendor (eg. MySQL). + Query logger ^^^^^^^^^^^^ diff --git a/docs/querying.rst b/docs/querying.rst index bf140a12..be7d5592 100644 --- a/docs/querying.rst +++ b/docs/querying.rst @@ -344,7 +344,7 @@ column using a second parameter: ->find_many(); // Creates SQL: - SELECT * FROM `widget` WHERE (( `name` = 'Joe' AND `age` > '10' ) OR ( `name` = 'Fred' AND `age` > '20' )); + SELECT * FROM `widget` WHERE (( `name` = 'Joe' AND `age` = '10' ) OR ( `name` = 'Fred' AND `age` > '20' )); If you want to set the default operator for all the columns, just pass it as the second parameter: diff --git a/idiorm.php b/idiorm.php index 80e9a4de..a382e9e2 100644 --- a/idiorm.php +++ b/idiorm.php @@ -36,6 +36,80 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * + * + * The methods documented below are magic methods that conform to PSR-1. + * This documentation exposes these methods to doc generators and IDEs. + * @see http://www.php-fig.org/psr/psr-1/ + * + * @method static array|string getConfig($key = null, $connection_name = self::DEFAULT_CONNECTION) + * @method static null resetConfig() + * @method static \ORM forTable($table_name, $connection_name = self::DEFAULT_CONNECTION) + * @method static null setDb($db, $connection_name = self::DEFAULT_CONNECTION) + * @method static null resetDb() + * @method static null setupLimitClauseStyle($connection_name) + * @method static \PDO getDb($connection_name = self::DEFAULT_CONNECTION) + * @method static bool rawExecute($query, $parameters = array()) + * @method static \PDOStatement getLastStatement() + * @method static string getLastQuery($connection_name = null) + * @method static array getQueryLog($connection_name = self::DEFAULT_CONNECTION) + * @method array getConnectionNames() + * @method $this useIdColumn($id_column) + * @method \ORM|bool findOne($id=null) + * @method array|\IdiormResultSet findMany() + * @method \IdiormResultSet findResultSet() + * @method array findArray() + * @method $this forceAllDirty() + * @method $this rawQuery($query, $parameters = array()) + * @method $this tableAlias($alias) + * @method int countNullIdColumns() + * @method $this selectExpr($expr, $alias=null) + * @method \ORM selectMany() + * @method \ORM selectManyExpr() + * @method $this rawJoin($table, $constraint, $table_alias, $parameters = array()) + * @method $this innerJoin($table, $constraint, $table_alias=null) + * @method $this leftOuterJoin($table, $constraint, $table_alias=null) + * @method $this rightOuterJoin($table, $constraint, $table_alias=null) + * @method $this fullOuterJoin($table, $constraint, $table_alias=null) + * @method $this whereEqual($column_name, $value=null) + * @method $this whereNotEqual($column_name, $value=null) + * @method $this whereIdIs($id) + * @method $this whereAnyIs($values, $operator='=') + * @method array|string whereIdIn($ids) + * @method $this whereLike($column_name, $value=null) + * @method $this whereNotLike($column_name, $value=null) + * @method $this whereGt($column_name, $value=null) + * @method $this whereLt($column_name, $value=null) + * @method $this whereGte($column_name, $value=null) + * @method $this whereLte($column_name, $value=null) + * @method $this whereIn($column_name, $values) + * @method $this whereNotIn($column_name, $values) + * @method $this whereNull($column_name) + * @method $this whereNotNull($column_name) + * @method $this whereRaw($clause, $parameters=array()) + * @method $this orderByDesc($column_name) + * @method $this orderByAsc($column_name) + * @method $this orderByExpr($clause) + * @method $this groupBy($column_name) + * @method $this groupByExpr($expr) + * @method $this havingEqual($column_name, $value=null) + * @method $this havingNotEqual($column_name, $value=null) + * @method $this havingIdIs($id) + * @method $this havingLike($column_name, $value=null) + * @method $this havingNotLike($column_name, $value=null) + * @method $this havingGt($column_name, $value=null) + * @method $this havingLt($column_name, $value=null) + * @method $this havingGte($column_name, $value=null) + * @method $this havingLte($column_name, $value=null) + * @method $this havingIn($column_name, $values=null) + * @method $this havingNotIn($column_name, $values=null) + * @method $this havingNull($column_name) + * @method $this havingNotNull($column_name) + * @method $this havingRaw($clause, $parameters=array()) + * @method static this clearCache($table_name = null, $connection_name = self::DEFAULT_CONNECTION) + * @method array asArray() + * @method bool setExpr($key, $value = null) + * @method bool isDirty($key) + * @method bool isNew() */ class ORM implements ArrayAccess { @@ -456,29 +530,33 @@ protected static function _log_query($query, $parameters, $connection_name, $que self::$_query_log[$connection_name] = array(); } - // Strip out any non-integer indexes from the parameters - foreach($parameters as $key => $value) { - if (!is_int($key)) unset($parameters[$key]); - } - - if (count($parameters) > 0) { + if (empty($parameters)) { + $bound_query = $query; + } else { // Escape the parameters $parameters = array_map(array(self::get_db($connection_name), 'quote'), $parameters); - // Avoid %format collision for vsprintf - $query = str_replace("%", "%%", $query); + if (array_values($parameters) === $parameters) { + // ? placeholders + // Avoid %format collision for vsprintf + $query = str_replace("%", "%%", $query); - // Replace placeholders in the query for vsprintf - if(false !== strpos($query, "'") || false !== strpos($query, '"')) { - $query = IdiormString::str_replace_outside_quotes("?", "%s", $query); + // Replace placeholders in the query for vsprintf + if(false !== strpos($query, "'") || false !== strpos($query, '"')) { + $query = IdiormString::str_replace_outside_quotes("?", "%s", $query); + } else { + $query = str_replace("?", "%s", $query); + } + + // Replace the question marks in the query with the parameters + $bound_query = vsprintf($query, $parameters); } else { - $query = str_replace("?", "%s", $query); + // named placeholders + foreach ($parameters as $key => $val) { + $query = str_replace($key, $val, $query); + } + $bound_query = $query; } - - // Replace the question marks in the query with the parameters - $bound_query = vsprintf($query, $parameters); - } else { - $bound_query = $query; } self::$_last_query = $bound_query; @@ -1255,14 +1333,14 @@ public function where_any_is($values, $operator='=') { $data = array(); $query = array("(("); $first = true; - foreach ($values as $item) { + foreach ($values as $value) { if ($first) { $first = false; } else { $query[] = ") OR ("; } $firstsub = true; - foreach($item as $key => $item) { + foreach($value as $key => $item) { $op = is_string($operator) ? $operator : (isset($operator[$key]) ? $operator[$key] : '='); if ($firstsub) { $firstsub = false; @@ -1469,7 +1547,7 @@ public function having_not_equal($column_name, $value=null) { */ public function having_id_is($id) { return (is_array($this->_get_id_column_name())) ? - $this->having($this->_get_compound_id_column_values($value)) : + $this->having($this->_get_compound_id_column_values($id), null) : $this->having($this->_get_id_column_name(), $id); } @@ -1964,7 +2042,7 @@ protected function _set_orm_property($key, $value = null, $expr = false) { * object was saved. */ public function is_dirty($key) { - return isset($this->_dirty_fields[$key]); + return array_key_exists($key, $this->_dirty_fields); } /** @@ -2023,7 +2101,7 @@ public function save() { // if the primary key is compound, assign the last inserted id // to the first column if (is_array($column)) { - $column = array_slice($column, 0, 1); + $column = reset($column); } $this->_data[$column] = $db->lastInsertId(); } @@ -2313,6 +2391,8 @@ protected function _str_replace_outside_quotes_cb($matches) { /** * A result set class for working with collections of model instances * @author Simon Holywell + * @method null setResults(array $results) + * @method array getResults() */ class IdiormResultSet implements Countable, IteratorAggregate, ArrayAccess, Serializable { /** diff --git a/test/ORMTest.php b/test/ORMTest.php index f244f120..b85b0f65 100644 --- a/test/ORMTest.php +++ b/test/ORMTest.php @@ -43,9 +43,15 @@ public function testIsNew() { public function testIsDirty() { $model = ORM::for_table('test')->create(); $this->assertFalse($model->is_dirty('test')); - + $model = ORM::for_table('test')->create(array('test' => 'test')); $this->assertTrue($model->is_dirty('test')); + + $model->test = null; + $this->assertTrue($model->is_dirty('test')); + + $model->test = ''; + $this->assertTrue($model->is_dirty('test')); } public function testArrayAccess() { diff --git a/test/QueryBuilderPsr1Test53.php b/test/QueryBuilderPsr1Test53.php index 0aa89303..3b267eba 100644 --- a/test/QueryBuilderPsr1Test53.php +++ b/test/QueryBuilderPsr1Test53.php @@ -274,6 +274,12 @@ public function testRawQueryWithParameters() { $this->assertEquals($expected, ORM::getLastQuery()); } + public function testRawQueryWithNamedPlaceholders() { + ORM::forTable('widget')->rawQuery('SELECT `w`.* FROM `widget` w WHERE `name` = :name AND `age` = :age', array(':name' => 'Fred', ':age' => 5))->findMany(); + $expected = "SELECT `w`.* FROM `widget` w WHERE `name` = 'Fred' AND `age` = '5'"; + $this->assertEquals($expected, ORM::getLastQuery()); + } + public function testSimpleResultColumn() { ORM::forTable('widget')->select('name')->findMany(); $expected = "SELECT `name` FROM `widget`"; diff --git a/test/QueryBuilderTest.php b/test/QueryBuilderTest.php index 9c4ef973..bbd5831f 100644 --- a/test/QueryBuilderTest.php +++ b/test/QueryBuilderTest.php @@ -292,6 +292,12 @@ public function testRawWhereClauseInMethodChain() { $this->assertEquals($expected, ORM::get_last_query()); } + public function testRawWhereClauseMultiples() { + ORM::for_table('widget')->where('age', 18)->where_raw('(`name` = ? OR `name` = ?)', array('Fred', 'Bob'))->where_raw('(`name` = ? OR `name` = ?)', array('Sarah', 'Jane'))->where('size', 'large')->find_many(); + $expected = "SELECT * FROM `widget` WHERE `age` = '18' AND (`name` = 'Fred' OR `name` = 'Bob') AND (`name` = 'Sarah' OR `name` = 'Jane') AND `size` = 'large'"; + $this->assertEquals($expected, ORM::get_last_query()); + } + public function testRawQuery() { ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w')->find_many(); $expected = "SELECT `w`.* FROM `widget` w"; @@ -304,6 +310,12 @@ public function testRawQueryWithParameters() { $this->assertEquals($expected, ORM::get_last_query()); } + public function testRawQueryWithNamedPlaceholders() { + ORM::for_table('widget')->raw_query('SELECT `w`.* FROM `widget` w WHERE `name` = :name AND `age` = :age', array(':name' => 'Fred', ':age' => 5))->find_many(); + $expected = "SELECT `w`.* FROM `widget` w WHERE `name` = 'Fred' AND `age` = '5'"; + $this->assertEquals($expected, ORM::get_last_query()); + } + public function testSimpleResultColumn() { ORM::for_table('widget')->select('name')->find_many(); $expected = "SELECT `name` FROM `widget`"; diff --git a/test/bootstrap.php b/test/bootstrap.php index 3b3cacea..7fa1c48b 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -46,15 +46,14 @@ public function execute($params = NULL) { /** * Add data to arrays */ - public function bindParam($index, $value, $type) + public function bindParam($paramno, &$param, $type = NULL, $maxlen = NULL, $driverdata = NULL) { - // Do check on index, and type - if (!is_int($index)) throw new Exception('Incorrect parameter type. Expected $index to be an integer.'); + // Do check on type if (!is_int($type) || ($type != PDO::PARAM_STR && $type != PDO::PARAM_NULL && $type != PDO::PARAM_BOOL && $type != PDO::PARAM_INT)) throw new Exception('Incorrect parameter type. Expected $type to be an integer.'); // Add param to array - $this->bindParams[$index - 1] = $value; + $this->bindParams[is_int($paramno) ? --$paramno : $paramno] = $param; } /**