eZ Publish  [trunk]
ezdb.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZDB class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*! \defgroup eZDB Database abstraction layer */
00012 
00013 /*!
00014   \class eZDB ezdb.php
00015   \ingroup eZDB
00016   \brief The eZDB class provides database wrapper functions
00017   The eZ db library procides a database independent framework for
00018   SQL databases. The current supported databases are: PostgreSQL and
00019   MySQL.
00020 
00021   eZ db is designed to be used with the following type subset of SQL:
00022   int, float, varchar and text.
00023 
00024   To store date and time values int's are used. eZ locale is used to
00025   present the date and times on a localized format. That way we don't have
00026   to worry about the different date and time formats used in the different
00027   databases.
00028 
00029   Auto incrementing numbers, sequences, are used to generate unique id's
00030   for a table row. This functionality is abstracted as it works different
00031   in the different databases.
00032 
00033   Limit and offset functionality is also abstracted by the eZ db library.
00034 
00035   eZ db is designed to use lowercase in all table/column names. This is
00036   done to prevent errors as the different databases handles this differently.
00037   Especially when returning the data as an associative array.
00038 
00039   \code
00040 
00041   // Get the current database instance
00042   // will create a new database object and connect to the database backend
00043   // if there is already created an instance for this session the existing
00044   // object will be returned.
00045   // the settings for the database connections are set in site.ini
00046   $db = eZDB::instance();
00047 
00048   // Run a simple query
00049   $db->query( 'DELETE FROM sql_test' );
00050 
00051   // insert some data
00052   $str = $db->escapeString( "Testing escaping'\"" );
00053   $db->query( "INSERT INTO sql_test ( name, description ) VALUES ( 'New test', '$str' )" );
00054 
00055   // Get the last serial value for the sql_test table
00056   $rowID = $db->lastSerialID( 'sql_test', 'id' );
00057 
00058   // fetch some data into an array of associative arrays
00059   $rows = $db->arrayQuery( 'SELECT * FROM sql_test' );
00060 
00061   foreach ( $rows as $row )
00062   {
00063      print( $row['name'] );
00064   }
00065 
00066   // fetch some data with a limit
00067   // will return the 10 first rows in the result
00068   $ret = $db->arrayQuery( 'SELECT id, name, description, rownum FROM sql_test',
00069                            array( 'offset' => 0, 'limit' => 10 ) );
00070 
00071   // check which implementation we're running
00072   print( $db->databaseName() );
00073 
00074   \endcode
00075 
00076   \sa eZLocale eZINI
00077 */
00078 
00079 class eZDB
00080 {
00081     /*!
00082       Constructor.
00083       NOTE: Should not be used.
00084     */
00085     private function __construct()
00086     {
00087         eZDebug::writeError( 'This class should not be instantiated', __METHOD__ );
00088     }
00089 
00090     /*!
00091       \static
00092       Returns an instance of the database object.
00093     */
00094     static function hasInstance()
00095     {
00096         return isset( $GLOBALS['eZDBGlobalInstance'] ) && $GLOBALS['eZDBGlobalInstance'] instanceof eZDBInterface;
00097     }
00098 
00099     /*!
00100      \static
00101      Sets the global database instance to \a $instance.
00102     */
00103     static function setInstance( $instance )
00104     {
00105         $GLOBALS['eZDBGlobalInstance'] = $instance;
00106     }
00107 
00108     /**
00109      * Returns a shared instance of the eZDBInterface class aka database object.
00110      * If you want to change the current database values you should use $forceNewInstance.
00111      *
00112      * @param string|false $databaseImplementation
00113      * @param array|false $databaseParameters If array, then key 'use_defaults' (bool) is used.
00114      * @param bool $forceNewInstance
00115      * @return eZDBInterface
00116      */
00117     static function instance( $databaseImplementation = false, $databaseParameters = false, $forceNewInstance = false )
00118     {
00119         $impl =& $GLOBALS['eZDBGlobalInstance'];
00120 
00121         $fetchInstance = false;
00122         if ( !( $impl instanceof eZDBInterface ) )
00123             $fetchInstance = true;
00124 
00125         if ( $forceNewInstance  )
00126         {
00127             unset($impl);
00128             $impl = false;
00129             $fetchInstance = true;
00130         }
00131 
00132         $useDefaults = true;
00133         if ( is_array( $databaseParameters ) and isset( $databaseParameters['use_defaults'] ) )
00134             $useDefaults = $databaseParameters['use_defaults'];
00135 
00136         if ( $fetchInstance )
00137         {
00138             $ini = eZINI::instance();
00139             if ( $databaseImplementation === false and $useDefaults )
00140                 $databaseImplementation = $ini->variable( 'DatabaseSettings', 'DatabaseImplementation' );
00141 
00142             $server = $user = $pwd = $db = $usePersistentConnection = $port = false;
00143             if ( $useDefaults )
00144                 list( $server, $port, $user, $pwd, $db, $usePersistentConnection ) =
00145                     $ini->variableMulti( 'DatabaseSettings', array( 'Server', 'Port', 'User', 'Password', 'Database', 'UsePersistentConnection', ) );
00146 
00147             $socketPath = false;
00148             if ( $useDefaults )
00149             {
00150                 $socket = $ini->variable( 'DatabaseSettings', 'Socket' );
00151                 if ( trim( $socket != "" ) and $socket != "disabled" )
00152                 {
00153                     $socketPath = $socket;
00154                 }
00155             }
00156 
00157             // Check slave servers
00158             $slaveServer = null;
00159             $slaveServerPort = null;
00160             $slaveServerUser = null;
00161             $slaveServerPassword = null;
00162             $slaveServerDatabase = null;
00163             $useSlave = $ini->variable( 'DatabaseSettings', 'UseSlaveServer' );
00164             if ( $useSlave == "enabled" )
00165             {
00166                 $slaveServers = $ini->variable( 'DatabaseSettings', 'SlaveServerArray' );
00167                 $slaveServerPorts = $ini->variable( 'DatabaseSettings', 'SlaveServerPort' );
00168                 $slaveServerUsers = $ini->variable( 'DatabaseSettings', 'SlaverServerUser' );
00169                 $slaveServerPasswords = $ini->variable( 'DatabaseSettings', 'SlaverServerPassword' );
00170                 $slaveServerDatabases = $ini->variable( 'DatabaseSettings', 'SlaverServerDatabase' );
00171                 $numberServers = count( $slaveServers );
00172                 if ( $numberServers > 1 )
00173                 {
00174                     $index = mt_rand( 1, $numberServers ) - 1;
00175                 }
00176                 else
00177                     $index = 0;
00178                 $slaveServer = $slaveServers[$index];
00179                 $slaveServerPort = $slaveServerPorts[$index];
00180                 $slaveServerUser = $slaveServerUsers[$index];
00181                 $slaveServerPassword = $slaveServerPasswords[$index];
00182                 $slaveServerDatabase = $slaveServerDatabases[$index];
00183             }
00184 
00185             list( $charset, $retries ) =
00186                 $ini->variableMulti( 'DatabaseSettings', array( 'Charset', 'ConnectRetries' ) );
00187 
00188             $isInternalCharset = false;
00189             if ( trim( $charset ) == '' )
00190             {
00191                 $charset = eZTextCodec::internalCharset();
00192                 $isInternalCharset = true;
00193             }
00194             $builtinEncoding = ( $ini->variable( 'DatabaseSettings', 'UseBuiltinEncoding' ) == 'true' );
00195 
00196             $impl = null;
00197 
00198             $useSlaveServer = false;
00199             if ( $useSlave == "enabled" )
00200                 $useSlaveServer = true;
00201             $defaultDatabaseParameters = array( 'server' => $server,
00202                                                 'port' => $port,
00203                                                 'user' => $user,
00204                                                 'password' => $pwd,
00205                                                 'database' => $db,
00206                                                 'use_slave_server' => $useSlaveServer,
00207                                                 'slave_server' => $slaveServer,
00208                                                 'slave_port' => $slaveServerPort,
00209                                                 'slave_user' => $slaveServerUser,
00210                                                 'slave_password' => $slaveServerPassword,
00211                                                 'slave_database' => $slaveServerDatabase,
00212                                                 'charset' => $charset,
00213                                                 'is_internal_charset' => $isInternalCharset,
00214                                                 'socket' => $socketPath,
00215                                                 'builtin_encoding' => $builtinEncoding,
00216                                                 'connect_retries' => $retries,
00217                                                 'use_persistent_connection' => $usePersistentConnection,
00218                                                 'show_errors' => true );
00219             /* This looks funny, but is needed to fix a crash in PHP */
00220             $b = $databaseParameters;
00221             $databaseParameters = $defaultDatabaseParameters;
00222             if ( isset( $b['server'] ) )
00223                 $databaseParameters['server'] = $b['server'];
00224             if ( isset( $b['port'] ) )
00225                 $databaseParameters['port'] = $b['port'];
00226             if ( isset( $b['user'] ) )
00227                 $databaseParameters['user'] = $b['user'];
00228             if ( isset( $b['password'] ) )
00229                 $databaseParameters['password'] = $b['password'];
00230             if ( isset( $b['database'] ) )
00231                 $databaseParameters['database'] = $b['database'];
00232             if ( isset( $b['use_slave_server'] ) )
00233                 $databaseParameters['use_slave_server'] = $b['use_slave_server'];
00234             if ( isset( $b['slave_server'] ) )
00235                 $databaseParameters['slave_server'] = $b['slave_server'];
00236             if ( isset( $b['slave_port'] ) )
00237                 $databaseParameters['slave_port'] = $b['slave_port'];
00238             if ( isset( $b['slave_user'] ) )
00239                 $databaseParameters['slave_user'] = $b['slave_user'];
00240             if ( isset( $b['slave_password'] ) )
00241                 $databaseParameters['slave_password'] = $b['slave_password'];
00242             if ( isset( $b['slave_database'] ) )
00243                 $databaseParameters['slave_database'] = $b['slave_database'];
00244             if ( isset( $b['charset'] ) )
00245             {
00246                 $databaseParameters['charset'] = $b['charset'];
00247                 $databaseParameters['is_internal_charset'] = false;
00248             }
00249             if ( isset( $b['socket'] ) )
00250                 $databaseParameters['socket'] = $b['socket'];
00251             if ( isset( $b['builtin_encoding'] ) )
00252                 $databaseParameters['builtin_encoding'] = $b['builtin_encoding'];
00253             if ( isset( $b['connect_retries'] ) )
00254                 $databaseParameters['connect_retries'] = $b['connect_retries'];
00255             if ( isset( $b['use_persistent_connection'] ) )
00256                 $databaseParameters['use_persistent_connection'] = $b['use_persistent_connection'];
00257             if ( isset( $b['show_errors'] ) )
00258                 $databaseParameters['show_errors'] = $b['show_errors'];
00259 
00260             $optionArray = array( 'iniFile'       => 'site.ini',
00261                                   'iniSection'    => 'DatabaseSettings',
00262                                   'iniVariable'   => 'ImplementationAlias',
00263                                   'handlerIndex'  => $databaseImplementation,
00264                                   'handlerParams' => array( $databaseParameters ) );
00265 
00266             $options = new ezpExtensionOptions( $optionArray );
00267 
00268             $impl = eZExtension::getHandlerClass( $options );
00269 
00270             if ( !$impl )
00271             {
00272                 $impl = new eZNullDB( $databaseParameters );
00273                 $impl->ErrorMessage = "No database handler was found for '$databaseImplementation'";
00274                 $impl->ErrorNumber = -1;
00275                 if ( $databaseParameters['show_errors'] )
00276                 {
00277                     eZDebug::writeError( 'Database implementation not supported: ' . $databaseImplementation, __METHOD__ );
00278                 }
00279             }
00280 
00281             $impl->setErrorHandling( self::$errorHandling );
00282         }
00283         return $impl;
00284     }
00285 
00286     /*!
00287       Checks transaction counter
00288       If the current transaction counter is 1 or higher
00289       means 1 or more transactions are running and a negative value
00290       means something is wrong.
00291       Prints the error.
00292     */
00293     static function checkTransactionCounter()
00294     {
00295         $result = true;
00296         $ini = eZINI::instance();
00297         $checkValidity = ( $ini->variable( "SiteAccessSettings", "CheckValidity" ) == "true" );
00298         if ( $checkValidity )
00299             return $result;
00300 
00301         $db = eZDB::instance();
00302 
00303         if ( $db->transactionCounter() > 0 )
00304         {
00305             $result = array();
00306             $result['error'] = "Internal transaction counter mismatch : " . $db->transactionCounter() . ". Should be zero.";
00307             eZDebug::writeError( $result['error'] );
00308             $stack = $db->generateFailedTransactionStack();
00309             if ( $stack !== false )
00310             {
00311                 eZDebug::writeError( $stack, 'Transaction stack' );
00312             }
00313             $ini = eZINI::instance();
00314             // In debug mode the transaction will be invalidated causing the top-level commit
00315             // to issue an error.
00316             if ( $ini->variable( "DatabaseSettings", "DebugTransactions" ) == "enabled" )
00317             {
00318                 $db->invalidateTransaction();
00319                 $db->reportError();
00320             }
00321             else
00322             {
00323                 while ( $db->transactionCounter() > 0 )
00324                 {
00325                     $db->commit();
00326                 }
00327             }
00328         }
00329 
00330         return $result;
00331     }
00332 
00333     /**
00334      * Sets the default eZDB error handling mode.
00335      * Use eZDB::instance()->setErrorHandling() with the same parameters to set error handling for one instance only
00336      *
00337      * @param int $errorHandling
00338      *        Possible values are:pm
00339      *        - eZDB::ERROR_HANDLING_STANDARD: backward compatible error handling, using reportError
00340      *        - eZDB::ERROR_HANDLING_EXCEPTION: using exceptions
00341      * @throw RuntimeException thrown when an invalid error handling is given
00342      * @access private
00343      * @since 4.5
00344      */
00345     static function setErrorHandling( $errorHandling )
00346     {
00347         if ( $errorHandling != self::ERROR_HANDLING_EXCEPTIONS && $errorHandling != self::ERROR_HANDLING_STANDARD )
00348             throw new RuntimeException( "Unknown eZDB error handling mode '$errorHandling'" );
00349         self::$errorHandling = $errorHandling;
00350 
00351         if ( self::hasInstance() )
00352         {
00353             self::instance()->setErrorHandling( $errorHandling );
00354         }
00355     }
00356 
00357     /**
00358      * Error handling mode
00359      */
00360     const ERROR_HANDLING_STANDARD = 1;
00361     const ERROR_HANDLING_EXCEPTIONS = 2;
00362 
00363     protected static $errorHandling = self::ERROR_HANDLING_STANDARD;
00364 }
00365 
00366 ?>