diff --git a/src/Database/Base.php b/src/Database/Base.php index 2a85d2d..bdcb768 100644 --- a/src/Database/Base.php +++ b/src/Database/Base.php @@ -24,6 +24,15 @@ */ class Base { + /** + * The private data object used to store class attributes, so that magic + * methods work as intended. + * + * @since 1.1.0 + * @var object + */ + private $data; + /** * The name of the PHP global that contains the primary database interface. * @@ -60,7 +69,7 @@ class Base { /** Public ****************************************************************/ /** - * Magic isset'ter for immutability. + * Magic isset method. * * @since 1.0.0 * @@ -69,20 +78,23 @@ class Base { */ public function __isset( $key = '' ) { - // No more uppercase ID properties ever - if ( 'ID' === $key ) { - $key = 'id'; + // Validate the key + $key = $this->validate_key( $key ); + + // Bail if invalid key + if ( empty( $key ) ) { + return false; } // Class method to try and call - $method = "get_{$key}"; + $method = "__get_{$key}"; - // Return property if exists + // Return true if method exists if ( method_exists( $this, $method ) ) { return true; - // Return get method results if exists - } elseif ( property_exists( $this, $key ) ) { + // Return true if data exists + } elseif ( isset( $this->data->{$key} ) ) { return true; } @@ -91,34 +103,101 @@ public function __isset( $key = '' ) { } /** - * Magic getter for immutability. + * Magic unset method. + * + * @since 1.1.0 + * + * @param string $key User meta key to unset. + */ + public function __unset( $key = '' ) { + + // Validate the key + $key = $this->validate_key( $key ); + + // Bail if invalid key + if ( empty( $key ) ) { + return false; + } + + // Maybe unset from data array + if ( isset( $this->data->{$key} ) ) { + unset( $this->data->{$key} ); + } + } + + /** + * Magic set method. + * + * @since 1.1.0 + * + * @param string $key + * @return mixed + */ + public function __set( $key = '', $value = '' ) { + + // Validate the key + $key = $this->validate_key( $key ); + + // Bail if invalid key + if ( empty( $key ) ) { + return false; + } + + // Class method to try and call + $method = "__set_{$key}"; + + // Maybe override the value + if ( method_exists( $this, $method ) ) { + $value = call_user_func( array( $this, $method ), $value ); + } + + // Set the key to the value + $this->data->{$key} = $value; + } + + /** + * Magic get method. * * @since 1.0.0 * * @param string $key * @return mixed */ - public function __get( $key = '' ) { + public function &__get( $key = '' ) { - // No more uppercase ID properties ever - if ( 'ID' === $key ) { - $key = 'id'; + // Validate the key + $key = $this->validate_key( $key ); + + // Bail if invalid key + if ( empty( $key ) ) { + return false; } + // Default return value + $retval = null; + // Class method to try and call - $method = "get_{$key}"; + $method = "__get_{$key}"; - // Return property if exists + // Return from method if exists if ( method_exists( $this, $method ) ) { - return call_user_func( array( $this, $method ) ); + $retval = call_user_func( array( $this, $method ) ); + + // Return from data array if set + } elseif ( isset( $this->data->{$key} ) ) { + $retval = $this->data->{$key}; + } - // Return get method results if exists - } elseif ( property_exists( $this, $key ) ) { - return $this->{$key}; + // Return if not null + if ( ! is_null( $retval ) ) { + return $retval; } - // Return null if not exists - return null; + // Set key to null, so array operations work correctly + $this->data->{$key} = $retval; + + // Return variable byref + return $this->data->{$key}; } /** @@ -129,7 +208,28 @@ public function __get( $key = '' ) { * @return array Array version of the given object. */ public function to_array() { - return get_object_vars( $this ); + return get_object_vars( $this->data ); + } + + /** + * Get this objects default properties and set them up in the private data + * array. Use this in the Constructor in any class that extends this class. + * + * @since 1.1.0 + */ + public function set_defaults() { + + // Get the hard-coded object variables + $r = get_object_vars( $this ); + + // Data is private, so don't set it recursively + unset( $r['data'] ); + + // Set those vars + $this->set_vars( $r ); + + // Unset those vars + $this->unset_vars( $r ); } /** Protected *************************************************************/ @@ -252,6 +352,11 @@ protected function sanitize_table_name( $name = '' ) { /** * Set class variables from arguments. * + * This method accepts a key/value array of class variables to set, and is + * used by set_defaults() to prepare a class for magic property overrides. + * + * It can also be called directly to set multiple class variables. + * * @since 1.0.0 * @param array $args */ @@ -267,9 +372,44 @@ protected function set_vars( $args = array() ) { $args = (array) $args; } + // Set empty class + $this->data = new \stdClass(); + // Set all properties foreach ( $args as $key => $value ) { - $this->{$key} = $value; + $this->data->{$key} = $value; + } + } + + /** + * Unset class variables from arguments. + * + * This method accepts a key/value array of class variables to unset, and is + * used by set_defaults() to prepare a class for magic property overrides. + * + * It can also be called directly to unset multiple class variables. + * + * @since 1.0.0 + * @param array $args + */ + protected function unset_vars( $args = array() ) { + + // Bail if no vars to clean + if ( empty( $args ) ) { + return; + } + + // Cast to an array + if ( ! is_array( $args ) ) { + $args = (array) $args; + } + + // Get keys + $keys = array_keys( $args ); + + // Cleanup class properties + foreach ( $keys as $key ) { + unset( $this->{$key} ); } } @@ -288,7 +428,7 @@ protected function get_db() { $retval = false; // Look for a commonly used global database interface - if ( isset( $GLOBALS[ $this->db_global ] ) ) { + if ( ! empty( $this->db_global ) && isset( $GLOBALS[ $this->db_global ] ) ) { $retval = $GLOBALS[ $this->db_global ]; } @@ -340,4 +480,35 @@ protected function is_success( $result = false ) { // Return the result return (bool) $retval; } + + /** Private ***************************************************************/ + + /** + * Validate a data key. + * + * @since 1.0.0 + * + * @param string $key + * @return boolean|string + */ + private function validate_key( $key = '' ) { + + // Bail if empty key + if ( empty( $key ) ) { + return false; + } + + // Bail if setting data + if ( 'data' === $key ) { + return false; + } + + // No more uppercase ID properties ever + if ( 'ID' === $key ) { + return 'id'; + } + + // Return the original key + return $key; + } } diff --git a/src/Database/Column.php b/src/Database/Column.php index dd7fc9c..307b59d 100644 --- a/src/Database/Column.php +++ b/src/Database/Column.php @@ -409,6 +409,9 @@ class Column extends Base { */ public function __construct( $args = array() ) { + // Setup the defaults + $this->set_defaults(); + // Parse arguments $r = $this->parse_args( $args ); @@ -538,7 +541,7 @@ private function validate_args( $args = array() ) { foreach ( $args as $key => $value ) { // Callback is callable - if ( isset( $callbacks[ $key ] ) && is_callable( $callbacks[ $key ] ) ) { + if ( ! empty( $callbacks[ $key ] ) && is_callable( $callbacks[ $key ] ) ) { $r[ $key ] = call_user_func( $callbacks[ $key ], $value ); // Callback is malformed so just let it through to avoid breakage @@ -721,7 +724,7 @@ private function sanitize_pattern( $pattern = '%s' ) { private function sanitize_validation( $callback = '' ) { // Return callback if it's callable - if ( is_callable( $callback ) ) { + if ( ! empty( $callback ) && is_callable( $callback ) ) { return $callback; } diff --git a/src/Database/Queries/Compare.php b/src/Database/Queries/Compare.php index 2e4b352..c03e526 100644 --- a/src/Database/Queries/Compare.php +++ b/src/Database/Queries/Compare.php @@ -177,4 +177,4 @@ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) // Return return $sql_chunks; } -} \ No newline at end of file +} diff --git a/src/Database/Queries/Date.php b/src/Database/Queries/Date.php index 2772cce..d727fd4 100644 --- a/src/Database/Queries/Date.php +++ b/src/Database/Queries/Date.php @@ -240,6 +240,9 @@ class Date extends Base { */ public function __construct( $date_query = array() ) { + // Setup the defaults + $this->set_defaults(); + // Bail if empty or not an array. if ( empty( $date_query ) || ! is_array( $date_query ) ) { return; @@ -1281,8 +1284,11 @@ public function build_time_query( $column, $compare, $hour = null, $minute = nul // Build the SQL $query = "DATE_FORMAT( {$column}, %s ) {$compare} %f"; + // Get the return value + $retval = $this->get_db()->prepare( $query, $format, $time ); + // Return the prepared SQL - return $this->get_db()->prepare( $query, $format, $time ); + return $retval; } /** diff --git a/src/Database/Query.php b/src/Database/Query.php index 0ad187f..44c020c 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -158,6 +158,7 @@ class Query extends Base { protected $query_clauses = array( 'select' => '', 'from' => '', + 'join' => array(), 'where' => array(), 'groupby' => '', 'orderby' => '', @@ -173,6 +174,7 @@ class Query extends Base { protected $request_clauses = array( 'select' => '', 'from' => '', + 'join' => '', 'where' => '', 'groupby' => '', 'orderby' => '', @@ -321,6 +323,7 @@ class Query extends Base { public function __construct( $query = array() ) { // Setup + $this->set_defaults(); $this->set_alias(); $this->set_prefix(); $this->set_columns(); @@ -467,32 +470,41 @@ private function set_query_var_defaults() { return; } + // Default defaults + $defaults = array(); + // Direct column names $names = wp_list_pluck( $this->columns, 'name' ); foreach ( $names as $name ) { - $this->query_var_defaults[ $name ] = $this->query_var_default_value; + $defaults[ $name ] = $this->query_var_default_value; } // Possible ins $possible_ins = $this->get_columns( array( 'in' => true ), 'and', 'name' ); foreach ( $possible_ins as $in ) { $key = "{$in}__in"; - $this->query_var_defaults[ $key ] = false; + $defaults[ $key ] = false; } // Possible not ins $possible_not_ins = $this->get_columns( array( 'not_in' => true ), 'and', 'name' ); foreach ( $possible_not_ins as $in ) { $key = "{$in}__not_in"; - $this->query_var_defaults[ $key ] = false; + $defaults[ $key ] = false; } // Possible dates $possible_dates = $this->get_columns( array( 'date_query' => true ), 'and', 'name' ); foreach ( $possible_dates as $date ) { $key = "{$date}_query"; - $this->query_var_defaults[ $key ] = false; + $defaults[ $key ] = false; } + + // Set the default query variables + $this->query_var_defaults = array_merge( + $this->query_var_defaults, + $defaults + ); } /** @@ -542,15 +554,18 @@ private function set_request_clauses( $clauses = array() ) { // Select & From $table = $this->get_table_name(); $select = "SELECT {$found_rows} {$fields}"; - $from = "FROM {$table} {$this->table_alias} {$join}"; + $from = "FROM {$table} {$this->table_alias}"; // Put query into clauses array - $this->request_clauses['select'] = $select; - $this->request_clauses['from'] = $from; - $this->request_clauses['where'] = $where; - $this->request_clauses['groupby'] = $groupby; - $this->request_clauses['orderby'] = $orderby; - $this->request_clauses['limits'] = $limits; + $this->request_clauses = array( + 'select' => $select, + 'from' => $from, + 'join' => $join, + 'where' => $where, + 'groupby' => $groupby, + 'orderby' => $orderby, + 'limits' => $limits + ); } /** diff --git a/src/Database/Row.php b/src/Database/Row.php index 99c7852..c5a2790 100644 --- a/src/Database/Row.php +++ b/src/Database/Row.php @@ -36,6 +36,11 @@ class Row extends Base { * @param mixed Null by default, Array/Object if not */ public function __construct( $item = null ) { + + // Setup the defaults + $this->set_defaults(); + + // Maybe initialize if ( ! empty( $item ) ) { $this->init( $item ); } diff --git a/src/Database/Schema.php b/src/Database/Schema.php index 80a7c99..af2afe0 100644 --- a/src/Database/Schema.php +++ b/src/Database/Schema.php @@ -39,6 +39,20 @@ class Schema extends Base { */ public function __construct() { + // Setup the defaults + $this->set_defaults(); + + // Setup the columns + $this->set_columns(); + } + + /** + * Setup the columns. + * + * @since 1.0.0 + */ + public function set_columns() { + // Bail if no columns if ( empty( $this->columns ) || ! is_array( $this->columns ) ) { return; @@ -46,16 +60,20 @@ public function __construct() { // Juggle original columns array $columns = $this->columns; - $this->columns = array(); + $this->columns = $new_columns = array(); // Loop through columns and create objects from them foreach ( $columns as $column ) { if ( is_array( $column ) ) { - $this->columns[] = new Column( $column ); + $new_columns[] = new Column( $column ); + } elseif ( $column instanceof Column ) { - $this->columns[] = $column; + $new_columns[] = $column; } } + + // Set the columns + $this->columns = $new_columns; } /** diff --git a/src/Database/Table.php b/src/Database/Table.php index 6a12b4d..b9227f0 100644 --- a/src/Database/Table.php +++ b/src/Database/Table.php @@ -145,6 +145,9 @@ public function __construct() { return; } + // Setup the defaults + $this->set_defaults(); + // Add the table to the database interface $this->set_db_interface();