1<?php 2/** 3 * $Header$ 4 * 5 * @version $Revision$ 6 * @package Log 7 */ 8 9/** PEAR's MDB2 package */ 10require_once 'MDB2.php'; 11MDB2::loadFile('Date'); 12 13/** 14 * The Log_mdb2 class is a concrete implementation of the Log:: abstract class 15 * which sends messages to an SQL server. Each entry occupies a separate row 16 * in the database. 17 * 18 * This implementation uses PEAR's MDB2 database abstraction layer. 19 * 20 * CREATE TABLE log_table ( 21 * id INT NOT NULL, 22 * logtime TIMESTAMP NOT NULL, 23 * ident CHAR(16) NOT NULL, 24 * priority INT NOT NULL, 25 * message VARCHAR(200), 26 * PRIMARY KEY (id) 27 * ); 28 * 29 * @author Lukas Smith <smith@backendmedia.com> 30 * @author Jon Parise <jon@php.net> 31 * @since Log 1.9.0 32 * @package Log 33 */ 34class Log_mdb2 extends Log 35{ 36 /** 37 * Variable containing the DSN information. 38 * @var mixed 39 * @access private 40 */ 41 var $_dsn = ''; 42 43 /** 44 * Array containing our set of DB configuration options. 45 * @var array 46 * @access private 47 */ 48 var $_options = array('persistent' => true); 49 50 /** 51 * Object holding the database handle. 52 * @var object 53 * @access private 54 */ 55 var $_db = null; 56 57 /** 58 * Resource holding the prepared statement handle. 59 * @var resource 60 * @access private 61 */ 62 var $_statement = null; 63 64 /** 65 * Flag indicating that we're using an existing database connection. 66 * @var boolean 67 * @access private 68 */ 69 var $_existingConnection = false; 70 71 /** 72 * String holding the database table to use. 73 * @var string 74 * @access private 75 */ 76 var $_table = 'log_table'; 77 78 /** 79 * String holding the name of the ID sequence. 80 * @var string 81 * @access private 82 */ 83 var $_sequence = 'log_id'; 84 85 /** 86 * Maximum length of the $ident string. This corresponds to the size of 87 * the 'ident' column in the SQL table. 88 * @var integer 89 * @access private 90 */ 91 var $_identLimit = 16; 92 93 /** 94 * Set of field types used in the database table. 95 * @var array 96 * @access private 97 */ 98 var $_types = array( 99 'id' => 'integer', 100 'logtime' => 'timestamp', 101 'ident' => 'text', 102 'priority' => 'text', 103 'message' => 'clob' 104 ); 105 106 /** 107 * Constructs a new sql logging object. 108 * 109 * @param string $name The target SQL table. 110 * @param string $ident The identification field. 111 * @param array $conf The connection configuration array. 112 * @param int $level Log messages up to and including this level. 113 * @access public 114 */ 115 public function __construct($name, $ident = '', $conf = array(), 116 $level = PEAR_LOG_DEBUG) 117 { 118 $this->_id = md5(microtime().rand()); 119 $this->_table = $name; 120 $this->_mask = Log::UPTO($level); 121 122 /* If an options array was provided, use it. */ 123 if (isset($conf['options']) && is_array($conf['options'])) { 124 $this->_options = $conf['options']; 125 } 126 127 /* If a specific sequence name was provided, use it. */ 128 if (!empty($conf['sequence'])) { 129 $this->_sequence = $conf['sequence']; 130 } 131 132 /* If a specific sequence name was provided, use it. */ 133 if (isset($conf['identLimit'])) { 134 $this->_identLimit = $conf['identLimit']; 135 } 136 137 /* Now that the ident limit is confirmed, set the ident string. */ 138 $this->setIdent($ident); 139 140 /* If an existing database connection was provided, use it. */ 141 if (isset($conf['db'])) { 142 $this->_db = &$conf['db']; 143 $this->_existingConnection = true; 144 $this->_opened = true; 145 } elseif (isset($conf['singleton'])) { 146 $this->_db = &MDB2::singleton($conf['singleton'], $this->_options); 147 $this->_existingConnection = true; 148 $this->_opened = true; 149 } else { 150 $this->_dsn = $conf['dsn']; 151 } 152 } 153 154 /** 155 * Opens a connection to the database, if it has not already 156 * been opened. This is implicitly called by log(), if necessary. 157 * 158 * @return boolean True on success, false on failure. 159 * @access public 160 */ 161 function open() 162 { 163 if (!$this->_opened) { 164 /* Use the DSN and options to create a database connection. */ 165 $this->_db = &MDB2::connect($this->_dsn, $this->_options); 166 if (PEAR::isError($this->_db)) { 167 return false; 168 } 169 170 /* Create a prepared statement for repeated use in log(). */ 171 if (!$this->_prepareStatement()) { 172 return false; 173 } 174 175 /* We now consider out connection open. */ 176 $this->_opened = true; 177 } 178 179 return $this->_opened; 180 } 181 182 /** 183 * Closes the connection to the database if it is still open and we were 184 * the ones that opened it. It is the caller's responsible to close an 185 * existing connection that was passed to us via $conf['db']. 186 * 187 * @return boolean True on success, false on failure. 188 * @access public 189 */ 190 function close() 191 { 192 /* If we have a statement object, free it. */ 193 if (is_object($this->_statement)) { 194 $this->_statement->free(); 195 $this->_statement = null; 196 } 197 198 /* If we opened the database connection, disconnect it. */ 199 if ($this->_opened && !$this->_existingConnection) { 200 $this->_opened = false; 201 return $this->_db->disconnect(); 202 } 203 204 return ($this->_opened === false); 205 } 206 207 /** 208 * Sets this Log instance's identification string. Note that this 209 * SQL-specific implementation will limit the length of the $ident string 210 * to sixteen (16) characters. 211 * 212 * @param string $ident The new identification string. 213 * 214 * @access public 215 * @since Log 1.8.5 216 */ 217 function setIdent($ident) 218 { 219 $this->_ident = substr($ident, 0, $this->_identLimit); 220 } 221 222 /** 223 * Inserts $message to the currently open database. Calls open(), 224 * if necessary. Also passes the message along to any Log_observer 225 * instances that are observing this Log. 226 * 227 * @param mixed $message String or object containing the message to log. 228 * @param string $priority The priority of the message. Valid 229 * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, 230 * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, 231 * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. 232 * @return boolean True on success or false on failure. 233 * @access public 234 */ 235 function log($message, $priority = null) 236 { 237 /* If a priority hasn't been specified, use the default value. */ 238 if ($priority === null) { 239 $priority = $this->_priority; 240 } 241 242 /* Abort early if the priority is above the maximum logging level. */ 243 if (!$this->_isMasked($priority)) { 244 return false; 245 } 246 247 /* If the connection isn't open and can't be opened, return failure. */ 248 if (!$this->_opened && !$this->open()) { 249 return false; 250 } 251 252 /* If we don't already have a statement object, create one. */ 253 if (!is_object($this->_statement) && !$this->_prepareStatement()) { 254 return false; 255 } 256 257 /* Extract the string representation of the message. */ 258 $message = $this->_extractMessage($message); 259 260 /* Build our set of values for this log entry. */ 261 $values = array( 262 'id' => $this->_db->nextId($this->_sequence), 263 'logtime' => MDB2_Date::mdbNow(), 264 'ident' => $this->_ident, 265 'priority' => $priority, 266 'message' => $message 267 ); 268 269 /* Execute the SQL query for this log entry insertion. */ 270 $this->_db->expectError(MDB2_ERROR_NOSUCHTABLE); 271 $result = &$this->_statement->execute($values); 272 $this->_db->popExpect(); 273 274 /* Attempt to handle any errors. */ 275 if (PEAR::isError($result)) { 276 /* We can only handle MDB2_ERROR_NOSUCHTABLE errors. */ 277 if ($result->getCode() != MDB2_ERROR_NOSUCHTABLE) { 278 return false; 279 } 280 281 /* Attempt to create the target table. */ 282 if (!$this->_createTable()) { 283 return false; 284 } 285 286 /* Recreate our prepared statement resource. */ 287 $this->_statement->free(); 288 if (!$this->_prepareStatement()) { 289 return false; 290 } 291 292 /* Attempt to re-execute the insertion query. */ 293 $result = $this->_statement->execute($values); 294 if (PEAR::isError($result)) { 295 return false; 296 } 297 } 298 299 $this->_announce(array('priority' => $priority, 'message' => $message)); 300 301 return true; 302 } 303 304 /** 305 * Create the log table in the database. 306 * 307 * @return boolean True on success or false on failure. 308 * @access private 309 */ 310 function _createTable() 311 { 312 $this->_db->loadModule('Manager', null, true); 313 $result = $this->_db->manager->createTable( 314 $this->_table, 315 array( 316 'id' => array('type' => $this->_types['id']), 317 'logtime' => array('type' => $this->_types['logtime']), 318 'ident' => array('type' => $this->_types['ident']), 319 'priority' => array('type' => $this->_types['priority']), 320 'message' => array('type' => $this->_types['message']) 321 ) 322 ); 323 if (PEAR::isError($result)) { 324 return false; 325 } 326 327 $result = $this->_db->manager->createIndex( 328 $this->_table, 329 'unique_id', 330 array('fields' => array('id' => true), 'unique' => true) 331 ); 332 if (PEAR::isError($result)) { 333 return false; 334 } 335 336 return true; 337 } 338 339 /** 340 * Prepare the SQL insertion statement. 341 * 342 * @return boolean True if the statement was successfully created. 343 * 344 * @access private 345 * @since Log 1.9.0 346 */ 347 function _prepareStatement() 348 { 349 $this->_statement = &$this->_db->prepare( 350 'INSERT INTO ' . $this->_table . 351 ' (id, logtime, ident, priority, message)' . 352 ' VALUES(:id, :logtime, :ident, :priority, :message)', 353 $this->_types, MDB2_PREPARE_MANIP); 354 355 /* Return success if we didn't generate an error. */ 356 return (PEAR::isError($this->_statement) === false); 357 } 358} 359