• Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • Examples
  • File List

system/classes/databaseconnection.php

00001 <?php
00007 namespace Habari;
00008 
00014 class DatabaseConnection
00015 {
00016   private $fetch_mode = \PDO::FETCH_CLASS;          // PDO Fetch mode
00017   private $fetch_class_name = 'QueryRecord';       // The default class name for fetching classes
00018   private $driver;                                 // PDO driver name
00019   private $keep_profile = DEBUG;                   // keep profiling and timing information?
00021   protected $pdo = null;                           // handle to the PDO interface
00023   private $pdo_statement = null;                   // handle for a PDOStatement
00024   private $pdo_transaction = false;                // handle for transaction status
00025 
00029   private $tables = array(
00030     'blocks',
00031     'blocks_areas',
00032     'commentinfo',
00033     'comments',
00034     'commentstatus',
00035     'commenttype',
00036     'crontab',
00037     'groups',
00038     'group_token_permissions',
00039     'log',
00040     'log_types',
00041     'object_terms',
00042     'object_types',
00043     'options',
00044     'post_tokens',
00045     'postinfo',
00046     'posts',
00047     'poststatus',
00048     'posttype',
00049     'revisions',
00050     'rewrite_rules',
00051     'scopes',
00052     'sessions',
00053     'terms',
00054     'terminfo',
00055     'tokens',
00056     'userinfo',
00057     'users',
00058     'user_token_permissions',
00059     'users_groups',
00060     'vocabularies',
00061   );
00062 
00066   private $sql_tables = array();
00067   private $sql_tables_repl = array();
00068   private $errors = array();                       // an array of errors related to queries
00069   private $profiles = array();                     // an array of query profiles
00070 
00071   protected $prefix = '';                          // class protected storage of the database table prefix, defaults to ''
00072   private $current_table;
00073 
00080   public static function ConnectionFactory( $connect_string )
00081   {
00082     list( $engine ) = explode( ':', $connect_string, 2 );
00083     $engines = array(
00084       'sqlite' => '\Habari\SQLiteConnection',
00085       'mysql' => '\Habari\MySQLConnection',
00086       'pgsql' => '\Habari\PGSQLConnection',
00087     );
00088 
00089     require_once( HABARI_PATH . "/system/schema/{$engine}/connection.php" );
00090     $engine_class = $engines[$engine];
00091     return new $engine_class();
00092   }
00093 
00099   protected function load_tables()
00100   {
00101     if ( isset( Config::get( 'db_connection' )->prefix ) ) {
00102       $prefix = Config::get( 'db_connection' )->prefix;
00103     }
00104     else {
00105       $prefix = $this->prefix;
00106     }
00107     $this->prefix = $prefix;
00108 
00109     // build the mapping with prefixes
00110     foreach ( $this->tables as $t ) {
00111       $this->sql_tables[$t] = $prefix . $t;
00112       $this->sql_tables_repl[$t] = '{' . $t . '}';
00113     }
00114   }
00115 
00124   public function connect ( $connect_string, $db_user, $db_pass )
00125   {
00126     // Do not display errors so that they can be handled by the error method
00127     $this->pdo = @new \PDO( $connect_string, $db_user, $db_pass );
00128     $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_WARNING );
00129     $this->load_tables();
00130     return true;
00131   }
00132 
00138   public function disconnect()
00139   {
00140     $this->pdo = null; // this is the canonical way :o
00141 
00142     return true;
00143   }
00144 
00150   public function is_connected()
00151   {
00152     return ( null != $this->pdo );
00153   }
00154 
00160   public function in_transaction()
00161   {
00162     return $this->pdo_transaction;
00163   }
00164 
00171   public function table( $name )
00172   {
00173     if ( isset( $this->sql_tables[$name] ) ) {
00174       return $this->sql_tables[$name];
00175     }
00176     else {
00177       return $name;
00178     }
00179   }
00180 
00188   public function register_table( $name )
00189   {
00190     $this->tables[] = $name;
00191     $this->load_tables();
00192   }
00193 
00199   public function set_fetch_mode( $mode )
00200   {
00201     $this->fetch_mode = $mode;
00202   }
00203 
00209   public function set_fetch_class( $class_name )
00210   {
00211     $this->fetch_class_name = $class_name;
00212   }
00213 
00222   public function exec( $query )
00223   {
00224     // Allow plugins to modify the query
00225     $query = Plugins::filter( 'db_exec', $query, array() );
00226     // Translate the query for the database engine
00227     $query = $this->sql_t( $query, array() );
00228     // Replace braced table names in the query with their prefixed counterparts
00229     $query = self::filter_tables( $query );
00230     // Allow plugins to modify the query after it has been processed
00231     $query = Plugins::filter( 'db_exec_postprocess', $query, array() );
00232 
00233     return ( $this->pdo->exec( $query ) !== false );
00234   }
00235 
00243   public function query( $query, $args = array() )
00244   {
00245     if ( $this->pdo_statement != null ) {
00246       $this->pdo_statement->closeCursor();
00247     }
00248 
00249     // Allow plugins to modify the query
00250     $query = Plugins::filter( 'query', $query, $args );
00251     // Translate the query for the database engine
00252     $query = $this->sql_t( $query, $args );
00253     // Replace braced table names in the query with their prefixed counterparts
00254     $query = self::filter_tables( $query );
00255     // Allow plugins to modify the query after it has been processed
00256     $query = Plugins::filter( 'query_postprocess', $query, $args );
00257 
00258     if ( $this->pdo_statement = $this->pdo->prepare( $query ) ) {
00259       if ( $this->fetch_mode == \PDO::FETCH_CLASS ) {
00260         /* Try to get the result class autoloaded. */
00261         if ( ! class_exists( $this->fetch_class_name, true ) ) {
00262           $class_name = $this->fetch_class_name;
00263           // @todo This is a GIANT namespace kludge, replacing Model class names with no namespace that don't exist with a default prefixed class
00264           if(strpos($class_name, '\\') == false) {
00265             $class_name = '\\Habari\\' . $class_name;
00266             $this->fetch_class_name = $class_name;
00267             if ( ! class_exists( $this->fetch_class_name, true ) ) {
00268               throw new \Exception('The model class that was specified for data retreival could not be loaded.');
00269             }
00270           }
00271         }
00272         /* Ensure that the class is actually available now, otherwise segfault happens (if we haven't died earlier anyway). */
00273         if ( class_exists( $this->fetch_class_name ) ) {
00274           $this->pdo_statement->setFetchMode( \PDO::FETCH_CLASS, $this->fetch_class_name, array() );
00275         }
00276         else {
00277           /* Die gracefully before the segfault occurs */
00278           echo '<br><br>' . _t( 'Attempt to fetch in class mode with a non-included class' ) . '<br><br>';
00279           return false;
00280         }
00281       }
00282       else {
00283         $this->pdo_statement->setFetchMode( $this->fetch_mode );
00284       }
00285 
00286       /* If we are profiling, then time the query */
00287       if ( $this->keep_profile ) {
00288         $profile = new QueryProfile( $query );
00289         $profile->params = $args;
00290         $profile->start();
00291       }
00292       if ( ! $this->pdo_statement->execute( $args ) ) {
00293         $this->add_error( array( 'query'=>$query,'error'=>$this->pdo_statement->errorInfo() ) );
00294         return false;
00295       }
00296       if ( $this->keep_profile ) {
00297         $profile->stop();
00298         $this->profiles[] = $profile;
00299       }
00300       return true;
00301     }
00302     else {
00303       $this->add_error( array(
00304         'query' => $query,
00305         'error' => $this->pdo->errorInfo(),
00306       ) );
00307       return false;
00308     }
00309   }
00310 
00324   public function execute_procedure( $procedure, $args = array() )
00325   {
00326     /* Local scope caching */
00327     $pdo = $this->pdo;
00328     $pdo_statement = $this->pdo_statement;
00329 
00330     if ( $pdo_statement != null ) {
00331       $pdo_statement->closeCursor();
00332     }
00333 
00334     $query = 'CALL ' . $procedure . '( ';
00335     if ( count( $args ) > 0 ) {
00336       $query .= str_repeat( '?,', count( $args ) ); // Add the placeholders
00337       $query = substr( $query, 0, strlen( $query ) - 1 ); // Strip the last comma
00338     }
00339     $query .= ' )';
00340     $query = $this->sql_t( $query, $args );
00341 
00342     if ( $pdo_statement = $pdo->prepare( $query ) ) {
00343       /* If we are profiling, then time the query */
00344       if ( $this->keep_profile ) {
00345         $profile = new QueryProfile( $query );
00346         $profile->start();
00347       }
00348       if ( ! $pdo_statement->execute( $args ) ) {
00349         $this->add_error( array( 'query'=>$query,'error'=>$pdo_statement->errorInfo() ) );
00350         return false;
00351       }
00352       if ( $this->keep_profile ) {
00353         $profile->stop();
00354         $this->profiles[] = $profile;
00355       }
00356       return true;
00357     }
00358     else {
00359       $this->add_error( array( 'query'=>$query,'error'=>$pdo_statement->errorInfo() ) );
00360       return false;
00361     }
00362   }
00363 
00368   public function begin_transaction()
00369   {
00370     if ( ! $this->pdo_transaction ) {
00371       if( $this->pdo->beginTransaction() ) {
00372         $this->pdo_transaction = true;
00373       }
00374     }
00375   }
00376 
00382   public function rollback()
00383   {
00384     if ( $this->pdo_transaction ) {
00385       if( $this->pdo->rollBack() ) {
00386         $this->pdo_transaction = false;
00387       }
00388     }
00389   }
00390 
00394   public function commit()
00395   {
00396     if ( $this->pdo_transaction ) {
00397       if( $this->pdo->commit() ) {
00398         $this->pdo_transaction = false;
00399       }
00400     }
00401   }
00402 
00408   public function get_profiles()
00409   {
00410     return $this->profiles;
00411   }
00412 
00418   public function add_error( $error )
00419   {
00420     $backtrace1 = debug_backtrace();
00421     $backtrace = array();
00422     foreach ( $backtrace1 as $trace ) {
00423       $backtrace[] = array_intersect_key( $trace, array('file'=>1, 'line'=>1, 'function'=>1, 'class'=>1) );
00424     }
00425     $this->errors[] = array_merge( $error, array( 'backtrace'=> $backtrace ) );
00426   }
00427 
00432   public function get_errors()
00433   {
00434     return $this->errors;
00435   }
00436 
00441   public function has_errors()
00442   {
00443     return ( count( $this->errors ) > 0 );
00444   }
00445 
00449   public function clear_errors()
00450   {
00451     $this->errors = array();
00452   }
00453 
00458   public function get_last_error()
00459   {
00460     $error = end( $this->errors );
00461     return ( array( 'query'=>$error['query'], 'message'=>$error['error'][2] ) );
00462   }
00463 
00472   public function get_results( $query, $args = array(), $class_name = '\Habari\QueryRecord' )
00473   {
00474     $this->set_fetch_mode( \PDO::FETCH_CLASS );
00475     $this->set_fetch_class( $class_name );
00476     if ( $this->query( $query, $args ) ) {
00477       return $this->pdo_statement->fetchAll();
00478     }
00479     else {
00480       return false;
00481     }
00482   }
00483 
00492   public function get_row( $query, $args = array(), $class_name = '\Habari\QueryRecord' )
00493   {
00494     $this->set_fetch_mode( \PDO::FETCH_CLASS );
00495     $this->set_fetch_class( $class_name );
00496 
00497     if ( $this->query( $query, $args ) ) {
00498       return $this->pdo_statement->fetch();
00499     }
00500     else {
00501       return false;
00502     }
00503   }
00504 
00513   public function get_column( $query, $args = array() )
00514   {
00515     if ( $this->query( $query, $args ) ) {
00516       return $this->pdo_statement->fetchAll( \PDO::FETCH_COLUMN );
00517     }
00518     else {
00519       return false;
00520     }
00521   }
00522 
00530   public function get_value( $query, $args = array() )
00531   {
00532     if ( $this->query( $query, $args ) ) {
00533       $result = $this->pdo_statement->fetch( \PDO::FETCH_NUM );
00534       return $result[0];
00535     }
00536     else {
00537       return false;
00538     }
00539   }
00540 
00549   public function get_keyvalue( $query, $args = array() )
00550   {
00551     if ( $this->query( $query, $args ) ) {
00552       $output = $this->pdo_statement->fetchAll( \PDO::FETCH_KEY_PAIR );
00553       return $output;
00554     }
00555     else {
00556       return false;
00557     }
00558   }
00559 
00567   public function insert( $table, $fieldvalues )
00568   {
00569     ksort( $fieldvalues );
00570 
00571     $fields = array_keys( $fieldvalues );
00572     $values = array_values( $fieldvalues );
00573     
00574     $query = "INSERT INTO {$table} ( " . implode( ', ', $fields ) . ' ) VALUES ( ' . implode( ', ', array_fill( 0, count( $values ), '?' ) ) . ' )';
00575 
00576     // need to pass $table on to the $o singleton object;
00577     $this->current_table = $table;
00578 
00579     return $this->query( $query, $values );
00580   }
00581 
00589   public function exists( $table, $keyfieldvalues )
00590   {
00591     $qry = "SELECT 1 as c FROM {$table} WHERE 1=1 ";
00592 
00593     $values = array();
00594     foreach ( $keyfieldvalues as $keyfield => $keyvalue ) {
00595       $qry .= " AND {$keyfield} = ? ";
00596       $values[] = $keyvalue;
00597     }
00598     $result = $this->get_row( $qry, $values );
00599     return ( $result !== false );
00600   }
00601 
00612   public function update( $table, $fieldvalues, $keyfields )
00613   {
00614     ksort( $fieldvalues );
00615     ksort( $keyfields );
00616 
00617     $keyfieldvalues = array();
00618     foreach ( $keyfields as $keyfield => $keyvalue ) {
00619       if ( is_numeric( $keyfield ) ) {
00620         // if the key is numeric, assume we were handed a simple list of fields that are keys and fetch its value from $fieldvalues
00621         $keyfieldvalues[$keyvalue] = $fieldvalues[$keyvalue];
00622       }
00623       else {
00624         // otherwise we were handed a key => value pair, use it as-is
00625         $keyfieldvalues[$keyfield] = $keyvalue;
00626       }
00627     }
00628     if ( $this->exists( $table, $keyfieldvalues ) ) {
00629       $qry = "UPDATE {$table} SET";
00630       $values = array();
00631       $comma = '';
00632       foreach ( $fieldvalues as $fieldname => $fieldvalue ) {
00633         $qry .= $comma . " {$fieldname} = ?";
00634         $values[] = $fieldvalue;
00635         $comma = ' ,';
00636       }
00637       $qry .= ' WHERE 1=1 ';
00638 
00639       foreach ( $keyfieldvalues as $keyfield => $keyvalue ) {
00640         $qry .= "AND {$keyfield} = ? ";
00641         $values[] = $keyvalue;
00642       }
00643       return $this->query( $qry, $values );
00644     }
00645     else {
00646       // We want the keyfieldvalues to be included in
00647       // the insert, with fieldvalues taking precedence.
00648       $fieldvalues = $fieldvalues + $keyfieldvalues;
00649       return $this->insert( $table, $fieldvalues );
00650     }
00651   }
00652 
00660   public function delete( $table, $keyfields )
00661   {
00662     $qry = "DELETE FROM {$table} WHERE 1=1 ";
00663     foreach ( $keyfields as $keyfield => $keyvalue ) {
00664       $qry .= "AND {$keyfield} = ? ";
00665       $values[] = $keyvalue;
00666     }
00667 
00668     return $this->query( $qry, $values );
00669   }
00670 
00680   public function last_insert_id()
00681   {
00682     if ( $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME ) == 'pgsql' ) {
00683       return $this->pdo->lastInsertId( $this->current_table. '_pkey_seq' );
00684     }
00685     else {
00686       return $this->pdo->lastInsertId( func_num_args() == 1 ? func_get_arg( 0 ) : '' );
00687     }
00688   }
00689 
00694   public function dbdelta( $queries, $execute = true, $silent = true ){}
00695 
00696   public function upgrade_pre( $old_version ){}
00697 
00698   public function upgrade_post( $old_version ){}
00699 
00700 
00709   public function upgrade( $old_version, $upgrade_path = '' )
00710   {
00711     // Get all the upgrade files
00712     $upgrade_files = Utils::glob( "{$upgrade_path}/*.sql" );
00713 
00714     // Put the upgrade files into an array using the 0-padded revision + '_0' as the key
00715     $upgrades = array();
00716     foreach ( $upgrade_files as $file ) {
00717       if ( intval( basename( $file, '.sql' ) ) > $old_version ) {
00718         $upgrades[ sprintf( '%010s_0', basename( $file, '.sql' ) )] = $file;
00719       }
00720     }
00721     // Put the upgrade functions into an array using the 0-padded revision + '_1' as the key
00722     $upgrade_functions = get_class_methods( $this );
00723     foreach ( $upgrade_functions as $fn ) {
00724       if ( preg_match( '%^upgrade_([0-9]+)$%i', $fn, $matches ) ) {
00725         if ( intval( $matches[1] ) > $old_version ) {
00726           $upgrades[ sprintf( '%010s_1', $matches[1] )] = array( $this, $fn );
00727         }
00728       }
00729     }
00730 
00731     // Sort the upgrades by revision, ascending
00732     ksort( $upgrades );
00733 
00734     // Execute all of the upgrade functions
00735     $result = true;
00736     foreach ( $upgrades as $upgrade ) {
00737       if ( is_array( $upgrade ) ) {
00739         $result &= $upgrade();
00740       }
00741       else {
00742         $result &= $this->query_file( $upgrade );
00743       }
00744       if ( !$result ) {
00745         break;
00746       }
00747     }
00748 
00749     return $result;
00750   }
00751 
00758   public function query_file( $file )
00759   {
00760     $upgrade_sql = trim( file_get_contents( $file ) );
00761     $upgrade_sql = str_replace( '{$prefix}', $this->prefix, $upgrade_sql );
00762 
00763     // Split up the queries
00764     $queries = explode( ';', $upgrade_sql );
00765 
00766     foreach ( $queries as $query ) {
00767       if ( trim( $query ) != '' ) {
00768         if ( !$this->query( $query ) ) {
00769           return false;
00770         }
00771       }
00772     }
00773 
00774     return true;
00775   }
00776 
00777 
00784   public function sql_t( $query )
00785   {
00786     return $query;
00787   }
00788 
00795   public function filter_tables( $query )
00796   {
00797     return str_replace( $this->sql_tables_repl, $this->sql_tables, $query );
00798   }
00799 
00800   public function get_driver_name()
00801   {
00802     if ( ! $this->driver ) {
00803       $this->driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
00804     }
00805     return $this->driver;
00806   }
00807 
00808   public function get_driver_version()
00809   {
00810     return $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION );
00811   }
00812 
00818   public function row_count()
00819   {
00820     return $this->pdo_statement->rowCount();
00821   }
00822 
00828   public function list_tables()
00829   {
00830     return $this->sql_tables;
00831   }
00832 
00841   public function quote ( $string )
00842   {
00843     return $this->pdo->quote( $string );
00844   }
00845 
00846 }
00847 
00848 ?>

Generated on Sun Aug 4 2013 12:51:43 for Habari by  doxygen 1.7.1