From 92187f8cb8976318e091c4da12952d68cd4a0181 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Thu, 3 May 2018 17:36:42 -0600 Subject: [PATCH 01/33] Model: update count() for PHP 7.2 --- lib/Model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 99667b41f..ea86ab441 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1665,13 +1665,13 @@ public static function find_by_pk($values, $options) $options['conditions'] = static::pk_conditions($values); $list = $table->find($options); } - $results = count($list); + + if (!is_array($values)) $values = array($values); if ($results != ($expected = count($values))) { $class = get_called_class(); - if (is_array($values)) - $values = join(',',$values); + $values = join(',',$values); if ($expected == 1) { From 0a55d0e020f9c1b95f3d8bc3e7390e405053151b Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Thu, 3 May 2018 17:52:26 -0600 Subject: [PATCH 02/33] Model: update count() for PHP 7.2 --- lib/Model.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Model.php b/lib/Model.php index ea86ab441..29a8759cf 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1666,6 +1666,8 @@ public static function find_by_pk($values, $options) $list = $table->find($options); } + $results = count($list); + if (!is_array($values)) $values = array($values); if ($results != ($expected = count($values))) From 6c7a0ea16c975545359c7e23e03e3bab5f3e2632 Mon Sep 17 00:00:00 2001 From: shaneiseminger Date: Sun, 26 Jul 2020 19:25:20 -0600 Subject: [PATCH 03/33] Add Postgres 12 compatibility --- lib/adapters/PgsqlAdapter.php | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 72da44182..86cf21734 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -6,13 +6,14 @@ /** * Adapter for Postgres (not completed yet) - * + * * @package ActiveRecord */ class PgsqlAdapter extends Connection { static $QUOTE_CHARACTER = '"'; static $DEFAULT_PORT = 5432; + static $VERSION; public function supports_sequences() { @@ -34,8 +35,25 @@ public function limit($sql, $offset, $limit) return $sql . ' LIMIT ' . intval($limit) . ' OFFSET ' . intval($offset); } + public function get_version() + { + if (self::$VERSION === null) { + $version_stmt = $this->query("SELECT version();"); + $version_stmt->execute(); + $version_string = $version_stmt->fetch()['version']; + preg_match('/^PostgreSQL ([0-9\.]+)/', $version_string, $matches); + self::$VERSION = $matches[1]; + } + return self::$VERSION; + } + public function query_column_info($table) { + + $default_select = self::get_version() < 12 ? + 'pg_attrdef.adsrc' : + 'pg_get_expr(adbin, adrelid)'; + $sql = << From 7494464f15a123022b87c8a676e8c56152a2c7d2 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 28 Aug 2020 18:41:06 -0600 Subject: [PATCH 04/33] Bind PDO values using correct type (fixes ability to use boolean values to for boolean tables) --- lib/Connection.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/Connection.php b/lib/Connection.php index 69e8a1b20..d8773f543 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -329,8 +329,32 @@ public function query($sql, &$values=array()) $sth->setFetchMode(PDO::FETCH_ASSOC); + $numeric_keys = false; + + foreach ($values as $key => &$value) { + switch (gettype($value)) { + case 'boolean' : + $pdo_type = PDO::PARAM_BOOL; + break; + case 'integer' : + $pdo_type = PDO::PARAM_INT; + break; + case 'NULL' : + $pdo_type = PDO::PARAM_NULL; + break; + default : + $pdo_type = PDO::PARAM_STR; + break; + } + if ($numeric_keys || $key === 0) { + $numeric_keys = true; + ++$key; + } + $sth->bindParam($key, $value, $pdo_type); + } + try { - if (!$sth->execute($values)) + if (!$sth->execute()) throw new DatabaseException($this); } catch (PDOException $e) { throw new DatabaseException($e); From d0b2a6310f6a0a0f09e9678a85a8c966fc9adf02 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 31 Aug 2020 12:06:37 -0600 Subject: [PATCH 05/33] Add boolean compatibility when loading data --- lib/Column.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Column.php b/lib/Column.php index db6c0a3a6..d102629e1 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. @@ -25,6 +26,7 @@ class Column * @var array */ static $TYPE_MAPPING = array( + 'datetime' => self::DATETIME, 'timestamp' => self::DATETIME, 'date' => self::DATE, @@ -40,7 +42,11 @@ class Column 'double' => self::DECIMAL, 'numeric' => self::DECIMAL, 'decimal' => self::DECIMAL, - 'dec' => self::DECIMAL); + 'dec' => self::DECIMAL, + + 'boolean' => self::BOOLEAN, + + ); /** * The true name of this column. @@ -160,6 +166,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 (bool) $value; case self::DATETIME: case self::DATE: if (!$value) From c9a8ca67c47b8dd3f105cb33a9a4ae10b7a527d8 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Tue, 29 Sep 2020 14:35:28 -0600 Subject: [PATCH 06/33] fix: parse default boolean values correctly --- lib/adapters/PgsqlAdapter.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 86cf21734..8e5ec78bb 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -122,11 +122,13 @@ public function create_column(&$column) if ($column['default']) { preg_match("/^nextval\('(.*)'\)$/",$column['default'],$matches); - - if (count($matches) == 2) + if (count($matches) == 2) { $c->sequence = $matches[1]; - else - $c->default = $c->cast($column['default'],$this); + } elseif ($column['type'] == 'boolean') { + $c->default = $c->cast($column['default'] === 'true' ? true : ($column['default'] === 'false' ? false : $column['default']), $this); + } else { + $c->default = $c->cast($column['default'], $this); + } } return $c; } From 4ae80ac040bbd8847cd0f115aff9a9e50983d7dc Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 19 Oct 2020 16:17:50 -0600 Subject: [PATCH 07/33] getters and associations should count as being isset() - fixes null coalescing issues --- lib/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Model.php b/lib/Model.php index 29a8759cf..752dc6367 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -355,7 +355,7 @@ public function &__get($name) */ public function __isset($attribute_name) { - return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute); + return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute) || static::table()->get_relationship($attribute_name) || method_exists($this, "get_$attribute_name"); } /** From ee830cdc041e9fe87270856b6c8ce9a38859274e Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 9 Nov 2020 11:41:13 -0700 Subject: [PATCH 08/33] Add support for Postgres arrays --- lib/Column.php | 19 ++++++++++++++- lib/Connection.php | 26 ++++++++++++++++++++ lib/Table.php | 14 ++++++----- lib/adapters/PgsqlAdapter.php | 45 +++++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/lib/Column.php b/lib/Column.php index d102629e1..aaab699a2 100644 --- a/lib/Column.php +++ b/lib/Column.php @@ -108,6 +108,8 @@ class Column */ public $sequence; + public $is_array = false; + /** * Cast a value to an integer type safely * @@ -156,11 +158,25 @@ public static function castIntegerSafely($value) * @param Connection $connection The Connection this column belongs to * @return mixed type-casted value */ - public function cast($value, $connection) + public function cast($value, $connection, bool $process_arrays = true) { if ($value === null) return null; + if ($this->is_array && $process_arrays) { + + // Convert database array representation to PHP array + $value_array = is_array($value) ? $value : $connection->database_string_to_array($value); + + // Cast each array member according to database type + $self = $this; + + return array_map(function ($value) use ($self, $connection) { + return $self->cast($value, $connection, false); + }, $value_array); + + } + switch ($this->type) { case self::STRING: return (string)$value; @@ -186,6 +202,7 @@ public function cast($value, $connection) return $connection->string_to_datetime($value); } + return $value; } diff --git a/lib/Connection.php b/lib/Connection.php index d8773f543..87e137339 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -523,6 +523,32 @@ public function string_to_datetime($string) ); } + /** + * + * Converts arrays to string for inserting/updating in database. Necessary because PDO doesn't support arrays directly. + * + * @param array $array The array to serialize + * @return string The serialized array + */ + + public function array_to_database_string(array $array) + { + throw new DatabaseException(get_called_class() . ' does not support arrays'); + } + + /** + * + * Converts arrays to string for inserting/updating in database. Necessary because PDO doesn't support arrays directly. + * + * @param array $array The array to serialize + * @return string The serialized array + */ + + public function database_string_to_array(array $array) + { + throw new DatabaseException(get_called_class() . ' does not support arrays'); + } + /** * Adds a limit clause to the SQL query. * diff --git a/lib/Table.php b/lib/Table.php index 7434ff11c..a55c1e2e0 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -431,15 +431,17 @@ private function &process_data($hash) $date_class = Config::instance()->get_date_class(); foreach ($hash as $name => &$value) { - if ($value instanceof $date_class || $value instanceof \DateTime) - { - if (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE) + if ($value instanceof $date_class || $value instanceof \DateTime) { + if (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE) { $hash[$name] = $this->conn->date_to_string($value); - else + } else { $hash[$name] = $this->conn->datetime_to_string($value); - } - else + } + } elseif (is_array($value)) { + $hash[$name] = $this->conn->array_to_database_string($value); + } else { $hash[$name] = $value; + } } return $hash; } diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 8e5ec78bb..d5df3f3fe 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -96,6 +96,8 @@ public function create_column(&$column) $c->pk = ($column['pk'] ? true : false); $c->auto_increment = false; + $is_array = false; + if (substr($column['type'],0,9) == 'timestamp') { $c->raw_type = 'datetime'; @@ -108,15 +110,30 @@ public function create_column(&$column) } else { - preg_match('/^([A-Za-z0-9_]+)(\(([0-9]+(,[0-9]+)?)\))?/',$column['type'],$matches); + preg_match('/^([A-Za-z0-9_]+(?:\[\])?)(\(([0-9]+(,[0-9]+)?)\))?/',$column['type'],$matches); + + if (!is_array($matches)) { + var_dump($column['type']); + var_dump($matches); + exit; + } - $c->raw_type = (count($matches) > 0 ? $matches[1] : $column['type']); + $raw_type = (count($matches) > 0 ? $matches[1] : $column['type']); $c->length = count($matches) >= 4 ? intval($matches[3]) : intval($column['attlen']); if ($c->length < 0) $c->length = null; + + if (substr($raw_type, -2) == '[]') { + $raw_type = substr($raw_type, 0, -2); + $is_array = true; + } + + $c->raw_type = $raw_type; + } + $c->is_array = $is_array; $c->map_raw_type(); if ($column['default']) @@ -155,4 +172,28 @@ public function native_database_types() ); } + public function array_to_database_string(array $value) + { + return '{' . $this->str_putcsv($value) . '}'; + } + + public function database_string_to_array(string $value) + { + preg_match('/^{(.*)}$/', $value, $matches); + if ($matches && !strlen($matches[1])) { + return []; + } + return $matches ? str_getcsv($matches[1]) : $value; + } + + private function str_putcsv(array $input, $delimiter = ',', $enclosure = '"') + { + $h = fopen('php://temp', 'r+b'); + fputcsv($h, $input, $delimiter, $enclosure); + rewind($h); + $data = rtrim(stream_get_contents($h), "\n"); + fclose($h); + return $data; + } + } From 3277380b080c2e7a88b8350266e7f02db29a78f0 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 9 Nov 2020 14:35:20 -0700 Subject: [PATCH 09/33] Remove debugging code --- lib/adapters/PgsqlAdapter.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index d5df3f3fe..87efc74a9 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -112,12 +112,6 @@ public function create_column(&$column) { preg_match('/^([A-Za-z0-9_]+(?:\[\])?)(\(([0-9]+(,[0-9]+)?)\))?/',$column['type'],$matches); - if (!is_array($matches)) { - var_dump($column['type']); - var_dump($matches); - exit; - } - $raw_type = (count($matches) > 0 ? $matches[1] : $column['type']); $c->length = count($matches) >= 4 ? intval($matches[3]) : intval($column['attlen']); From da0aabf49687fb912cf154541a029b5f52cc35e6 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 9 Nov 2020 14:36:22 -0700 Subject: [PATCH 10/33] Handle encoding of database arrays in SELECT queries correctly --- lib/SQLBuilder.php | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index ef239ed87..2ff808e70 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -305,15 +305,46 @@ private function apply_where_conditions($args) { // if the values has a nested array then we'll need to use Expressions to expand the bind marker for us $values = array_slice($args,1); + $index_position = 0; foreach ($values as $name => &$value) { + ++$index_position; + if (is_array($value)) { $e = new Expressions($this->connection,$args[0]); $e->bind_values($values); $this->where = $e->to_s(); - $this->where_values = array_flatten($e->values()); + + // TODO: Maybe this block should only be executed if the adapter (and therefore the database) supports native database arrays. + + // While a regex expression here would be more concise, the below process of stepping through charactes should be faster. + + $placeholder_position = strpos( + $args[0], + is_int($name) ? Expressions::ParameterMarker : $name, + $index_position + ); + + $char_position_previous = $char_position_next = $placeholder_position; + + while (in_array($args[0][$char_position_previous], [' ', ',', ':', '?'])) { + --$char_position_previous; + } + + while (in_array($args[0][$char_position_next], [' ', ',', ':', '?'])) { + ++$char_position_next; + } + + if ($args[0][$char_position_previous] == '(' && $args[0][$char_position_next] == ')') { + // Looks like the placeholder is enclosed in parentheses, meaning it's intended as a list of values. + $this->where_values = array_flatten($e->values()); + } else { + // Looks like the placeholder is no enclosed in parentheses, so we think it's intended as a native database array. + $this->where_values = $e->values(); + } + return; } } @@ -419,4 +450,4 @@ private function quoted_key_names() return $keys; } -} \ No newline at end of file +} From b1c54f87ef58854647eb6f6177a0e5ba5288c4c8 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Mon, 9 Nov 2020 16:17:34 -0700 Subject: [PATCH 11/33] Avoid unnecessarily creating arrays --- lib/Utils.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Utils.php b/lib/Utils.php index 105d28c3a..2cd38daa0 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -150,7 +150,7 @@ function wrap_strings_in_arrays(&$strings) { if (!is_array($strings)) $strings = array(array($strings)); - else + else { foreach ($strings as &$str) { @@ -182,7 +182,8 @@ public static function add_condition(&$conditions=array(), $condition, $conjucti else { $conditions[0] .= " $conjuction " . array_shift($condition); - $conditions[] = array_flatten($condition); + $flattened_condition = array_flatten($condition); + $conditions[] = count($flattened_condition) > 1 ? $flattened_condition : $flattened_condition[0]; } } elseif (is_string($condition)) @@ -367,4 +368,4 @@ public static function add_irregular($singular, $plural) { self::$irregular[$singular] = $plural; } -} \ No newline at end of file +} From 9474b75229acff9ca839886326f798bd469aba23 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Tue, 10 Nov 2020 14:26:32 -0700 Subject: [PATCH 12/33] Implement alternate method of handling Postgres arrays --- lib/Expressions.php | 7 +++--- lib/Model.php | 2 +- lib/SQLBuilder.php | 61 ++++++++++++++++----------------------------- lib/Table.php | 14 +++++++---- lib/Utils.php | 3 +-- 5 files changed, 37 insertions(+), 50 deletions(-) diff --git a/lib/Expressions.php b/lib/Expressions.php index 3fa2962fa..c407472d4 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -11,7 +11,7 @@ * 'name = :name AND author = :author' * 'id = IN(:ids)' * 'id IN(:subselect)' - * + * * @package ActiveRecord */ class Expressions @@ -90,7 +90,7 @@ public function set_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 = ""; @@ -189,4 +189,5 @@ private function quote_string($value) return "'" . str_replace("'","''",$value) . "'"; } -} \ No newline at end of file +} + diff --git a/lib/Model.php b/lib/Model.php index 752dc6367..aa9e307bb 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1311,7 +1311,7 @@ public function reset_dirty() * * @var array */ - static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having'); + static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having', 'array_placeholders'); /** * Enables the use of dynamic finders. diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index 2ff808e70..9a01d95ba 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -22,6 +22,7 @@ class SQLBuilder private $group; private $having; private $update; + private $array_placeholders = []; // for where private $where; @@ -105,6 +106,12 @@ public function order($order) return $this; } + public function array_placeholders(array $array_placeholders) + { + $this->array_placeholders = $array_placeholders; + return $this; + } + public function group($group) { $this->group = $group; @@ -303,52 +310,28 @@ private function apply_where_conditions($args) } elseif ($num_args > 0) { + // if the values has a nested array then we'll need to use Expressions to expand the bind marker for us $values = array_slice($args,1); $index_position = 0; - foreach ($values as $name => &$value) - { - ++$index_position; - - if (is_array($value)) - { - $e = new Expressions($this->connection,$args[0]); - $e->bind_values($values); - $this->where = $e->to_s(); - - // TODO: Maybe this block should only be executed if the adapter (and therefore the database) supports native database arrays. - - // While a regex expression here would be more concise, the below process of stepping through charactes should be faster. - - $placeholder_position = strpos( - $args[0], - is_int($name) ? Expressions::ParameterMarker : $name, - $index_position - ); - - $char_position_previous = $char_position_next = $placeholder_position; - - while (in_array($args[0][$char_position_previous], [' ', ',', ':', '?'])) { - --$char_position_previous; - } - - while (in_array($args[0][$char_position_next], [' ', ',', ':', '?'])) { - ++$char_position_next; - } - - if ($args[0][$char_position_previous] == '(' && $args[0][$char_position_next] == ')') { - // Looks like the placeholder is enclosed in parentheses, meaning it's intended as a list of values. - $this->where_values = array_flatten($e->values()); - } else { - // Looks like the placeholder is no enclosed in parentheses, so we think it's intended as a native database array. - $this->where_values = $e->values(); - } - - return; + foreach ($values as $name => &$value) { + if (!is_array($value)) { + continue; } + $e = new Expressions($this->connection, $args[0]); + $e->bind_values($values); + $this->where = $e->to_s(); + + if (in_array($name, $this->array_placeholders)) { + $this->where_values = $e->values(); + } else { + $this->where_values = array_flatten($e->values()); + } + return; } + // no nested array so nothing special to do $this->where = $args[0]; $this->where_values = &$values; diff --git a/lib/Table.php b/lib/Table.php index a55c1e2e0..f197ca9d1 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -162,6 +162,9 @@ public function options_to_sql($options) $table = array_key_exists('from', $options) ? $options['from'] : $this->get_fully_qualified_table_name(); $sql = new SQLBuilder($this->conn, $table); + if (array_key_exists('array_placeholders',$options)) + $sql->array_placeholders($options['array_placeholders']); + if (array_key_exists('joins',$options)) { $sql->joins($this->create_joins($options['joins'])); @@ -215,8 +218,9 @@ public function find($options) $sql = $this->options_to_sql($options); $readonly = (array_key_exists('readonly',$options) && $options['readonly']) ? true : false; $eager_load = array_key_exists('include',$options) ? $options['include'] : null; + $array_placeholders = array_key_exists('array_placeholders',$options) ? $options['array_placeholders'] : []; - return $this->find_by_sql($sql->to_s(),$sql->get_where_values(), $readonly, $eager_load); + return $this->find_by_sql($sql->to_s(),$sql->get_where_values(), $readonly, $eager_load, $array_placeholders); } public function cache_key_for_model($pk) @@ -228,13 +232,13 @@ public function cache_key_for_model($pk) return $this->class->name . '-' . $pk; } - public function find_by_sql($sql, $values=null, $readonly=false, $includes=null) + public function find_by_sql($sql, $values=null, $readonly=false, $includes=null, $array_placeholders = []) { $this->last_sql = $sql; $collect_attrs_for_includes = is_null($includes) ? false : true; $list = $attrs = array(); - $sth = $this->conn->query($sql,$this->process_data($values)); + $sth = $this->conn->query($sql, $this->process_data($values, $array_placeholders)); $self = $this; while (($row = $sth->fetch())) @@ -423,7 +427,7 @@ private function map_names(&$hash, &$map) return $ret; } - private function &process_data($hash) + private function &process_data($hash, $array_placeholders = []) { if (!$hash) return $hash; @@ -437,7 +441,7 @@ private function &process_data($hash) } else { $hash[$name] = $this->conn->datetime_to_string($value); } - } elseif (is_array($value)) { + } elseif (is_array($value) && ((isset($this->columns[$name]) && $this->columns[$name]->is_array || in_array($name, $array_placeholders))) ) { $hash[$name] = $this->conn->array_to_database_string($value); } else { $hash[$name] = $value; diff --git a/lib/Utils.php b/lib/Utils.php index 2cd38daa0..1acd59b3d 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -182,8 +182,7 @@ public static function add_condition(&$conditions=array(), $condition, $conjucti else { $conditions[0] .= " $conjuction " . array_shift($condition); - $flattened_condition = array_flatten($condition); - $conditions[] = count($flattened_condition) > 1 ? $flattened_condition : $flattened_condition[0]; + $conditions[] = array_flatten($condition); } } elseif (is_string($condition)) From ed5a8875f889adabea43eeecaaf237dbee4cebde Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Tue, 10 Nov 2020 15:39:30 -0700 Subject: [PATCH 13/33] Implement alternate method of handling Postgres arrays --- lib/Expressions.php | 6 ++++-- lib/SQLBuilder.php | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/Expressions.php b/lib/Expressions.php index c407472d4..4b3964b3c 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -55,9 +55,10 @@ public function bind($parameter_number, $value) $this->values[$parameter_number-1] = $value; } - public function bind_values($values) + public function bind_values($values, $array_placeholders = []) { $this->values = $values; + $this->array_placeholders = $array_placeholders; } /** @@ -145,6 +146,7 @@ private function build_sql_from_hash(&$hash, $glue) private function substitute(&$values, $substitute, $pos, $parameter_index) { $value = $values[$parameter_index]; + $is_array_placeholder = in_array($parameter_index, $this->array_placeholders); if (is_array($value)) { @@ -165,7 +167,7 @@ private function substitute(&$values, $substitute, $pos, $parameter_index) return $ret; } - return join(',',array_fill(0,$value_count,self::ParameterMarker)); + return join(',', array_fill(0, $is_array_placeholder ? 1 : $value_count, self::ParameterMarker)); } if ($substitute) diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index 9a01d95ba..26dd4463d 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -319,8 +319,11 @@ private function apply_where_conditions($args) if (!is_array($value)) { continue; } + + $is_array_placeholder = in_array($name, $this->array_placeholders); + $e = new Expressions($this->connection, $args[0]); - $e->bind_values($values); + $e->bind_values($values, $this->array_placeholders); $this->where = $e->to_s(); if (in_array($name, $this->array_placeholders)) { From 474ece76e47fb73196af07ae9bae4efe3565656e Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Sat, 12 Dec 2020 13:25:34 -0700 Subject: [PATCH 14/33] 7.4 compat fix --- ActiveRecord.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveRecord.php b/ActiveRecord.php index 084587547..14b244154 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -40,11 +40,11 @@ function activerecord_autoload($class_name) foreach ($namespaces as $directory) $directories[] = $directory; - $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR); + $root .= DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $directories); } $file = "$root/$class_name.php"; if (file_exists($file)) require_once $file; -} \ No newline at end of file +} From 9c328df9e45e4d94b7ef372c6c33cd09569ae2cb Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Sun, 31 Jan 2021 15:53:03 -0700 Subject: [PATCH 15/33] Fix missing obj property --- lib/Expressions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Expressions.php b/lib/Expressions.php index 4b3964b3c..be55b3cd4 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -21,6 +21,7 @@ class Expressions private $expressions; private $values = array(); private $connection; + private $array_placeholders = []; public function __construct($connection, $expressions=null /* [, $values ... ] */) { From 0bedb976aa86493069c68b6103f7d1e25025875d Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Wed, 24 Feb 2021 09:23:38 -0700 Subject: [PATCH 16/33] Cloned model should be a new record, and should have no PK value --- lib/Model.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Model.php b/lib/Model.php index aa9e307bb..20032011c 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1291,6 +1291,9 @@ public function reload() public function __clone() { + $this->__new_record = true; + $pk = $this->get_primary_key(true); + $this->assign_attribute($pk, null); $this->__relationships = array(); $this->reset_dirty(); return $this; From 15c0d41624bde9255c7b23e444a52f7bd6b18773 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Thu, 25 Feb 2021 14:27:53 -0700 Subject: [PATCH 17/33] Revert "Cloned model should be a new record, and should have no PK value" This reverts commit 0bedb976aa86493069c68b6103f7d1e25025875d. --- lib/Model.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index 20032011c..aa9e307bb 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1291,9 +1291,6 @@ public function reload() public function __clone() { - $this->__new_record = true; - $pk = $this->get_primary_key(true); - $this->assign_attribute($pk, null); $this->__relationships = array(); $this->reset_dirty(); return $this; From 2e2a4c54cb319ae5d15d404cf6900657e7e6be09 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 10:41:39 -0600 Subject: [PATCH 18/33] Private methods cannot be final --- lib/Singleton.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Singleton.php b/lib/Singleton.php index fe370c2fc..2cb46696c 100644 --- a/lib/Singleton.php +++ b/lib/Singleton.php @@ -41,7 +41,7 @@ final public static function instance() * * @return void */ - final private function __clone() {} + private function __clone() {} /** * Similar to a get_called_class() for a child class to invoke. @@ -53,4 +53,4 @@ final protected function get_called_class() $backtrace = debug_backtrace(); return get_class($backtrace[2]['object']); } -} \ No newline at end of file +} From 8e90f80f161a5d1efdbfbe303f99ab6082176a4c Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 10:43:04 -0600 Subject: [PATCH 19/33] Fix required parameter following optional parameter --- lib/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Utils.php b/lib/Utils.php index 1acd59b3d..d7da16d19 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, $condition, $conjuction='AND') { if (is_array($condition)) { From e19ff51fbb83bb80533865fd5d4948ab9ac65841 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 10:44:46 -0600 Subject: [PATCH 20/33] spl_autoload_register() will always throw now --- ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveRecord.php b/ActiveRecord.php index 14b244154..7728ad40e 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -25,7 +25,7 @@ require __DIR__.'/lib/Cache.php'; if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE')) - spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND); + spl_autoload_register('activerecord_autoload',true,PHP_ACTIVERECORD_AUTOLOAD_PREPEND); function activerecord_autoload($class_name) { From a375c7d12487f94b5e28f84a480fe111400b027e Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 10:57:10 -0600 Subject: [PATCH 21/33] Correct parameters --- lib/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Connection.php b/lib/Connection.php index 87e137339..ca62aca73 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -544,7 +544,7 @@ public function array_to_database_string(array $array) * @return string The serialized array */ - public function database_string_to_array(array $array) + public function database_string_to_array(string $value) { throw new DatabaseException(get_called_class() . ' does not support arrays'); } From ea3e8316bd1565b036215779071e06d1aea82a58 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 11:00:13 -0600 Subject: [PATCH 22/33] Update tests for PHPUnit 9 --- composer.json | 5 ++++- test/SqliteAdapterTest.php | 6 +++--- test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 63f932948..58fe1e912 100644 --- a/composer.json +++ b/composer.json @@ -9,11 +9,14 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "4.*", + "phpunit/phpunit": "^9", "pear/pear_exception": "1.0-beta1", "pear/log": "~1.12" }, "autoload": { "files": [ "ActiveRecord.php" ] + }, + "autoload-dev": { + "classmap": ["test/helpers"] } } diff --git a/test/SqliteAdapterTest.php b/test/SqliteAdapterTest.php index 0490c8137..874201cc7 100644 --- a/test/SqliteAdapterTest.php +++ b/test/SqliteAdapterTest.php @@ -8,7 +8,7 @@ public function set_up($connection_name=null) parent::set_up('sqlite'); } - public function tearDown() + public function tearDown() : void { parent::tearDown(); @@ -16,7 +16,7 @@ public function tearDown() } - public static function tearDownAfterClass() + public static function tearDownAfterClass() : void { parent::tearDownAfterClass(); @@ -79,4 +79,4 @@ public function test_date_to_string() // not supported public function test_connect_with_port() {} } -?> \ No newline at end of file +?> diff --git a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php index a5ac79c85..f25879a11 100644 --- a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php +++ b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php @@ -1,5 +1,5 @@ assert_equals($expected->format(DateTime::ISO8601),$actual->format(DateTime::ISO8601)); } } -?> \ No newline at end of file +?> From cc7dcce91428b9a888c4c78cd88ae9516dde748f Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 11:00:56 -0600 Subject: [PATCH 23/33] $values should default to empty array --- lib/Model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index aa9e307bb..dd1aa30b0 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1697,7 +1697,7 @@ public static function find_by_pk($values, $options) * @param array $values An array of values for any parameters that needs to be bound * @return array An array of models */ - public static function find_by_sql($sql, $values=null) + public static function find_by_sql($sql, $values=[]) { return static::table()->find_by_sql($sql, $values, true); } @@ -1709,7 +1709,7 @@ public static function find_by_sql($sql, $values=null) * @param array $values Bind values, if any, for the query * @return object A PDOStatement object */ - public static function query($sql, $values=null) + public static function query($sql, $values=[]) { return static::connection()->query($sql, $values); } From a06524a1b2da68c6e2ab8df3c8d59f84143bced3 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 14:39:45 -0600 Subject: [PATCH 24/33] Temp debugging --- lib/Model.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Model.php b/lib/Model.php index dd1aa30b0..c17c94588 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -427,6 +427,14 @@ public function __set($name, $value) foreach (static::$delegate as &$item) { + if (!is_array($item)) { + $services = \Zend_Registry::get('publisher-services'); + + if ($sentry = $services['sentry']) { + $sentry->captureMessage('Delegate is ' . var_export($item, true)); + } + continue; + } if (($delegated_name = $this->is_delegated($name,$item))) return $this->{$item['to']}->{$delegated_name} = $value; } From c509af4ecb77e89288fae50b9562dc4c0ce65ecf Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 15:05:01 -0600 Subject: [PATCH 25/33] Temp debugging --- lib/Model.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Model.php b/lib/Model.php index c17c94588..f85ed185c 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -534,6 +534,14 @@ public function &read_attribute($name) foreach (static::$delegate as &$item) { + if (!is_array($item)) { + $services = \Zend_Registry::get('publisher-services'); + + if ($sentry = $services['sentry']) { + $sentry->captureMessage('Delegate is ' . var_export($item, true)); + } + continue; + } if (($delegated_name = $this->is_delegated($name,$item))) { $to = $item['to']; From d5822772dc275fc4d7df473fd2544dfe13a40766 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 15:20:53 -0600 Subject: [PATCH 26/33] Temp debugging --- lib/Model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index f85ed185c..e4f79981b 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -431,7 +431,7 @@ public function __set($name, $value) $services = \Zend_Registry::get('publisher-services'); if ($sentry = $services['sentry']) { - $sentry->captureMessage('Delegate is ' . var_export($item, true)); + $sentry->captureMessage('Delegates: ' . var_export(static::$delegate, true)); } continue; } @@ -538,7 +538,7 @@ public function &read_attribute($name) $services = \Zend_Registry::get('publisher-services'); if ($sentry = $services['sentry']) { - $sentry->captureMessage('Delegate is ' . var_export($item, true)); + $sentry->captureMessage('Delegates: ' . var_export(static::$delegate, true)); } continue; } From 0492ddad86c93e44c7df2397526091c6929a68b5 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Fri, 16 Apr 2021 16:09:18 -0600 Subject: [PATCH 27/33] workaround weird bug --- lib/Model.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/Model.php b/lib/Model.php index e4f79981b..f25947011 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -428,11 +428,6 @@ public function __set($name, $value) foreach (static::$delegate as &$item) { if (!is_array($item)) { - $services = \Zend_Registry::get('publisher-services'); - - if ($sentry = $services['sentry']) { - $sentry->captureMessage('Delegates: ' . var_export(static::$delegate, true)); - } continue; } if (($delegated_name = $this->is_delegated($name,$item))) @@ -535,11 +530,6 @@ public function &read_attribute($name) foreach (static::$delegate as &$item) { if (!is_array($item)) { - $services = \Zend_Registry::get('publisher-services'); - - if ($sentry = $services['sentry']) { - $sentry->captureMessage('Delegates: ' . var_export(static::$delegate, true)); - } continue; } if (($delegated_name = $this->is_delegated($name,$item))) From d90e2f0eb80d782246bcc85424887e09ff9ce091 Mon Sep 17 00:00:00 2001 From: shaneiseminger Date: Tue, 10 May 2022 10:57:44 -0600 Subject: [PATCH 28/33] Always return array --- lib/adapters/PgsqlAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 87efc74a9..86e400206 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -177,7 +177,7 @@ public function database_string_to_array(string $value) if ($matches && !strlen($matches[1])) { return []; } - return $matches ? str_getcsv($matches[1]) : $value; + return $matches ? str_getcsv($matches[1]) : [$value]; } private function str_putcsv(array $input, $delimiter = ',', $enclosure = '"') From efc44ce7db240a0a151f11d2d546c23759472b57 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Wed, 6 Sep 2023 11:51:55 -0400 Subject: [PATCH 29/33] No sequence for UUIDs --- lib/adapters/PgsqlAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 87efc74a9..3257f3746 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -22,7 +22,7 @@ public function supports_sequences() public function get_sequence_name($table, $column_name) { - return "{$table}_{$column_name}_seq"; + return $column_name !== 'uuid' ? "{$table}_{$column_name}_seq" : null; } public function next_sequence_value($sequence_name) From b562a941cebe41d6e1acf5c463b7f3d777b88594 Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Wed, 6 Sep 2023 12:29:27 -0400 Subject: [PATCH 30/33] No sequence for UUIDs --- lib/adapters/PgsqlAdapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/adapters/PgsqlAdapter.php b/lib/adapters/PgsqlAdapter.php index 938ff2b7d..f49207a9a 100644 --- a/lib/adapters/PgsqlAdapter.php +++ b/lib/adapters/PgsqlAdapter.php @@ -22,12 +22,12 @@ public function supports_sequences() public function get_sequence_name($table, $column_name) { - return $column_name !== 'uuid' ? "{$table}_{$column_name}_seq" : null; + return "{$table}_{$column_name}_seq"; } public function next_sequence_value($sequence_name) { - return "nextval('" . str_replace("'","\\'",$sequence_name) . "')"; + return !stristr($sequence_name, 'uuid_seq') ? "nextval('" . str_replace("'","\\'",$sequence_name) . "')" : 'DEFAULT'; } public function limit($sql, $offset, $limit) From 5c1ebb4718f59aa83c318b7c4c6528e1cd759a5c Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Thu, 16 May 2024 16:52:40 -0600 Subject: [PATCH 31/33] Fix passing null --- composer.json | 3 ++- lib/DateTime.php | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 58fe1e912..b978187f1 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "homepage": "http://www.phpactiverecord.org/", "license": "MIT", "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "rector/rector": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/lib/DateTime.php b/lib/DateTime.php index 43ef8a8ef..88fe7017f 100644 --- a/lib/DateTime.php +++ b/lib/DateTime.php @@ -84,7 +84,7 @@ public function attribute_of($model, $attribute_name) * @param string $format A format string accepted by get_format() * @return string formatted date and time string */ - public function format($format=null) + public function format($format=null) : string { return parent::format(self::get_format($format)); } @@ -117,13 +117,13 @@ public static function get_format($format=null) * This needs to be overriden so it returns an instance of this class instead of PHP's \DateTime. * See http://php.net/manual/en/datetime.createfromformat.php */ - public static function createFromFormat($format, $time, $tz = null) + public static function createFromFormat($format, $time, $tz = null) : \DateTime|false { $phpDate = $tz ? parent::createFromFormat($format, $time, $tz) : parent::createFromFormat($format, $time); if (!$phpDate) return false; // convert to this class using the timestamp - $ourDate = new static(null, $phpDate->getTimezone()); + $ourDate = new static('', $phpDate->getTimezone()); $ourDate->setTimestamp($phpDate->getTimestamp()); return $ourDate; } @@ -153,49 +153,49 @@ private function flag_dirty() $this->model->flag_dirty($this->attribute_name); } - public function setDate($year, $month, $day) + public function setDate($year, $month, $day) : \DateTime { $this->flag_dirty(); return parent::setDate($year, $month, $day); } - public function setISODate($year, $week , $day = 1) + public function setISODate($year, $week , $day = 1) : \DateTime { $this->flag_dirty(); return parent::setISODate($year, $week, $day); } - public function setTime($hour, $minute, $second = 0, $microseconds = 0) + public function setTime($hour, $minute, $second = 0, $microseconds = 0) : \DateTime { $this->flag_dirty(); return parent::setTime($hour, $minute, $second); } - public function setTimestamp($unixtimestamp) + public function setTimestamp($unixtimestamp) : \DateTime { $this->flag_dirty(); return parent::setTimestamp($unixtimestamp); } - public function setTimezone($timezone) + public function setTimezone($timezone) : \DateTime { $this->flag_dirty(); return parent::setTimezone($timezone); } - - public function modify($modify) + + public function modify($modify) : \DateTime { $this->flag_dirty(); return parent::modify($modify); } - - public function add($interval) + + public function add($interval) : \DateTime { $this->flag_dirty(); return parent::add($interval); } - public function sub($interval) + public function sub($interval) : \DateTime { $this->flag_dirty(); return parent::sub($interval); From 9993dab003651f69a51641c949eeccba94272abe Mon Sep 17 00:00:00 2001 From: Shane Iseminger Date: Thu, 16 May 2024 17:13:46 -0600 Subject: [PATCH 32/33] No longer can add string + int --- lib/Column.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Column.php b/lib/Column.php index aaab699a2..45d66bc3f 100644 --- a/lib/Column.php +++ b/lib/Column.php @@ -136,11 +136,6 @@ public static function castIntegerSafely($value) 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 From 114668ff55a0eaad9df473d7a01cae158381745b Mon Sep 17 00:00:00 2001 From: luckyastro Date: Thu, 13 Jun 2024 01:35:00 +0200 Subject: [PATCH 33/33] fix datetime issue --- lib/DateTime.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DateTime.php b/lib/DateTime.php index 88fe7017f..80537f232 100644 --- a/lib/DateTime.php +++ b/lib/DateTime.php @@ -117,7 +117,7 @@ public static function get_format($format=null) * This needs to be overriden so it returns an instance of this class instead of PHP's \DateTime. * See http://php.net/manual/en/datetime.createfromformat.php */ - public static function createFromFormat($format, $time, $tz = null) : \DateTime|false + public static function createFromFormat($format, $time, $tz = null) { $phpDate = $tz ? parent::createFromFormat($format, $time, $tz) : parent::createFromFormat($format, $time); if (!$phpDate)