From cf930369757a5949f809d228686978221040f369 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Mon, 8 Jul 2013 17:27:57 -0400 Subject: [PATCH 01/53] Allow custom setter methods to run for aliased attributes (fixes #350) The logic in the Model::__set() magic method allowed for either an aliased attribute *or* a custom setter, but not both. If a custom setter method was defined for an aliased attribute, then the attribute value would be set without going through the custom setter method. Changed the logic so that the _set() method first normalizes an aliased attribute into the real attribute name, then checks to see if an there is a custom setter method defined and runs it if it exists. --- lib/Model.php | 2 +- test/ActiveRecordTest.php | 16 ++++++++++++++++ test/models/Venue.php | 9 +++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/Model.php b/lib/Model.php index 1d98aca58..a956063c4 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -399,7 +399,7 @@ public function __set($name, $value) if (array_key_exists($name, static::$alias_attribute)) $name = static::$alias_attribute[$name]; - elseif (method_exists($this,"set_$name")) + if (method_exists($this,"set_$name")) { $name = "set_$name"; return $this->$name($value); diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index a20aa913f..f47667645 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -259,6 +259,22 @@ public function test_alias_attribute_setter() $this->assert_equals($venue->marquee, $venue->name); } + public function test_alias_attribute_custom_setter() + { + Venue::$use_custom_set_city_setter = true; + $venue = Venue::find(1); + + $venue->mycity = 'cityname'; + $this->assert_equals($venue->mycity, 'cityname#'); + $this->assert_equals($venue->mycity, $venue->city); + + $venue->city = 'anothercity'; + $this->assert_equals($venue->city, 'anothercity#'); + $this->assert_equals($venue->city, $venue->mycity); + + Venue::$use_custom_set_city_setter = false; + } + public function test_alias_from_mass_attributes() { $venue = new Venue(array('marquee' => 'meme', 'id' => 123)); diff --git a/test/models/Venue.php b/test/models/Venue.php index 38ad9241b..fd6e7c0b5 100644 --- a/test/models/Venue.php +++ b/test/models/Venue.php @@ -4,6 +4,7 @@ class Venue extends ActiveRecord\Model static $use_custom_get_state_getter = false; static $use_custom_set_state_setter = false; + static $use_custom_set_city_setter = false; static $has_many = array( 'events', @@ -32,6 +33,14 @@ public function set_state($value) else return $this->assign_attribute('state', $value); } + + public function set_city($value) + { + if (self::$use_custom_set_city_setter) + return $this->assign_attribute('city', $value . '#'); + else + return $this->assign_attribute('city', $value); + } }; ?> From 7e58a68869bf3871854bf1517ff7fd9e96a8081b Mon Sep 17 00:00:00 2001 From: Vendan <andyleap@gmail.com> Date: Tue, 28 May 2013 23:01:33 -0400 Subject: [PATCH 02/53] Remove require_once from inside functions --- lib/SQLBuilder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index ef239ed87..39c4daafa 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -291,7 +291,6 @@ private function prepend_table_name_to_fields($hash=array()) private function apply_where_conditions($args) { - require_once 'Expressions.php'; $num_args = count($args); if ($num_args == 1 && is_hash($args[0])) @@ -345,7 +344,6 @@ private function build_delete() private function build_insert() { - require_once 'Expressions.php'; $keys = join(',',$this->quoted_key_names()); if ($this->sequence) From dc13eeecfab58f904c42ed488fa62cdb6a4f3dd5 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Thu, 17 Jul 2014 10:48:32 +0200 Subject: [PATCH 03/53] remove all carriage returns <CR> --- ActiveRecord.php | 96 +-- lib/Column.php | 390 +++++----- lib/Expressions.php | 382 +++++----- lib/Inflector.php | 236 +++--- test/ValidatesFormatOfTest.php | 220 +++--- test/ValidatesInclusionAndExclusionOfTest.php | 312 ++++---- test/ValidatesLengthOfTest.php | 694 +++++++++--------- test/ValidatesPresenceOfTest.php | 146 ++-- test/models/RmBldg.php | 66 +- 9 files changed, 1258 insertions(+), 1284 deletions(-) diff --git a/ActiveRecord.php b/ActiveRecord.php index 17b325703..762a48d9f 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -1,49 +1,49 @@ -<?php -if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) - die('PHP ActiveRecord requires PHP 5.3 or higher'); - -define('PHP_ACTIVERECORD_VERSION_ID','1.0'); - -if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND')) - define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true); - -require __DIR__.'/lib/Singleton.php'; -require __DIR__.'/lib/Config.php'; -require __DIR__.'/lib/Utils.php'; -require __DIR__.'/lib/DateTime.php'; -require __DIR__.'/lib/Model.php'; -require __DIR__.'/lib/Table.php'; -require __DIR__.'/lib/ConnectionManager.php'; -require __DIR__.'/lib/Connection.php'; -require __DIR__.'/lib/Serialization.php'; -require __DIR__.'/lib/SQLBuilder.php'; -require __DIR__.'/lib/Reflections.php'; -require __DIR__.'/lib/Inflector.php'; -require __DIR__.'/lib/CallBack.php'; -require __DIR__.'/lib/Exceptions.php'; -require __DIR__.'/lib/Cache.php'; - -if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE')) - spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND); - -function activerecord_autoload($class_name) -{ - $path = ActiveRecord\Config::instance()->get_model_directory(); - $root = realpath(isset($path) ? $path : '.'); - - if (($namespaces = ActiveRecord\get_namespaces($class_name))) - { - $class_name = array_pop($namespaces); - $directories = array(); - - foreach ($namespaces as $directory) - $directories[] = $directory; - - $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR); - } - - $file = "$root/$class_name.php"; - - if (file_exists($file)) - require_once $file; +<?php +if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) + die('PHP ActiveRecord requires PHP 5.3 or higher'); + +define('PHP_ACTIVERECORD_VERSION_ID','1.0'); + +if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND')) + define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true); + +require __DIR__.'/lib/Singleton.php'; +require __DIR__.'/lib/Config.php'; +require __DIR__.'/lib/Utils.php'; +require __DIR__.'/lib/DateTime.php'; +require __DIR__.'/lib/Model.php'; +require __DIR__.'/lib/Table.php'; +require __DIR__.'/lib/ConnectionManager.php'; +require __DIR__.'/lib/Connection.php'; +require __DIR__.'/lib/Serialization.php'; +require __DIR__.'/lib/SQLBuilder.php'; +require __DIR__.'/lib/Reflections.php'; +require __DIR__.'/lib/Inflector.php'; +require __DIR__.'/lib/CallBack.php'; +require __DIR__.'/lib/Exceptions.php'; +require __DIR__.'/lib/Cache.php'; + +if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE')) + spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND); + +function activerecord_autoload($class_name) +{ + $path = ActiveRecord\Config::instance()->get_model_directory(); + $root = realpath(isset($path) ? $path : '.'); + + if (($namespaces = ActiveRecord\get_namespaces($class_name))) + { + $class_name = array_pop($namespaces); + $directories = array(); + + foreach ($namespaces as $directory) + $directories[] = $directory; + + $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR); + } + + $file = "$root/$class_name.php"; + + if (file_exists($file)) + require_once $file; } \ No newline at end of file diff --git a/lib/Column.php b/lib/Column.php index 6cb45c1c2..de431b626 100644 --- a/lib/Column.php +++ b/lib/Column.php @@ -1,195 +1,195 @@ -<?php -/** - * @package ActiveRecord - */ -namespace ActiveRecord; - -/** - * Class for a table column. - * - * @package ActiveRecord - */ -class Column -{ - // types for $type - const STRING = 1; - const INTEGER = 2; - const DECIMAL = 3; - const DATETIME = 4; - const DATE = 5; - const TIME = 6; - - /** - * Map a type to an column type. - * @static - * @var array - */ - static $TYPE_MAPPING = array( - 'datetime' => self::DATETIME, - 'timestamp' => self::DATETIME, - 'date' => self::DATE, - 'time' => self::TIME, - - 'tinyint' => self::INTEGER, - 'smallint' => self::INTEGER, - 'mediumint' => self::INTEGER, - 'int' => self::INTEGER, - 'bigint' => self::INTEGER, - - 'float' => self::DECIMAL, - 'double' => self::DECIMAL, - 'numeric' => self::DECIMAL, - 'decimal' => self::DECIMAL, - 'dec' => self::DECIMAL); - - /** - * The true name of this column. - * @var string - */ - public $name; - - /** - * The inflected name of this columns .. hyphens/spaces will be => _. - * @var string - */ - public $inflected_name; - - /** - * The type of this column: STRING, INTEGER, ... - * @var integer - */ - public $type; - - /** - * The raw database specific type. - * @var string - */ - public $raw_type; - - /** - * The maximum length of this column. - * @var int - */ - public $length; - - /** - * True if this column allows null. - * @var boolean - */ - public $nullable; - - /** - * True if this column is a primary key. - * @var boolean - */ - public $pk; - - /** - * The default value of the column. - * @var mixed - */ - public $default; - - /** - * True if this column is set to auto_increment. - * @var boolean - */ - public $auto_increment; - - /** - * Name of the sequence to use for this column if any. - * @var boolean - */ - public $sequence; - - /** - * Cast a value to an integer type safely - * - * This will attempt to cast a value to an integer, - * unless its detected that the casting will cause - * the number to overflow or lose precision, in which - * case the number will be returned as a string, so - * that large integers (BIGINTS, unsigned INTS, etc) - * can still be stored without error - * - * This would ideally be done with bcmath or gmp, but - * requiring a new PHP extension for a bug-fix is a - * little ridiculous - * - * @param mixed $value The value to cast - * @return int|string type-casted value - */ - public static function castIntegerSafely($value) - { - if (is_int($value)) - return $value; - - // Its just a decimal number - elseif (is_numeric($value) && floor($value) != $value) - return (int) $value; - - // If adding 0 to a string causes a float conversion, - // we have a number over PHP_INT_MAX - elseif (is_string($value) && is_float($value + 0)) - return (string) $value; - - // If a float was passed and its greater than PHP_INT_MAX - // (which could be wrong due to floating point precision) - // We'll also check for equal to (>=) in case the precision - // loss creates an overflow on casting - elseif (is_float($value) && $value >= PHP_INT_MAX) - return number_format($value, 0, '', ''); - - return (int) $value; - } - - /** - * Casts a value to the column's type. - * - * @param mixed $value The value to cast - * @param Connection $connection The Connection this column belongs to - * @return mixed type-casted value - */ - public function cast($value, $connection) - { - if ($value === null) - return null; - - switch ($this->type) - { - case self::STRING: return (string)$value; - case self::INTEGER: return static::castIntegerSafely($value); - case self::DECIMAL: return (double)$value; - case self::DATETIME: - case self::DATE: - if (!$value) - return null; - - if ($value instanceof DateTime) - return $value; - - if ($value instanceof \DateTime) - return new DateTime($value->format('Y-m-d H:i:s T')); - - return $connection->string_to_datetime($value); - } - return $value; - } - - /** - * Sets the $type member variable. - * @return mixed - */ - public function map_raw_type() - { - if ($this->raw_type == 'integer') - $this->raw_type = 'int'; - - if (array_key_exists($this->raw_type,self::$TYPE_MAPPING)) - $this->type = self::$TYPE_MAPPING[$this->raw_type]; - else - $this->type = self::STRING; - - return $this->type; - } -} +<?php +/** + * @package ActiveRecord + */ +namespace ActiveRecord; + +/** + * Class for a table column. + * + * @package ActiveRecord + */ +class Column +{ + // types for $type + const STRING = 1; + const INTEGER = 2; + const DECIMAL = 3; + const DATETIME = 4; + const DATE = 5; + const TIME = 6; + + /** + * Map a type to an column type. + * @static + * @var array + */ + static $TYPE_MAPPING = array( + 'datetime' => self::DATETIME, + 'timestamp' => self::DATETIME, + 'date' => self::DATE, + 'time' => self::TIME, + + 'tinyint' => self::INTEGER, + 'smallint' => self::INTEGER, + 'mediumint' => self::INTEGER, + 'int' => self::INTEGER, + 'bigint' => self::INTEGER, + + 'float' => self::DECIMAL, + 'double' => self::DECIMAL, + 'numeric' => self::DECIMAL, + 'decimal' => self::DECIMAL, + 'dec' => self::DECIMAL); + + /** + * The true name of this column. + * @var string + */ + public $name; + + /** + * The inflected name of this columns .. hyphens/spaces will be => _. + * @var string + */ + public $inflected_name; + + /** + * The type of this column: STRING, INTEGER, ... + * @var integer + */ + public $type; + + /** + * The raw database specific type. + * @var string + */ + public $raw_type; + + /** + * The maximum length of this column. + * @var int + */ + public $length; + + /** + * True if this column allows null. + * @var boolean + */ + public $nullable; + + /** + * True if this column is a primary key. + * @var boolean + */ + public $pk; + + /** + * The default value of the column. + * @var mixed + */ + public $default; + + /** + * True if this column is set to auto_increment. + * @var boolean + */ + public $auto_increment; + + /** + * Name of the sequence to use for this column if any. + * @var boolean + */ + public $sequence; + + /** + * Cast a value to an integer type safely + * + * This will attempt to cast a value to an integer, + * unless its detected that the casting will cause + * the number to overflow or lose precision, in which + * case the number will be returned as a string, so + * that large integers (BIGINTS, unsigned INTS, etc) + * can still be stored without error + * + * This would ideally be done with bcmath or gmp, but + * requiring a new PHP extension for a bug-fix is a + * little ridiculous + * + * @param mixed $value The value to cast + * @return int|string type-casted value + */ + public static function castIntegerSafely($value) + { + if (is_int($value)) + return $value; + + // Its just a decimal number + elseif (is_numeric($value) && floor($value) != $value) + return (int) $value; + + // If adding 0 to a string causes a float conversion, + // we have a number over PHP_INT_MAX + elseif (is_string($value) && is_float($value + 0)) + return (string) $value; + + // If a float was passed and its greater than PHP_INT_MAX + // (which could be wrong due to floating point precision) + // We'll also check for equal to (>=) in case the precision + // loss creates an overflow on casting + elseif (is_float($value) && $value >= PHP_INT_MAX) + return number_format($value, 0, '', ''); + + return (int) $value; + } + + /** + * Casts a value to the column's type. + * + * @param mixed $value The value to cast + * @param Connection $connection The Connection this column belongs to + * @return mixed type-casted value + */ + public function cast($value, $connection) + { + if ($value === null) + return null; + + switch ($this->type) + { + case self::STRING: return (string)$value; + case self::INTEGER: return static::castIntegerSafely($value); + case self::DECIMAL: return (double)$value; + case self::DATETIME: + case self::DATE: + if (!$value) + return null; + + if ($value instanceof DateTime) + return $value; + + if ($value instanceof \DateTime) + return new DateTime($value->format('Y-m-d H:i:s T')); + + return $connection->string_to_datetime($value); + } + return $value; + } + + /** + * Sets the $type member variable. + * @return mixed + */ + public function map_raw_type() + { + if ($this->raw_type == 'integer') + $this->raw_type = 'int'; + + if (array_key_exists($this->raw_type,self::$TYPE_MAPPING)) + $this->type = self::$TYPE_MAPPING[$this->raw_type]; + else + $this->type = self::STRING; + + return $this->type; + } +} diff --git a/lib/Expressions.php b/lib/Expressions.php index 3fa2962fa..881fa4afd 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -1,192 +1,192 @@ -<?php -/** - * @package ActiveRecord - */ -namespace ActiveRecord; - -/** - * Templating like class for building SQL statements. - * - * Examples: - * 'name = :name AND author = :author' - * 'id = IN(:ids)' - * 'id IN(:subselect)' - * - * @package ActiveRecord - */ -class Expressions -{ - const ParameterMarker = '?'; - - private $expressions; - private $values = array(); - private $connection; - - public function __construct($connection, $expressions=null /* [, $values ... ] */) - { - $values = null; - $this->connection = $connection; - - if (is_array($expressions)) - { - $glue = func_num_args() > 2 ? func_get_arg(2) : ' AND '; - list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue); - } - - if ($expressions != '') - { - if (!$values) - $values = array_slice(func_get_args(),2); - - $this->values = $values; - $this->expressions = $expressions; - } - } - - /** - * Bind a value to the specific one based index. There must be a bind marker - * for each value bound or to_s() will throw an exception. - */ - public function bind($parameter_number, $value) - { - if ($parameter_number <= 0) - throw new ExpressionsException("Invalid parameter index: $parameter_number"); - - $this->values[$parameter_number-1] = $value; - } - - public function bind_values($values) - { - $this->values = $values; - } - - /** - * Returns all the values currently bound. - */ - public function values() - { - return $this->values; - } - - /** - * Returns the connection object. - */ - public function get_connection() - { - return $this->connection; - } - - /** - * Sets the connection object. It is highly recommended to set this so we can - * use the adapter's native escaping mechanism. - * - * @param string $connection a Connection instance - */ - public function set_connection($connection) - { - $this->connection = $connection; - } - - public function to_s($substitute=false, &$options=null) - { - if (!$options) $options = array(); - - $values = array_key_exists('values',$options) ? $options['values'] : $this->values; - - $ret = ""; - $replace = array(); - $num_values = count($values); - $len = strlen($this->expressions); - $quotes = 0; - - for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i) - { - $ch = $this->expressions[$i]; - - if ($ch == self::ParameterMarker) - { - if ($quotes % 2 == 0) - { - if ($j > $num_values-1) - throw new ExpressionsException("No bound parameter for index $j"); - - $ch = $this->substitute($values,$substitute,$i,$j++); - } - } - elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\') - ++$quotes; - - $ret .= $ch; - } - return $ret; - } - - private function build_sql_from_hash(&$hash, $glue) - { - $sql = $g = ""; - - foreach ($hash as $name => $value) - { - if ($this->connection) - $name = $this->connection->quote_name($name); - - if (is_array($value)) - $sql .= "$g$name IN(?)"; - elseif (is_null($value)) - $sql .= "$g$name IS ?"; - else - $sql .= "$g$name=?"; - - $g = $glue; - } - return array($sql,array_values($hash)); - } - - private function substitute(&$values, $substitute, $pos, $parameter_index) - { - $value = $values[$parameter_index]; - - if (is_array($value)) - { - $value_count = count($value); - - if ($value_count === 0) - if ($substitute) - return 'NULL'; - else - return self::ParameterMarker; - - if ($substitute) - { - $ret = ''; - - for ($i=0, $n=$value_count; $i<$n; ++$i) - $ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]); - - return $ret; - } - return join(',',array_fill(0,$value_count,self::ParameterMarker)); - } - - if ($substitute) - return $this->stringify_value($value); - - return $this->expressions[$pos]; - } - - private function stringify_value($value) - { - if (is_null($value)) - return "NULL"; - - return is_string($value) ? $this->quote_string($value) : $value; - } - - private function quote_string($value) - { - if ($this->connection) - return $this->connection->escape($value); - - return "'" . str_replace("'","''",$value) . "'"; - } +<?php +/** + * @package ActiveRecord + */ +namespace ActiveRecord; + +/** + * Templating like class for building SQL statements. + * + * Examples: + * 'name = :name AND author = :author' + * 'id = IN(:ids)' + * 'id IN(:subselect)' + * + * @package ActiveRecord + */ +class Expressions +{ + const ParameterMarker = '?'; + + private $expressions; + private $values = array(); + private $connection; + + public function __construct($connection, $expressions=null /* [, $values ... ] */) + { + $values = null; + $this->connection = $connection; + + if (is_array($expressions)) + { + $glue = func_num_args() > 2 ? func_get_arg(2) : ' AND '; + list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue); + } + + if ($expressions != '') + { + if (!$values) + $values = array_slice(func_get_args(),2); + + $this->values = $values; + $this->expressions = $expressions; + } + } + + /** + * Bind a value to the specific one based index. There must be a bind marker + * for each value bound or to_s() will throw an exception. + */ + public function bind($parameter_number, $value) + { + if ($parameter_number <= 0) + throw new ExpressionsException("Invalid parameter index: $parameter_number"); + + $this->values[$parameter_number-1] = $value; + } + + public function bind_values($values) + { + $this->values = $values; + } + + /** + * Returns all the values currently bound. + */ + public function values() + { + return $this->values; + } + + /** + * Returns the connection object. + */ + public function get_connection() + { + return $this->connection; + } + + /** + * Sets the connection object. It is highly recommended to set this so we can + * use the adapter's native escaping mechanism. + * + * @param string $connection a Connection instance + */ + public function set_connection($connection) + { + $this->connection = $connection; + } + + public function to_s($substitute=false, &$options=null) + { + if (!$options) $options = array(); + + $values = array_key_exists('values',$options) ? $options['values'] : $this->values; + + $ret = ""; + $replace = array(); + $num_values = count($values); + $len = strlen($this->expressions); + $quotes = 0; + + for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i) + { + $ch = $this->expressions[$i]; + + if ($ch == self::ParameterMarker) + { + if ($quotes % 2 == 0) + { + if ($j > $num_values-1) + throw new ExpressionsException("No bound parameter for index $j"); + + $ch = $this->substitute($values,$substitute,$i,$j++); + } + } + elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\') + ++$quotes; + + $ret .= $ch; + } + return $ret; + } + + private function build_sql_from_hash(&$hash, $glue) + { + $sql = $g = ""; + + foreach ($hash as $name => $value) + { + if ($this->connection) + $name = $this->connection->quote_name($name); + + if (is_array($value)) + $sql .= "$g$name IN(?)"; + elseif (is_null($value)) + $sql .= "$g$name IS ?"; + else + $sql .= "$g$name=?"; + + $g = $glue; + } + return array($sql,array_values($hash)); + } + + private function substitute(&$values, $substitute, $pos, $parameter_index) + { + $value = $values[$parameter_index]; + + if (is_array($value)) + { + $value_count = count($value); + + if ($value_count === 0) + if ($substitute) + return 'NULL'; + else + return self::ParameterMarker; + + if ($substitute) + { + $ret = ''; + + for ($i=0, $n=$value_count; $i<$n; ++$i) + $ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]); + + return $ret; + } + return join(',',array_fill(0,$value_count,self::ParameterMarker)); + } + + if ($substitute) + return $this->stringify_value($value); + + return $this->expressions[$pos]; + } + + private function stringify_value($value) + { + if (is_null($value)) + return "NULL"; + + return is_string($value) ? $this->quote_string($value) : $value; + } + + private function quote_string($value) + { + if ($this->connection) + return $this->connection->escape($value); + + return "'" . str_replace("'","''",$value) . "'"; + } } \ No newline at end of file diff --git a/lib/Inflector.php b/lib/Inflector.php index 732fb3422..cea9229c3 100644 --- a/lib/Inflector.php +++ b/lib/Inflector.php @@ -1,119 +1,119 @@ -<?php -/** - * @package ActiveRecord - */ -namespace ActiveRecord; - -/** - * @package ActiveRecord - */ -abstract class Inflector -{ - /** - * Get an instance of the {@link Inflector} class. - * - * @return object - */ - public static function instance() - { - return new StandardInflector(); - } - - /** - * Turn a string into its camelized version. - * - * @param string $s string to convert - * @return string - */ - public function camelize($s) - { - $s = preg_replace('/[_-]+/','_',trim($s)); - $s = str_replace(' ', '_', $s); - - $camelized = ''; - - for ($i=0,$n=strlen($s); $i<$n; ++$i) - { - if ($s[$i] == '_' && $i+1 < $n) - $camelized .= strtoupper($s[++$i]); - else - $camelized .= $s[$i]; - } - - $camelized = trim($camelized,' _'); - - if (strlen($camelized) > 0) - $camelized[0] = strtolower($camelized[0]); - - return $camelized; - } - - /** - * Determines if a string contains all uppercase characters. - * - * @param string $s string to check - * @return bool - */ - public static function is_upper($s) - { - return (strtoupper($s) === $s); - } - - /** - * Determines if a string contains all lowercase characters. - * - * @param string $s string to check - * @return bool - */ - public static function is_lower($s) - { - return (strtolower($s) === $s); - } - - /** - * Convert a camelized string to a lowercase, underscored string. - * - * @param string $s string to convert - * @return string - */ - public function uncamelize($s) - { - $normalized = ''; - - for ($i=0,$n=strlen($s); $i<$n; ++$i) - { - if (ctype_alpha($s[$i]) && self::is_upper($s[$i])) - $normalized .= '_' . strtolower($s[$i]); - else - $normalized .= $s[$i]; - } - return trim($normalized,' _'); - } - - /** - * Convert a string with space into a underscored equivalent. - * - * @param string $s string to convert - * @return string - */ - public function underscorify($s) - { - return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s)); - } - - public function keyify($class_name) - { - return strtolower($this->underscorify(denamespace($class_name))) . '_id'; - } - - abstract function variablize($s); -} - -/** - * @package ActiveRecord - */ -class StandardInflector extends Inflector -{ - public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); } - public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); } +<?php +/** + * @package ActiveRecord + */ +namespace ActiveRecord; + +/** + * @package ActiveRecord + */ +abstract class Inflector +{ + /** + * Get an instance of the {@link Inflector} class. + * + * @return object + */ + public static function instance() + { + return new StandardInflector(); + } + + /** + * Turn a string into its camelized version. + * + * @param string $s string to convert + * @return string + */ + public function camelize($s) + { + $s = preg_replace('/[_-]+/','_',trim($s)); + $s = str_replace(' ', '_', $s); + + $camelized = ''; + + for ($i=0,$n=strlen($s); $i<$n; ++$i) + { + if ($s[$i] == '_' && $i+1 < $n) + $camelized .= strtoupper($s[++$i]); + else + $camelized .= $s[$i]; + } + + $camelized = trim($camelized,' _'); + + if (strlen($camelized) > 0) + $camelized[0] = strtolower($camelized[0]); + + return $camelized; + } + + /** + * Determines if a string contains all uppercase characters. + * + * @param string $s string to check + * @return bool + */ + public static function is_upper($s) + { + return (strtoupper($s) === $s); + } + + /** + * Determines if a string contains all lowercase characters. + * + * @param string $s string to check + * @return bool + */ + public static function is_lower($s) + { + return (strtolower($s) === $s); + } + + /** + * Convert a camelized string to a lowercase, underscored string. + * + * @param string $s string to convert + * @return string + */ + public function uncamelize($s) + { + $normalized = ''; + + for ($i=0,$n=strlen($s); $i<$n; ++$i) + { + if (ctype_alpha($s[$i]) && self::is_upper($s[$i])) + $normalized .= '_' . strtolower($s[$i]); + else + $normalized .= $s[$i]; + } + return trim($normalized,' _'); + } + + /** + * Convert a string with space into a underscored equivalent. + * + * @param string $s string to convert + * @return string + */ + public function underscorify($s) + { + return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s)); + } + + public function keyify($class_name) + { + return strtolower($this->underscorify(denamespace($class_name))) . '_id'; + } + + abstract function variablize($s); +} + +/** + * @package ActiveRecord + */ +class StandardInflector extends Inflector +{ + public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); } + public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); } } \ No newline at end of file diff --git a/test/ValidatesFormatOfTest.php b/test/ValidatesFormatOfTest.php index 76c52d5ee..718e4314e 100644 --- a/test/ValidatesFormatOfTest.php +++ b/test/ValidatesFormatOfTest.php @@ -1,111 +1,111 @@ -<?php - -class BookFormat extends ActiveRecord\Model -{ - static $table = 'books'; - static $validates_format_of = array( - array('name') - ); -}; - -class ValidatesFormatOfTest extends DatabaseTest -{ - public function set_up($connection_name=null) - { - parent::set_up($connection_name); - BookFormat::$validates_format_of[0] = array('name'); - } - - public function test_format() - { - BookFormat::$validates_format_of[0]['with'] = '/^[a-z\W]*$/'; - $book = new BookFormat(array('author_id' => 1, 'name' => 'testing reg')); - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - - BookFormat::$validates_format_of[0]['with'] = '/[0-9]/'; - $book = new BookFormat(array('author_id' => 1, 'name' => 12)); - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_invalid_null() - { - BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; - $book = new BookFormat; - $book->name = null; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_invalid_blank() - { - BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; - $book = new BookFormat; - $book->name = ''; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_valid_blank_andallow_blank() - { - BookFormat::$validates_format_of[0]['allow_blank'] = true; - BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; - $book = new BookFormat(array('author_id' => 1, 'name' => '')); - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_valid_null_and_allow_null() - { - BookFormat::$validates_format_of[0]['allow_null'] = true; - BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; - $book = new BookFormat(); - $book->author_id = 1; - $book->name = null; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - /** - * @expectedException ActiveRecord\ValidationsArgumentError - */ - public function test_invalid_lack_of_with_key() - { - $book = new BookFormat; - $book->name = null; - $book->save(); - } - - /** - * @expectedException ActiveRecord\ValidationsArgumentError - */ - public function test_invalid_with_expression_as_non_string() - { - BookFormat::$validates_format_of[0]['with'] = array('test'); - $book = new BookFormat; - $book->name = null; - $book->save(); - } - - public function test_invalid_with_expression_as_non_regexp() - { - BookFormat::$validates_format_of[0]['with'] = 'blah'; - $book = new BookFormat; - $book->name = 'blah'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_custom_message() - { - BookFormat::$validates_format_of[0]['message'] = 'is using a custom message.'; - BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; - - $book = new BookFormat; - $book->name = null; - $book->save(); - $this->assert_equals('is using a custom message.', $book->errors->on('name')); - } -}; +<?php + +class BookFormat extends ActiveRecord\Model +{ + static $table = 'books'; + static $validates_format_of = array( + array('name') + ); +}; + +class ValidatesFormatOfTest extends DatabaseTest +{ + public function set_up($connection_name=null) + { + parent::set_up($connection_name); + BookFormat::$validates_format_of[0] = array('name'); + } + + public function test_format() + { + BookFormat::$validates_format_of[0]['with'] = '/^[a-z\W]*$/'; + $book = new BookFormat(array('author_id' => 1, 'name' => 'testing reg')); + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + + BookFormat::$validates_format_of[0]['with'] = '/[0-9]/'; + $book = new BookFormat(array('author_id' => 1, 'name' => 12)); + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_invalid_null() + { + BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; + $book = new BookFormat; + $book->name = null; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_invalid_blank() + { + BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; + $book = new BookFormat; + $book->name = ''; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_valid_blank_andallow_blank() + { + BookFormat::$validates_format_of[0]['allow_blank'] = true; + BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; + $book = new BookFormat(array('author_id' => 1, 'name' => '')); + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_valid_null_and_allow_null() + { + BookFormat::$validates_format_of[0]['allow_null'] = true; + BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; + $book = new BookFormat(); + $book->author_id = 1; + $book->name = null; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + /** + * @expectedException ActiveRecord\ValidationsArgumentError + */ + public function test_invalid_lack_of_with_key() + { + $book = new BookFormat; + $book->name = null; + $book->save(); + } + + /** + * @expectedException ActiveRecord\ValidationsArgumentError + */ + public function test_invalid_with_expression_as_non_string() + { + BookFormat::$validates_format_of[0]['with'] = array('test'); + $book = new BookFormat; + $book->name = null; + $book->save(); + } + + public function test_invalid_with_expression_as_non_regexp() + { + BookFormat::$validates_format_of[0]['with'] = 'blah'; + $book = new BookFormat; + $book->name = 'blah'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_custom_message() + { + BookFormat::$validates_format_of[0]['message'] = 'is using a custom message.'; + BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/'; + + $book = new BookFormat; + $book->name = null; + $book->save(); + $this->assert_equals('is using a custom message.', $book->errors->on('name')); + } +}; ?> \ No newline at end of file diff --git a/test/ValidatesInclusionAndExclusionOfTest.php b/test/ValidatesInclusionAndExclusionOfTest.php index ade280165..38bbaee4d 100644 --- a/test/ValidatesInclusionAndExclusionOfTest.php +++ b/test/ValidatesInclusionAndExclusionOfTest.php @@ -1,157 +1,157 @@ -<?php - -class BookExclusion extends ActiveRecord\Model -{ - static $table = 'books'; - public static $validates_exclusion_of = array( - array('name', 'in' => array('blah', 'alpha', 'bravo')) - ); -}; - -class BookInclusion extends ActiveRecord\Model -{ - static $table = 'books'; - public static $validates_inclusion_of = array( - array('name', 'in' => array('blah', 'tanker', 'shark')) - ); -}; - -class ValidatesInclusionAndExclusionOfTest extends DatabaseTest -{ - public function set_up($connection_name=null) - { - parent::set_up($connection_name); - BookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark')); - BookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo')); - } - - public function test_inclusion() - { - $book = new BookInclusion; - $book->name = 'blah'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_exclusion() - { - $book = new BookExclusion; - $book->name = 'blahh'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_invalid_inclusion() - { - $book = new BookInclusion; - $book->name = 'thanker'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - $book->name = 'alpha '; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_invalid_exclusion() - { - $book = new BookExclusion; - $book->name = 'alpha'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - - $book = new BookExclusion; - $book->name = 'bravo'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_inclusion_with_numeric() - { - BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2); - $book = new BookInclusion; - $book->name = 2; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_inclusion_with_boolean() - { - BookInclusion::$validates_inclusion_of[0]['in']= array(true); - $book = new BookInclusion; - $book->name = true; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_inclusion_with_null() - { - BookInclusion::$validates_inclusion_of[0]['in']= array(null); - $book = new BookInclusion; - $book->name = null; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_invalid_inclusion_with_numeric() - { - BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2); - $book = new BookInclusion; - $book->name = 5; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function tes_inclusion_within_option() - { - BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay')); - $book = new BookInclusion; - $book->name = 'okay'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function tes_inclusion_scalar_value() - { - BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay'); - $book = new BookInclusion; - $book->name = 'okay'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_valid_null() - { - BookInclusion::$validates_inclusion_of[0]['allow_null'] = true; - $book = new BookInclusion; - $book->name = null; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_valid_blank() - { - BookInclusion::$validates_inclusion_of[0]['allow_blank'] = true; - $book = new BookInclusion; - $book->name = ''; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_custom_message() - { - $msg = 'is using a custom message.'; - BookInclusion::$validates_inclusion_of[0]['message'] = $msg; - BookExclusion::$validates_exclusion_of[0]['message'] = $msg; - - $book = new BookInclusion; - $book->name = 'not included'; - $book->save(); - $this->assert_equals('is using a custom message.', $book->errors->on('name')); - $book = new BookExclusion; - $book->name = 'bravo'; - $book->save(); - $this->assert_equals('is using a custom message.', $book->errors->on('name')); - } - -}; +<?php + +class BookExclusion extends ActiveRecord\Model +{ + static $table = 'books'; + public static $validates_exclusion_of = array( + array('name', 'in' => array('blah', 'alpha', 'bravo')) + ); +}; + +class BookInclusion extends ActiveRecord\Model +{ + static $table = 'books'; + public static $validates_inclusion_of = array( + array('name', 'in' => array('blah', 'tanker', 'shark')) + ); +}; + +class ValidatesInclusionAndExclusionOfTest extends DatabaseTest +{ + public function set_up($connection_name=null) + { + parent::set_up($connection_name); + BookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark')); + BookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo')); + } + + public function test_inclusion() + { + $book = new BookInclusion; + $book->name = 'blah'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_exclusion() + { + $book = new BookExclusion; + $book->name = 'blahh'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_invalid_inclusion() + { + $book = new BookInclusion; + $book->name = 'thanker'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + $book->name = 'alpha '; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_invalid_exclusion() + { + $book = new BookExclusion; + $book->name = 'alpha'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + + $book = new BookExclusion; + $book->name = 'bravo'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_inclusion_with_numeric() + { + BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2); + $book = new BookInclusion; + $book->name = 2; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_inclusion_with_boolean() + { + BookInclusion::$validates_inclusion_of[0]['in']= array(true); + $book = new BookInclusion; + $book->name = true; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_inclusion_with_null() + { + BookInclusion::$validates_inclusion_of[0]['in']= array(null); + $book = new BookInclusion; + $book->name = null; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_invalid_inclusion_with_numeric() + { + BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2); + $book = new BookInclusion; + $book->name = 5; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function tes_inclusion_within_option() + { + BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay')); + $book = new BookInclusion; + $book->name = 'okay'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function tes_inclusion_scalar_value() + { + BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay'); + $book = new BookInclusion; + $book->name = 'okay'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_valid_null() + { + BookInclusion::$validates_inclusion_of[0]['allow_null'] = true; + $book = new BookInclusion; + $book->name = null; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_valid_blank() + { + BookInclusion::$validates_inclusion_of[0]['allow_blank'] = true; + $book = new BookInclusion; + $book->name = ''; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_custom_message() + { + $msg = 'is using a custom message.'; + BookInclusion::$validates_inclusion_of[0]['message'] = $msg; + BookExclusion::$validates_exclusion_of[0]['message'] = $msg; + + $book = new BookInclusion; + $book->name = 'not included'; + $book->save(); + $this->assert_equals('is using a custom message.', $book->errors->on('name')); + $book = new BookExclusion; + $book->name = 'bravo'; + $book->save(); + $this->assert_equals('is using a custom message.', $book->errors->on('name')); + } + +}; ?> \ No newline at end of file diff --git a/test/ValidatesLengthOfTest.php b/test/ValidatesLengthOfTest.php index bd93a1d80..9df1cb9e9 100644 --- a/test/ValidatesLengthOfTest.php +++ b/test/ValidatesLengthOfTest.php @@ -1,360 +1,334 @@ -<?php - -class BookLength extends ActiveRecord\Model -{ - static $table = 'books'; - static $validates_length_of = array(); -} - -class BookSize extends ActiveRecord\Model -{ - static $table = 'books'; - static $validates_size_of = array(); -} - -class ValidatesLengthOfTest extends DatabaseTest -{ - public function set_up($connection_name=null) - { - parent::set_up($connection_name); - BookLength::$validates_length_of[0] = array('name', 'allow_blank' => false, 'allow_null' => false); - } - - public function test_within() - { - BookLength::$validates_length_of[0]['within'] = array(1, 5); - $book = new BookLength; - $book->name = '12345'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_within_error_message() - { - BookLength::$validates_length_of[0]['within'] = array(2,5); - $book = new BookLength(); - $book->name = '1'; - $book->is_valid(); - $this->assert_equals(array('Name is too short (minimum is 2 characters)'),$book->errors->full_messages()); - - $book->name = '123456'; - $book->is_valid(); - $this->assert_equals(array('Name is too long (maximum is 5 characters)'),$book->errors->full_messages()); - } - - public function test_within_custom_error_message() - { - BookLength::$validates_length_of[0]['within'] = array(2,5); - BookLength::$validates_length_of[0]['too_short'] = 'is too short'; - BookLength::$validates_length_of[0]['message'] = 'is not between 2 and 5 characters'; - $book = new BookLength(); - $book->name = '1'; - $book->is_valid(); - $this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages()); - - $book->name = '123456'; - $book->is_valid(); - $this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages()); - } - - public function test_valid_in() - { - BookLength::$validates_length_of[0]['in'] = array(1, 5); - $book = new BookLength; - $book->name = '12345'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_aliased_size_of() - { - BookSize::$validates_size_of = BookLength::$validates_length_of; - BookSize::$validates_size_of[0]['within'] = array(1, 5); - $book = new BookSize; - $book->name = '12345'; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_invalid_within_and_in() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - $book = new BookLength; - $book->name = 'four'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - - $this->set_up(); - BookLength::$validates_length_of[0]['in'] = array(1, 3); - $book = new BookLength; - $book->name = 'four'; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - } - - public function test_valid_null() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - BookLength::$validates_length_of[0]['allow_null'] = true; - - $book = new BookLength; - $book->name = null; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_valid_blank() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - BookLength::$validates_length_of[0]['allow_blank'] = true; - - $book = new BookLength; - $book->name = ''; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_invalid_blank() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - - $book = new BookLength; - $book->name = ''; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); - } - - public function test_invalid_null_within() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - - $book = new BookLength; - $book->name = null; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); - } - - public function test_invalid_null_minimum() - { - BookLength::$validates_length_of[0]['minimum'] = 1; - - $book = new BookLength; - $book->name = null; - $book->save(); - $this->assert_true($book->errors->is_invalid('name')); - $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); - - } - - public function test_valid_null_maximum() - { - BookLength::$validates_length_of[0]['maximum'] = 1; - - $book = new BookLength; - $book->name = null; - $book->save(); - $this->assert_false($book->errors->is_invalid('name')); - } - - public function test_float_as_impossible_range_option() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3.6); - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('maximum value cannot use a float for length.', $e->getMessage()); - } - - $this->set_up(); - BookLength::$validates_length_of[0]['is'] = 1.8; - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('is value cannot use a float for length.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_signed_integer_as_impossible_within_option() - { - BookLength::$validates_length_of[0]['within'] = array(-1, 3); - - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('minimum value cannot use a signed integer.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_not_array_as_impossible_range_option() - { - BookLength::$validates_length_of[0]['within'] = 'string'; - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('within must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage()); - } - - $this->set_up(); - BookLength::$validates_length_of[0]['in'] = 'string'; - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('in must be an array composing a range of numbers with key [0] being less than key [1]', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_signed_integer_as_impossible_is_option() - { - BookLength::$validates_length_of[0]['is'] = -8; - - $book = new BookLength; - $book->name = '123'; - try { - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('is value cannot use a signed integer.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_lack_of_option() - { - try { - $book = new BookLength; - $book->name = null; - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('Range unspecified. Specify the [within], [maximum], or [is] option.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_too_many_options() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - BookLength::$validates_length_of[0]['in'] = array(1, 3); - - try { - $book = new BookLength; - $book->name = null; - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('Too many range options specified. Choose only one.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - public function test_too_many_options_with_different_option_types() - { - BookLength::$validates_length_of[0]['within'] = array(1, 3); - BookLength::$validates_length_of[0]['is'] = 3; - - try { - $book = new BookLength; - $book->name = null; - $book->save(); - } catch (ActiveRecord\ValidationsArgumentError $e) { - $this->assert_equals('Too many range options specified. Choose only one.', $e->getMessage()); - return; - } - - $this->fail('An expected exception has not be raised.'); - } - - /** - * @expectedException ActiveRecord\ValidationsArgumentError - */ - public function test_with_option_as_non_numeric() - { - BookLength::$validates_length_of[0]['with'] = array('test'); - - $book = new BookLength; - $book->name = null; - $book->save(); - } - - /** - * @expectedException ActiveRecord\ValidationsArgumentError - */ - public function test_with_option_as_non_numeric_non_array() - { - BookLength::$validates_length_of[0]['with'] = 'test'; - - $book = new BookLength; - $book->name = null; - $book->save(); - } - - public function test_validates_length_of_maximum() - { - BookLength::$validates_length_of[0] = array('name', 'maximum' => 10); - $book = new BookLength(array('name' => '12345678901')); - $book->is_valid(); - $this->assert_equals(array("Name is too long (maximum is 10 characters)"),$book->errors->full_messages()); - } - - public function test_validates_length_of_minimum() - { - BookLength::$validates_length_of[0] = array('name', 'minimum' => 2); - $book = new BookLength(array('name' => '1')); - $book->is_valid(); - $this->assert_equals(array("Name is too short (minimum is 2 characters)"),$book->errors->full_messages()); - } - - public function test_validates_length_of_min_max_custom_message() - { - BookLength::$validates_length_of[0] = array('name', 'maximum' => 10, 'message' => 'is far too long'); - $book = new BookLength(array('name' => '12345678901')); - $book->is_valid(); - $this->assert_equals(array("Name is far too long"),$book->errors->full_messages()); - - BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'message' => 'is far too short'); - $book = new BookLength(array('name' => '123456789')); - $book->is_valid(); - $this->assert_equals(array("Name is far too short"),$book->errors->full_messages()); - } - - public function test_validates_length_of_min_max_custom_message_overridden() - { - BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'too_short' => 'is too short', 'message' => 'is custom message'); - $book = new BookLength(array('name' => '123456789')); - $book->is_valid(); - $this->assert_equals(array("Name is custom message"),$book->errors->full_messages()); - } - - public function test_validates_length_of_is() - { - BookLength::$validates_length_of[0] = array('name', 'is' => 2); - $book = new BookLength(array('name' => '123')); - $book->is_valid(); - $this->assert_equals(array("Name is the wrong length (should be 2 characters)"),$book->errors->full_messages()); - } -}; -?> \ No newline at end of file +<?php + +class BookLength extends ActiveRecord\Model +{ + static $table = 'books'; + static $validates_length_of = array(); +} + +class BookSize extends ActiveRecord\Model +{ + static $table = 'books'; + static $validates_size_of = array(); +} + +class ValidatesLengthOfTest extends DatabaseTest +{ + public function set_up($connection_name=null) + { + parent::set_up($connection_name); + BookLength::$validates_length_of[0] = array('name', 'allow_blank' => false, 'allow_null' => false); + } + + public function test_within() + { + BookLength::$validates_length_of[0]['within'] = array(1, 5); + $book = new BookLength; + $book->name = '12345'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_within_error_message() + { + BookLength::$validates_length_of[0]['within'] = array(2,5); + $book = new BookLength(); + $book->name = '1'; + $book->is_valid(); + $this->assert_equals(array('Name is too short (minimum is 2 characters)'),$book->errors->full_messages()); + + $book->name = '123456'; + $book->is_valid(); + $this->assert_equals(array('Name is too long (maximum is 5 characters)'),$book->errors->full_messages()); + } + + public function test_within_custom_error_message() + { + BookLength::$validates_length_of[0]['within'] = array(2,5); + BookLength::$validates_length_of[0]['too_short'] = 'is too short'; + BookLength::$validates_length_of[0]['message'] = 'is not between 2 and 5 characters'; + $book = new BookLength(); + $book->name = '1'; + $book->is_valid(); + $this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages()); + + $book->name = '123456'; + $book->is_valid(); + $this->assert_equals(array('Name is not between 2 and 5 characters'),$book->errors->full_messages()); + } + + public function test_valid_in() + { + BookLength::$validates_length_of[0]['in'] = array(1, 5); + $book = new BookLength; + $book->name = '12345'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_aliased_size_of() + { + BookSize::$validates_size_of = BookLength::$validates_length_of; + BookSize::$validates_size_of[0]['within'] = array(1, 5); + $book = new BookSize; + $book->name = '12345'; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_invalid_within_and_in() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + $book = new BookLength; + $book->name = 'four'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + + $this->set_up(); + BookLength::$validates_length_of[0]['in'] = array(1, 3); + $book = new BookLength; + $book->name = 'four'; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + } + + public function test_valid_null() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + BookLength::$validates_length_of[0]['allow_null'] = true; + + $book = new BookLength; + $book->name = null; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_valid_blank() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + BookLength::$validates_length_of[0]['allow_blank'] = true; + + $book = new BookLength; + $book->name = ''; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_invalid_blank() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + + $book = new BookLength; + $book->name = ''; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); + } + + public function test_invalid_null_within() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + + $book = new BookLength; + $book->name = null; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); + } + + public function test_invalid_null_minimum() + { + BookLength::$validates_length_of[0]['minimum'] = 1; + + $book = new BookLength; + $book->name = null; + $book->save(); + $this->assert_true($book->errors->is_invalid('name')); + $this->assert_equals('is too short (minimum is 1 characters)', $book->errors->on('name')); + + } + + public function test_valid_null_maximum() + { + BookLength::$validates_length_of[0]['maximum'] = 1; + + $book = new BookLength; + $book->name = null; + $book->save(); + $this->assert_false($book->errors->is_invalid('name')); + } + + public function test_float_as_impossible_range_option() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3.6); + $book = new BookLength; + $book->name = '123'; + try { + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('maximum value cannot use a float for length.', $e->getMessage()); + } + + $this->set_up(); + BookLength::$validates_length_of[0]['is'] = 1.8; + $book = new BookLength; + $book->name = '123'; + try { + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('is value cannot use a float for length.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + public function test_signed_integer_as_impossible_within_option() + { + BookLength::$validates_length_of[0]['within'] = array(-1, 3); + + $book = new BookLength; + $book->name = '123'; + try { + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('minimum value cannot use a signed integer.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + public function test_signed_integer_as_impossible_is_option() + { + BookLength::$validates_length_of[0]['is'] = -8; + + $book = new BookLength; + $book->name = '123'; + try { + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('is value cannot use a signed integer.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + public function test_lack_of_option() + { + try { + $book = new BookLength; + $book->name = null; + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('Range unspecified. Specify the [within], [maximum], or [is] option.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + public function test_too_many_options() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + BookLength::$validates_length_of[0]['in'] = array(1, 3); + + try { + $book = new BookLength; + $book->name = null; + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('Too many range options specified. Choose only one.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + public function test_too_many_options_with_different_option_types() + { + BookLength::$validates_length_of[0]['within'] = array(1, 3); + BookLength::$validates_length_of[0]['is'] = 3; + + try { + $book = new BookLength; + $book->name = null; + $book->save(); + } catch (ActiveRecord\ValidationsArgumentError $e) { + $this->assert_equals('Too many range options specified. Choose only one.', $e->getMessage()); + return; + } + + $this->fail('An expected exception has not be raised.'); + } + + /** + * @expectedException ActiveRecord\ValidationsArgumentError + */ + public function test_with_option_as_non_numeric() + { + BookLength::$validates_length_of[0]['with'] = array('test'); + + $book = new BookLength; + $book->name = null; + $book->save(); + } + + /** + * @expectedException ActiveRecord\ValidationsArgumentError + */ + public function test_with_option_as_non_numeric_non_array() + { + BookLength::$validates_length_of[0]['with'] = 'test'; + + $book = new BookLength; + $book->name = null; + $book->save(); + } + + public function test_validates_length_of_maximum() + { + BookLength::$validates_length_of[0] = array('name', 'maximum' => 10); + $book = new BookLength(array('name' => '12345678901')); + $book->is_valid(); + $this->assert_equals(array("Name is too long (maximum is 10 characters)"),$book->errors->full_messages()); + } + + public function test_validates_length_of_minimum() + { + BookLength::$validates_length_of[0] = array('name', 'minimum' => 2); + $book = new BookLength(array('name' => '1')); + $book->is_valid(); + $this->assert_equals(array("Name is too short (minimum is 2 characters)"),$book->errors->full_messages()); + } + + public function test_validates_length_of_min_max_custom_message() + { + BookLength::$validates_length_of[0] = array('name', 'maximum' => 10, 'message' => 'is far too long'); + $book = new BookLength(array('name' => '12345678901')); + $book->is_valid(); + $this->assert_equals(array("Name is far too long"),$book->errors->full_messages()); + + BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'message' => 'is far too short'); + $book = new BookLength(array('name' => '123456789')); + $book->is_valid(); + $this->assert_equals(array("Name is far too short"),$book->errors->full_messages()); + } + + public function test_validates_length_of_min_max_custom_message_overridden() + { + BookLength::$validates_length_of[0] = array('name', 'minimum' => 10, 'too_short' => 'is too short', 'message' => 'is custom message'); + $book = new BookLength(array('name' => '123456789')); + $book->is_valid(); + $this->assert_equals(array("Name is custom message"),$book->errors->full_messages()); + } + + public function test_validates_length_of_is() + { + BookLength::$validates_length_of[0] = array('name', 'is' => 2); + $book = new BookLength(array('name' => '123')); + $book->is_valid(); + $this->assert_equals(array("Name is the wrong length (should be 2 characters)"),$book->errors->full_messages()); + } +}; diff --git a/test/ValidatesPresenceOfTest.php b/test/ValidatesPresenceOfTest.php index 745c55939..b0b3aabc7 100644 --- a/test/ValidatesPresenceOfTest.php +++ b/test/ValidatesPresenceOfTest.php @@ -1,74 +1,74 @@ -<?php - -class BookPresence extends ActiveRecord\Model -{ - static $table_name = 'books'; - - static $validates_presence_of = array( - array('name') - ); -} - -class AuthorPresence extends ActiveRecord\Model -{ - static $table_name = 'authors'; - - static $validates_presence_of = array( - array('some_date') - ); -}; - -class ValidatesPresenceOfTest extends DatabaseTest -{ - public function test_presence() - { - $book = new BookPresence(array('name' => 'blah')); - $this->assert_false($book->is_invalid()); - } - - public function test_presence_on_date_field_is_valid() - { - $author = new AuthorPresence(array('some_date' => '2010-01-01')); - $this->assert_true($author->is_valid()); - } - - public function test_presence_on_date_field_is_not_valid() - { - $author = new AuthorPresence(); - $this->assert_false($author->is_valid()); - } - - public function test_invalid_null() - { - $book = new BookPresence(array('name' => null)); - $this->assert_true($book->is_invalid()); - } - - public function test_invalid_blank() - { - $book = new BookPresence(array('name' => '')); - $this->assert_true($book->is_invalid()); - } - - public function test_valid_white_space() - { - $book = new BookPresence(array('name' => ' ')); - $this->assert_false($book->is_invalid()); - } - - public function test_custom_message() - { - BookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.'; - - $book = new BookPresence(array('name' => null)); - $book->is_valid(); - $this->assert_equals('is using a custom message.', $book->errors->on('name')); - } - - public function test_valid_zero() - { - $book = new BookPresence(array('name' => 0)); - $this->assert_true($book->is_valid()); - } -}; +<?php + +class BookPresence extends ActiveRecord\Model +{ + static $table_name = 'books'; + + static $validates_presence_of = array( + array('name') + ); +} + +class AuthorPresence extends ActiveRecord\Model +{ + static $table_name = 'authors'; + + static $validates_presence_of = array( + array('some_date') + ); +}; + +class ValidatesPresenceOfTest extends DatabaseTest +{ + public function test_presence() + { + $book = new BookPresence(array('name' => 'blah')); + $this->assert_false($book->is_invalid()); + } + + public function test_presence_on_date_field_is_valid() + { + $author = new AuthorPresence(array('some_date' => '2010-01-01')); + $this->assert_true($author->is_valid()); + } + + public function test_presence_on_date_field_is_not_valid() + { + $author = new AuthorPresence(); + $this->assert_false($author->is_valid()); + } + + public function test_invalid_null() + { + $book = new BookPresence(array('name' => null)); + $this->assert_true($book->is_invalid()); + } + + public function test_invalid_blank() + { + $book = new BookPresence(array('name' => '')); + $this->assert_true($book->is_invalid()); + } + + public function test_valid_white_space() + { + $book = new BookPresence(array('name' => ' ')); + $this->assert_false($book->is_invalid()); + } + + public function test_custom_message() + { + BookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.'; + + $book = new BookPresence(array('name' => null)); + $book->is_valid(); + $this->assert_equals('is using a custom message.', $book->errors->on('name')); + } + + public function test_valid_zero() + { + $book = new BookPresence(array('name' => 0)); + $this->assert_true($book->is_valid()); + } +}; ?> \ No newline at end of file diff --git a/test/models/RmBldg.php b/test/models/RmBldg.php index fbc9ce79d..04169ba25 100644 --- a/test/models/RmBldg.php +++ b/test/models/RmBldg.php @@ -1,33 +1,33 @@ -<?php -class RmBldg extends ActiveRecord\Model -{ - static $table = 'rm-bldg'; - - static $validates_presence_of = array( - array('space_out', 'message' => 'is missing!@#'), - array('rm_name') - ); - - static $validates_length_of = array( - array('space_out', 'within' => array(1, 5)), - array('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long') - ); - - static $validates_inclusion_of = array( - array('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'), - ); - - static $validates_exclusion_of = array( - array('space_out', 'in' => array('jpeg')) - ); - - static $validates_format_of = array( - array('space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i' ) - ); - - static $validates_numericality_of = array( - array('space_out', 'less_than' => 9, 'greater_than' => '5'), - array('rm_id', 'less_than' => 10, 'odd' => null) - ); -} -?> +<?php +class RmBldg extends ActiveRecord\Model +{ + static $table = 'rm-bldg'; + + static $validates_presence_of = array( + array('space_out', 'message' => 'is missing!@#'), + array('rm_name') + ); + + static $validates_length_of = array( + array('space_out', 'within' => array(1, 5)), + array('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long') + ); + + static $validates_inclusion_of = array( + array('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'), + ); + + static $validates_exclusion_of = array( + array('space_out', 'in' => array('jpeg')) + ); + + static $validates_format_of = array( + array('space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i' ) + ); + + static $validates_numericality_of = array( + array('space_out', 'less_than' => 9, 'greater_than' => '5'), + array('rm_id', 'less_than' => 10, 'odd' => null) + ); +} +?> From 65d5c4816066571d67ca8e86304c3b99eddee38d Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Thu, 17 Jul 2014 11:00:10 +0200 Subject: [PATCH 04/53] normalize file endings --- examples/orders/models/Order.php | 1 - examples/orders/models/Payment.php | 1 - examples/orders/models/Person.php | 1 - examples/orders/orders.php | 2 +- examples/simple/simple.php | 1 - examples/simple/simple_with_options.php | 1 - lib/Serialization.php | 2 +- lib/adapters/MysqlAdapter.php | 1 - lib/adapters/OciAdapter.php | 1 - lib/adapters/PgsqlAdapter.php | 1 - lib/adapters/SqliteAdapter.php | 1 - test/ActiveRecordCacheTest.php | 2 +- test/ActiveRecordFindTest.php | 3 +-- test/ActiveRecordTest.php | 3 +-- test/ActiveRecordWriteTest.php | 4 ++-- test/CacheTest.php | 7 +++---- test/CallbackTest.php | 3 +-- test/ColumnTest.php | 1 - test/ConfigTest.php | 1 - test/ConnectionManagerTest.php | 21 +++++++++---------- test/ConnectionTest.php | 1 - test/DateFormatTest.php | 3 +-- test/DateTimeTest.php | 1 - test/ExpressionsTest.php | 1 - test/HasManyThroughTest.php | 2 -- test/InflectorTest.php | 3 +-- test/ModelCallbackTest.php | 1 - test/MysqlAdapterTest.php | 1 - test/OciAdapterTest.php | 1 - test/PgsqlAdapterTest.php | 1 - test/RelationshipTest.php | 5 ++--- test/SQLBuilderTest.php | 3 +-- test/SerializationTest.php | 3 +-- test/SqliteAdapterTest.php | 1 - test/UtilsTest.php | 4 ++-- test/ValidatesFormatOfTest.php | 5 ++--- test/ValidatesInclusionAndExclusionOfTest.php | 7 +++---- test/ValidatesLengthOfTest.php | 2 +- test/ValidatesNumericalityOfTest.php | 3 +-- test/ValidatesPresenceOfTest.php | 5 ++--- test/ValidationsTest.php | 4 ++-- test/helpers/AdapterTest.php | 1 - test/helpers/DatabaseLoader.php | 1 - test/helpers/DatabaseTest.php | 1 - .../SnakeCase_PHPUnit_Framework_TestCase.php | 1 - test/helpers/config.php | 1 - test/helpers/foo.php | 3 --- test/models/Amenity.php | 3 +-- test/models/Author.php | 3 +-- test/models/AuthorAttrAccessible.php | 3 +-- test/models/AwesomePerson.php | 1 - test/models/Book.php | 3 +-- test/models/BookAttrAccessible.php | 3 +-- test/models/BookAttrProtected.php | 4 ++-- test/models/Employee.php | 3 +-- test/models/Event.php | 4 ++-- test/models/Host.php | 1 - test/models/JoinAuthor.php | 3 +-- test/models/JoinBook.php | 3 +-- test/models/NamespaceTest/Book.php | 1 - .../NamespaceTest/SubNamespaceTest/Page.php | 1 - test/models/Position.php | 3 +-- test/models/Property.php | 3 +-- test/models/PropertyAmenity.php | 3 +-- test/models/RmBldg.php | 1 - test/models/Venue.php | 3 +-- test/models/VenueCB.php | 1 - 67 files changed, 56 insertions(+), 117 deletions(-) diff --git a/examples/orders/models/Order.php b/examples/orders/models/Order.php index 642943c4b..8c0464c28 100644 --- a/examples/orders/models/Order.php +++ b/examples/orders/models/Order.php @@ -34,4 +34,3 @@ public function apply_tax() $this->tax = $this->price * $tax; } } -?> diff --git a/examples/orders/models/Payment.php b/examples/orders/models/Payment.php index faef5ef91..777f2b6a0 100644 --- a/examples/orders/models/Payment.php +++ b/examples/orders/models/Payment.php @@ -6,4 +6,3 @@ class Payment extends ActiveRecord\Model array('person'), array('order')); } -?> diff --git a/examples/orders/models/Person.php b/examples/orders/models/Person.php index b2e586064..da8a6e550 100644 --- a/examples/orders/models/Person.php +++ b/examples/orders/models/Person.php @@ -10,4 +10,3 @@ class Person extends ActiveRecord\Model static $validates_presence_of = array( array('name'), array('state')); } -?> diff --git a/examples/orders/orders.php b/examples/orders/orders.php index 8222ad448..44815b820 100644 --- a/examples/orders/orders.php +++ b/examples/orders/orders.php @@ -76,4 +76,4 @@ foreach ($order->people as $person) echo " payment of $$person->amount by " . $person->name . "\n"; -?> + diff --git a/examples/simple/simple.php b/examples/simple/simple.php index 0acff65d2..537919d4d 100644 --- a/examples/simple/simple.php +++ b/examples/simple/simple.php @@ -14,4 +14,3 @@ class Book extends ActiveRecord\Model { } }); print_r(Book::first()->attributes()); -?> diff --git a/examples/simple/simple_with_options.php b/examples/simple/simple_with_options.php index 1056435f7..6e42fca92 100644 --- a/examples/simple/simple_with_options.php +++ b/examples/simple/simple_with_options.php @@ -29,4 +29,3 @@ class Book extends ActiveRecord\Model }); print_r(Book::first()->attributes()); -?> diff --git a/lib/Serialization.php b/lib/Serialization.php index 552b13ef9..b50a4d33b 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -241,7 +241,7 @@ final public function __toString() * @return string */ abstract public function to_s(); -}; +} /** * Array serializer. diff --git a/lib/adapters/MysqlAdapter.php b/lib/adapters/MysqlAdapter.php index 12a50db63..ddd545059 100644 --- a/lib/adapters/MysqlAdapter.php +++ b/lib/adapters/MysqlAdapter.php @@ -96,4 +96,3 @@ public function native_database_types() } } -?> diff --git a/lib/adapters/OciAdapter.php b/lib/adapters/OciAdapter.php index 353fffefa..1b9336931 100644 --- a/lib/adapters/OciAdapter.php +++ b/lib/adapters/OciAdapter.php @@ -143,4 +143,3 @@ public function native_database_types() ); } } -?> diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 72da44182..399256477 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -136,4 +136,3 @@ public function native_database_types() } } -?> diff --git a/lib/adapters/SqliteAdapter.php b/lib/adapters/SqliteAdapter.php index 0917043be..61eb257f2 100644 --- a/lib/adapters/SqliteAdapter.php +++ b/lib/adapters/SqliteAdapter.php @@ -110,4 +110,3 @@ public function native_database_types() } } -?> \ No newline at end of file diff --git a/test/ActiveRecordCacheTest.php b/test/ActiveRecordCacheTest.php index 59749d613..6d43cca45 100644 --- a/test/ActiveRecordCacheTest.php +++ b/test/ActiveRecordCacheTest.php @@ -41,4 +41,4 @@ public function test_caches_column_meta_data() $this->assert_true(is_array($value)); } } -?> + diff --git a/test/ActiveRecordFindTest.php b/test/ActiveRecordFindTest.php index 329eb9e67..53c742157 100644 --- a/test/ActiveRecordFindTest.php +++ b/test/ActiveRecordFindTest.php @@ -476,5 +476,4 @@ public function test_find_by_datetime() $this->assert_not_null(Author::find_by_created_at($now)); $this->assert_not_null(Author::find_by_created_at($arnow)); } -}; -?> +} diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index a20aa913f..0bff0c0f2 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -562,5 +562,4 @@ public function test_query() $row = Author::query('SELECT COUNT(*) AS n FROM authors WHERE name=?',array('Tito'))->fetch(); $this->assert_equals(array('n' => 1), $row); } -}; -?> +} diff --git a/test/ActiveRecordWriteTest.php b/test/ActiveRecordWriteTest.php index 8a6cc8cd7..e0f0c4523 100644 --- a/test/ActiveRecordWriteTest.php +++ b/test/ActiveRecordWriteTest.php @@ -10,7 +10,7 @@ public function before_save() { $this->name = 'i saved'; } -}; +} class AuthorWithoutSequence extends ActiveRecord\Model { @@ -424,4 +424,4 @@ public function test_update_all_with_limit_and_order() $this->assert_equals(1, $num_affected); $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false); } -}; +} diff --git a/test/CacheTest.php b/test/CacheTest.php index 73ba5ea87..febd94201 100644 --- a/test/CacheTest.php +++ b/test/CacheTest.php @@ -74,9 +74,8 @@ public function test_cache_expire() public function test_namespace_is_set_properly() { - Cache::$options['namespace'] = 'myapp'; - $this->cache_get(); - $this->assert_same("abcd", Cache::$adapter->read("myapp::1337")); + Cache::$options['namespace'] = 'myapp'; + $this->cache_get(); + $this->assert_same("abcd", Cache::$adapter->read("myapp::1337")); } } -?> diff --git a/test/CallbackTest.php b/test/CallbackTest.php index 00615b12d..9c1e46d29 100644 --- a/test/CallbackTest.php +++ b/test/CallbackTest.php @@ -290,5 +290,4 @@ public function test_before_validation_returned_false_halts_execution() $this->assert_false($ret); $this->assert_true(strpos(ActiveRecord\Table::load('VenueCB')->last_sql, 'UPDATE') === false); } -}; -?> \ No newline at end of file +} diff --git a/test/ColumnTest.php b/test/ColumnTest.php index f5769bc68..89b53d878 100644 --- a/test/ColumnTest.php +++ b/test/ColumnTest.php @@ -127,4 +127,3 @@ public function test_empty_and_null_datetime_strings_should_return_null() $this->assert_equals(null,$column->cast('',$this->conn)); } } -?> diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 59e44541b..2dbafbd90 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -92,4 +92,3 @@ public function test_logger_object_must_implement_log_method() } } } -?> diff --git a/test/ConnectionManagerTest.php b/test/ConnectionManagerTest.php index ca29b0452..f4616a940 100644 --- a/test/ConnectionManagerTest.php +++ b/test/ConnectionManagerTest.php @@ -24,16 +24,15 @@ public function test_get_connection_uses_existing_object() $this->assert_same($a,ConnectionManager::get_connection('mysql')); } - public function test_gh_91_get_connection_with_null_connection_is_always_default() - { - $conn_one = ConnectionManager::get_connection('mysql'); - $conn_two = ConnectionManager::get_connection(); - $conn_three = ConnectionManager::get_connection('mysql'); - $conn_four = ConnectionManager::get_connection(); + public function test_gh_91_get_connection_with_null_connection_is_always_default() + { + $conn_one = ConnectionManager::get_connection('mysql'); + $conn_two = ConnectionManager::get_connection(); + $conn_three = ConnectionManager::get_connection('mysql'); + $conn_four = ConnectionManager::get_connection(); - $this->assert_same($conn_one, $conn_three); - $this->assert_same($conn_two, $conn_three); - $this->assert_same($conn_four, $conn_three); - } + $this->assert_same($conn_one, $conn_three); + $this->assert_same($conn_two, $conn_three); + $this->assert_same($conn_four, $conn_three); + } } -?> diff --git a/test/ConnectionTest.php b/test/ConnectionTest.php index 59f91c7c6..a9083b356 100644 --- a/test/ConnectionTest.php +++ b/test/ConnectionTest.php @@ -77,4 +77,3 @@ public function test_encoding() $this->assert_equals('utf8', $info->charset); } } -?> diff --git a/test/DateFormatTest.php b/test/DateFormatTest.php index ab857e90a..b18faffd7 100644 --- a/test/DateFormatTest.php +++ b/test/DateFormatTest.php @@ -14,5 +14,4 @@ public function test_datefield_gets_converted_to_ar_datetime() $this->assert_is_a("ActiveRecord\\DateTime",$author->some_date); } -}; -?> +} diff --git a/test/DateTimeTest.php b/test/DateTimeTest.php index 285ca0ea8..e489e0f74 100644 --- a/test/DateTimeTest.php +++ b/test/DateTimeTest.php @@ -124,4 +124,3 @@ public function test_to_string() $this->assert_equals(date(DateTime::get_format()), "" . $this->date); } } -?> diff --git a/test/ExpressionsTest.php b/test/ExpressionsTest.php index fdddb57ea..5f089e60e 100644 --- a/test/ExpressionsTest.php +++ b/test/ExpressionsTest.php @@ -205,4 +205,3 @@ public function test_hash_with_array() $this->assert_equals('id=? AND name IN(?,?)',$a->to_s()); } } -?> diff --git a/test/HasManyThroughTest.php b/test/HasManyThroughTest.php index ff9586cc1..3e9982526 100644 --- a/test/HasManyThroughTest.php +++ b/test/HasManyThroughTest.php @@ -56,5 +56,3 @@ public function test_gh107_has_many_though_include_eager_with_namespace() $this->assert_equals(1, $user->newsletters[0]->id); } } -# vim: noet ts=4 nobinary -?> diff --git a/test/InflectorTest.php b/test/InflectorTest.php index 7726592f0..d93f40b38 100644 --- a/test/InflectorTest.php +++ b/test/InflectorTest.php @@ -24,5 +24,4 @@ public function test_keyify() { $this->assert_equals('building_type_id', $this->inflector->keyify('BuildingType')); } -}; -?> \ No newline at end of file +} diff --git a/test/ModelCallbackTest.php b/test/ModelCallbackTest.php index 949706385..b9ffd7466 100644 --- a/test/ModelCallbackTest.php +++ b/test/ModelCallbackTest.php @@ -94,4 +94,3 @@ public function test_destroy() function($model) { $model->delete(); }); } } -?> diff --git a/test/MysqlAdapterTest.php b/test/MysqlAdapterTest.php index 2a93570e6..90837ba57 100644 --- a/test/MysqlAdapterTest.php +++ b/test/MysqlAdapterTest.php @@ -34,4 +34,3 @@ public function test_limit_with_null_offset_does_not_contain_offset() $this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false); } } -?> diff --git a/test/OciAdapterTest.php b/test/OciAdapterTest.php index d820c0fd5..f93170b11 100644 --- a/test/OciAdapterTest.php +++ b/test/OciAdapterTest.php @@ -43,4 +43,3 @@ public function test_set_charset() $this->assert_equals(';charset=utf8', $conn->dsn_params); } } -?> diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php index 348b11e9f..71b672557 100644 --- a/test/PgsqlAdapterTest.php +++ b/test/PgsqlAdapterTest.php @@ -41,4 +41,3 @@ public function test_gh96_columns_not_duplicated_by_index() $this->assert_equals(3,$this->conn->query_column_info("user_newsletters")->rowCount()); } } -?> diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index d3ce103d6..e726a6b71 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -1,6 +1,6 @@ <?php -class NotModel {}; +class NotModel {} class AuthorWithNonModelRelationship extends ActiveRecord\Model { @@ -732,5 +732,4 @@ public function test_dont_attempt_eager_load_when_record_does_not_exist() { Author::find(999999, array('include' => array('books'))); } -}; -?> +} diff --git a/test/SQLBuilderTest.php b/test/SQLBuilderTest.php index 603b60b6b..b44ca1c41 100644 --- a/test/SQLBuilderTest.php +++ b/test/SQLBuilderTest.php @@ -278,5 +278,4 @@ public function test_where_with_joins_prepends_table_name_to_fields() $this->assert_sql_has("SELECT * FROM authors $joins WHERE authors.id=? AND authors.name=?",(string)$this->sql); } -}; -?> \ No newline at end of file +} diff --git a/test/SerializationTest.php b/test/SerializationTest.php index e8ef9cebd..fe0364287 100644 --- a/test/SerializationTest.php +++ b/test/SerializationTest.php @@ -211,5 +211,4 @@ public function test_to_csv_with_custom_enclosure() ActiveRecord\CsvSerializer::$enclosure="'"; $this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv()); } -}; -?> +} diff --git a/test/SqliteAdapterTest.php b/test/SqliteAdapterTest.php index 0490c8137..146152cf8 100644 --- a/test/SqliteAdapterTest.php +++ b/test/SqliteAdapterTest.php @@ -79,4 +79,3 @@ public function test_date_to_string() // not supported public function test_connect_with_port() {} } -?> \ No newline at end of file diff --git a/test/UtilsTest.php b/test/UtilsTest.php index 9205a6b8e..20074bbc9 100644 --- a/test/UtilsTest.php +++ b/test/UtilsTest.php @@ -103,5 +103,5 @@ public function test_wrap_strings_in_arrays() $x = '1'; $this->assert_equals(array(array('1')),ActiveRecord\wrap_strings_in_arrays($x)); } -}; -?> +} + diff --git a/test/ValidatesFormatOfTest.php b/test/ValidatesFormatOfTest.php index 718e4314e..0aaac2f34 100644 --- a/test/ValidatesFormatOfTest.php +++ b/test/ValidatesFormatOfTest.php @@ -6,7 +6,7 @@ class BookFormat extends ActiveRecord\Model static $validates_format_of = array( array('name') ); -}; +} class ValidatesFormatOfTest extends DatabaseTest { @@ -107,5 +107,4 @@ public function test_custom_message() $book->save(); $this->assert_equals('is using a custom message.', $book->errors->on('name')); } -}; -?> \ No newline at end of file +} diff --git a/test/ValidatesInclusionAndExclusionOfTest.php b/test/ValidatesInclusionAndExclusionOfTest.php index 38bbaee4d..efa6a28f2 100644 --- a/test/ValidatesInclusionAndExclusionOfTest.php +++ b/test/ValidatesInclusionAndExclusionOfTest.php @@ -6,7 +6,7 @@ class BookExclusion extends ActiveRecord\Model public static $validates_exclusion_of = array( array('name', 'in' => array('blah', 'alpha', 'bravo')) ); -}; +} class BookInclusion extends ActiveRecord\Model { @@ -14,7 +14,7 @@ class BookInclusion extends ActiveRecord\Model public static $validates_inclusion_of = array( array('name', 'in' => array('blah', 'tanker', 'shark')) ); -}; +} class ValidatesInclusionAndExclusionOfTest extends DatabaseTest { @@ -153,5 +153,4 @@ public function test_custom_message() $this->assert_equals('is using a custom message.', $book->errors->on('name')); } -}; -?> \ No newline at end of file +} diff --git a/test/ValidatesLengthOfTest.php b/test/ValidatesLengthOfTest.php index 9df1cb9e9..99ebe786c 100644 --- a/test/ValidatesLengthOfTest.php +++ b/test/ValidatesLengthOfTest.php @@ -331,4 +331,4 @@ public function test_validates_length_of_is() $book->is_valid(); $this->assert_equals(array("Name is the wrong length (should be 2 characters)"),$book->errors->full_messages()); } -}; +} diff --git a/test/ValidatesNumericalityOfTest.php b/test/ValidatesNumericalityOfTest.php index 08eb7f2ec..ac9d6e2e3 100644 --- a/test/ValidatesNumericalityOfTest.php +++ b/test/ValidatesNumericalityOfTest.php @@ -158,8 +158,7 @@ public function test_custom_message() $book->is_valid(); $this->assert_equals(array('Numeric test Hello'),$book->errors->full_messages()); } -}; +} array_merge(ValidatesNumericalityOfTest::$INTEGERS, ValidatesNumericalityOfTest::$INTEGER_STRINGS); array_merge(ValidatesNumericalityOfTest::$FLOATS, ValidatesNumericalityOfTest::$FLOAT_STRINGS); -?> diff --git a/test/ValidatesPresenceOfTest.php b/test/ValidatesPresenceOfTest.php index b0b3aabc7..16c2354de 100644 --- a/test/ValidatesPresenceOfTest.php +++ b/test/ValidatesPresenceOfTest.php @@ -16,7 +16,7 @@ class AuthorPresence extends ActiveRecord\Model static $validates_presence_of = array( array('some_date') ); -}; +} class ValidatesPresenceOfTest extends DatabaseTest { @@ -70,5 +70,4 @@ public function test_valid_zero() $book = new BookPresence(array('name' => 0)); $this->assert_true($book->is_valid()); } -}; -?> \ No newline at end of file +} diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index 3e0eef257..7ec55beff 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -178,5 +178,5 @@ public function test_gh131_custom_validation() $this->assert_true($book->errors->is_invalid('name')); $this->assert_equals(BookValidations::$custom_validator_error_msg, $book->errors->on('name')); } -}; -?> +} + diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index 97685f6bc..a3884af9f 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -408,4 +408,3 @@ public function test_date_to_string() $this->assert_equals($datetime,$this->conn->date_to_string(date_create($datetime))); } } -?> diff --git a/test/helpers/DatabaseLoader.php b/test/helpers/DatabaseLoader.php index 8449e1bb2..be250b9b0 100644 --- a/test/helpers/DatabaseLoader.php +++ b/test/helpers/DatabaseLoader.php @@ -127,4 +127,3 @@ public function quote_name($name) return $this->db->quote_name($name); } } -?> diff --git a/test/helpers/DatabaseTest.php b/test/helpers/DatabaseTest.php index b99e44949..f3d26bc31 100644 --- a/test/helpers/DatabaseTest.php +++ b/test/helpers/DatabaseTest.php @@ -79,4 +79,3 @@ public function assert_sql_doesnt_has($needle, $haystack) return $this->assertNotContains($needle, $haystack); } } -?> diff --git a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php index a5ac79c85..1016aa044 100644 --- a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php +++ b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php @@ -61,4 +61,3 @@ public function assert_datetime_equals($expected, $actual) $this->assert_equals($expected->format(DateTime::ISO8601),$actual->format(DateTime::ISO8601)); } } -?> \ No newline at end of file diff --git a/test/helpers/config.php b/test/helpers/config.php index 49a488660..a71243e14 100644 --- a/test/helpers/config.php +++ b/test/helpers/config.php @@ -82,4 +82,3 @@ }); error_reporting(E_ALL | E_STRICT); -?> \ No newline at end of file diff --git a/test/helpers/foo.php b/test/helpers/foo.php index 1ed77ca51..49e3194eb 100644 --- a/test/helpers/foo.php +++ b/test/helpers/foo.php @@ -23,6 +23,3 @@ class UserNewsletter extends \ActiveRecord\Model { array('newsletter'), ); } - -# vim: ts=4 noet nobinary -?> diff --git a/test/models/Amenity.php b/test/models/Amenity.php index ce6e3f163..70f6e442b 100644 --- a/test/models/Amenity.php +++ b/test/models/Amenity.php @@ -7,5 +7,4 @@ class Amenity extends ActiveRecord\Model static $has_many = array( 'property_amenities' ); -}; -?> +} diff --git a/test/models/Author.php b/test/models/Author.php index 321ffe9d1..16987facd 100644 --- a/test/models/Author.php +++ b/test/models/Author.php @@ -25,5 +25,4 @@ public function return_something() { return array("sharks" => "lasers"); } -}; -?> +} diff --git a/test/models/AuthorAttrAccessible.php b/test/models/AuthorAttrAccessible.php index 1443629c7..fb870c486 100644 --- a/test/models/AuthorAttrAccessible.php +++ b/test/models/AuthorAttrAccessible.php @@ -13,5 +13,4 @@ class AuthorAttrAccessible extends ActiveRecord\Model // No attributes should be accessible static $attr_accessible = array(null); -}; -?> +} diff --git a/test/models/AwesomePerson.php b/test/models/AwesomePerson.php index 33aeece65..3bf9d8b07 100644 --- a/test/models/AwesomePerson.php +++ b/test/models/AwesomePerson.php @@ -3,4 +3,3 @@ class AwesomePerson extends ActiveRecord\Model { static $belongs_to = array('author'); } -?> diff --git a/test/models/Book.php b/test/models/Book.php index 88e35d403..d001b37d3 100644 --- a/test/models/Book.php +++ b/test/models/Book.php @@ -32,5 +32,4 @@ public function get_lower_name() { return strtolower($this->name); } -}; -?> +} diff --git a/test/models/BookAttrAccessible.php b/test/models/BookAttrAccessible.php index 59d1efbcc..f9204d6fd 100644 --- a/test/models/BookAttrAccessible.php +++ b/test/models/BookAttrAccessible.php @@ -6,5 +6,4 @@ class BookAttrAccessible extends ActiveRecord\Model static $attr_accessible = array('author_id'); static $attr_protected = array('book_id'); -}; -?> \ No newline at end of file +} diff --git a/test/models/BookAttrProtected.php b/test/models/BookAttrProtected.php index ab1305009..876925f9d 100644 --- a/test/models/BookAttrProtected.php +++ b/test/models/BookAttrProtected.php @@ -9,5 +9,5 @@ class BookAttrProtected extends ActiveRecord\Model // No attributes should be accessible static $attr_accessible = array(null); -}; -?> +} + diff --git a/test/models/Employee.php b/test/models/Employee.php index dfdf2ccf6..606ccb6ad 100644 --- a/test/models/Employee.php +++ b/test/models/Employee.php @@ -2,5 +2,4 @@ class Employee extends ActiveRecord\Model { static $has_one; -}; -?> \ No newline at end of file +} diff --git a/test/models/Event.php b/test/models/Event.php index ca3c13d74..5243cbda3 100644 --- a/test/models/Event.php +++ b/test/models/Event.php @@ -10,5 +10,5 @@ class Event extends ActiveRecord\Model array('state', 'address', 'to' => 'venue'), array('name', 'to' => 'host', 'prefix' => 'woot') ); -}; -?> +} + diff --git a/test/models/Host.php b/test/models/Host.php index 7c66e3f08..fcc6a9626 100644 --- a/test/models/Host.php +++ b/test/models/Host.php @@ -6,4 +6,3 @@ class Host extends ActiveRecord\Model array('venues', 'through' => 'events') ); } -?> diff --git a/test/models/JoinAuthor.php b/test/models/JoinAuthor.php index d39fb873a..f2b250c43 100644 --- a/test/models/JoinAuthor.php +++ b/test/models/JoinAuthor.php @@ -3,5 +3,4 @@ class JoinAuthor extends ActiveRecord\Model { static $table_name = 'authors'; static $pk = 'author_id'; -}; -?> \ No newline at end of file +} diff --git a/test/models/JoinBook.php b/test/models/JoinBook.php index be845534a..2b1199db5 100644 --- a/test/models/JoinBook.php +++ b/test/models/JoinBook.php @@ -4,5 +4,4 @@ class JoinBook extends ActiveRecord\Model static $table_name = 'books'; static $belongs_to = array(); -}; -?> \ No newline at end of file +} diff --git a/test/models/NamespaceTest/Book.php b/test/models/NamespaceTest/Book.php index f2e237e5b..ee5456628 100644 --- a/test/models/NamespaceTest/Book.php +++ b/test/models/NamespaceTest/Book.php @@ -14,4 +14,3 @@ class Book extends \ActiveRecord\Model array('pages_2', 'class_name' => 'SubNamespaceTest\Page'), ); } -?> diff --git a/test/models/NamespaceTest/SubNamespaceTest/Page.php b/test/models/NamespaceTest/SubNamespaceTest/Page.php index 2d59f2c65..c0cd7f07e 100644 --- a/test/models/NamespaceTest/SubNamespaceTest/Page.php +++ b/test/models/NamespaceTest/SubNamespaceTest/Page.php @@ -7,4 +7,3 @@ class Page extends \ActiveRecord\Model array('book', 'class_name' => '\NamespaceTest\Book'), ); } -?> diff --git a/test/models/Position.php b/test/models/Position.php index ce409d815..9d98089c2 100644 --- a/test/models/Position.php +++ b/test/models/Position.php @@ -2,5 +2,4 @@ class Position extends ActiveRecord\Model { static $belongs_to; -}; -?> \ No newline at end of file +} diff --git a/test/models/Property.php b/test/models/Property.php index e0f913cc9..9205c96d6 100644 --- a/test/models/Property.php +++ b/test/models/Property.php @@ -8,5 +8,4 @@ class Property extends ActiveRecord\Model 'property_amenities', array('amenities', 'through' => 'property_amenities') ); -}; -?> +} diff --git a/test/models/PropertyAmenity.php b/test/models/PropertyAmenity.php index 47f80e966..6ab72f4b2 100644 --- a/test/models/PropertyAmenity.php +++ b/test/models/PropertyAmenity.php @@ -8,5 +8,4 @@ class PropertyAmenity extends ActiveRecord\Model 'amenity', 'property' ); -}; -?> +} diff --git a/test/models/RmBldg.php b/test/models/RmBldg.php index 04169ba25..b6fb38006 100644 --- a/test/models/RmBldg.php +++ b/test/models/RmBldg.php @@ -30,4 +30,3 @@ class RmBldg extends ActiveRecord\Model array('rm_id', 'less_than' => 10, 'odd' => null) ); } -?> diff --git a/test/models/Venue.php b/test/models/Venue.php index 38ad9241b..3c13adeee 100644 --- a/test/models/Venue.php +++ b/test/models/Venue.php @@ -33,5 +33,4 @@ public function set_state($value) return $this->assign_attribute('state', $value); } -}; -?> +} diff --git a/test/models/VenueCB.php b/test/models/VenueCB.php index 7d96f2427..32255881a 100644 --- a/test/models/VenueCB.php +++ b/test/models/VenueCB.php @@ -41,4 +41,3 @@ public function before_validation_halt_execution() return false; } } -?> \ No newline at end of file From 7de8cd36029d070e4469223564562af9840a0470 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Wed, 27 Jun 2012 11:38:31 +0200 Subject: [PATCH 05/53] Added option for multiple model directories --- ActiveRecord.php | 27 +++++++++++++++++---------- README.md | 19 +++++++++++-------- lib/Config.php | 46 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/ActiveRecord.php b/ActiveRecord.php index 762a48d9f..d00dae47b 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -28,9 +28,8 @@ function activerecord_autoload($class_name) { - $path = ActiveRecord\Config::instance()->get_model_directory(); - $root = realpath(isset($path) ? $path : '.'); - + $paths = ActiveRecord\Config::instance()->get_model_directories(); + $namespace_directory = ''; if (($namespaces = ActiveRecord\get_namespaces($class_name))) { $class_name = array_pop($namespaces); @@ -39,11 +38,19 @@ function activerecord_autoload($class_name) foreach ($namespaces as $directory) $directories[] = $directory; - $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR); + $namespace_directory = DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR); } - - $file = "$root/$class_name.php"; - - if (file_exists($file)) - require_once $file; -} \ No newline at end of file + $paths = count($paths) ? $paths : array('.'); + + foreach($paths as $path) + { + $root = realpath($path); + $file = "{$root}{$namespace_directory}/{$class_name}.php"; + + if (file_exists($file)) + { + require $file; + return; + } + } +} diff --git a/README.md b/README.md index 9f7fe5a4a..04389392a 100755 --- a/README.md +++ b/README.md @@ -63,14 +63,17 @@ Example: ```php ActiveRecord\Config::initialize(function($cfg) { - $cfg->set_model_directory('/path/to/your/model_directory'); - $cfg->set_connections( - array( - 'development' => 'mysql://username:password@localhost/development_database_name', - 'test' => 'mysql://username:password@localhost/test_database_name', - 'production' => 'mysql://username:password@localhost/production_database_name' - ) - ); + $cfg->set_model_directories(array( + '/path/to/your/model_directory', + '/some/other/path/to/your/model_directory' + )); + $cfg->set_connections( + array( + 'development' => 'mysql://username:password@localhost/development_database_name', + 'test' => 'mysql://username:password@localhost/test_database_name', + 'production' => 'mysql://username:password@localhost/production_database_name' + ) + ); }); ``` diff --git a/lib/Config.php b/lib/Config.php index 5038af257..3d44b22cf 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -51,12 +51,12 @@ class Config extends Singleton private $connections = array(); /** - * Directory for the auto_loading of model classes. + * Array of directories for the auto_loading of model classes. * * @see activerecord_autoload - * @var string + * @var array */ - private $model_directory; + private $model_directories; /** * Switch for logging. @@ -197,9 +197,9 @@ public function set_default_connection($name) */ public function set_model_directory($dir) { - $this->model_directory = $dir; + $this->set_model_directories(array($dir)); } - + /** * Returns the model directory. * @@ -208,10 +208,38 @@ public function set_model_directory($dir) */ public function get_model_directory() { - if ($this->model_directory && !file_exists($this->model_directory)) - throw new ConfigException('Invalid or non-existent directory: '.$this->model_directory); + $model_directories = $this->get_model_directories(); + return array_shift($model_directories); + } + + /** + * Sets the directories where models are located. + * + * @param array $dir Array with directory paths containing your models + * @return void + */ + public function set_model_directories(array $dirs) + { + $this->model_directories = $dirs; + } - return $this->model_directory; + /** + * Returns the array of model directories. + * + * @return array + * @throws ConfigException if one of the model directories was not found + */ + public function get_model_directories() + { + if ($this->model_directories) + { + foreach($this->model_directories as $model_directory) + { + if(!file_exists($model_directory)) + throw new ConfigException('Invalid or non-existent directory: '. $model_directory); + } + } + return $this->model_directories; } /** @@ -300,4 +328,4 @@ public function set_cache($url, $options=array()) { Cache::initialize($url,$options); } -} \ No newline at end of file +} From 9f150f9dd79659fef79eb3e9286eb7d173268a6e Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 20 Apr 2013 15:58:49 +0200 Subject: [PATCH 06/53] Added tests --- lib/Config.php | 35 ++++++++--------- test/ActiveRecordTest.php | 24 +++++++++++- test/ConfigTest.php | 43 +++++++++++++++++++++ test/backup-models/Backup.php | 6 +++ test/backup-models/NamespaceTest/Backup.php | 7 ++++ 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 test/backup-models/Backup.php create mode 100644 test/backup-models/NamespaceTest/Backup.php diff --git a/lib/Config.php b/lib/Config.php index 3d44b22cf..7fbcfcf09 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -56,7 +56,7 @@ class Config extends Singleton * @see activerecord_autoload * @var array */ - private $model_directories; + private $model_directories = array(); /** * Switch for logging. @@ -192,19 +192,18 @@ public function set_default_connection($name) /** * Sets the directory where models are located. * - * @param string $dir Directory path containing your models + * @param string $directory Directory path containing your models * @return void */ - public function set_model_directory($dir) + public function set_model_directory($directory) { - $this->set_model_directories(array($dir)); + $this->set_model_directories(array($directory)); } /** - * Returns the model directory. + * Returns the first model directory. * * @return string - * @throws ConfigException if specified directory was not found */ public function get_model_directory() { @@ -215,30 +214,30 @@ public function get_model_directory() /** * Sets the directories where models are located. * - * @param array $dir Array with directory paths containing your models + * @param array $directories Array with directory paths containing your models * @return void + * @throws ConfigException if one of the model directories was not found */ - public function set_model_directories(array $dirs) + public function set_model_directories($directories) { - $this->model_directories = $dirs; + if (!is_array($directories)) + throw new ConfigException("Directories must be an array"); + + foreach($directories as $directory) + { + if(!file_exists($directory) || !is_dir($directory)) + throw new ConfigException('Invalid or non-existent directory: '. $directory); + } + $this->model_directories = $directories; } /** * Returns the array of model directories. * * @return array - * @throws ConfigException if one of the model directories was not found */ public function get_model_directories() { - if ($this->model_directories) - { - foreach($this->model_directories as $model_directory) - { - if(!file_exists($model_directory)) - throw new ConfigException('Invalid or non-existent directory: '. $model_directory); - } - } return $this->model_directories; } diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 0bff0c0f2..dac8e6d17 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -167,17 +167,39 @@ public function test_reload_protected_attribute() public function test_active_record_model_home_not_set() { $home = ActiveRecord\Config::instance()->get_model_directory(); - ActiveRecord\Config::instance()->set_model_directory(__FILE__); + ActiveRecord\Config::instance()->set_model_directory(__DIR__); $this->assert_equals(false,class_exists('TestAutoload')); ActiveRecord\Config::instance()->set_model_directory($home); } + public function test_auto_load_with_model_in_secondary_model_directory(){ + $home = ActiveRecord\Config::instance()->get_model_directory(); + ActiveRecord\Config::instance()->set_model_directories(array( + realpath(__DIR__ . '/models'), + realpath(__DIR__ . '/backup-models'), + )); + $this->assert_true(class_exists('Backup')); + + ActiveRecord\Config::instance()->set_model_directory($home); + } + public function test_auto_load_with_namespaced_model() { $this->assert_true(class_exists('NamespaceTest\Book')); } + public function test_auto_load_with_namespaced_model_in_secondary_model_directory(){ + $home = ActiveRecord\Config::instance()->get_model_directory(); + ActiveRecord\Config::instance()->set_model_directories(array( + realpath(__DIR__ . '/models'), + realpath(__DIR__ . '/backup-models'), + )); + $this->assert_true(class_exists('NamespaceTest\Backup')); + + ActiveRecord\Config::instance()->set_model_directory($home); + } + public function test_namespace_gets_stripped_from_table_name() { $model = new NamespaceTest\Book(); diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 2dbafbd90..73451be14 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -71,6 +71,49 @@ public function test_set_connections_with_default() $this->assert_equals('test',$this->config->get_default_connection()); } + /** + * @expectedException ActiveRecord\ConfigException + */ + public function test_set_model_directories_must_be_array() + { + $this->config->set_model_directories(null); + } + + /** + * @expectedException ActiveRecord\ConfigException + */ + public function test_set_model_directories_directories_must_exist(){ + $home = ActiveRecord\Config::instance()->get_model_directory(); + + $this->config->set_model_directories(array( + realpath(__DIR__ . '/models'), + '/some-non-existing-directory' + )); + + ActiveRecord\Config::instance()->set_model_directory($home); + } + + public function test_set_model_directory_stores_as_array(){ + $home = ActiveRecord\Config::instance()->get_model_directory(); + + $this->config->set_model_directory(realpath(__DIR__ . '/models')); + $this->assertInternalType('array', $this->config->get_model_directories()); + + ActiveRecord\Config::instance()->set_model_directory($home); + } + + public function test_get_model_directory_returns_first_model_directory(){ + $home = ActiveRecord\Config::instance()->get_model_directory(); + + $this->config->set_model_directories(array( + realpath(__DIR__ . '/models'), + realpath(__DIR__ . '/backup-models'), + )); + $this->assert_equals(realpath(__DIR__ . '/models'), $this->config->get_model_directory()); + + ActiveRecord\Config::instance()->set_model_directory($home); + } + public function test_initialize_closure() { $test = $this; diff --git a/test/backup-models/Backup.php b/test/backup-models/Backup.php new file mode 100644 index 000000000..e2a4bab40 --- /dev/null +++ b/test/backup-models/Backup.php @@ -0,0 +1,6 @@ +<?php + +class Backup extends \ActiveRecord\Model +{ +} +?> \ No newline at end of file diff --git a/test/backup-models/NamespaceTest/Backup.php b/test/backup-models/NamespaceTest/Backup.php new file mode 100644 index 000000000..48f77a37e --- /dev/null +++ b/test/backup-models/NamespaceTest/Backup.php @@ -0,0 +1,7 @@ +<?php +namespace NamespaceTest; + +class Backup extends \ActiveRecord\Model +{ +} +?> \ No newline at end of file From c8bb4b9ad588843f723b6f0d4d62c82d21d33627 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Thu, 8 Nov 2012 13:28:57 -0500 Subject: [PATCH 07/53] Adding custom support for boolean casting based on connection The default PHP boolean-to-string casting does not work for some database connection types (e.g. PostgreSQL). Added a BOOLEAN type to the Column class, and then the cast() method delegates to the a new boolean_to_string() method in the Connection class. By default the boolean_to_string() method returns the standard PHP string cast for the boolean, retaining the original behavior. This method can be overridden in adapter classes to provides custom casting of boolean values. Added an overridden boolean_to_string() method into the adapters/PgsqlAdapter.php class which casts the boolean values false and true to the strings "0" and "1" respectively. These string values are then recognized by the Postgres database as boolean values, as opposed to the values "" and "1" generated by the standard PHP cast. --- lib/Column.php | 4 ++++ lib/Connection.php | 17 ++++++++++++++++- lib/adapters/PgsqlAdapter.php | 4 ++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/Column.php b/lib/Column.php index de431b626..0bedc8b5a 100644 --- a/lib/Column.php +++ b/lib/Column.php @@ -18,6 +18,7 @@ class Column const DATETIME = 4; const DATE = 5; const TIME = 6; + const BOOLEAN = 7; /** * Map a type to an column type. @@ -30,6 +31,8 @@ class Column 'date' => self::DATE, 'time' => self::TIME, + 'boolean' => self::BOOLEAN, + 'tinyint' => self::INTEGER, 'smallint' => self::INTEGER, 'mediumint' => self::INTEGER, @@ -160,6 +163,7 @@ public function cast($value, $connection) case self::STRING: return (string)$value; case self::INTEGER: return static::castIntegerSafely($value); case self::DECIMAL: return (double)$value; + case self::BOOLEAN: return $connection->boolean_to_string($value); case self::DATETIME: case self::DATE: if (!$value) diff --git a/lib/Connection.php b/lib/Connection.php index c32311bcb..b1cb3e8ce 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -482,6 +482,21 @@ public function string_to_datetime($string) return new DateTime($date->format(static::$datetime_format)); } + /** + * Converts a boolean value to a string representation. + * + * The converted string representation should be in a format acceptable by the + * underlying database connection. + * + * @param bool $boolean + * @access public + * @return string + */ + public function boolean_to_string($boolean) + { + return (string)$boolean; + } + /** * Adds a limit clause to the SQL query. * @@ -530,4 +545,4 @@ public function accepts_limit_and_order_for_update_and_delete() return false; } -} \ No newline at end of file +} diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 399256477..c854aaed5 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -135,4 +135,8 @@ public function native_database_types() ); } + public function boolean_to_string($boolean) { + return $boolean ? "1" : "0"; + } + } From 1bd40ea034e8d08d10d0d83033be570c63b9a4e8 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Thu, 8 Nov 2012 13:51:16 -0500 Subject: [PATCH 08/53] Updated ColumnTest for new BOOLEAN type cast --- test/ColumnTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/ColumnTest.php b/test/ColumnTest.php index 89b53d878..a058e7a7b 100644 --- a/test/ColumnTest.php +++ b/test/ColumnTest.php @@ -95,6 +95,9 @@ public function test_cast() // 64 bit elseif (PHP_INT_SIZE === 8) $this->assert_cast(Column::INTEGER,'9223372036854775808',(((float) PHP_INT_MAX) + 1)); + + $this->assert_cast(Column::BOOLEAN,'1',true); + $this->assert_cast(Column::BOOLEAN,'',false); } public function test_cast_leave_null_alone() From 9800a139cca383f2c3b4eec555cb965937d81ee4 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Tue, 30 Apr 2013 13:51:21 -0400 Subject: [PATCH 09/53] fixed indentation formatting to match existing code --- lib/Connection.php | 28 ++++++++++++++-------------- lib/adapters/PgsqlAdapter.php | 8 ++++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/Connection.php b/lib/Connection.php index b1cb3e8ce..fec038836 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -482,20 +482,20 @@ public function string_to_datetime($string) return new DateTime($date->format(static::$datetime_format)); } - /** - * Converts a boolean value to a string representation. - * - * The converted string representation should be in a format acceptable by the - * underlying database connection. - * - * @param bool $boolean - * @access public - * @return string - */ - public function boolean_to_string($boolean) - { - return (string)$boolean; - } + /** + * Converts a boolean value to a string representation. + * + * The converted string representation should be in a format acceptable by the + * underlying database connection. + * + * @param bool $boolean + * @access public + * @return string + */ + public function boolean_to_string($boolean) + { + return (string)$boolean; + } /** * Adds a limit clause to the SQL query. diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index c854aaed5..bec6cef9a 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -135,8 +135,8 @@ public function native_database_types() ); } - public function boolean_to_string($boolean) { - return $boolean ? "1" : "0"; - } - + public function boolean_to_string($boolean) + { + return $boolean ? "1" : "0"; + } } From b9605752b78755a1f90557ef0771d7f32425f169 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Tue, 30 Apr 2013 17:07:14 -0400 Subject: [PATCH 10/53] Updated tests for boolean value casting Updated the ColumnTest::assert_cast() to test that boolean values are being casted using the Connection boolean_to_string() method. Added a PostgreSQL specific test to ensure that boolean values are being casted as the correct string value. --- test/ColumnTest.php | 7 ++++--- test/PgsqlAdapterTest.php | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/test/ColumnTest.php b/test/ColumnTest.php index a058e7a7b..71db768b0 100644 --- a/test/ColumnTest.php +++ b/test/ColumnTest.php @@ -96,8 +96,8 @@ public function test_cast() elseif (PHP_INT_SIZE === 8) $this->assert_cast(Column::INTEGER,'9223372036854775808',(((float) PHP_INT_MAX) + 1)); - $this->assert_cast(Column::BOOLEAN,'1',true); - $this->assert_cast(Column::BOOLEAN,'',false); + $this->assert_cast(Column::BOOLEAN,$this->conn->boolean_to_string(true),true); + $this->assert_cast(Column::BOOLEAN,$this->conn->boolean_to_string(false),false); } public function test_cast_leave_null_alone() @@ -107,7 +107,8 @@ public function test_cast_leave_null_alone() Column::INTEGER, Column::DECIMAL, Column::DATETIME, - Column::DATE); + Column::DATE, + Column::BOOLEAN); foreach ($types as $type) { $this->assert_cast($type,null,null); diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php index 71b672557..c9a893037 100644 --- a/test/PgsqlAdapterTest.php +++ b/test/PgsqlAdapterTest.php @@ -40,4 +40,10 @@ public function test_gh96_columns_not_duplicated_by_index() { $this->assert_equals(3,$this->conn->query_column_info("user_newsletters")->rowCount()); } + + public function test_boolean_to_string() + { + $this->assert_equals("0", $this->conn->boolean_to_string(false)); + $this->assert_equals("1", $this->conn->boolean_to_string(true)); + } } From 71fe0d4a38b0bfce9ddb6baa5a869411db0abb22 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Tue, 30 Apr 2013 17:56:20 -0400 Subject: [PATCH 11/53] Updated PgsqlAdapter boolean_to_string() for Postgres boolean values Modified the boolean_to_string() method in the PgsqlAdapter class to properly convert values that PHP considers a boolean, as well as what PostgreSQL considers a boolean value. E.g., PostgreSQL considers the values 'f', 'false', 'n', 'no', and 'off' as valid values for `false`, but PHP does not. The original implementation of this method would return `true` for one of these values which is incorrect for a PostgreSQL database. Also modified the base Connection class boolean_to_string() method to first cast its `$value` parameter as a boolean before casting it to a string. This will account for PHP's loose type-casting and make sure the method returns a consistent casted value. --- lib/Connection.php | 5 +++-- lib/adapters/PgsqlAdapter.php | 27 +++++++++++++++++++++++++-- test/PgsqlAdapterTest.php | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/Connection.php b/lib/Connection.php index fec038836..0010cb490 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -488,12 +488,13 @@ public function string_to_datetime($string) * The converted string representation should be in a format acceptable by the * underlying database connection. * - * @param bool $boolean + * @param mixed $value * @access public * @return string */ - public function boolean_to_string($boolean) + public function boolean_to_string($value) { + $boolean = (boolean)$value; return (string)$boolean; } diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index bec6cef9a..34d8fb898 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -135,8 +135,31 @@ public function native_database_types() ); } - public function boolean_to_string($boolean) + public function boolean_to_string($value) { - return $boolean ? "1" : "0"; + if + ( // possible "false" values + // from php 'type-juggling': + // http://us1.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting + // with additional strings recognized by postgres added + $value === false ||#php + $value === 0 ||#php + $value === 0.0 ||#php + $value === "" ||#php + $value === null ||#php + $value === "0" ||#php/postgres + strtolower($value) === 'f' ||#postgres + strtolower($value) === 'false' ||#postgres + strtolower($value) === 'n' ||#postgres + strtolower($value) === 'no' ||#postgres + strtolower($value) === 'off' #postgres + ) + { + return "0"; + } + else + { // treat anything else as true + return "1"; + } } } diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php index c9a893037..b4fcb0f41 100644 --- a/test/PgsqlAdapterTest.php +++ b/test/PgsqlAdapterTest.php @@ -43,7 +43,21 @@ public function test_gh96_columns_not_duplicated_by_index() public function test_boolean_to_string() { + // false values $this->assert_equals("0", $this->conn->boolean_to_string(false)); + $this->assert_equals("0", $this->conn->boolean_to_string('0')); + $this->assert_equals("0", $this->conn->boolean_to_string('f')); + $this->assert_equals("0", $this->conn->boolean_to_string('false')); + $this->assert_equals("0", $this->conn->boolean_to_string('n')); + $this->assert_equals("0", $this->conn->boolean_to_string('no')); + $this->assert_equals("0", $this->conn->boolean_to_string('off')); + // true values $this->assert_equals("1", $this->conn->boolean_to_string(true)); + $this->assert_equals("1", $this->conn->boolean_to_string('1')); + $this->assert_equals("1", $this->conn->boolean_to_string('t')); + $this->assert_equals("1", $this->conn->boolean_to_string('true')); + $this->assert_equals("1", $this->conn->boolean_to_string('y')); + $this->assert_equals("1", $this->conn->boolean_to_string('yes')); + $this->assert_equals("1", $this->conn->boolean_to_string('on')); } } From 09e6207182c9344463c4fd8ddce4abab943f20f0 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Wed, 1 May 2013 16:30:10 -0400 Subject: [PATCH 12/53] Shortened the false condition in PgsqlAdapter::boolean_to_string() While the previous version was explicit and descriptive, it wasn't that elegant. This is much nicer. --- lib/adapters/PgsqlAdapter.php | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 34d8fb898..97f700d23 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -137,29 +137,9 @@ public function native_database_types() public function boolean_to_string($value) { - if - ( // possible "false" values - // from php 'type-juggling': - // http://us1.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting - // with additional strings recognized by postgres added - $value === false ||#php - $value === 0 ||#php - $value === 0.0 ||#php - $value === "" ||#php - $value === null ||#php - $value === "0" ||#php/postgres - strtolower($value) === 'f' ||#postgres - strtolower($value) === 'false' ||#postgres - strtolower($value) === 'n' ||#postgres - strtolower($value) === 'no' ||#postgres - strtolower($value) === 'off' #postgres - ) - { + if (!$value || in_array(strtolower($value), array('f','false','n','no','off'))) return "0"; - } else - { // treat anything else as true return "1"; - } } } From f892c2cafb32c862aa6ea8372028e830e93e99fd Mon Sep 17 00:00:00 2001 From: Taddeus Kroes <taddeuskroes@hotmail.com> Date: Sat, 21 Jul 2012 21:49:01 +0200 Subject: [PATCH 13/53] Added some newlines for readability. --- lib/Validations.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Validations.php b/lib/Validations.php index 4a812ab67..2ddeff410 100644 --- a/lib/Validations.php +++ b/lib/Validations.php @@ -414,7 +414,11 @@ public function validates_size_of($attrs) */ public function validates_format_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null)); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array( + 'message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], + 'on' => 'save', + 'with' => null + )); foreach ($attrs as $attr) { From 5cbb53667ad791ccaeb5ea447a60992bdbfb3a78 Mon Sep 17 00:00:00 2001 From: Taddeus Kroes <taddeuskroes@hotmail.com> Date: Sun, 22 Jul 2012 00:58:26 +0200 Subject: [PATCH 14/53] Implemented 'on' option for validations. --- lib/Model.php | 6 ++-- lib/Validations.php | 73 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 36e0a4918..bd3c4c21f 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -801,7 +801,7 @@ private function insert($validate=true) { $this->verify_not_readonly('insert'); - if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false))) + if (($validate && !$this->_validate()) || !$this->invoke_callback('before_create',false)) return false; $table = static::table(); @@ -1082,7 +1082,9 @@ private function _validate() require_once 'Validations.php'; $validator = new Validations($this); - $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update'); + $validation_mode = $this->is_new_record() ? 'create' : 'update'; + $validation_on = 'validation_on_' . $validation_mode; + $validator->set_validation_mode($validation_mode); foreach (array('before_validation', "before_$validation_on") as $callback) { diff --git a/lib/Validations.php b/lib/Validations.php index 2ddeff410..322dea247 100644 --- a/lib/Validations.php +++ b/lib/Validations.php @@ -45,6 +45,7 @@ class Validations private $options = array(); private $validators = array(); private $record; + private $validation_mode = null; private static $VALIDATION_FUNCTIONS = array( 'validates_presence_of', @@ -179,7 +180,9 @@ public function validates_presence_of($attrs) foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); - $this->record->add_on_blank($options[0], $options['message']); + + if( $this->validation_mode_matches($options['on']) ) + $this->record->add_on_blank($options[0], $options['message']); } } @@ -249,11 +252,17 @@ public function validates_exclusion_of($attrs) */ public function validates_inclusion_or_exclusion_of($type, $attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save')); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array( + 'message' => Errors::$DEFAULT_ERROR_MESSAGES[$type] + )); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); + + if (!$this->validation_mode_matches($options['on'])) + continue; + $attribute = $options[0]; $var = $this->model->$attribute; @@ -313,6 +322,10 @@ public function validates_numericality_of($attrs) foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); + + if (!$this->validation_mode_matches($options['on'])) + continue; + $attribute = $options[0]; $var = $this->model->$attribute; @@ -416,13 +429,16 @@ public function validates_format_of($attrs) { $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array( 'message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], - 'on' => 'save', 'with' => null )); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); + + if (!$this->validation_mode_matches($options['on'])) + continue; + $attribute = $options[0]; $var = $this->model->$attribute; @@ -474,6 +490,10 @@ public function validates_length_of($attrs) foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); + + if (!$this->validation_mode_matches($options['on'])) + continue; + $range_options = array_intersect(array_keys(self::$ALL_RANGE_OPTIONS), array_keys($attr)); sort($range_options); @@ -575,6 +595,10 @@ public function validates_uniqueness_of($attrs) foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); + + if (!$this->validation_mode_matches($options['on'])) + continue; + $pk = $this->model->get_primary_key(); $pk_value = $this->model->$pk[0]; @@ -614,6 +638,49 @@ public function validates_uniqueness_of($attrs) $this->record->add($add_record, $options['message']); } } + + /** + * Set the validation mode. + * + * The validation mode is used to check if a validation rule should be executed. + * This is decided based on the "on" option for validation rules. + * + * The "on" option can have the following values: + * + * <ul> + * <li><b>save:</b> Invoked before saving changes to either a new or an existing model. + * <li><b>create:</b> Invoked only before inserting a new model. + * <li><b>update:</b> Invoked only before updating an existing model. + * <li><b>delete:</b> Invoked before deletion of a model (unimplemented). + * </ul> + * + * @param string $mode The mode to set, one of "create", "update" or "delete". + * @todo implement "delete" value for option "on" + */ + public function set_validation_mode($mode) + { + $this->validation_mode = $mode; + } + + /** + * Check if the current validation mode matches the specified mode. + * + * See {@link set_validation_mode} for more information on validation modes. + * + * @param string $mode The mode to be matched, one of "create", "update" or "delete". + * @return boolean + */ + private function validation_mode_matches($mode) + { + // The validation mode is not always set, run the validation anyway in that case + if( $this->validation_mode == null ) + return true; + + if( $mode == 'save' ) + return in_array($this->validation_mode, array('create', 'update')); + + return $mode == $this->validation_mode; + } private function is_null_with_option($var, &$options) { From b2e23371d0c1a2f00c1a92f81ad86abe306eb614 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 25 Sep 2012 11:47:27 +0200 Subject: [PATCH 15/53] Added tests for on create/update/save --- test/ValidatesOnSaveCreateUpdateTest.php | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/ValidatesOnSaveCreateUpdateTest.php diff --git a/test/ValidatesOnSaveCreateUpdateTest.php b/test/ValidatesOnSaveCreateUpdateTest.php new file mode 100644 index 000000000..70f7a9c25 --- /dev/null +++ b/test/ValidatesOnSaveCreateUpdateTest.php @@ -0,0 +1,73 @@ +<?php + +class BookOn extends ActiveRecord\Model +{ + static $table_name = 'books'; + + static $validates_presence_of = array(); +} + +class ValidatesOnSaveCreateUpdateTest extends DatabaseTest{ + + public function test_validations_only_run_on_create() + { + BookOn::$validates_presence_of[0] = array('name', 'on' => 'create'); + $book = new BookOn(); + $this->assert_false($book->is_valid()); + + $book->secondary_author_id = 1; + $this->assert_false( $book->save() ); + + $book->name = 'Baz'; + $this->assert_true( $book->save() ); + + $book->name = null; + $this->assert_true( $book->save() ); + } + + public function test_validations_only_run_on_update() + { + BookOn::$validates_presence_of[0] = array('name', 'on' => 'update'); + $book = new BookOn(); + $this->assert_true($book->save()); + + $book->name = null; + $this->assert_false( $book->save() ); + + $book->name = 'Baz'; + $this->assert_true( $book->save() ); + } + + public function test_validations_only_run_on_save() + { + BookOn::$validates_presence_of[0] = array('name', 'on' => 'save'); + $book = new BookOn(); + $this->assert_false($book->is_valid()); + + $book->secondary_author_id = 1; + $this->assert_false( $book->save() ); + + $book->name = 'Baz'; + $this->assert_true( $book->save() ); + + $book->name = null; + $this->assert_false( $book->save() ); + } + + public function test_validations_run_always_without_on() + { + BookOn::$validates_presence_of[0] = array('name'); + $book = new BookOn(); + $this->assert_false($book->is_valid()); + + $book->secondary_author_id = 1; + $this->assert_false( $book->save() ); + + $book->name = 'Baz'; + $this->assert_true( $book->save() ); + + $book->name = null; + $this->assert_false( $book->save() ); + } + +} \ No newline at end of file From c81587551752a4e7a833baa25a529b8f00f84a03 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Tue, 9 Jul 2013 21:16:07 -0400 Subject: [PATCH 16/53] Only flag attributes as dirty if the value changes Attributes should only be flagged as dirty if the assigned attribute has changed. Assigning the same value to an attribute as the current value should not flag the value as dirty. --- lib/Model.php | 5 +++-- test/ActiveRecordTest.php | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index bd3c4c21f..a2d1e69ec 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -468,8 +468,9 @@ public function assign_attribute($name, $value) if ($value instanceof DateTime) $value->attribute_of($this,$name); - $this->attributes[$name] = $value; - $this->flag_dirty($name); + $flag_dirty = isset($this->attributes[$name]) && ($this->attributes[$name] === $value) ? false : true; + $this->attributes[$name] = $value; + if ($flag_dirty) $this->flag_dirty($name); return $value; } diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index dac8e6d17..57b3ba97a 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -548,6 +548,12 @@ public function test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_di $this->assert_true($event->attribute_is_dirty('title')); } + public function test_attribute_is_not_flagged_dirty_if_assigning_same_value() { + $event = Event::find(1); + $event->type = "Music"; + $this->assert_false($event->attribute_is_dirty('type')); + } + public function test_assigning_php_datetime_gets_converted_to_ar_datetime() { $author = new Author(); From 4f209d9ca3cc94a3ff3efd3fba6e59249550c63b Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Wed, 10 Jul 2013 16:50:14 -0400 Subject: [PATCH 17/53] Added tracking of changed attributes To complement the flagging of dirty attributes, added functionality similar to Rails which will track the values of changed attributes. Added the following methods: - $model->changed_attributes() Returns a hash of all attributes that have changed and their original values. #=> $changed_attributes[$attribute] = $original_value; - $model->changes() Returns a hash of attributes that have changed with the original and current values. #=> $changes[$attribute] = array($original_value, $current_value) - $model->previous_changes() Returns the changes to a model's attributes before it was saved. - $model->attribute_was($attribute) Returns the original value of an attribute before it was changed Also made a change to the read_attribute() method. It seems that there is a quirk in PHP when the return value of the __get() method is passed by reference and you are trying to set a value using one of the 'combined assignment' operators (e.g. .=). If the return value of the __get() method is a reference to an array element then a weird thing happens - the value passed to the __set() method is the same as the current value. That's a super confusing explanation, so an example will clear it up: class Person { private $attributes = array('name' => "John"); public function &__get($name) { return $this->attributes[$name]; } public function __set($name, $value) { echo "original value: {$this->attributes[$name]}\n"; $this->attributes[$name] = $value; echo "new value: {$this->attributes[$name]}\n"; } } $person = new Person(); $person->name .= " Doe"; A simple change to the __get() method: public function &__get($name) { $value = $this->attributes[$name]; return $value; } seems to clear up the issue. So I applied that same logic to the Model::read_attribute() method, assigning the $this->attributes[$name] value to a variable, and then returning that. I don't think that should mess anything up, all unit tests still pass. --- lib/Model.php | 89 ++++++++++++++++++++++++++++++++++++--- lib/Table.php | 2 +- test/ActiveRecordTest.php | 81 ++++++++++++++++++++++++++++++++--- 3 files changed, 160 insertions(+), 12 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index a2d1e69ec..804d621ca 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -87,6 +87,23 @@ class Model */ private $attributes = array(); + /** + * Contains changes made to model attributes as + * $attribute_name => array($original_value, $current_value) + * + * @var array + * @access private + */ + private $changed_attributes = array(); + + /** + * Contains changes made to model attributes before it was saved + * + * @var array + * @access private + */ + private $previously_changed = array(); + /** * Flag whether or not this model's attributes have been modified since it will either be null or an array of column_names that have been modified * @@ -468,9 +485,21 @@ public function assign_attribute($name, $value) if ($value instanceof DateTime) $value->attribute_of($this,$name); - $flag_dirty = isset($this->attributes[$name]) && ($this->attributes[$name] === $value) ? false : true; - $this->attributes[$name] = $value; - if ($flag_dirty) $this->flag_dirty($name); + // only update the attribute if it isn't set or has changed + if (!isset($this->attributes[$name]) || ($this->attributes[$name] !== $value)) { + // track changes to the attribute + if (isset($this->attributes[$name]) && !isset($this->changed_attributes[$name])) { + // convert DateTime objects to a string + if ($this->attributes[$name] instanceof DateTime) + $this->changed_attributes[$name] = strval($this->attributes[$name]); + else + $this->changed_attributes[$name] = $this->attributes[$name]; + } + + // set the attribute and flag as dirty + $this->attributes[$name] = $value; + $this->flag_dirty($name); + } return $value; } @@ -490,8 +519,10 @@ public function &read_attribute($name) $name = static::$alias_attribute[$name]; // check for attribute - if (array_key_exists($name,$this->attributes)) - return $this->attributes[$name]; + if (array_key_exists($name,$this->attributes)) { + $value = $this->attributes[$name]; + return $value; + } // check relationships if no attribute if (array_key_exists($name,$this->__relationships)) @@ -581,6 +612,50 @@ public function attributes() return $this->attributes; } + /** + * Returns a copy of the model's changed attributes hash with the + * attribute name and the original value. + * + * @return array A copy of the model's changed attribute data + */ + public function changed_attributes() + { + return $this->changed_attributes; + } + + /** + * Returns a copy of the model's changed attributes as a hash + * in the form $attribute => array($original_value, $current_value) + * + * @return array A copy of the model's attribute changes + */ + public function changes() + { + return array_merge_recursive( + $this->changed_attributes, + array_intersect_key($this->attributes, $this->changed_attributes) + ); + } + + /** + * Returns a copy of the model's changed attributes before it was saved + * + * @return array A copy of the model's changed attribute before a save + */ + public function previous_changes() + { + return $this->previously_changed; + } + + /** + * Returns the value of an attribute before it was changed + * + * @return string The original value of an attribute + */ + public function attribute_was($name) { + return isset($this->changed_attributes[$name]) ? $this->changed_attributes[$name] : null; + } + /** * Retrieve the primary key name. * @@ -1305,9 +1380,11 @@ public function __clone() * * @see dirty_attributes */ - public function reset_dirty() + public function reset_dirty($model_was_saved=false) { $this->__dirty = null; + $this->previously_changed = $model_was_saved ? $this->changes() : array(); + $this->changed_attributes = array(); } /** diff --git a/lib/Table.php b/lib/Table.php index 5f08e1895..dc5582d2f 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -98,7 +98,7 @@ public function __construct($class_name) $this->callback = new CallBack($class_name); $this->callback->register('before_save', function(Model $model) { $model->set_timestamps(); }, array('prepend' => true)); - $this->callback->register('after_save', function(Model $model) { $model->reset_dirty(); }, array('prepend' => true)); + $this->callback->register('after_save', function(Model $model) { $model->reset_dirty(true); }, array('prepend' => true)); } public function reestablish_connection($close=true) diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 57b3ba97a..31713eb23 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -548,11 +548,82 @@ public function test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_di $this->assert_true($event->attribute_is_dirty('title')); } - public function test_attribute_is_not_flagged_dirty_if_assigning_same_value() { - $event = Event::find(1); - $event->type = "Music"; - $this->assert_false($event->attribute_is_dirty('type')); - } + public function test_attribute_is_not_flagged_dirty_if_assigning_same_value() { + $event = Event::find(1); + $event->type = "Music"; + $this->assert_false($event->attribute_is_dirty('type')); + } + + public function test_changed_attributes() { + $event = Event::find(1); + + $event->type = "Groovy Music"; + $changed_attributes = $event->changed_attributes(); + $this->assert_true(is_array($changed_attributes)); + $this->assert_equals(1, count($changed_attributes)); + $this->assert_true(isset($changed_attributes['type'])); + $this->assert_equals("Music", $changed_attributes['type']); + + $event->type = "Funky Music"; + $changed_attributes = $event->changed_attributes(); + $this->assert_true(is_array($changed_attributes)); + $this->assert_equals(1, count($changed_attributes)); + $this->assert_true(isset($changed_attributes['type'])); + $this->assert_equals("Music", $changed_attributes['type']); + } + + public function test_changes() { + $event = Event::find(1); + + $event->type = "Groovy Music"; + $changes = $event->changes(); + $this->assert_true(is_array($changes)); + $this->assert_equals(1, count($changes)); + $this->assert_true(isset($changes['type'])); + $this->assert_true(is_array($changes['type'])); + $this->assert_equals("Music", $changes['type'][0]); + $this->assert_equals("Groovy Music", $changes['type'][1]); + + $event->type = "Funky Music"; + $changes = $event->changes(); + $this->assert_true(is_array($changes)); + $this->assert_equals(1, count($changes)); + $this->assert_true(isset($changes['type'])); + $this->assert_true(is_array($changes['type'])); + $this->assert_equals("Music", $changes['type'][0]); + $this->assert_equals("Funky Music", $changes['type'][1]); + } + + public function test_attribute_was() { + $event = Event::find(1); + $event->type = "Funky Music"; + $this->assert_equals("Music", $event->attribute_was("type")); + $event->type = "Groovy Music"; + $this->assert_equals("Music", $event->attribute_was("type")); + } + + public function test_previous_changes() { + $event = Event::find(1); + $event->type = "Groovy Music"; + $previous_changes = $event->previous_changes(); + $this->assert_true(empty($previous_changes)); + $event->save(); + $previous_changes = $event->previous_changes(); + $this->assert_true(is_array($previous_changes)); + $this->assert_equals(1, count($previous_changes)); + $this->assert_true(isset($previous_changes['type'])); + $this->assert_true(is_array($previous_changes['type'])); + $this->assert_equals("Music", $previous_changes['type'][0]); + $this->assert_equals("Groovy Music", $previous_changes['type'][1]); + } + + public function test_save_resets_changed_attributes() { + $event = Event::find(1); + $event->type = "Groovy Music"; + $event->save(); + $changed_attributes = $event->changed_attributes(); + $this->assert_true(empty($changed_attributes)); + } public function test_assigning_php_datetime_gets_converted_to_ar_datetime() { From 6770e71d07c8ec64e13659936469b31b9546786c Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Wed, 10 Jul 2013 21:08:22 -0400 Subject: [PATCH 18/53] Fix handling of changed DateTime attributes Took out the conversion of DateTime objects to strings, that is just unnecessary. DateTime objects should be kept as they are. Also added test to make sure that changing a DateTime attribute tracks a change. --- lib/Model.php | 19 ++++++++----------- test/ActiveRecordTest.php | 10 ++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 804d621ca..12f9ff2ea 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -488,13 +488,8 @@ public function assign_attribute($name, $value) // only update the attribute if it isn't set or has changed if (!isset($this->attributes[$name]) || ($this->attributes[$name] !== $value)) { // track changes to the attribute - if (isset($this->attributes[$name]) && !isset($this->changed_attributes[$name])) { - // convert DateTime objects to a string - if ($this->attributes[$name] instanceof DateTime) - $this->changed_attributes[$name] = strval($this->attributes[$name]); - else - $this->changed_attributes[$name] = $this->attributes[$name]; - } + if (isset($this->attributes[$name]) && !isset($this->changed_attributes[$name])) + $this->changed_attributes[$name] = $this->attributes[$name]; // set the attribute and flag as dirty $this->attributes[$name] = $value; @@ -631,10 +626,12 @@ public function changed_attributes() */ public function changes() { - return array_merge_recursive( - $this->changed_attributes, - array_intersect_key($this->attributes, $this->changed_attributes) - ); + $changes = array(); + $attributes = array_intersect_key($this->attributes, $this->changed_attributes); + foreach($attributes as $name => $value) { + $changes[$name] = array($this->changed_attributes[$name],$value); + } + return $changes; } /** diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 31713eb23..072291b89 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -625,6 +625,16 @@ public function test_save_resets_changed_attributes() { $this->assert_true(empty($changed_attributes)); } + public function test_changing_datetime_attribute_tracks_change() { + $author = new Author(); + $author->created_at = $original = new \DateTime("yesterday"); + $author->created_at = $now = new \DateTime(); + $changes = $author->changes(); + $this->assert_true(isset($changes['created_at'])); + $this->assert_datetime_equals($original, $changes['created_at'][0]); + $this->assert_datetime_equals($now, $changes['created_at'][1]); + } + public function test_assigning_php_datetime_gets_converted_to_ar_datetime() { $author = new Author(); From 53b9951bb8c28f91baee50efafef5e17286f5ce0 Mon Sep 17 00:00:00 2001 From: Leighton Shank <leighton@getloaded.com> Date: Fri, 19 Jul 2013 14:19:40 -0400 Subject: [PATCH 19/53] Fix so that empty attributes still track a change --- lib/Model.php | 2 +- test/ActiveRecordTest.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Model.php b/lib/Model.php index 12f9ff2ea..0c20ac9ab 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -488,7 +488,7 @@ public function assign_attribute($name, $value) // only update the attribute if it isn't set or has changed if (!isset($this->attributes[$name]) || ($this->attributes[$name] !== $value)) { // track changes to the attribute - if (isset($this->attributes[$name]) && !isset($this->changed_attributes[$name])) + if (array_key_exists($name, $this->attributes) && !isset($this->changed_attributes[$name])) $this->changed_attributes[$name] = $this->attributes[$name]; // set the attribute and flag as dirty diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 072291b89..617fddf4c 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -635,6 +635,15 @@ public function test_changing_datetime_attribute_tracks_change() { $this->assert_datetime_equals($now, $changes['created_at'][1]); } + public function test_changing_empty_attribute_value_tracks_change() { + $event = new Event(); + $event->description = "The most fun"; + $changes = $event->changes(); + $this->assert_true(array_key_exists("description", $changes)); + $this->assert_equals("", $changes['description'][0]); + $this->assert_equals("The most fun", $changes['description'][1]); + } + public function test_assigning_php_datetime_gets_converted_to_ar_datetime() { $author = new Author(); From b958b058a247f9cc84889bdd3f34c4a8315a5bf6 Mon Sep 17 00:00:00 2001 From: Justin Painter <justin@getloaded.com> Date: Thu, 13 Jun 2013 10:13:45 -0400 Subject: [PATCH 20/53] Fix PgsqlAdapter native types, default values. * The native types array were missing 'real','bigint','smallint', 'double precision', 'decimal' and 'numeric'. See: http://www.postgresql.org/docs/9.1/static/datatype-numeric.html * Both 0 and the empty string "" are valid defaults but were not being recognized. Changed to check explicitly for NULL defaults. --- lib/adapters/PgsqlAdapter.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 97f700d23..17e7eb113 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -101,7 +101,7 @@ public function create_column(&$column) $c->map_raw_type(); - if ($column['default']) + if (!is_null($column['default'])) { preg_match("/^nextval\('(.*)'\)$/",$column['default'],$matches); @@ -131,7 +131,13 @@ public function native_database_types() 'time' => array('name' => 'time'), 'date' => array('name' => 'date'), 'binary' => array('name' => 'binary'), - 'boolean' => array('name' => 'boolean') + 'boolean' => array('name' => 'boolean'), + 'bigint' => array('name' => 'integer'), + 'smallint' => array('name' => 'integer'), + 'real' => array('name' => 'float'), + 'double precision' => array('name' => 'float'), + 'numeric' => array('name' => 'float'), + 'decimal' => array('name' => 'float') ); } From 198fc20b19b6073e4a5b7fae994e3e2b11364372 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 20 Jul 2014 13:15:29 +0200 Subject: [PATCH 21/53] fix indentation --- lib/Serialization.php | 58 +++++++++---------- test/SerializationTest.php | 116 ++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/lib/Serialization.php b/lib/Serialization.php index b50a4d33b..6b3b456e8 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -344,32 +344,32 @@ private function write($data, $tag=null) */ class CsvSerializer extends Serialization { - public static $delimiter = ','; - public static $enclosure = '"'; - - public function to_s() - { - if (@$this->options['only_header'] == true) return $this->header(); - return $this->row(); - } - - private function header() - { - return $this->to_csv(array_keys($this->to_a())); - } - - private function row() - { - return $this->to_csv($this->to_a()); - } - - private function to_csv($arr) - { - $outstream = fopen('php://temp', 'w'); - fputcsv($outstream, $arr, self::$delimiter, self::$enclosure); - rewind($outstream); - $buffer = trim(stream_get_contents($outstream)); - fclose($outstream); - return $buffer; - } -} \ No newline at end of file + public static $delimiter = ','; + public static $enclosure = '"'; + + public function to_s() + { + if (@$this->options['only_header'] == true) return $this->header(); + return $this->row(); + } + + private function header() + { + return $this->to_csv(array_keys($this->to_a())); + } + + private function row() + { + return $this->to_csv($this->to_a()); + } + + private function to_csv($arr) + { + $outstream = fopen('php://temp', 'w'); + fputcsv($outstream, $arr, self::$delimiter, self::$enclosure); + rewind($outstream); + $buffer = trim(stream_get_contents($outstream)); + fclose($outstream); + return $buffer; + } +} diff --git a/test/SerializationTest.php b/test/SerializationTest.php index fe0364287..d82e4277c 100644 --- a/test/SerializationTest.php +++ b/test/SerializationTest.php @@ -123,30 +123,30 @@ public function test_to_xml() $this->assert_equals($book->attributes(),get_object_vars(new SimpleXMLElement($book->to_xml()))); } - public function test_to_array() - { - $book = Book::find(1); + public function test_to_array() + { + $book = Book::find(1); $array = $book->to_array(); $this->assert_equals($book->attributes(), $array); - } + } - public function test_to_array_include_root() - { + public function test_to_array_include_root() + { ActiveRecord\ArraySerializer::$include_root = true; - $book = Book::find(1); + $book = Book::find(1); $array = $book->to_array(); - $book_attributes = array('book' => $book->attributes()); + $book_attributes = array('book' => $book->attributes()); $this->assert_equals($book_attributes, $array); - } + } - public function test_to_array_except() - { - $book = Book::find(1); + public function test_to_array_except() + { + $book = Book::find(1); $array = $book->to_array(array('except' => array('special'))); $book_attributes = $book->attributes(); unset($book_attributes['special']); $this->assert_equals($book_attributes, $array); - } + } public function test_works_with_datetime() { @@ -166,49 +166,49 @@ public function test_only_method() $this->assert_contains('<sharks>lasers</sharks>', Author::first()->to_xml(array('only_method' => 'return_something'))); } - public function test_to_csv() - { - $book = Book::find(1); - $this->assert_equals('1,1,2,"Ancient Art of Main Tanking",0,0',$book->to_csv()); - } - - public function test_to_csv_only_header() - { - $book = Book::find(1); - $this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special', - $book->to_csv(array('only_header'=>true)) - ); - } - - public function test_to_csv_only_method() - { - $book = Book::find(1); - $this->assert_equals('2,"Ancient Art of Main Tanking"', - $book->to_csv(array('only'=>array('name','secondary_author_id'))) - ); - } - - public function test_to_csv_only_method_on_header() - { - $book = Book::find(1); - $this->assert_equals('secondary_author_id,name', - $book->to_csv(array('only'=>array('secondary_author_id','name'), - 'only_header'=>true)) - ); - } - - public function test_to_csv_with_custom_delimiter() - { - $book = Book::find(1); - ActiveRecord\CsvSerializer::$delimiter=';'; - $this->assert_equals('1;1;2;"Ancient Art of Main Tanking";0;0',$book->to_csv()); - } - - public function test_to_csv_with_custom_enclosure() - { - $book = Book::find(1); - ActiveRecord\CsvSerializer::$delimiter=','; - ActiveRecord\CsvSerializer::$enclosure="'"; - $this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv()); - } + public function test_to_csv() + { + $book = Book::find(1); + $this->assert_equals('1,1,2,"Ancient Art of Main Tanking",0,0',$book->to_csv()); + } + + public function test_to_csv_only_header() + { + $book = Book::find(1); + $this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special', + $book->to_csv(array('only_header'=>true)) + ); + } + + public function test_to_csv_only_method() + { + $book = Book::find(1); + $this->assert_equals('2,"Ancient Art of Main Tanking"', + $book->to_csv(array('only'=>array('name','secondary_author_id'))) + ); + } + + public function test_to_csv_only_method_on_header() + { + $book = Book::find(1); + $this->assert_equals('secondary_author_id,name', + $book->to_csv(array('only'=>array('secondary_author_id','name'), + 'only_header'=>true)) + ); + } + + public function test_to_csv_with_custom_delimiter() + { + $book = Book::find(1); + ActiveRecord\CsvSerializer::$delimiter=';'; + $this->assert_equals('1;1;2;"Ancient Art of Main Tanking";0;0',$book->to_csv()); + } + + public function test_to_csv_with_custom_enclosure() + { + $book = Book::find(1); + ActiveRecord\CsvSerializer::$delimiter=','; + ActiveRecord\CsvSerializer::$enclosure="'"; + $this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv()); + } } From 044b0e1f3e4bf5d2c56e2ef0505c07f494b57422 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 19 Jul 2014 23:33:07 +0200 Subject: [PATCH 22/53] require Expressions by default otherwise, when running tests on their own they would miss the Expressions class --- ActiveRecord.php | 1 + test/ExpressionsTest.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveRecord.php b/ActiveRecord.php index d00dae47b..f7c04a4d5 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -16,6 +16,7 @@ require __DIR__.'/lib/ConnectionManager.php'; require __DIR__.'/lib/Connection.php'; require __DIR__.'/lib/Serialization.php'; +require __DIR__.'/lib/Expressions.php'; require __DIR__.'/lib/SQLBuilder.php'; require __DIR__.'/lib/Reflections.php'; require __DIR__.'/lib/Inflector.php'; diff --git a/test/ExpressionsTest.php b/test/ExpressionsTest.php index 5f089e60e..c1d90aab3 100644 --- a/test/ExpressionsTest.php +++ b/test/ExpressionsTest.php @@ -1,5 +1,4 @@ <?php -require_once __DIR__ . '/../lib/Expressions.php'; use ActiveRecord\Expressions; use ActiveRecord\ConnectionManager; From 52f422a1e47a92ca3f00d7f92532f66425fdc0b3 Mon Sep 17 00:00:00 2001 From: thesocialgeek <kamran.usman@gmail.com> Date: Thu, 18 Apr 2013 18:15:57 +0300 Subject: [PATCH 23/53] Make sure that the primary key is stored in lowercase Often, developers set the column names in different cases for the $primary_key static field in the Model. However, the object for the Model stores column names as lowercase (in $attributes). This causes confusion at times (for example, when checking if "$attributes[$pk]" is set while inserting a new record). This small bit of code makes sure no matter what case the developer sets his primary key ("myPrimaryKey" or "myprimaryKey"), the key will always be stored in all lowercase ("myprimarykey"). --- lib/Table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Table.php b/lib/Table.php index dc5582d2f..044b1455c 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -457,6 +457,8 @@ private function set_primary_key() $this->pk[] = $c->inflected_name; } } + // make sure the primary key is stored in lowercase + $this->pk = array_map('strtolower', $this->pk); } private function set_table_name() From 427a419a85e07f0526e648bce12b22c96c3ae59c Mon Sep 17 00:00:00 2001 From: Igor Santos <igorsantos07@gmail.com> Date: Thu, 20 Jun 2013 03:35:12 -0300 Subject: [PATCH 24/53] Allows for aliased methods in serialization, so we can do stuff like `$model->to_array(['methods' => ['get_name' => 'name']]);` --- lib/Serialization.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Serialization.php b/lib/Serialization.php index 6b3b456e8..9285d5ed9 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -136,10 +136,13 @@ private function check_methods() { $this->options_to_a('methods'); - foreach ($this->options['methods'] as $method) + foreach ($this->options['methods'] as $method => $name) { + if (is_numeric($method)) + $method = $name; + if (method_exists($this->model, $method)) - $this->attributes[$method] = $this->model->$method(); + $this->attributes[$name] = $this->model->$method(); } } } From 1d4f31250efa2254e1bda83c9872c3f7bc0a4460 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 20 Jul 2014 13:32:12 +0200 Subject: [PATCH 25/53] add test for serialization method alias --- test/SerializationTest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/SerializationTest.php b/test/SerializationTest.php index d82e4277c..562e24d39 100644 --- a/test/SerializationTest.php +++ b/test/SerializationTest.php @@ -64,14 +64,21 @@ public function test_methods_takes_a_string() $this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']); } - // methods added last should we shuld have value of the method in our json - // rather than the regular attribute value + // methods should take precedence over attributes public function test_methods_method_same_as_attribute() { $a = $this->_a(array('methods' => 'name')); $this->assert_equals('ancient art of main tanking', $a['name']); } + public function test_methods_method_alias() + { + $a = $this->_a(array('methods' => array('name' => 'alias_name'))); + $this->assert_equals('ancient art of main tanking', $a['alias_name']); + $a = $this->_a(array('methods' => array('upper_name' => 'name'))); + $this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['name']); + } + public function test_include() { $a = $this->_a(array('include' => array('author'))); From 8bfb68b359c5d9359894861a7ba1bb0cce89f4a4 Mon Sep 17 00:00:00 2001 From: Daniel Aranda <javierdaniel@gmail.com> Date: Fri, 23 Jan 2015 14:51:51 -0500 Subject: [PATCH 26/53] cast count result as integer Removing second return unifying line --- lib/Model.php | 2 +- test/ActiveRecordFindTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 36e0a4918..9371b40cb 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1464,7 +1464,7 @@ public static function count(/* ... */) $table = static::table(); $sql = $table->options_to_sql($options); $values = $sql->get_where_values(); - return static::connection()->query_and_fetch_one($sql->to_s(),$values); + return (int) static::connection()->query_and_fetch_one($sql->to_s(),$values); } /** diff --git a/test/ActiveRecordFindTest.php b/test/ActiveRecordFindTest.php index 329eb9e67..da27ad1d9 100644 --- a/test/ActiveRecordFindTest.php +++ b/test/ActiveRecordFindTest.php @@ -226,12 +226,12 @@ public function test_fetch_all() public function test_count() { - $this->assert_equals(1,Author::count(1)); - $this->assert_equals(2,Author::count(array(1,2))); + $this->assert_same(1,Author::count(1)); + $this->assert_same(2,Author::count(array(1,2))); $this->assert_true(Author::count() > 1); - $this->assert_equals(0,Author::count(array('conditions' => 'author_id=99999999999999'))); - $this->assert_equals(2,Author::count(array('conditions' => 'author_id=1 or author_id=2'))); - $this->assert_equals(1,Author::count(array('name' => 'Tito', 'author_id' => 1))); + $this->assert_same(0,Author::count(array('conditions' => 'author_id=99999999999999'))); + $this->assert_same(2,Author::count(array('conditions' => 'author_id=1 or author_id=2'))); + $this->assert_same(1,Author::count(array('name' => 'Tito', 'author_id' => 1))); } public function test_gh149_empty_count() From 8b0b67ea77b2f130bae409f905885775d89aee2b Mon Sep 17 00:00:00 2001 From: Bogdan D <dmbohdan@mail.ru> Date: Mon, 13 Jan 2014 15:28:09 +0200 Subject: [PATCH 27/53] FIX jpfuentes2#361 Group association conditions in parentheses --- lib/Utils.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Utils.php b/lib/Utils.php index af5840cd6..7e6e2559c 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -181,12 +181,13 @@ public static function add_condition(&$conditions=array(), $condition, $conjucti $conditions = array_flatten($condition); else { - $conditions[0] .= " $conjuction " . array_shift($condition); + $conditions[0] = "(" . $conditions[0]; + $conditions[0] .= ") $conjuction (" . array_shift($condition) . ")"; $conditions[] = array_flatten($condition); } } elseif (is_string($condition)) - $conditions[0] .= " $conjuction $condition"; + $conditions[0] = "(" . $conditions[0] . ") $conjuction ($condition)"; return $conditions; } @@ -367,4 +368,4 @@ public static function add_irregular($singular, $plural) { self::$irregular[$singular] = $plural; } -} \ No newline at end of file +} From 9e8c27b14e32bbf36c1e0ef28a9d1385b0691984 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Mon, 8 Dec 2014 23:20:27 +0100 Subject: [PATCH 28/53] make string buildup more readable update test to match query string --- lib/Utils.php | 7 +++---- test/RelationshipTest.php | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Utils.php b/lib/Utils.php index 7e6e2559c..45bb0a459 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -173,7 +173,7 @@ public static function extract_options($options) return is_array(end($options)) ? end($options) : array(); } - public static function add_condition(&$conditions=array(), $condition, $conjuction='AND') + public static function add_condition(&$conditions=array(), $condition, $conjunction='AND') { if (is_array($condition)) { @@ -181,13 +181,12 @@ public static function add_condition(&$conditions=array(), $condition, $conjucti $conditions = array_flatten($condition); else { - $conditions[0] = "(" . $conditions[0]; - $conditions[0] .= ") $conjuction (" . array_shift($condition) . ")"; + $conditions[0] = "({$conditions[0]}) $conjunction (" . array_shift($condition) . ")"; $conditions[] = array_flatten($condition); } } elseif (is_string($condition)) - $conditions[0] = "(" . $conditions[0] . ") $conjuction ($condition)"; + $conditions[0] = "({$conditions[0]}) {$conjunction} ($condition)"; return $conditions; } diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index e726a6b71..65a640647 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -540,7 +540,7 @@ public function test_gh93_and_gh100_eager_loading_respects_association_options() Venue::$has_many = array(array('events', 'class_name' => 'Event', 'order' => 'id asc', 'conditions' => array('length(title) = ?', 14))); $venues = Venue::find(array(2, 6), array('include' => 'events')); - $this->assert_sql_has("WHERE length(title) = ? AND venue_id IN(?,?) ORDER BY id asc",ActiveRecord\Table::load('Event')->last_sql); + $this->assert_sql_has("WHERE (length(title) = ?) AND (venue_id IN(?,?)) ORDER BY id asc",ActiveRecord\Table::load('Event')->last_sql); $this->assert_equals(1, count($venues[0]->events)); } From 14e926ae87275607b5dff9cd59c55c12132315f4 Mon Sep 17 00:00:00 2001 From: Kevin Foster <kevin@socialhp.com> Date: Sat, 13 Dec 2014 09:26:40 -0500 Subject: [PATCH 29/53] Refactor is_hash() --- lib/Utils.php | 8 +++++--- test/UtilsTest.php | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/Utils.php b/lib/Utils.php index 45bb0a459..3db34514c 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -63,13 +63,15 @@ function array_flatten(array $array) /** * Somewhat naive way to determine if an array is a hash. */ -function is_hash(&$array) +function is_hash($array) { - if (!is_array($array)) + if (!is_array($array)) { return false; + } $keys = array_keys($array); - return @is_string($keys[0]) ? true : false; + + return isset($keys[0]) && is_string($keys[0]); } /** diff --git a/test/UtilsTest.php b/test/UtilsTest.php index 20074bbc9..37d8bf151 100644 --- a/test/UtilsTest.php +++ b/test/UtilsTest.php @@ -103,5 +103,19 @@ public function test_wrap_strings_in_arrays() $x = '1'; $this->assert_equals(array(array('1')),ActiveRecord\wrap_strings_in_arrays($x)); } -} + public function test_is_hash() + { + $hash = array('key' => 'value'); + $this->assert_true(ActiveRecord\is_hash($hash)); + + $notHash = array(0 => 'value'); + $this->assert_false(ActiveRecord\is_hash($notHash)); + } + + public function test_is_hash_empty_array() + { + $notHash = array(); + $this->assert_false(ActiveRecord\is_hash($notHash)); + } +}; From bdbaebbb361ed9a319b54683e17c2b662d4bbb38 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Fri, 12 Dec 2014 15:10:15 +0100 Subject: [PATCH 30/53] Feature: StrongParameters Protect mass assignment by filtering input data using StrongParameters $user_params = new ActiveRecord\StrongParameters($_POST['user']); $user_params->permit('name', 'bio'); $user = new User($user_params); $user->save(); --- lib/Config.php | 29 ++++++++ lib/Exceptions.php | 7 ++ lib/Model.php | 58 ++++++++++------ lib/StrongParameters.php | 107 +++++++++++++++++++++++++++++ test/StrongParametersModelTest.php | 76 ++++++++++++++++++++ test/StrongParametersTest.php | 53 ++++++++++++++ 6 files changed, 309 insertions(+), 21 deletions(-) create mode 100644 lib/StrongParameters.php create mode 100644 test/StrongParametersModelTest.php create mode 100644 test/StrongParametersTest.php diff --git a/lib/Config.php b/lib/Config.php index 378a5b2e3..887608b69 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -87,6 +87,13 @@ class Config extends Singleton */ private $date_format = \DateTime::ISO8601; + /** + * Switch for use of StrongParameters + * + * @var bool + */ + private $require_strong_parameters = false; + /** * Allows config initialization using a closure. * @@ -357,4 +364,26 @@ public function set_cache($url, $options=array()) { Cache::initialize($url,$options); } + + /** + * Enable or disable use of StrongParameters + * + * @param bool $flag + * @return void + */ + public function set_require_strong_parameters($flag) + { + $this->require_strong_parameters = $flag; + } + + /** + * Returns whether or not to require StrongParameters + * + * @return bool + */ + public function get_require_strong_parameters() + { + return $this->require_strong_parameters; + } + } diff --git a/lib/Exceptions.php b/lib/Exceptions.php index d3de8ab22..1860910e3 100644 --- a/lib/Exceptions.php +++ b/lib/Exceptions.php @@ -141,3 +141,10 @@ class RelationshipException extends ActiveRecordException {} * @package ActiveRecord */ class HasManyThroughAssociationException extends RelationshipException {} + +/** + * Thrown for unsafe in attributes in mass assignment + * + * @package ActiveRecord + */ +class UnsafeParametersException extends ActiveRecordException {} diff --git a/lib/Model.php b/lib/Model.php index ffd676696..1a42f49ae 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -272,13 +272,14 @@ class Model * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief')); * </code> * - * @param array $attributes Hash containing names and values to mass assign to the model + * @param array $attributes Hash containing names and values to mass assign to the model or + * StrongParameters object * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes * @param boolean $instantiating_via_find Set to true if this model is being created from a find call * @param boolean $new_record Set to true if this should be considered a new record * @return Model */ - public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true) + public function __construct($attributes=null, $guard_attributes=true, $instantiating_via_find=false, $new_record=true) { $this->__new_record = $new_record; @@ -1225,9 +1226,10 @@ public function set_timestamps() } /** - * Mass update the model with an array of attribute data and saves to the database. + * Mass update the model with attribute data and saves to the database. * - * @param array $attributes An attribute data array in the form array(name => value, ...) + * @param StrongParameters|array $attributes An StrongParameters object or array + * containing data to update in the form array(name => value, ...) * @return boolean True if successfully updated and saved otherwise false */ public function update_attributes($attributes) @@ -1250,15 +1252,16 @@ public function update_attribute($name, $value) } /** - * Mass update the model with data from an attributes hash. + * Mass update the model with data from an attributes hash or object. * * Unlike update_attributes() this method only updates the model's data * but DOES NOT save it to the database. * * @see update_attributes - * @param array $attributes An array containing data to update in the form array(name => value, ...) + * @param StrongParameters|array $attributes An StrongParameters object or array + * containing data to update in the form array(name => value, ...) */ - public function set_attributes(array $attributes) + public function set_attributes($attributes) { $this->set_attributes_via_mass_assignment($attributes, true); } @@ -1267,35 +1270,48 @@ public function set_attributes(array $attributes) * Passing $guard_attributes as true will throw an exception if an attribute does not exist. * * @throws \ActiveRecord\UndefinedPropertyException - * @param array $attributes An array in the form array(name => value, ...) + * @param StrongParameters|array $attributes An StrongParameters object or array + * containing data to update in the form array(name => value, ...) * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded */ - private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes) + private function set_attributes_via_mass_assignment(&$attributes, $guard_attributes) { - //access uninflected columns since that is what we would have in result set + // return fast when no attributes are given + if(empty($attributes)) return; + + $require_strong_parameters = Config::instance()->get_require_strong_parameters(); + if($guard_attributes && $require_strong_parameters && !($attributes instanceof StrongParameters)) + { + throw new UnsafeParametersException(); + } + + // Legacy support for attr_accessible/attr_protected + // Recommended way of protecting attributes is by using StrongParameters + if($guard_attributes && !($attributes instanceof StrongParameters)) + { + if (!empty(static::$attr_accessible)) + $attributes = array_intersect_key($attributes, array_flip(static::$attr_accessible)); + + if (!empty(static::$attr_protected)) + $attributes = array_diff_key($attributes, array_flip(static::$attr_protected)); + } + $table = static::table(); $exceptions = array(); - $use_attr_accessible = !empty(static::$attr_accessible); - $use_attr_protected = !empty(static::$attr_protected); $connection = static::connection(); + // access uninflected columns since that is what we would have in result set foreach ($attributes as $name => $value) { // is a normal field on the table - if (array_key_exists($name,$table->columns)) + if (array_key_exists($name, $table->columns)) { - $value = $table->columns[$name]->cast($value,$connection); + $value = $table->columns[$name]->cast($value, $connection); $name = $table->columns[$name]->inflected_name; } if ($guard_attributes) { - if ($use_attr_accessible && !in_array($name,static::$attr_accessible)) - continue; - - if ($use_attr_protected && in_array($name,static::$attr_protected)) - continue; - // set valid table data try { $this->$name = $value; @@ -1310,7 +1326,7 @@ private function set_attributes_via_mass_assignment(array &$attributes, $guard_a continue; // set arbitrary data - $this->assign_attribute($name,$value); + $this->assign_attribute($name, $value); } } diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php new file mode 100644 index 000000000..d92b66c68 --- /dev/null +++ b/lib/StrongParameters.php @@ -0,0 +1,107 @@ +<?php + +namespace ActiveRecord; + +use IteratorAggregate; +use ArrayIterator; + +/** + * The use of StrongParameters is implementation dependent. + * + * You probably want to integrate this somewhere in your application where the POST data is processed. + * + * For example, in your Controller class you can process the POST data: + * + * public function beforeFilter() + * { + * foreach($_POST as $group => $params) + * { + * $this->request->data[$group] = new ActiveRecord\StrongParameters($params); + * } + * } + * + * This assumes that your POST data is grouped as follows; + * + * $_POST = array( + * "user" => array( + * "name" => "Foo Bar", + * "bio" => "I'm Foo Bar", + * "email" => "foo@bar.baz" + * ) + * ) + * + * And then in the UserController class you can access the data + * + * public function update_profile() + * { + * $user = User::find($this->request->params['id']); + * $user->update_attributes($this->user_params()); + * $this->redirect('back'); + * } + * + * protected function user_params() + * { + * return $this->request->data['user']->permit('name', 'bio', 'email'); + * } + * + * + * @package ActiveRecord + */ +class StrongParameters implements IteratorAggregate +{ + /** + * Array containing data + * + * @var array + */ + protected $data = array(); + + /** + * Array containing permitted attributes + * + * @var array + */ + protected $permitted = array(); + + /** + * Construct StrongParameters object with data + * + * @param array $data + * @return void + */ + public function __construct(array $data = array()) + { + $this->data = $data; + } + + /** + * Permit the specified attributes to be returned for mass assignment. + * + * @param mixed $attrs,... + * @return void + */ + public function permit($attrs = array()/* [, ...$attr] */) + { + if(func_num_args() > 1) + { + $attrs = func_get_args(); + } + elseif(!is_array($attrs)) + { + $attrs = array($attrs); + } + $this->permitted = $attrs; + } + + /** + * Required method for IteratorAggregate interface. + * + * @return ArrayIterator iterator for permitted data. + */ + public function getIterator() + { + $permitted_data = array_intersect_key($this->data, array_flip($this->permitted)); + return new ArrayIterator($permitted_data); + } + +} diff --git a/test/StrongParametersModelTest.php b/test/StrongParametersModelTest.php new file mode 100644 index 000000000..1ca927d80 --- /dev/null +++ b/test/StrongParametersModelTest.php @@ -0,0 +1,76 @@ +<?php + +class StrongParametersModelTest extends DatabaseTest +{ + + private static $require_strong_parameters; + + public static function setUpBeforeClass() + { + $config = ActiveRecord\Config::instance(); + self::$require_strong_parameters = $config->get_require_strong_parameters(); + $config->set_require_strong_parameters(true); + } + + public static function tearDownAfterClass() + { + $config = ActiveRecord\Config::instance(); + $config->set_require_strong_parameters(self::$require_strong_parameters); + } + + public function testConstructWithStrongParameters() + { + $params = new ActiveRecord\StrongParameters(array( + 'name' => 'Foo', + )); + $book = new Book($params); + $this->assertNull($book->name); + + $params->permit('name'); + $book = new Book($params); + $this->assertEquals('Foo', $book->name); + } + + /** + * @expectedException ActiveRecord\UnsafeParametersException + */ + public function testConstructWithoutStrongParametersThrowsException() + { + $config = ActiveRecord\Config::instance(); + $require_strong_parameters = $config->get_require_strong_parameters(); + $config->set_require_strong_parameters(true); + + $params = array('name' => 'Foo'); + $book = new Book($params); + } + + public function testUpdateAttributesWithStrongParameters() + { + $params = new ActiveRecord\StrongParameters(array( + 'name' => 'Foo', + )); + $book = new Book(); + $book->update_attributes($params); + $this->assertNull($book->name); + + $params->permit('name'); + $book->update_attributes($params); + $this->assertEquals('Foo', $book->name); + } + + /** + * @expectedException ActiveRecord\UnsafeParametersException + */ + public function testUpdateAttributesWithoutStrongParameters() + { + $config = ActiveRecord\Config::instance(); + $require_strong_parameters = $config->get_require_strong_parameters(); + $config->set_require_strong_parameters(true); + + $params = array('name' => 'Foo'); + $book = new Book(); + $book->update_attributes($params); + } + + +} diff --git a/test/StrongParametersTest.php b/test/StrongParametersTest.php new file mode 100644 index 000000000..93d5eaf3e --- /dev/null +++ b/test/StrongParametersTest.php @@ -0,0 +1,53 @@ +<?php + +class StrongParametersTest extends SnakeCase_PHPUnit_Framework_TestCase +{ + + public function setUp(){ + $this->obj = new ActiveRecord\StrongParameters(array( + 'name' => 'Foo Bar', + 'email' => 'foo@bar.baz', + 'bio' => 'I am Foo Bar', + 'is_admin' => true + )); + } + + public function testConstruct() + { + $obj = new ActiveRecord\StrongParameters(array('name' => 'Foo Bar')); + $this->assertInstanceOf('ActiveRecord\StrongParameters', $obj); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testConstructWithObjectTriggersError() + { + $obj = new ActiveRecord\StrongParameters((object)array('name' => 'Foo Bar')); + } + + public function testPermit() + { + $this->obj->permit(); + $expected = array(); + $actual = iterator_to_array($this->obj); + $this->assertEquals($expected, $actual); + + $this->obj->permit(array('name', 'email')); + $expected = array('name' => 'Foo Bar', 'email' => 'foo@bar.baz'); + $actual = iterator_to_array($this->obj); + $this->assertEquals($expected, $actual); + + $this->obj->permit('name', 'bio'); + $expected = array('name' => 'Foo Bar', 'bio' => 'I am Foo Bar'); + $actual = iterator_to_array($this->obj); + $this->assertEquals($expected, $actual); + } + + public function testGetIterator() + { + $this->assertInstanceOf('ArrayIterator', $this->obj->getIterator()); + } + +} + From 16e6f4bac73d6f580d55dd93f8316b56b00cbe41 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Mon, 19 Jan 2015 11:59:41 +0100 Subject: [PATCH 31/53] Require StrongParameters --- ActiveRecord.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ActiveRecord.php b/ActiveRecord.php index 0283b49b7..f766f1c04 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -24,6 +24,7 @@ require __DIR__.'/lib/CallBack.php'; require __DIR__.'/lib/Exceptions.php'; require __DIR__.'/lib/Cache.php'; +require __DIR__.'/lib/StrongParameters.php'; if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE')) spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND); From 14ef26008506aa20f59ade5d98b06d82bbb63f9e Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 3 Jun 2017 17:24:38 +0200 Subject: [PATCH 32/53] add requireParam and fetch function --- lib/Exceptions.php | 7 +++ lib/StrongParameters.php | 48 +++++++++++++--- test/StrongParametersModelTest.php | 18 +++--- test/StrongParametersTest.php | 90 ++++++++++++++++++++++-------- 4 files changed, 125 insertions(+), 38 deletions(-) diff --git a/lib/Exceptions.php b/lib/Exceptions.php index 1860910e3..d2ca7672b 100644 --- a/lib/Exceptions.php +++ b/lib/Exceptions.php @@ -148,3 +148,10 @@ class HasManyThroughAssociationException extends RelationshipException {} * @package ActiveRecord */ class UnsafeParametersException extends ActiveRecordException {} + +/** + * Thrown for non existing parameter from requireParam + * + * @package ActiveRecord + */ +class ParameterMissingException extends ActiveRecordException {} diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php index d92b66c68..68cb5e4e4 100644 --- a/lib/StrongParameters.php +++ b/lib/StrongParameters.php @@ -14,10 +14,7 @@ * * public function beforeFilter() * { - * foreach($_POST as $group => $params) - * { - * $this->request->data[$group] = new ActiveRecord\StrongParameters($params); - * } + * $this->request->params = new ActiveRecord\StrongParameters($_POST); * } * * This assumes that your POST data is grouped as follows; @@ -41,7 +38,7 @@ * * protected function user_params() * { - * return $this->request->data['user']->permit('name', 'bio', 'email'); + * return $this->request->params->requireParam('user')->permit('name', 'bio', 'email'); * } * * @@ -69,16 +66,33 @@ class StrongParameters implements IteratorAggregate * @param array $data * @return void */ - public function __construct(array $data = array()) + public function __construct(array $data) { - $this->data = $data; + $this->data = $this->parse($data); + } + + /** + * Recursively parse array into StrongParameter + * @param array $data + * @return array + */ + protected function parse(array $data) + { + return array_map(function($value) + { + if (!is_array($value)) + { + return $value; + } + return new StrongParameters($value); + }, $data); } /** * Permit the specified attributes to be returned for mass assignment. * * @param mixed $attrs,... - * @return void + * @return this */ public function permit($attrs = array()/* [, ...$attr] */) { @@ -91,6 +105,24 @@ public function permit($attrs = array()/* [, ...$attr] */) $attrs = array($attrs); } $this->permitted = $attrs; + return $this; + } + + public function fetch($key) + { + if (isset($this->data[$key])) { + return $this->data[$key]; + } + return null; + } + + public function requireParam($key) + { + $param = $this->fetch($key); + if (empty($param)) { + throw new ParameterMissingException("Missing param '$key'"); + } + return $param; } /** diff --git a/test/StrongParametersModelTest.php b/test/StrongParametersModelTest.php index 1ca927d80..b2972cca4 100644 --- a/test/StrongParametersModelTest.php +++ b/test/StrongParametersModelTest.php @@ -1,26 +1,30 @@ <?php -class StrongParametersModelTest extends DatabaseTest +namespace ActiveRecord; + +use Book; + +class StrongParametersModelTest extends \DatabaseTest { private static $require_strong_parameters; public static function setUpBeforeClass() { - $config = ActiveRecord\Config::instance(); + $config = Config::instance(); self::$require_strong_parameters = $config->get_require_strong_parameters(); $config->set_require_strong_parameters(true); } public static function tearDownAfterClass() { - $config = ActiveRecord\Config::instance(); + $config = Config::instance(); $config->set_require_strong_parameters(self::$require_strong_parameters); } public function testConstructWithStrongParameters() { - $params = new ActiveRecord\StrongParameters(array( + $params = new StrongParameters(array( 'name' => 'Foo', )); $book = new Book($params); @@ -36,7 +40,7 @@ public function testConstructWithStrongParameters() */ public function testConstructWithoutStrongParametersThrowsException() { - $config = ActiveRecord\Config::instance(); + $config = Config::instance(); $require_strong_parameters = $config->get_require_strong_parameters(); $config->set_require_strong_parameters(true); @@ -46,7 +50,7 @@ public function testConstructWithoutStrongParametersThrowsException() public function testUpdateAttributesWithStrongParameters() { - $params = new ActiveRecord\StrongParameters(array( + $params = new StrongParameters(array( 'name' => 'Foo', )); $book = new Book(); @@ -63,7 +67,7 @@ public function testUpdateAttributesWithStrongParameters() */ public function testUpdateAttributesWithoutStrongParameters() { - $config = ActiveRecord\Config::instance(); + $config = Config::instance(); $require_strong_parameters = $config->get_require_strong_parameters(); $config->set_require_strong_parameters(true); diff --git a/test/StrongParametersTest.php b/test/StrongParametersTest.php index 93d5eaf3e..5345d13ce 100644 --- a/test/StrongParametersTest.php +++ b/test/StrongParametersTest.php @@ -1,52 +1,96 @@ <?php -class StrongParametersTest extends SnakeCase_PHPUnit_Framework_TestCase +namespace ActiveRecord; + +class StrongParametersTest extends \PHPUnit_Framework_TestCase { public function setUp(){ - $this->obj = new ActiveRecord\StrongParameters(array( - 'name' => 'Foo Bar', - 'email' => 'foo@bar.baz', - 'bio' => 'I am Foo Bar', - 'is_admin' => true + $this->params = new StrongParameters(array( + 'user' => array( + 'name' => 'Foo Bar', + 'email' => 'foo@bar.baz', + 'bio' => 'I am Foo Bar', + 'is_admin' => true, + ), )); } public function testConstruct() { - $obj = new ActiveRecord\StrongParameters(array('name' => 'Foo Bar')); - $this->assertInstanceOf('ActiveRecord\StrongParameters', $obj); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testConstructWithObjectTriggersError() - { - $obj = new ActiveRecord\StrongParameters((object)array('name' => 'Foo Bar')); + $params = new StrongParameters(array('name' => 'Foo Bar')); + $this->assertInstanceOf('ActiveRecord\StrongParameters', $params); } public function testPermit() { - $this->obj->permit(); + $params = new StrongParameters(array( + 'name' => 'Foo Bar', + 'email' => 'foo@bar.baz', + 'bio' => 'I am Foo Bar', + 'is_admin' => true, + )); + $params->permit(); $expected = array(); - $actual = iterator_to_array($this->obj); + $actual = iterator_to_array($params); $this->assertEquals($expected, $actual); - $this->obj->permit(array('name', 'email')); + $params->permit(array('name', 'email')); $expected = array('name' => 'Foo Bar', 'email' => 'foo@bar.baz'); - $actual = iterator_to_array($this->obj); + $actual = iterator_to_array($params); + $this->assertEquals($expected, $actual); + + $params->permit('name', 'bio'); + $expected = array('name' => 'Foo Bar', 'bio' => 'I am Foo Bar'); + $actual = iterator_to_array($params); + $this->assertEquals($expected, $actual); + } + + public function testPermitReturnsSelf() + { + $params = new StrongParameters(array( + 'name' => 'Foo Bar', + 'email' => 'foo@bar.baz', + 'bio' => 'I am Foo Bar', + 'is_admin' => true, + )); + + $expected = array('name' => 'Foo Bar'); + $actual = iterator_to_array($params->permit('name')); $this->assertEquals($expected, $actual); - $this->obj->permit('name', 'bio'); $expected = array('name' => 'Foo Bar', 'bio' => 'I am Foo Bar'); - $actual = iterator_to_array($this->obj); + $actual = iterator_to_array($params->permit('name', 'bio')); $this->assertEquals($expected, $actual); } + public function testRequireParam() + { + $this->assertInstanceOf('ActiveRecord\StrongParameters', $this->params->requireParam('user')); + } + + /** + * @expectedException ActiveRecord\ParameterMissingException + */ + public function testRequireParamThrowsOnMissingParam() + { + $this->params->requireParam('nonexisting'); + } + + + public function testFetch() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $this->assertEquals('Foo Bar', $params->fetch('name')); + + $this->assertInstanceOf('ActiveRecord\StrongParameters', $this->params->fetch('user')); + + $this->assertEquals('Foo Bar', $this->params->fetch('user')->fetch('name')); + } + public function testGetIterator() { - $this->assertInstanceOf('ArrayIterator', $this->obj->getIterator()); + $this->assertInstanceOf('ArrayIterator', $this->params->getIterator()); } } From f941f45bb1121ff914c39152d5ddba0a4a57ec17 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 3 Jun 2017 18:56:56 +0200 Subject: [PATCH 33/53] add some docblocks --- lib/StrongParameters.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php index 68cb5e4e4..353428a63 100644 --- a/lib/StrongParameters.php +++ b/lib/StrongParameters.php @@ -108,6 +108,12 @@ public function permit($attrs = array()/* [, ...$attr] */) return $this; } + /** + * Fetch a value from the data + * + * @param string $key + * @return mixed + */ public function fetch($key) { if (isset($this->data[$key])) { @@ -116,6 +122,14 @@ public function fetch($key) return null; } + /** + * Fetch a required value from the data. If the param doesn't exist an error + * is thrown + * + * @param string $key + * @throws ActiveRecord\ParameterMissingException + * @return mixed + */ public function requireParam($key) { $param = $this->fetch($key); From ab0432cb012501f2d93831559356f08e0f89f6af Mon Sep 17 00:00:00 2001 From: Pelligra S <pelligra.s@gmail.com> Date: Thu, 25 Apr 2013 03:22:40 +0200 Subject: [PATCH 34/53] is_valid don't always revalidate the model. Added test also. --- lib/Model.php | 50 ++++++++++++++++++++--- test/ValidationsTest.php | 87 ++++++++++++++++++++++++++++++++++++++++ test/fixtures/users.csv | 4 +- 3 files changed, 133 insertions(+), 8 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index ffd676696..c7e9900a6 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -87,6 +87,11 @@ class Model */ private $attributes = array(); + /** + * Flag wheter or not this model has been validated + */ + private $_flag_validated = false; + /** * Contains changes made to model attributes as * $attribute_name => array($original_value, $current_value) @@ -503,6 +508,7 @@ public function assign_attribute($name, $value) // set the attribute and flag as dirty $this->attributes[$name] = $value; $this->flag_dirty($name); + $this->nullify_validation(); } return $value; } @@ -1154,6 +1160,8 @@ private function _validate() { require_once 'Validations.php'; + $this->validated(); + $validator = new Validations($this); $validation_mode = $this->is_new_record() ? 'create' : 'update'; $validation_on = 'validation_on_' . $validation_mode; @@ -1178,6 +1186,16 @@ private function _validate() return true; } + private function is_validated() { + return $this->_flag_validated; + } + private function validated() { + $this->_flag_validated = true; + } + private function nullify_validation() { + $this->_flag_validated = false; + } + /** * Returns true if the model has been modified. * @@ -1189,25 +1207,43 @@ public function is_dirty() } /** - * Run validations on model and returns whether or not model passed validation. + * Returns whether or not model passed validation. + * + * <p> + * Case of use for <code>is_valid(true)</code>: + * You call some method that will run validations (including this), and + * got a result. + * After, a <strong>virtual attribute</strong> (attribute not stored in Database) + * <strong>used by validation</strong> change is value. + * So, in this case, and only this, you have to use `is_valid(true)` to + * get a correct value. + * </p> + * + * Probably you never use <code>is_valid(true)</code>, but if you spot a + * bug related to validation, you know what to try! * + * @param boolean $force_validation If true, will always run validations. * @see is_invalid * @return boolean */ - public function is_valid() + public function is_valid($force_validation = false) { - return $this->_validate(); + if( $force_validation || !$this->is_validated() ) + return $this->_validate(); + + return $this->errors->is_empty(); } /** - * Runs validations and returns true if invalid. + * Returns true if invalid. * + * @param boolean $force_validation If true, will always run validations. * @see is_valid * @return boolean */ - public function is_invalid() + public function is_invalid($force_validation = false) { - return !$this->_validate(); + return !$this->is_valid($force_validation); } /** @@ -1360,6 +1396,7 @@ public function reload() $this->expire_cache(); $this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false); $this->reset_dirty(); + $this->nullify_validation(); return $this; } @@ -1368,6 +1405,7 @@ public function __clone() { $this->__relationships = array(); $this->reset_dirty(); + $this->nullify_validation(); return $this; } diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index 7ec55beff..bef851610 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -18,6 +18,31 @@ public function validate() } } +class UserValidations extends AR\Model +{ + static $table_name = 'user'; + public $password_confirm; + + // Only for test purpose. This will double encrypt pass from the DB! + public function set_password($pass) + { + $this->assign_attribute('password', static::encrypt($pass)); + } + + public function validate() + { + $this->password_confirm = static::encrypt($password_confirm); + if($this->password_confirm !== $this->password) + $this->errors->add('password', 'Password Mismatch'); + } + + public static function encrypt($data) + { + return base64_encode($data); + } + +} + class ValuestoreValidations extends ActiveRecord\Model { static $table_name = 'valuestore'; @@ -68,6 +93,68 @@ public function test_is_invalid_is_true() $this->assert_true($book->is_invalid()); } + public function test_is_valid_dont_revalidate() { + $attrs = array( + 'password' => 'secret', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $invalid = !( $valid = $user->is_valid() ); + /** + * The `is_valid()` method will validate the User. In this test it will + * be valid. + * If `is_valid()` had revalidated it again, `password_confirm` would be + * rehashed, becoming different from `password` and then the result + * would be different from precedent (and also a bug). + */ + $this->assert($valid, $user->is_valid()); + $this->assert($invalid, $user->is_invalid()); + } + public function test_is_valid_will_revalidate_if_attribute_changes() { + $attrs = array( + 'password' => 'bad', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $this->assert_false($user->is_valid()); + + $user->password = 'secret'; + $this->assert_true($user->is_valid()); + } + public function test_is_invalid_will_revalidate_if_attribute_changes() + { + $attrs = array( + 'password' => 'bad', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $this->assert_true($user->is_invalid()); + + $user->password = 'secret'; + $this->assert_false($user->is_invalid()); + } + public function test_is_valid_must_be_forced_if_a_virtual_attribute_changes() + { + $attrs = array( + 'password' => 'secret', + 'password_confirm' => 'bad' + ); + + $user = new UserValidations($attrs); + $this->assert_false($user->is_valid()); + + $user->password_confirm = 'secret'; + // Actually we check only attribute set by `__set` magic method. + $this->assert_false( $user->is_valid() ); + + // Passing `true` will force the validation of `user`, giving the + // right result. + $this->assert_true( $user->is_valid(true) ); + } + public function test_is_iterable() { $book = new BookValidations(); diff --git a/test/fixtures/users.csv b/test/fixtures/users.csv index 3ff3deb87..5976d917a 100644 --- a/test/fixtures/users.csv +++ b/test/fixtures/users.csv @@ -1,2 +1,2 @@ -id -1 +id,password +1,hello From f7241b5d4cda1be18d70fcebde9a756ccf4013e2 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 6 Dec 2014 22:51:52 +0100 Subject: [PATCH 35/53] fix whitespace and coding style --- lib/Model.php | 77 +++++++++-------- test/ValidationsTest.php | 174 +++++++++++++++++++++------------------ 2 files changed, 134 insertions(+), 117 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index c7e9900a6..1cbf12a47 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -87,10 +87,10 @@ class Model */ private $attributes = array(); - /** - * Flag wheter or not this model has been validated - */ - private $_flag_validated = false; + /** + * Flag wheter or not this model has been validated + */ + private $_flag_validated = false; /** * Contains changes made to model attributes as @@ -508,7 +508,7 @@ public function assign_attribute($name, $value) // set the attribute and flag as dirty $this->attributes[$name] = $value; $this->flag_dirty($name); - $this->nullify_validation(); + $this->nullify_validation(); } return $value; } @@ -1160,7 +1160,7 @@ private function _validate() { require_once 'Validations.php'; - $this->validated(); + $this->validated(); $validator = new Validations($this); $validation_mode = $this->is_new_record() ? 'create' : 'update'; @@ -1186,15 +1186,20 @@ private function _validate() return true; } - private function is_validated() { - return $this->_flag_validated; - } - private function validated() { - $this->_flag_validated = true; - } - private function nullify_validation() { - $this->_flag_validated = false; - } + private function is_validated() + { + return $this->_flag_validated; + } + + private function validated() + { + $this->_flag_validated = true; + } + + private function nullify_validation() + { + $this->_flag_validated = false; + } /** * Returns true if the model has been modified. @@ -1208,36 +1213,36 @@ public function is_dirty() /** * Returns whether or not model passed validation. - * - * <p> - * Case of use for <code>is_valid(true)</code>: - * You call some method that will run validations (including this), and - * got a result. - * After, a <strong>virtual attribute</strong> (attribute not stored in Database) - * <strong>used by validation</strong> change is value. - * So, in this case, and only this, you have to use `is_valid(true)` to - * get a correct value. - * </p> - * - * Probably you never use <code>is_valid(true)</code>, but if you spot a - * bug related to validation, you know what to try! - * - * @param boolean $force_validation If true, will always run validations. + * + * <p> + * Case of use for <code>is_valid(true)</code>: + * You call some method that will run validations (including this), and + * got a result. + * After, a <strong>virtual attribute</strong> (attribute not stored in Database) + * <strong>used by validation</strong> change is value. + * So, in this case, and only this, you have to use `is_valid(true)` to + * get a correct value. + * </p> + * + * Probably you never use <code>is_valid(true)</code>, but if you spot a + * bug related to validation, you know what to try! + * + * @param boolean $force_validation If true, will always run validations. * @see is_invalid * @return boolean */ public function is_valid($force_validation = false) { - if( $force_validation || !$this->is_validated() ) - return $this->_validate(); + if( $force_validation || !$this->is_validated() ) + return $this->_validate(); - return $this->errors->is_empty(); + return $this->errors->is_empty(); } /** * Returns true if invalid. * - * @param boolean $force_validation If true, will always run validations. + * @param boolean $force_validation If true, will always run validations. * @see is_valid * @return boolean */ @@ -1396,7 +1401,7 @@ public function reload() $this->expire_cache(); $this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false); $this->reset_dirty(); - $this->nullify_validation(); + $this->nullify_validation(); return $this; } @@ -1405,7 +1410,7 @@ public function __clone() { $this->__relationships = array(); $this->reset_dirty(); - $this->nullify_validation(); + $this->nullify_validation(); return $this; } diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index bef851610..499cfed4d 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -21,26 +21,28 @@ public function validate() class UserValidations extends AR\Model { static $table_name = 'user'; - public $password_confirm; - - // Only for test purpose. This will double encrypt pass from the DB! - public function set_password($pass) - { - $this->assign_attribute('password', static::encrypt($pass)); - } - - public function validate() - { - $this->password_confirm = static::encrypt($password_confirm); - if($this->password_confirm !== $this->password) - $this->errors->add('password', 'Password Mismatch'); - } - - public static function encrypt($data) - { - return base64_encode($data); - } - + + public $password_confirm; + + // Only for test purpose. This will double encrypt pass from the DB! + public function set_password($pass) + { + $this->assign_attribute('password', static::encrypt($pass)); + } + + public function validate() + { + // Another BAD idea + $this->password_confirm = static::encrypt($this->password_confirm); + if($this->password_confirm !== $this->password) + $this->errors->add('password', 'Password Mismatch'); + } + + public static function encrypt($data) + { + return md5($data); + } + } class ValuestoreValidations extends ActiveRecord\Model @@ -93,67 +95,77 @@ public function test_is_invalid_is_true() $this->assert_true($book->is_invalid()); } - public function test_is_valid_dont_revalidate() { - $attrs = array( - 'password' => 'secret', - 'password_confirm' => 'secret' - ); - - $user = new UserValidations($attrs); - $invalid = !( $valid = $user->is_valid() ); - /** - * The `is_valid()` method will validate the User. In this test it will - * be valid. - * If `is_valid()` had revalidated it again, `password_confirm` would be - * rehashed, becoming different from `password` and then the result - * would be different from precedent (and also a bug). - */ - $this->assert($valid, $user->is_valid()); - $this->assert($invalid, $user->is_invalid()); - } - public function test_is_valid_will_revalidate_if_attribute_changes() { - $attrs = array( - 'password' => 'bad', - 'password_confirm' => 'secret' - ); - - $user = new UserValidations($attrs); - $this->assert_false($user->is_valid()); - - $user->password = 'secret'; - $this->assert_true($user->is_valid()); - } - public function test_is_invalid_will_revalidate_if_attribute_changes() - { - $attrs = array( - 'password' => 'bad', - 'password_confirm' => 'secret' - ); - - $user = new UserValidations($attrs); - $this->assert_true($user->is_invalid()); - - $user->password = 'secret'; - $this->assert_false($user->is_invalid()); - } - public function test_is_valid_must_be_forced_if_a_virtual_attribute_changes() - { - $attrs = array( - 'password' => 'secret', - 'password_confirm' => 'bad' - ); - - $user = new UserValidations($attrs); - $this->assert_false($user->is_valid()); - - $user->password_confirm = 'secret'; - // Actually we check only attribute set by `__set` magic method. - $this->assert_false( $user->is_valid() ); - - // Passing `true` will force the validation of `user`, giving the - // right result. - $this->assert_true( $user->is_valid(true) ); - } + public function test_is_valid_does_not_revalidate() + { + $attrs = array( + 'password' => 'secret', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $invalid = !( $valid = $user->is_valid() ); + /** + * The `is_valid()` method will validate the User. In this test it will + * be valid. + * If `is_valid()` had revalidated it again, `password_confirm` would be + * rehashed, becoming different from `password` and then the result + * would be different from precedent (and also a bug). + */ + $this->assert_equals($valid, $user->is_valid()); + $this->assert_equals($invalid, $user->is_invalid()); + } + + public function test_is_valid_will_revalidate_if_attribute_changes() + { + $attrs = array( + 'password' => 'bad', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $this->assert_false($user->is_valid()); + + $user->password = 'secret'; + // because custom validation is codeded bad (on purpose), we have to + // reset password_confirm + $user->password_confirm = 'secret'; + $this->assert_true($user->is_valid()); + } + public function test_is_invalid_will_revalidate_if_attribute_changes() + { + $attrs = array( + 'password' => 'bad', + 'password_confirm' => 'secret' + ); + + $user = new UserValidations($attrs); + $this->assert_true($user->is_invalid()); + + $user->password = 'secret'; + // because custom validation is codeded bad (on purpose), we have to + // reset password_confirm + $user->password_confirm = 'secret'; + $this->assert_false($user->is_invalid()); + } + + public function test_is_valid_must_be_forced_if_a_virtual_attribute_changes() + { + $attrs = array( + 'password' => 'secret', + 'password_confirm' => 'bad' + ); + + $user = new UserValidations($attrs); + $this->assert_false($user->is_valid()); + + $user->password_confirm = 'secret'; + // Actually we check only attribute set by `__set` magic method. + $this->assert_false( $user->is_valid() ); + + // Passing `true` will force the validation of `user`, giving the + // right result. + $this->assert_true( $user->is_valid(true) ); + } public function test_is_iterable() { From e3d6a565b518e1a65f48bbc43c57ec858cba94c5 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Mon, 9 Feb 2015 22:49:33 +0100 Subject: [PATCH 36/53] separate connection string parsing from connection --- ActiveRecord.php | 1 + lib/Connection.php | 130 ++++++++--------------------------- lib/ConnectionInfo.php | 111 ++++++++++++++++++++++++++++++ lib/adapters/OciAdapter.php | 2 +- test/ConnectionInfoTest.php | 94 +++++++++++++++++++++++++ test/ConnectionTest.php | 79 --------------------- test/helpers/AdapterTest.php | 29 ++++++++ 7 files changed, 263 insertions(+), 183 deletions(-) create mode 100644 lib/ConnectionInfo.php create mode 100644 test/ConnectionInfoTest.php delete mode 100644 test/ConnectionTest.php diff --git a/ActiveRecord.php b/ActiveRecord.php index 0283b49b7..0577416c5 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -16,6 +16,7 @@ require __DIR__.'/lib/Table.php'; require __DIR__.'/lib/ConnectionManager.php'; require __DIR__.'/lib/Connection.php'; +require __DIR__.'/lib/ConnectionInfo.php'; require __DIR__.'/lib/Serialization.php'; require __DIR__.'/lib/Expressions.php'; require __DIR__.'/lib/SQLBuilder.php'; diff --git a/lib/Connection.php b/lib/Connection.php index 486885bd4..95ce96abd 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -97,35 +97,45 @@ abstract class Connection * A connection name that is set in ActiveRecord\Config * If null it will use the default connection specified by ActiveRecord\Config->set_default_connection * @return Connection - * @see parse_connection_url + * @see ConnectionInfo::from_connection_url */ - public static function instance($connection_string_or_connection_name=null) + public static function instance($connection_info_or_name=null) { $config = Config::instance(); - if (strpos($connection_string_or_connection_name, '://') === false) + if (!is_array($connection_info_or_name)) { - $connection_string = $connection_string_or_connection_name ? - $config->get_connection($connection_string_or_connection_name) : - $config->get_default_connection_string(); + if (strpos($connection_info_or_name, '://') === false) + { + $connection_url = $connection_info_or_name ? + $config->get_connection($connection_info_or_name) : + $config->get_default_connection_string(); + } + else + { + $connection_url = $connection_info_or_name; + } + + if (!$connection_url) + throw new DatabaseException("Empty connection string"); + + $connection_info = ConnectionInfo::from_connection_url($connection_url); } else - $connection_string = $connection_string_or_connection_name; - - if (!$connection_string) - throw new DatabaseException("Empty connection string"); + { + $connection_info = new ConnectionInfo($connection_info_or_name); + } - $info = static::parse_connection_url($connection_string); - $fqclass = static::load_adapter_class($info->protocol); + $fqclass = static::load_adapter_class($connection_info->protocol); try { - $connection = new $fqclass($info); - $connection->protocol = $info->protocol; + $connection = new $fqclass($connection_info); + $connection->protocol = $connection_info->protocol; $connection->logging = $config->get_logging(); $connection->logger = $connection->logging ? $config->get_logger() : null; - if (isset($info->charset)) - $connection->set_encoding($info->charset); + if (isset($connection_info->charset)) + $connection->set_encoding($connection_info->charset); } catch (PDOException $e) { throw new DatabaseException($e); } @@ -151,92 +161,6 @@ private static function load_adapter_class($adapter) return $fqclass; } - /** - * Use this for any adapters that can take connection info in the form below - * to set the adapters connection info. - * - * <code> - * protocol://username:password@host[:port]/dbname - * protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true - * protocol://username:password@unix(/some/file/path)/dbname - * </code> - * - * Sqlite has a special syntax, as it does not need a database name or user authentication: - * - * <code> - * sqlite://file.db - * sqlite://../relative/path/to/file.db - * sqlite://unix(/absolute/path/to/file.db) - * sqlite://windows(c%2A/absolute/path/to/file.db) - * </code> - * - * @param string $connection_url A connection URL - * @return object the parsed URL as an object. - */ - public static function parse_connection_url($connection_url) - { - $url = @parse_url($connection_url); - - if (!isset($url['host'])) - throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)'); - - $info = new \stdClass(); - $info->protocol = $url['scheme']; - $info->host = $url['host']; - $info->db = isset($url['path']) ? substr($url['path'], 1) : null; - $info->user = isset($url['user']) ? $url['user'] : null; - $info->pass = isset($url['pass']) ? $url['pass'] : null; - - $allow_blank_db = ($info->protocol == 'sqlite'); - - if ($info->host == 'unix(') - { - $socket_database = $info->host . '/' . $info->db; - - if ($allow_blank_db) - $unix_regex = '/^unix\((.+)\)\/?().*$/'; - else - $unix_regex = '/^unix\((.+)\)\/(.+)$/'; - - if (preg_match_all($unix_regex, $socket_database, $matches) > 0) - { - $info->host = $matches[1][0]; - $info->db = $matches[2][0]; - } - } elseif (substr($info->host, 0, 8) == 'windows(') - { - $info->host = urldecode(substr($info->host, 8) . '/' . substr($info->db, 0, -1)); - $info->db = null; - } - - if ($allow_blank_db && $info->db) - $info->host .= '/' . $info->db; - - if (isset($url['port'])) - $info->port = $url['port']; - - if (strpos($connection_url, 'decode=true') !== false) - { - if ($info->user) - $info->user = urldecode($info->user); - - if ($info->pass) - $info->pass = urldecode($info->pass); - } - - if (isset($url['query'])) - { - foreach (explode('/&/', $url['query']) as $pair) { - list($name, $value) = explode('=', $pair); - - if ($name == 'charset') - $info->charset = $value; - } - } - - return $info; - } - /** * Class Connection is a singleton. Access it via instance(). * @@ -257,7 +181,7 @@ protected function __construct($info) else $host = "unix_socket=$info->host"; - $this->connection = new PDO("$info->protocol:$host;dbname=$info->db", $info->user, $info->pass, static::$PDO_OPTIONS); + $this->connection = new PDO("$info->protocol:$host;dbname=$info->database", $info->username, $info->password, static::$PDO_OPTIONS); } catch (PDOException $e) { throw new DatabaseException($e); } diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php new file mode 100644 index 000000000..c128b76bf --- /dev/null +++ b/lib/ConnectionInfo.php @@ -0,0 +1,111 @@ +<?php + +namespace ActiveRecord; + +class ConnectionInfo { + + public $protocol = null; + public $host = null; + public $port = null; + public $database = null; + + public $username = null; + public $password = null; + + public function __construct($input = array()){ + foreach($input as $prop => $value){ + $this->{$prop} = $value; + } + } + + /** + * Parses a connection url and return a ConnectionInfo object + * + * Use this for any adapters that can take connection info in the form below + * to set the adapters connection info. + * + * <code> + * protocol://username:password@host[:port]/dbname + * protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true + * protocol://username:password@unix(/some/file/path)/dbname + * </code> + * + * Sqlite has a special syntax, as it does not need a database name or user authentication: + * + * <code> + * sqlite://file.db + * sqlite://../relative/path/to/file.db + * sqlite://unix(/absolute/path/to/file.db) + * sqlite://windows(c%2A/absolute/path/to/file.db) + * </code> + * + * @param string $connection_url A connection URL + * @return object the parsed URL as an object. + */ + public static function from_connection_url($connection_url){ + $url = @parse_url($connection_url); + + if (!isset($url['host'])) + throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)'); + + $info = new self(); + $info->protocol = $url['scheme']; + $info->host = $url['host']; + if(isset($url['path'])){ + $info->database = substr($url['path'], 1); + } + $info->username = isset($url['user']) ? $url['user'] : null; + $info->password = isset($url['pass']) ? $url['pass'] : null; + + $allow_blank_db = ($info->protocol == 'sqlite'); + + if ($info->host == 'unix(') + { + $socket_database = $info->host . '/' . $info->database; + + if ($allow_blank_db) + $unix_regex = '/^unix\((.+)\)\/?().*$/'; + else + $unix_regex = '/^unix\((.+)\)\/(.+)$/'; + + if (preg_match_all($unix_regex, $socket_database, $matches) > 0) + { + $info->host = $matches[1][0]; + $info->database = $matches[2][0]; + } + } elseif (substr($info->host, 0, 8) == 'windows(') + { + $info->host = urldecode(substr($info->host, 8) . '/' . substr($info->database, 0, -1)); + $info->database = null; + } + + if ($allow_blank_db && $info->database) + $info->host .= '/' . $info->database; + + if (isset($url['port'])) + $info->port = $url['port']; + + if (strpos($connection_url, 'decode=true') !== false) + { + if ($info->username) + $info->username = urldecode($info->username); + + if ($info->password) + $info->password = urldecode($info->password); + } + + if (isset($url['query'])) + { + + foreach (explode('/&/', $url['query']) as $pair) { + list($name, $value) = explode('=', $pair); + + if ($name == 'charset') + $info->charset = $value; + } + } + + return $info; + } + +} diff --git a/lib/adapters/OciAdapter.php b/lib/adapters/OciAdapter.php index 1b9336931..5e97343e2 100644 --- a/lib/adapters/OciAdapter.php +++ b/lib/adapters/OciAdapter.php @@ -22,7 +22,7 @@ protected function __construct($info) { try { $this->dsn_params = isset($info->charset) ? ";charset=$info->charset" : ""; - $this->connection = new PDO("oci:dbname=//$info->host/$info->db$this->dsn_params",$info->user,$info->pass,static::$PDO_OPTIONS); + $this->connection = new PDO("oci:dbname=//$info->host/$info->database$this->dsn_params",$info->username,$info->password,static::$PDO_OPTIONS); } catch (PDOException $e) { throw new DatabaseException($e); } diff --git a/test/ConnectionInfoTest.php b/test/ConnectionInfoTest.php new file mode 100644 index 000000000..9364ca368 --- /dev/null +++ b/test/ConnectionInfoTest.php @@ -0,0 +1,94 @@ +<?php + +use ActiveRecord\ConnectionInfo; + +class ConnectionInfoTest extends SnakeCase_PHPUnit_Framework_TestCase +{ + /** + * @expectedException ActiveRecord\DatabaseException + */ + public function test_connection_info_from_should_throw_exception_when_no_host() + { + ConnectionInfo::from_connection_url('mysql://user:pass@'); + } + + public function test_connection_info() + { + $info = ConnectionInfo::from_connection_url('mysql://user:pass@127.0.0.1:3306/dbname'); + $this->assert_equals('mysql',$info->protocol); + $this->assert_equals('user',$info->username); + $this->assert_equals('pass',$info->password); + $this->assert_equals('127.0.0.1',$info->host); + $this->assert_equals(3306,$info->port); + $this->assert_equals('dbname',$info->database); + } + + public function test_gh_103_sqlite_connection_string_relative() + { + $info = ConnectionInfo::from_connection_url('sqlite://../some/path/to/file.db'); + $this->assert_equals('../some/path/to/file.db', $info->host); + } + + /** + * @expectedException ActiveRecord\DatabaseException + */ + public function test_gh_103_sqlite_connection_string_absolute() + { + $info = ConnectionInfo::from_connection_url('sqlite:///some/path/to/file.db'); + } + + public function test_gh_103_sqlite_connection_string_unix() + { + $info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)'); + $this->assert_equals('/some/path/to/file.db', $info->host); + + $info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)/'); + $this->assert_equals('/some/path/to/file.db', $info->host); + + $info = ConnectionInfo::from_connection_url('sqlite://unix(/some/path/to/file.db)/dummy'); + $this->assert_equals('/some/path/to/file.db', $info->host); + } + + public function test_gh_103_sqlite_connection_string_windows() + { + $info = ConnectionInfo::from_connection_url('sqlite://windows(c%3A/some/path/to/file.db)'); + $this->assert_equals('c:/some/path/to/file.db', $info->host); + } + + public function test_parse_connection_url_with_unix_sockets() + { + $info = ConnectionInfo::from_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database'); + $this->assert_equals('/tmp/mysql.sock',$info->host); + } + + public function test_parse_connection_url_with_decode_option() + { + $info = ConnectionInfo::from_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true'); + $this->assert_equals('h az',$info->username); + $this->assert_equals('h@i',$info->password); + } + + public function test_encoding() + { + $info = ConnectionInfo::from_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8'); + $this->assert_equals('utf8', $info->charset); + } + + public function test_connection_info_from_array(){ + $info = new ConnectionInfo(array( + 'protocol' => 'mysql', + 'host' => '127.0.0.1', + 'port' => 3306, + 'database' => 'dbname', + 'username' => 'user', + 'password' => 'pass' + )); + $this->assert_equals('mysql',$info->protocol); + $this->assert_equals('user',$info->username); + $this->assert_equals('pass',$info->password); + $this->assert_equals('127.0.0.1',$info->host); + $this->assert_equals(3306,$info->port); + $this->assert_equals('dbname',$info->database); + } + +} diff --git a/test/ConnectionTest.php b/test/ConnectionTest.php deleted file mode 100644 index a9083b356..000000000 --- a/test/ConnectionTest.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -use ActiveRecord\Connection; - - -// Only use this to test static methods in Connection that are not specific -// to any database adapter. - -class ConnectionTest extends SnakeCase_PHPUnit_Framework_TestCase -{ - /** - * @expectedException ActiveRecord\DatabaseException - */ - public function test_connection_info_from_should_throw_exception_when_no_host() - { - ActiveRecord\Connection::parse_connection_url('mysql://user:pass@'); - } - - public function test_connection_info() - { - $info = ActiveRecord\Connection::parse_connection_url('mysql://user:pass@127.0.0.1:3306/dbname'); - $this->assert_equals('mysql',$info->protocol); - $this->assert_equals('user',$info->user); - $this->assert_equals('pass',$info->pass); - $this->assert_equals('127.0.0.1',$info->host); - $this->assert_equals(3306,$info->port); - $this->assert_equals('dbname',$info->db); - } - - public function test_gh_103_sqlite_connection_string_relative() - { - $info = ActiveRecord\Connection::parse_connection_url('sqlite://../some/path/to/file.db'); - $this->assert_equals('../some/path/to/file.db', $info->host); - } - - /** - * @expectedException ActiveRecord\DatabaseException - */ - public function test_gh_103_sqlite_connection_string_absolute() - { - $info = ActiveRecord\Connection::parse_connection_url('sqlite:///some/path/to/file.db'); - } - - public function test_gh_103_sqlite_connection_string_unix() - { - $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)'); - $this->assert_equals('/some/path/to/file.db', $info->host); - - $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/'); - $this->assert_equals('/some/path/to/file.db', $info->host); - - $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/dummy'); - $this->assert_equals('/some/path/to/file.db', $info->host); - } - - public function test_gh_103_sqlite_connection_string_windows() - { - $info = ActiveRecord\Connection::parse_connection_url('sqlite://windows(c%3A/some/path/to/file.db)'); - $this->assert_equals('c:/some/path/to/file.db', $info->host); - } - - public function test_parse_connection_url_with_unix_sockets() - { - $info = ActiveRecord\Connection::parse_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database'); - $this->assert_equals('/tmp/mysql.sock',$info->host); - } - - public function test_parse_connection_url_with_decode_option() - { - $info = ActiveRecord\Connection::parse_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true'); - $this->assert_equals('h az',$info->user); - $this->assert_equals('h@i',$info->pass); - } - - public function test_encoding() - { - $info = ActiveRecord\Connection::parse_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8'); - $this->assert_equals('utf8', $info->charset); - } -} diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index a3884af9f..1285104ad 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -99,6 +99,35 @@ public function test_connect_with_port() ActiveRecord\Connection::instance($connection_string); } + public function test_connect_with_array_connection_info() + { + $config = ActiveRecord\Config::instance(); + $name = $config->get_default_connection(); + $url = parse_url($config->get_connection($name)); + $conn = $this->conn; + $port = $conn::$DEFAULT_PORT; + + $info = array( + 'protocol' => $url['scheme'], + 'host' => $url['host'] + ); + + if(isset($url['path'])){ + $info['database'] = substr($url['path'], 1); + } + + if(isset($url['user'])){ + $info['username'] = $url['user']; + } + + if(isset($url['pass'])){ + $info['password'] = $url['pass']; + } + + if ($this->conn->protocol != 'sqlite') + ActiveRecord\Connection::instance($info); + } + /** * @expectedException ActiveRecord\DatabaseException */ From b7c617e1cdd3dbbb418fc9f908cf7825bc4cb85f Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Mon, 9 Feb 2015 23:33:51 +0100 Subject: [PATCH 37/53] use parse_str for query string parsing parse_str is about 60% faster than custom parsing using foreach loop --- lib/ConnectionInfo.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index c128b76bf..70c7c9f3d 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -12,6 +12,8 @@ class ConnectionInfo { public $username = null; public $password = null; + public $charset = null; + public function __construct($input = array()){ foreach($input as $prop => $value){ $this->{$prop} = $value; @@ -40,7 +42,7 @@ public function __construct($input = array()){ * </code> * * @param string $connection_url A connection URL - * @return object the parsed URL as an object. + * @return ConnectionInfo the parsed URL as an object. */ public static function from_connection_url($connection_url){ $url = @parse_url($connection_url); @@ -96,12 +98,9 @@ public static function from_connection_url($connection_url){ if (isset($url['query'])) { - - foreach (explode('/&/', $url['query']) as $pair) { - list($name, $value) = explode('=', $pair); - - if ($name == 'charset') - $info->charset = $value; + parse_str($url['query'], $params); + if(isset($params['charset'])){ + $info->charset = $params['charset']; } } From 7d611e1e0f78073fbeb001ac603eff70ea9fa91b Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Mon, 9 Feb 2015 23:51:28 +0100 Subject: [PATCH 38/53] use factual decode param checking --- lib/ConnectionInfo.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index 70c7c9f3d..63a1402c4 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -87,21 +87,21 @@ public static function from_connection_url($connection_url){ if (isset($url['port'])) $info->port = $url['port']; - if (strpos($connection_url, 'decode=true') !== false) - { - if ($info->username) - $info->username = urldecode($info->username); - - if ($info->password) - $info->password = urldecode($info->password); - } - if (isset($url['query'])) { parse_str($url['query'], $params); if(isset($params['charset'])){ $info->charset = $params['charset']; } + + if(isset($params['decode']) && $params['decode'] == 'true' ) + { + if ($info->username) + $info->username = urldecode($info->username); + + if ($info->password) + $info->password = urldecode($info->password); + } } return $info; From a90e8cd164c59309c9dd93eb7b80a90b5050d14a Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 00:24:23 +0100 Subject: [PATCH 39/53] use sscanf instead of regex sscanf is about 40% faster than using a regex --- lib/ConnectionInfo.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index 63a1402c4..754cd2dc9 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -65,24 +65,24 @@ public static function from_connection_url($connection_url){ { $socket_database = $info->host . '/' . $info->database; - if ($allow_blank_db) - $unix_regex = '/^unix\((.+)\)\/?().*$/'; - else - $unix_regex = '/^unix\((.+)\)\/(.+)$/'; + sscanf($socket_database, 'unix(%[^)])/%s', $host, $database); - if (preg_match_all($unix_regex, $socket_database, $matches) > 0) - { - $info->host = $matches[1][0]; - $info->database = $matches[2][0]; - } - } elseif (substr($info->host, 0, 8) == 'windows(') + $info->host = $host; + $info->database = $database; + + } + elseif (substr($info->host, 0, 8) == 'windows(') { $info->host = urldecode(substr($info->host, 8) . '/' . substr($info->database, 0, -1)); $info->database = null; } + else + { + if ($allow_blank_db && $info->database) + $info->host .= '/' . $info->database; + + } - if ($allow_blank_db && $info->database) - $info->host .= '/' . $info->database; if (isset($url['port'])) $info->port = $url['port']; From 5a6f015a5ee8f8333e5b0f8303d75ebe3671e740 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 00:36:39 +0100 Subject: [PATCH 40/53] assert database name for unix socket --- test/ConnectionInfoTest.php | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/test/ConnectionInfoTest.php b/test/ConnectionInfoTest.php index 9364ca368..294be042c 100644 --- a/test/ConnectionInfoTest.php +++ b/test/ConnectionInfoTest.php @@ -15,12 +15,12 @@ public function test_connection_info_from_should_throw_exception_when_no_host() public function test_connection_info() { $info = ConnectionInfo::from_connection_url('mysql://user:pass@127.0.0.1:3306/dbname'); - $this->assert_equals('mysql',$info->protocol); - $this->assert_equals('user',$info->username); - $this->assert_equals('pass',$info->password); - $this->assert_equals('127.0.0.1',$info->host); - $this->assert_equals(3306,$info->port); - $this->assert_equals('dbname',$info->database); + $this->assert_equals('mysql', $info->protocol); + $this->assert_equals('user', $info->username); + $this->assert_equals('pass', $info->password); + $this->assert_equals('127.0.0.1', $info->host); + $this->assert_equals(3306, $info->port); + $this->assert_equals('dbname', $info->database); } public function test_gh_103_sqlite_connection_string_relative() @@ -58,14 +58,15 @@ public function test_gh_103_sqlite_connection_string_windows() public function test_parse_connection_url_with_unix_sockets() { $info = ConnectionInfo::from_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database'); - $this->assert_equals('/tmp/mysql.sock',$info->host); + $this->assert_equals('/tmp/mysql.sock', $info->host); + $this->assert_equals('database', $info->database); } public function test_parse_connection_url_with_decode_option() { $info = ConnectionInfo::from_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true'); - $this->assert_equals('h az',$info->username); - $this->assert_equals('h@i',$info->password); + $this->assert_equals('h az', $info->username); + $this->assert_equals('h@i', $info->password); } public function test_encoding() @@ -83,12 +84,12 @@ public function test_connection_info_from_array(){ 'username' => 'user', 'password' => 'pass' )); - $this->assert_equals('mysql',$info->protocol); - $this->assert_equals('user',$info->username); - $this->assert_equals('pass',$info->password); - $this->assert_equals('127.0.0.1',$info->host); - $this->assert_equals(3306,$info->port); - $this->assert_equals('dbname',$info->database); + $this->assert_equals('mysql', $info->protocol); + $this->assert_equals('user', $info->username); + $this->assert_equals('pass', $info->password); + $this->assert_equals('127.0.0.1', $info->host); + $this->assert_equals(3306, $info->port); + $this->assert_equals('dbname', $info->database); } } From 35bf5f10233cb2e6525575aaa3ec9c44ceb1c994 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 13:26:56 +0100 Subject: [PATCH 41/53] check if property exists before assignment code formatting --- lib/ConnectionInfo.php | 55 ++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index 754cd2dc9..8418920bb 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -14,9 +14,16 @@ class ConnectionInfo { public $charset = null; - public function __construct($input = array()){ - foreach($input as $prop => $value){ - $this->{$prop} = $value; + public $decode = false; + + public function __construct($input = array()) + { + foreach($input as $prop => $value) + { + if(property_exists($this, $prop)) + { + $this->{$prop} = $value; + } } } @@ -44,7 +51,8 @@ public function __construct($input = array()){ * @param string $connection_url A connection URL * @return ConnectionInfo the parsed URL as an object. */ - public static function from_connection_url($connection_url){ + public static function from_connection_url($connection_url) + { $url = @parse_url($connection_url); if (!isset($url['host'])) @@ -53,11 +61,20 @@ public static function from_connection_url($connection_url){ $info = new self(); $info->protocol = $url['scheme']; $info->host = $url['host']; - if(isset($url['path'])){ - $info->database = substr($url['path'], 1); + + if (isset($url['query'])) + { + parse_str($url['query'], $params); + + if(isset($params['charset'])) + $info->charset = $params['charset']; + + if(isset($params['decode'])) + $info->decode = ($params['decode'] == 'true'); } - $info->username = isset($url['user']) ? $url['user'] : null; - $info->password = isset($url['pass']) ? $url['pass'] : null; + + if(isset($url['path'])) + $info->database = substr($url['path'], 1); $allow_blank_db = ($info->protocol == 'sqlite'); @@ -69,7 +86,6 @@ public static function from_connection_url($connection_url){ $info->host = $host; $info->database = $database; - } elseif (substr($info->host, 0, 8) == 'windows(') { @@ -80,29 +96,16 @@ public static function from_connection_url($connection_url){ { if ($allow_blank_db && $info->database) $info->host .= '/' . $info->database; - } - if (isset($url['port'])) $info->port = $url['port']; - if (isset($url['query'])) - { - parse_str($url['query'], $params); - if(isset($params['charset'])){ - $info->charset = $params['charset']; - } - - if(isset($params['decode']) && $params['decode'] == 'true' ) - { - if ($info->username) - $info->username = urldecode($info->username); + if (isset($url['user'])) + $info->username = $info->decode ? urldecode($url['user']) : $url['user']; - if ($info->password) - $info->password = urldecode($info->password); - } - } + if (isset($url['pass'])) + $info->password = $info->decode ? urldecode($url['pass']) : $url['pass']; return $info; } From 3781525540c4b417751d14a0b2c4709207510276 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 14:48:15 +0100 Subject: [PATCH 42/53] build ConnectionInfo objects on instantiation --- lib/Config.php | 29 ++++++++++++++++++++++---- lib/Connection.php | 32 ++++++++++++++--------------- lib/ConnectionManager.php | 2 +- test/ConfigTest.php | 28 +++++++++++++++----------- test/MysqlAdapterTest.php | 7 ++++--- test/OciAdapterTest.php | 5 +++-- test/PgsqlAdapterTest.php | 5 +++-- test/helpers/AdapterTest.php | 38 ++++++++--------------------------- test/helpers/DatabaseTest.php | 3 ++- 9 files changed, 77 insertions(+), 72 deletions(-) diff --git a/lib/Config.php b/lib/Config.php index 378a5b2e3..25d1e9f53 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -118,13 +118,25 @@ public static function initialize(Closure $initializer) } /** - * Sets the list of database connection strings. + * Sets the list of database connections. Can be an array of connection strings or an array of arrays. * * <code> * $config->set_connections(array( * 'development' => 'mysql://username:password@127.0.0.1/database_name')); * </code> * + * <code> + * $config->set_connections(array( + * 'development' => array( + * 'protocol' => 'mysql', + * 'host' => '127.0.0.1', + * 'database' => 'database_name', + * 'username' => 'username', + * 'password' => 'password' + * ) + * )); + * </code> + * * @param array $connections Array of connections * @param string $default_connection Optionally specify the default_connection * @return void @@ -138,7 +150,16 @@ public function set_connections($connections, $default_connection=null) if ($default_connection) $this->set_default_connection($default_connection); - $this->connections = $connections; + $this->connections = array_map(function($connection){ + if(is_string($connection)) + { + return ConnectionInfo::from_connection_url($connection); + } + else + { + return new ConnectionInfo($connection); + } + }, $connections); } /** @@ -157,7 +178,7 @@ public function get_connections() * @param string $name Name of connection to retrieve * @return string connection info for specified connection name */ - public function get_connection($name) + public function get_connection_info($name) { if (array_key_exists($name, $this->connections)) return $this->connections[$name]; @@ -170,7 +191,7 @@ public function get_connection($name) * * @return string */ - public function get_default_connection_string() + public function get_default_connection_info() { return array_key_exists($this->default_connection,$this->connections) ? $this->connections[$this->default_connection] : null; diff --git a/lib/Connection.php b/lib/Connection.php index 95ce96abd..81719b09e 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -91,7 +91,7 @@ abstract class Connection /** * Retrieve a database connection. * - * @param string $connection_string_or_connection_name A database connection string (ex. mysql://user:pass@host[:port]/dbname) + * @param string $connection_info_or_name A database connection string (ex. mysql://user:pass@host[:port]/dbname) * Everything after the protocol:// part is specific to the connection adapter. * OR * A connection name that is set in ActiveRecord\Config @@ -99,31 +99,29 @@ abstract class Connection * @return Connection * @see ConnectionInfo::from_connection_url */ - public static function instance($connection_info_or_name=null) + public static function instance($connection_info=null) { $config = Config::instance(); - if (!is_array($connection_info_or_name)) + if (!($connection_info instanceof ConnectionInfo)) { - if (strpos($connection_info_or_name, '://') === false) + // Connection instantiation using a connection array + if (is_array($connection_info)) { - $connection_url = $connection_info_or_name ? - $config->get_connection($connection_info_or_name) : - $config->get_default_connection_string(); + $connection_info = new ConnectionInfo($connection_info); } + // Connection instantiation using a connection url + else if (is_string($connection_info) && strpos($connection_info, '://') !== false) + { + $connection_info = ConnectionInfo::from_connection_url($connection_info); + } + // Connection instantiation using a connection name else { - $connection_url = $connection_info_or_name; + $connection_info = $connection_info ? + $config->get_connection_info($connection_info) : + $config->get_default_connection_info(); } - - if (!$connection_url) - throw new DatabaseException("Empty connection string"); - - $connection_info = ConnectionInfo::from_connection_url($connection_url); - } - else - { - $connection_info = new ConnectionInfo($connection_info_or_name); } $fqclass = static::load_adapter_class($connection_info->protocol); diff --git a/lib/ConnectionManager.php b/lib/ConnectionManager.php index 727c33dac..11ed5705a 100644 --- a/lib/ConnectionManager.php +++ b/lib/ConnectionManager.php @@ -30,7 +30,7 @@ public static function get_connection($name=null) $name = $name ? $name : $config->get_default_connection(); if (!isset(self::$connections[$name]) || !self::$connections[$name]->connection) - self::$connections[$name] = Connection::instance($config->get_connection($name)); + self::$connections[$name] = Connection::instance($config->get_connection_info($name)); return self::$connections[$name]; } diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 518413f11..a6b12a140 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -38,36 +38,40 @@ public function test_set_connections_must_be_array() public function test_get_connections() { - $this->assert_equals($this->connections,$this->config->get_connections()); + $connections = $this->config->get_connections(); + $this->assert_instance_of('ActiveRecord\ConnectionInfo', $connections['development']); + $this->assert_equals('development', $connections['development']->database); } - public function test_get_connection() + public function test_get_connection_info() { - $this->assert_equals($this->connections['development'],$this->config->get_connection('development')); + $connection = $this->config->get_connection_info('development'); + $this->assert_instance_of('ActiveRecord\ConnectionInfo', $connection); + $this->assert_equals('development', $connection->database); } public function test_get_invalid_connection() { - $this->assert_null($this->config->get_connection('whiskey tango foxtrot')); + $this->assert_null($this->config->get_connection_info('whiskey tango foxtrot')); } public function test_get_default_connection_and_connection() { - $this->config->set_default_connection('development'); - $this->assert_equals('development',$this->config->get_default_connection()); - $this->assert_equals($this->connections['development'],$this->config->get_default_connection_string()); + $this->config->set_default_connection('test'); + $this->assert_equals('test', $this->config->get_default_connection()); + $this->assert_equals('test', $this->config->get_default_connection_info()->database); } - public function test_get_default_connection_and_connection_string_defaults_to_development() + public function test_get_default_connection_and_connection_info_defaults_to_development() { - $this->assert_equals('development',$this->config->get_default_connection()); - $this->assert_equals($this->connections['development'],$this->config->get_default_connection_string()); + $this->assert_equals('development', $this->config->get_default_connection()); + $this->assert_equals('development', $this->config->get_default_connection_info()->database); } - public function test_get_default_connection_string_when_connection_name_is_not_valid() + public function test_get_default_connection_info_when_connection_name_is_not_valid() { $this->config->set_default_connection('little mac'); - $this->assert_null($this->config->get_default_connection_string()); + $this->assert_null($this->config->get_default_connection_info()); } public function test_default_connection_is_set_when_only_one_connection_is_present() diff --git a/test/MysqlAdapterTest.php b/test/MysqlAdapterTest.php index 90837ba57..f7153eea8 100644 --- a/test/MysqlAdapterTest.php +++ b/test/MysqlAdapterTest.php @@ -20,9 +20,10 @@ public function test_enum() public function test_set_charset() { - $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name); - $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8'); - $this->assert_equals('SET NAMES ?',$conn->last_query); + $connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name); + $connection_info->charset = 'utf8'; + $conn = ActiveRecord\Connection::instance($connection_info); + $this->assert_equals('SET NAMES ?', $conn->last_query); } public function test_limit_with_null_offset_does_not_contain_offset() diff --git a/test/OciAdapterTest.php b/test/OciAdapterTest.php index f93170b11..2a8fa7376 100644 --- a/test/OciAdapterTest.php +++ b/test/OciAdapterTest.php @@ -38,8 +38,9 @@ public function test_columns_sequence() {} public function test_set_charset() { - $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name); - $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8'); + $connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name); + $connection_info->charset = 'utf8'; + $conn = ActiveRecord\Connection::instance($connection_info); $this->assert_equals(';charset=utf8', $conn->dsn_params); } } diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php index b4fcb0f41..c7d23a594 100644 --- a/test/PgsqlAdapterTest.php +++ b/test/PgsqlAdapterTest.php @@ -31,8 +31,9 @@ public function test_insert_id_should_return_explicitly_inserted_id() public function test_set_charset() { - $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name); - $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8'); + $connection_info = ActiveRecord\Config::instance()->get_connection_info($this->connection_name); + $connection_info->charset = 'utf8'; + $conn = ActiveRecord\Connection::instance($connection_info); $this->assert_equals("SET NAMES 'utf8'",$conn->last_query); } diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index 1285104ad..c0dbd8d7e 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -8,7 +8,7 @@ class AdapterTest extends DatabaseTest public function set_up($connection_name=null) { if (($connection_name && !in_array($connection_name, PDO::getAvailableDrivers())) || - ActiveRecord\Config::instance()->get_connection($connection_name) == 'skip') + ActiveRecord\Config::instance()->get_connection_info($connection_name) == 'skip') $this->mark_test_skipped($connection_name . ' drivers are not present'); parent::set_up($connection_name); @@ -84,48 +84,26 @@ public function test_connect_failed() public function test_connect_with_port() { $config = ActiveRecord\Config::instance(); - $name = $config->get_default_connection(); - $url = parse_url($config->get_connection($name)); + $info = $config->get_default_connection_info(); + $conn = $this->conn; $port = $conn::$DEFAULT_PORT; - $connection_string = "{$url['scheme']}://{$url['user']}"; - if(isset($url['pass'])){ - $connection_string = "{$connection_string}:{$url['pass']}"; - } - $connection_string = "{$connection_string}@{$url['host']}:$port{$url['path']}"; + $info->port = $port; if ($this->conn->protocol != 'sqlite') - ActiveRecord\Connection::instance($connection_string); + ActiveRecord\Connection::instance($info); } public function test_connect_with_array_connection_info() { $config = ActiveRecord\Config::instance(); - $name = $config->get_default_connection(); - $url = parse_url($config->get_connection($name)); - $conn = $this->conn; - $port = $conn::$DEFAULT_PORT; - - $info = array( - 'protocol' => $url['scheme'], - 'host' => $url['host'] - ); + $info = $config->get_default_connection_info(); - if(isset($url['path'])){ - $info['database'] = substr($url['path'], 1); - } - - if(isset($url['user'])){ - $info['username'] = $url['user']; - } - - if(isset($url['pass'])){ - $info['password'] = $url['pass']; - } + $info_array = array_filter(get_object_vars($info)); if ($this->conn->protocol != 'sqlite') - ActiveRecord\Connection::instance($info); + ActiveRecord\Connection::instance($info_array); } /** diff --git a/test/helpers/DatabaseTest.php b/test/helpers/DatabaseTest.php index 6cdcadd17..a597b768d 100644 --- a/test/helpers/DatabaseTest.php +++ b/test/helpers/DatabaseTest.php @@ -22,7 +22,8 @@ public function set_up($connection_name=null) if ($connection_name == 'sqlite' || $config->get_default_connection() == 'sqlite') { // need to create the db. the adapter specifically does not create it for us. - static::$db = substr(ActiveRecord\Config::instance()->get_connection('sqlite'),9); + $info = ActiveRecord\Config::instance()->get_connection_info('sqlite'); + static::$db = $info->host; new SQLite3(static::$db); } From 7c8eaa8a1d74371a4ee8567a85ffd6e9d0bbebc8 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 15:12:31 +0100 Subject: [PATCH 43/53] add array example --- test/ConfigTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/ConfigTest.php b/test/ConfigTest.php index a6b12a140..ac2fe079b 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -24,7 +24,14 @@ class ConfigTest extends SnakeCase_PHPUnit_Framework_TestCase public function set_up() { $this->config = new Config(); - $this->connections = array('development' => 'mysql://blah/development', 'test' => 'mysql://blah/test'); + $this->connections = array( + 'development' => 'mysql://blah/development', + 'test' => 'mysql://blah/test', + 'production' => array( + 'protocol' => 'mysql', + 'host' => '127.0.0.1', + 'database' => 'production' + )); $this->config->set_connections($this->connections); } From 4507f7fe070769a2909e0a8e6c54746503f5f3a8 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 10 Feb 2015 17:12:42 +0100 Subject: [PATCH 44/53] rename protocol to adapter --- lib/Config.php | 2 +- lib/Connection.php | 12 ++++++------ lib/ConnectionInfo.php | 12 ++++++------ test/ConfigTest.php | 2 +- test/ConnectionInfoTest.php | 6 +++--- test/helpers/AdapterTest.php | 18 +++++++++--------- test/helpers/DatabaseLoader.php | 18 +++++++++--------- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/Config.php b/lib/Config.php index 25d1e9f53..d2ed17c44 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -128,7 +128,7 @@ public static function initialize(Closure $initializer) * <code> * $config->set_connections(array( * 'development' => array( - * 'protocol' => 'mysql', + * 'adapter' => 'mysql', * 'host' => '127.0.0.1', * 'database' => 'database_name', * 'username' => 'username', diff --git a/lib/Connection.php b/lib/Connection.php index 81719b09e..fc71cc4f8 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -54,10 +54,10 @@ abstract class Connection */ private $logger; /** - * The name of the protocol that is used. + * The name of the adapter that is used. * @var string */ - public $protocol; + public $adapter; /** * Database's date format * @var string @@ -92,7 +92,7 @@ abstract class Connection * Retrieve a database connection. * * @param string $connection_info_or_name A database connection string (ex. mysql://user:pass@host[:port]/dbname) - * Everything after the protocol:// part is specific to the connection adapter. + * Everything after the adapter:// part is specific to the connection adapter. * OR * A connection name that is set in ActiveRecord\Config * If null it will use the default connection specified by ActiveRecord\Config->set_default_connection @@ -124,11 +124,11 @@ public static function instance($connection_info=null) } } - $fqclass = static::load_adapter_class($connection_info->protocol); + $fqclass = static::load_adapter_class($connection_info->adapter); try { $connection = new $fqclass($connection_info); - $connection->protocol = $connection_info->protocol; + $connection->adapter = $connection_info->adapter; $connection->logging = $config->get_logging(); $connection->logger = $connection->logging ? $config->get_logger() : null; @@ -179,7 +179,7 @@ protected function __construct($info) else $host = "unix_socket=$info->host"; - $this->connection = new PDO("$info->protocol:$host;dbname=$info->database", $info->username, $info->password, static::$PDO_OPTIONS); + $this->connection = new PDO("$info->adapter:$host;dbname=$info->database", $info->username, $info->password, static::$PDO_OPTIONS); } catch (PDOException $e) { throw new DatabaseException($e); } diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index 8418920bb..cc0b74e20 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -4,7 +4,7 @@ class ConnectionInfo { - public $protocol = null; + public $adapter = null; public $host = null; public $port = null; public $database = null; @@ -34,9 +34,9 @@ public function __construct($input = array()) * to set the adapters connection info. * * <code> - * protocol://username:password@host[:port]/dbname - * protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true - * protocol://username:password@unix(/some/file/path)/dbname + * adapter://username:password@host[:port]/dbname + * adapter://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true + * adapter://username:password@unix(/some/file/path)/dbname * </code> * * Sqlite has a special syntax, as it does not need a database name or user authentication: @@ -59,7 +59,7 @@ public static function from_connection_url($connection_url) throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)'); $info = new self(); - $info->protocol = $url['scheme']; + $info->adapter = $url['scheme']; $info->host = $url['host']; if (isset($url['query'])) @@ -76,7 +76,7 @@ public static function from_connection_url($connection_url) if(isset($url['path'])) $info->database = substr($url['path'], 1); - $allow_blank_db = ($info->protocol == 'sqlite'); + $allow_blank_db = ($info->adapter == 'sqlite'); if ($info->host == 'unix(') { diff --git a/test/ConfigTest.php b/test/ConfigTest.php index ac2fe079b..f15510a27 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -28,7 +28,7 @@ public function set_up() 'development' => 'mysql://blah/development', 'test' => 'mysql://blah/test', 'production' => array( - 'protocol' => 'mysql', + 'adapter' => 'mysql', 'host' => '127.0.0.1', 'database' => 'production' )); diff --git a/test/ConnectionInfoTest.php b/test/ConnectionInfoTest.php index 294be042c..cea868509 100644 --- a/test/ConnectionInfoTest.php +++ b/test/ConnectionInfoTest.php @@ -15,7 +15,7 @@ public function test_connection_info_from_should_throw_exception_when_no_host() public function test_connection_info() { $info = ConnectionInfo::from_connection_url('mysql://user:pass@127.0.0.1:3306/dbname'); - $this->assert_equals('mysql', $info->protocol); + $this->assert_equals('mysql', $info->adapter); $this->assert_equals('user', $info->username); $this->assert_equals('pass', $info->password); $this->assert_equals('127.0.0.1', $info->host); @@ -77,14 +77,14 @@ public function test_encoding() public function test_connection_info_from_array(){ $info = new ConnectionInfo(array( - 'protocol' => 'mysql', + 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'dbname', 'username' => 'user', 'password' => 'pass' )); - $this->assert_equals('mysql', $info->protocol); + $this->assert_equals('mysql', $info->adapter); $this->assert_equals('user', $info->username); $this->assert_equals('pass', $info->password); $this->assert_equals('127.0.0.1', $info->host); diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index c0dbd8d7e..b4a156c26 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -25,7 +25,7 @@ public function test_i_has_a_default_port_unless_im_sqlite() public function test_should_set_adapter_variables() { - $this->assert_not_null($this->conn->protocol); + $this->assert_not_null($this->conn->adapter); } public function test_null_connection_string_uses_default_connection() @@ -38,7 +38,7 @@ public function test_null_connection_string_uses_default_connection() /** * @expectedException ActiveRecord\DatabaseException */ - public function test_invalid_connection_protocol() + public function test_invalid_connection_adapter() { ActiveRecord\Connection::instance('terribledb://user:pass@host/db'); } @@ -51,7 +51,7 @@ public function test_no_host_connection() if (!$GLOBALS['slow_tests']) throw new ActiveRecord\DatabaseException(""); - ActiveRecord\Connection::instance("{$this->conn->protocol}://user:pass"); + ActiveRecord\Connection::instance("{$this->conn->adapter}://user:pass"); } /** @@ -62,7 +62,7 @@ public function test_connection_failed_invalid_host() if (!$GLOBALS['slow_tests']) throw new ActiveRecord\DatabaseException(""); - ActiveRecord\Connection::instance("{$this->conn->protocol}://user:pass/1.1.1.1/db"); + ActiveRecord\Connection::instance("{$this->conn->adapter}://user:pass/1.1.1.1/db"); } /** @@ -70,7 +70,7 @@ public function test_connection_failed_invalid_host() */ public function test_connection_failed() { - ActiveRecord\Connection::instance("{$this->conn->protocol}://baduser:badpass@127.0.0.1/db"); + ActiveRecord\Connection::instance("{$this->conn->adapter}://baduser:badpass@127.0.0.1/db"); } /** @@ -78,7 +78,7 @@ public function test_connection_failed() */ public function test_connect_failed() { - ActiveRecord\Connection::instance("{$this->conn->protocol}://zzz:zzz@127.0.0.1/test"); + ActiveRecord\Connection::instance("{$this->conn->adapter}://zzz:zzz@127.0.0.1/test"); } public function test_connect_with_port() @@ -91,7 +91,7 @@ public function test_connect_with_port() $info->port = $port; - if ($this->conn->protocol != 'sqlite') + if ($this->conn->adapter != 'sqlite') ActiveRecord\Connection::instance($info); } @@ -102,7 +102,7 @@ public function test_connect_with_array_connection_info() $info_array = array_filter(get_object_vars($info)); - if ($this->conn->protocol != 'sqlite') + if ($this->conn->adapter != 'sqlite') ActiveRecord\Connection::instance($info_array); } @@ -111,7 +111,7 @@ public function test_connect_with_array_connection_info() */ public function test_connect_to_invalid_database() { - ActiveRecord\Connection::instance("{$this->conn->protocol}://test:test@127.0.0.1/" . self::InvalidDb); + ActiveRecord\Connection::instance("{$this->conn->adapter}://test:test@127.0.0.1/" . self::InvalidDb); } public function test_date_time_type() diff --git a/test/helpers/DatabaseLoader.php b/test/helpers/DatabaseLoader.php index be250b9b0..e478d7e1b 100644 --- a/test/helpers/DatabaseLoader.php +++ b/test/helpers/DatabaseLoader.php @@ -8,14 +8,14 @@ public function __construct($db) { $this->db = $db; - if (!isset(static::$instances[$db->protocol])) - static::$instances[$db->protocol] = 0; + if (!isset(static::$instances[$db->adapter])) + static::$instances[$db->adapter] = 0; - if (static::$instances[$db->protocol]++ == 0) + if (static::$instances[$db->adapter]++ == 0) { // drop and re-create the tables one time only $this->drop_tables(); - $this->exec_sql_script($db->protocol); + $this->exec_sql_script($db->adapter); } } @@ -23,14 +23,14 @@ public function reset_table_data() { foreach ($this->get_fixture_tables() as $table) { - if ($this->db->protocol == 'oci' && $table == 'rm-bldg') + if ($this->db->adapter == 'oci' && $table == 'rm-bldg') continue; $this->db->query('DELETE FROM ' . $this->quote_name($table)); $this->load_fixture_data($table); } - $after_fixtures = $this->db->protocol.'-after-fixtures'; + $after_fixtures = $this->db->adapter.'-after-fixtures'; try { $this->exec_sql_script($after_fixtures); } catch (Exception $e) { @@ -44,7 +44,7 @@ public function drop_tables() foreach ($this->get_fixture_tables() as $table) { - if ($this->db->protocol == 'oci') + if ($this->db->adapter == 'oci') { $table = strtoupper($table); @@ -55,7 +55,7 @@ public function drop_tables() if (in_array($table,$tables)) $this->db->query('DROP TABLE ' . $this->quote_name($table)); - if ($this->db->protocol == 'oci') + if ($this->db->adapter == 'oci') { try { $this->db->query("DROP SEQUENCE {$table}_seq"); @@ -121,7 +121,7 @@ public function load_fixture_data($table) public function quote_name($name) { - if ($this->db->protocol == 'oci') + if ($this->db->adapter == 'oci') $name = strtoupper($name); return $this->db->quote_name($name); From d447040a55fc9fca6fdf9c86fa9f7f284724c690 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 4 Jun 2017 02:15:12 +0200 Subject: [PATCH 45/53] add docblocks --- lib/ConnectionInfo.php | 49 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/ConnectionInfo.php b/lib/ConnectionInfo.php index cc0b74e20..74081b2ea 100644 --- a/lib/ConnectionInfo.php +++ b/lib/ConnectionInfo.php @@ -4,18 +4,55 @@ class ConnectionInfo { + /** + * The adapter to use for this connection + * + * @var string + */ public $adapter = null; + + /** + * The host to use for this connection. + * + * @var string + */ public $host = null; + + /** + * The port to use for this connection. + * + * @var int + */ public $port = null; + + /** + * The database to use for this connection. + * + * @var string + */ public $database = null; + /** + * The username to use for this connection. + * + * @var string + */ public $username = null; + + /** + * The password to use for this connection. + * + * @var string + */ public $password = null; + /** + * The charset to use for this connection. + * + * @var string + */ public $charset = null; - public $decode = false; - public function __construct($input = array()) { foreach($input as $prop => $value) @@ -62,6 +99,8 @@ public static function from_connection_url($connection_url) $info->adapter = $url['scheme']; $info->host = $url['host']; + $decode = false; + if (isset($url['query'])) { parse_str($url['query'], $params); @@ -70,7 +109,7 @@ public static function from_connection_url($connection_url) $info->charset = $params['charset']; if(isset($params['decode'])) - $info->decode = ($params['decode'] == 'true'); + $decode = ($params['decode'] == 'true'); } if(isset($url['path'])) @@ -102,10 +141,10 @@ public static function from_connection_url($connection_url) $info->port = $url['port']; if (isset($url['user'])) - $info->username = $info->decode ? urldecode($url['user']) : $url['user']; + $info->username = $decode ? urldecode($url['user']) : $url['user']; if (isset($url['pass'])) - $info->password = $info->decode ? urldecode($url['pass']) : $url['pass']; + $info->password = $decode ? urldecode($url['pass']) : $url['pass']; return $info; } From 290089b07208bb76c7fbc1104e91e187d8ec475f Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 11 Jun 2017 15:34:42 +0200 Subject: [PATCH 46/53] use is_hash instead of is_array --- lib/StrongParameters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php index 353428a63..13fc61b51 100644 --- a/lib/StrongParameters.php +++ b/lib/StrongParameters.php @@ -80,7 +80,7 @@ protected function parse(array $data) { return array_map(function($value) { - if (!is_array($value)) + if (!is_hash($value)) { return $value; } From 59d842782b17ec7392c3f65f4654b484d3d6bc8a Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 11 Jun 2017 15:34:54 +0200 Subject: [PATCH 47/53] add snake_case alias for requireParam --- lib/StrongParameters.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php index 13fc61b51..c761b5f7f 100644 --- a/lib/StrongParameters.php +++ b/lib/StrongParameters.php @@ -14,7 +14,7 @@ * * public function beforeFilter() * { - * $this->request->params = new ActiveRecord\StrongParameters($_POST); + * $this->params = new ActiveRecord\StrongParameters($_POST); * } * * This assumes that your POST data is grouped as follows; @@ -38,7 +38,7 @@ * * protected function user_params() * { - * return $this->request->params->requireParam('user')->permit('name', 'bio', 'email'); + * return $this->params->require_param('user')->permit('name', 'bio', 'email'); * } * * @@ -139,6 +139,14 @@ public function requireParam($key) return $param; } + /** + * @see requireParam + */ + public function require_param($key) + { + return $this->requireParam($key); + } + /** * Required method for IteratorAggregate interface. * From 939dc784075062a173c403606cb1514fd2c97c90 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 11 Jun 2017 16:15:07 +0200 Subject: [PATCH 48/53] implement ArrayAccess fetching values is now also possible using ArrayAccess. So instead of $params->fetch('name') You can now also do: $params['name']; --- lib/StrongParameters.php | 67 +++++++++++++++++++++++++++++++---- test/StrongParametersTest.php | 63 +++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/lib/StrongParameters.php b/lib/StrongParameters.php index c761b5f7f..45f42161b 100644 --- a/lib/StrongParameters.php +++ b/lib/StrongParameters.php @@ -4,22 +4,25 @@ use IteratorAggregate; use ArrayIterator; +use ArrayAccess; +use InvalidArgumentException; /** * The use of StrongParameters is implementation dependent. * * You probably want to integrate this somewhere in your application where the POST data is processed. * - * For example, in your Controller class you can process the POST data: + * For example, in your controller class you can process the request data: * * public function beforeFilter() * { - * $this->params = new ActiveRecord\StrongParameters($_POST); + * $this->params = new ActiveRecord\StrongParameters(array_merge($_GET, $_POST)); * } * - * This assumes that your POST data is grouped as follows; + * This example assumes that your request data is structured as follows; * - * $_POST = array( + * array( + * "id" => 1, * "user" => array( * "name" => "Foo Bar", * "bio" => "I'm Foo Bar", @@ -27,11 +30,11 @@ * ) * ) * - * And then in the UserController class you can access the data + * And then to use the data in a controller: * * public function update_profile() * { - * $user = User::find($this->request->params['id']); + * $user = User::find($this->params['id']); * $user->update_attributes($this->user_params()); * $this->redirect('back'); * } @@ -44,7 +47,7 @@ * * @package ActiveRecord */ -class StrongParameters implements IteratorAggregate +class StrongParameters implements IteratorAggregate, ArrayAccess { /** * Array containing data @@ -158,4 +161,54 @@ public function getIterator() return new ArrayIterator($permitted_data); } + /** + * Required method for ArrayAccess interface. + * + * @param string $offset + * @return bool true if offset exists + */ + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + /** + * Required method for ArrayAccess interface. + * + * @param string $offset + * @return mixed + * @see fetch + */ + public function offsetGet($offset) + { + return $this->fetch($offset); + } + + /** + * Required method for ArrayAccess interface. + * + * @param string $offset + * @param mixed $value + * @return void + * @throws InvalidArgumentException when offset is null + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + throw new InvalidArgumentException('offset cannot be null'); + } + $this->data[$offset] = $value; + } + + /** + * Required method for ArrayAccess interface. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + } diff --git a/test/StrongParametersTest.php b/test/StrongParametersTest.php index 5345d13ce..750ba6c60 100644 --- a/test/StrongParametersTest.php +++ b/test/StrongParametersTest.php @@ -77,7 +77,6 @@ public function testRequireParamThrowsOnMissingParam() $this->params->requireParam('nonexisting'); } - public function testFetch() { $params = new StrongParameters(array('name' => 'Foo Bar')); @@ -93,5 +92,67 @@ public function testGetIterator() $this->assertInstanceOf('ArrayIterator', $this->params->getIterator()); } + public function testArrayAccessOffsetExists() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $this->assertTrue(isset($params['name'])); + $this->assertFalse(isset($params['undefined'])); + } + + public function testArrayAccessOffsetGet() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $this->assertEquals('Foo Bar', $params['name']); + + $this->assertEquals('Foo Bar', $this->params['user']->fetch('name')); + $this->assertEquals('Foo Bar', $this->params['user']['name']); + } + + public function testArrayAccessOffsetSet() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $params['test'] = 'foobar'; + $params->permit('test', 'name'); + $this->assertEquals(array('name' => 'Foo Bar', 'test' => 'foobar'), iterator_to_array($params)); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage offset cannot be null + */ + public function testArrayAccessOffsetSetThrowsOnNull() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $params[] = 'foobar'; + } + + public function testArrayAccessOffsetUnset() + { + $params = new StrongParameters(array('name' => 'Foo Bar')); + $params['test'] = 'foobar'; + $params->permit('test', 'name'); + $this->assertEquals(array('name' => 'Foo Bar', 'test' => 'foobar'), iterator_to_array($params)); + unset($params['test']); + $this->assertEquals(array('name' => 'Foo Bar'), iterator_to_array($params)); + unset($params['name']); + $this->assertEquals(array(), iterator_to_array($params)); + } + + public function testArrayAccessOffsetUnsetNested() + { + $user = $this->params->fetch('user'); + $user->permit('name', 'email'); + + $this->assertEquals(array( + 'name' => 'Foo Bar', + 'email' => 'foo@bar.baz', + ), iterator_to_array($user)); + + unset($this->params['user']['name']); + $this->assertEquals(array( + 'email' => 'foo@bar.baz', + ), iterator_to_array($user)); + } + } From 74ac838418d005be166e7e6c32d8cf3ad61032a8 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 6 Dec 2014 23:09:24 +0100 Subject: [PATCH 49/53] resolve test issues add missing tables to pgsql fixture add password to users table make test more readable --- test/ValidationsTest.php | 16 ++++++++-------- test/sql/mysql.sql | 5 +++-- test/sql/pgsql.sql | 14 +++++++++++++- test/sql/sqlite.sql | 3 ++- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index 499cfed4d..d34f9e07c 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -20,7 +20,7 @@ public function validate() class UserValidations extends AR\Model { - static $table_name = 'user'; + static $table_name = 'users'; public $password_confirm; @@ -103,7 +103,6 @@ public function test_is_valid_does_not_revalidate() ); $user = new UserValidations($attrs); - $invalid = !( $valid = $user->is_valid() ); /** * The `is_valid()` method will validate the User. In this test it will * be valid. @@ -111,8 +110,8 @@ public function test_is_valid_does_not_revalidate() * rehashed, becoming different from `password` and then the result * would be different from precedent (and also a bug). */ - $this->assert_equals($valid, $user->is_valid()); - $this->assert_equals($invalid, $user->is_invalid()); + $this->assert_true($user->is_valid()); + $this->assert_equals(!$user->is_valid(), $user->is_invalid()); } public function test_is_valid_will_revalidate_if_attribute_changes() @@ -126,11 +125,12 @@ public function test_is_valid_will_revalidate_if_attribute_changes() $this->assert_false($user->is_valid()); $user->password = 'secret'; - // because custom validation is codeded bad (on purpose), we have to + // because custom validation is coded bad (on purpose), we have to // reset password_confirm $user->password_confirm = 'secret'; $this->assert_true($user->is_valid()); } + public function test_is_invalid_will_revalidate_if_attribute_changes() { $attrs = array( @@ -142,7 +142,7 @@ public function test_is_invalid_will_revalidate_if_attribute_changes() $this->assert_true($user->is_invalid()); $user->password = 'secret'; - // because custom validation is codeded bad (on purpose), we have to + // because custom validation is coded bad (on purpose), we have to // reset password_confirm $user->password_confirm = 'secret'; $this->assert_false($user->is_invalid()); @@ -160,11 +160,11 @@ public function test_is_valid_must_be_forced_if_a_virtual_attribute_changes() $user->password_confirm = 'secret'; // Actually we check only attribute set by `__set` magic method. - $this->assert_false( $user->is_valid() ); + $this->assert_false($user->is_valid()); // Passing `true` will force the validation of `user`, giving the // right result. - $this->assert_true( $user->is_valid(true) ); + $this->assert_true($user->is_valid(true)); } public function test_is_iterable() diff --git a/test/sql/mysql.sql b/test/sql/mysql.sql index 60e529bc6..90dd82242 100644 --- a/test/sql/mysql.sql +++ b/test/sql/mysql.sql @@ -92,7 +92,8 @@ CREATE TABLE property_amenities( ); CREATE TABLE users ( - id INT NOT NULL AUTO_INCREMENT PRIMARY KEY + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + password VARCHAR(255) NOT NULL ) ENGINE=InnoDB; CREATE TABLE newsletters ( @@ -109,4 +110,4 @@ CREATE TABLE valuestore ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `key` varchar(20) NOT NULL DEFAULT '', `value` varchar(255) NOT NULL DEFAULT '' -) ENGINE=InnoDB; \ No newline at end of file +) ENGINE=InnoDB; diff --git a/test/sql/pgsql.sql b/test/sql/pgsql.sql index edda21fae..fdc91dd80 100644 --- a/test/sql/pgsql.sql +++ b/test/sql/pgsql.sql @@ -91,7 +91,8 @@ CREATE TABLE property_amenities( ); CREATE TABLE users( - id serial primary key + id serial primary key, + password varchar(255) not null ); CREATE TABLE newsletters( @@ -104,6 +105,17 @@ CREATE TABLE user_newsletters( newsletter_id int not null ); +CREATE TABLE services( + id serial primary key, + code varchar(255) not null +); + +CREATE TABLE active_services ( + id serial primary key, + user_id int not null, + serv_id int not null +); + CREATE TABLE valuestore ( id serial primary key, key varchar(20) NOT NULL DEFAULT '', diff --git a/test/sql/sqlite.sql b/test/sql/sqlite.sql index b5abfeda0..35efdc6bf 100644 --- a/test/sql/sqlite.sql +++ b/test/sql/sqlite.sql @@ -91,7 +91,8 @@ CREATE TABLE property_amenities( ); CREATE TABLE users ( - id INTEGER NOT NULL PRIMARY KEY + id INTEGER NOT NULL PRIMARY KEY, + password VARCHAR(255) NOT NULL ); CREATE TABLE newsletters ( From 5af90efd69f6ddff06b885b5de224bfd4793f3d0 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sat, 6 Dec 2014 23:50:50 +0100 Subject: [PATCH 50/53] update naming of validated flag --- lib/Model.php | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 1cbf12a47..a8aa5a573 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -87,11 +87,6 @@ class Model */ private $attributes = array(); - /** - * Flag wheter or not this model has been validated - */ - private $_flag_validated = false; - /** * Contains changes made to model attributes as * $attribute_name => array($original_value, $current_value) @@ -137,6 +132,11 @@ class Model */ private $__new_record = true; + /** + * Flag whether or not this model has been validated + */ + private $__validated = false; + /** * Set to the name of the connection this {@link Model} should use. * @@ -508,7 +508,7 @@ public function assign_attribute($name, $value) // set the attribute and flag as dirty $this->attributes[$name] = $value; $this->flag_dirty($name); - $this->nullify_validation(); + $this->reset_validated(); } return $value; } @@ -1160,7 +1160,7 @@ private function _validate() { require_once 'Validations.php'; - $this->validated(); + $this->flag_validated(); $validator = new Validations($this); $validation_mode = $this->is_new_record() ? 'create' : 'update'; @@ -1186,19 +1186,34 @@ private function _validate() return true; } + /** + * Returns true if the model has been validated. + * + * @return boolean true if validated + */ private function is_validated() { - return $this->_flag_validated; + return $this->__validated; } - private function validated() + /** + * Flag model as validated + * + * @return void + */ + private function flag_validated() { - $this->_flag_validated = true; + $this->__validated = true; } - private function nullify_validation() + /** + * Resets the validated flag. + * + * @return void + */ + private function reset_validated() { - $this->_flag_validated = false; + $this->__validated = false; } /** @@ -1401,7 +1416,7 @@ public function reload() $this->expire_cache(); $this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false); $this->reset_dirty(); - $this->nullify_validation(); + $this->reset_validated(); return $this; } @@ -1410,7 +1425,7 @@ public function __clone() { $this->__relationships = array(); $this->reset_dirty(); - $this->nullify_validation(); + $this->reset_validated(); return $this; } From 2f23190a6fde3155396cdd6be38769d892e637e4 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 4 Jun 2017 01:30:15 +0200 Subject: [PATCH 51/53] simplify docblock for is_valid --- lib/Model.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index a8aa5a573..0635b50b8 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -134,6 +134,8 @@ class Model /** * Flag whether or not this model has been validated + * + * @var boolean */ private $__validated = false; @@ -1227,20 +1229,7 @@ public function is_dirty() } /** - * Returns whether or not model passed validation. - * - * <p> - * Case of use for <code>is_valid(true)</code>: - * You call some method that will run validations (including this), and - * got a result. - * After, a <strong>virtual attribute</strong> (attribute not stored in Database) - * <strong>used by validation</strong> change is value. - * So, in this case, and only this, you have to use `is_valid(true)` to - * get a correct value. - * </p> - * - * Probably you never use <code>is_valid(true)</code>, but if you spot a - * bug related to validation, you know what to try! + * Returns true if the model is valid. * * @param boolean $force_validation If true, will always run validations. * @see is_invalid @@ -1255,7 +1244,7 @@ public function is_valid($force_validation = false) } /** - * Returns true if invalid. + * Returns true if the model is invalid. * * @param boolean $force_validation If true, will always run validations. * @see is_valid From 54f8d4f93385b8806df1d69b88c236d30070924d Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Sun, 20 Jul 2014 00:07:39 +0200 Subject: [PATCH 52/53] failing test for #68 --- test/HasManyThroughTest.php | 37 +++++++++++++++++++++++++++++++++++++ test/RelationshipTest.php | 8 ++++++++ 2 files changed, 45 insertions(+) diff --git a/test/HasManyThroughTest.php b/test/HasManyThroughTest.php index 3e9982526..1dcbdf3c3 100644 --- a/test/HasManyThroughTest.php +++ b/test/HasManyThroughTest.php @@ -55,4 +55,41 @@ public function test_gh107_has_many_though_include_eager_with_namespace() $this->assert_equals(1, $user->id); $this->assert_equals(1, $user->newsletters[0]->id); } + + public function test_gh68_has_many_through_with_missing_associations() + { + Property::$has_many = array( + array('amenities', 'through' => 'property_amenities') + ); + Amenity::$has_many = array( + array('properties', 'through' => 'property_amenities') + ); + + $property = Property::find('first'); + try{ + $property->amenities; + $this->fail('Should trigger exception'); + }catch(ActiveRecord\HasManyThroughAssociationException $e){ + $this->assertEquals('Could not find the association property_amenities in model Property', $e->getMessage()); + } + } + + public function test_gh68_has_many_through_with_missing_association() + { + Property::$has_many = array( + 'property_amenities', + array('amenities', 'through' => 'property_amenities') + ); + Amenity::$has_many = array( + array('properties', 'through' => 'property_amenities') + ); + + $amenity = Amenity::find('first'); + try{ + $amenity->properties; + $this->fail('Should trigger exception'); + }catch(ActiveRecord\HasManyThroughAssociationException $e){ + $this->assertEquals('Could not find the association property_amenities in model Amenity', $e->getMessage()); + } + } } diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index 9837f67aa..6adce031d 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -341,6 +341,14 @@ public function test_has_many_through() public function test_gh27_has_many_through_with_explicit_keys() { + Property::$has_many = array( + 'property_amenities', + array('amenities', 'through' => 'property_amenities') + ); + Amenity::$has_many = array( + 'property_amenities' + ); + $property = Property::first(); $this->assert_equals(1, $property->amenities[0]->amenity_id); From 9c43bdbcb562c42e6469b2ee82646c3e61abb78f Mon Sep 17 00:00:00 2001 From: jmoberley <jmoberley@hotmail.com> Date: Wed, 13 Apr 2011 17:15:21 -0500 Subject: [PATCH 53/53] Fix for issue 68: relationship references wrong table Include @suxxes changes --- lib/Relationship.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Relationship.php b/lib/Relationship.php index 9846b44a4..16423609e 100644 --- a/lib/Relationship.php +++ b/lib/Relationship.php @@ -496,7 +496,7 @@ public function load(Model $model) if ($this->through) { // verify through is a belongs_to or has_many for access of keys - if (!($through_relationship = $this->get_table()->get_relationship($this->through))) + if (!($through_relationship = $model->table()->get_relationship($this->through))) throw new HasManyThroughAssociationException("Could not find the association $this->through in model " . get_class($model)); if (!($through_relationship instanceof HasMany) && !($through_relationship instanceof BelongsTo)) @@ -508,8 +508,7 @@ public function load(Model $model) $this->set_keys($this->get_table()->class->getName(), true); - $class = $this->class_name; - $relation = $class::table()->get_relationship($this->through); + $relation = $model::table()->get_relationship($this->through); $through_table = $relation->get_table(); $this->options['joins'] = $this->construct_inner_join_sql($through_table, true);