File indexing completed on 2024-04-21 16:36:32

0001 <?php
0002 
0003 /**
0004  * Flooer Framework
0005  *
0006  * LICENSE: BSD License (2 Clause)
0007  *
0008  * @category    Flooer
0009  * @package     Flooer_Db
0010  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
0011  * @copyright   Akira Ohgaki
0012  * @license     https://opensource.org/licenses/BSD-2-Clause  BSD License (2 Clause)
0013  * @link        https://github.com/akiraohgaki/flooer
0014  */
0015 
0016 require_once 'Flooer/Db/Statement.php';
0017 require_once 'Flooer/Db/Table.php';
0018 
0019 /**
0020  * Usage
0021  *
0022  * $db = new Flooer_Db(array(
0023  *     'dsn' => 'mysql:host=localhost;dbname=database',
0024  *     'username' => 'username',
0025  *     'password' => 'password'
0026  * ));
0027  * $value = $db->TableName->RecordID->ColumnName;
0028  */
0029 
0030 /**
0031  * Database connection class of SQL database abstraction layer
0032  *
0033  * @category    Flooer
0034  * @package     Flooer_Db
0035  * @author      Akira Ohgaki <akiraohgaki@gmail.com>
0036  */
0037 class Flooer_Db extends PDO
0038 {
0039 
0040     /**
0041      * Configuration options
0042      *
0043      * @var     array
0044      */
0045     protected $_config = array(
0046         'dsn' => null,
0047         'username' => null,
0048         'password' => null,
0049         'driverOptions' => array(),
0050         'tableConfig' => array()
0051     );
0052 
0053     /**
0054      * Table exists
0055      *
0056      * @var     array
0057      */
0058     protected $_tableExists = array();
0059 
0060     /**
0061      * Table object cache
0062      *
0063      * @var     array
0064      */
0065     protected $_tableCache = array();
0066 
0067     /**
0068      * SQL statement log
0069      *
0070      * @var     array
0071      */
0072     protected $_statementLog = array();
0073 
0074     /**
0075      * Constructor
0076      *
0077      * @param   array $config
0078      * @return  void
0079      */
0080     public function __construct(array $config = null)
0081     {
0082         if ($config) {
0083             $this->_config = $config + $this->_config;
0084         }
0085         set_exception_handler(array(__CLASS__, 'exceptionHandler'));
0086         parent::__construct(
0087             $this->_config['dsn'],
0088             $this->_config['username'],
0089             $this->_config['password'],
0090             $this->_config['driverOptions']
0091         );
0092         restore_exception_handler();
0093         if (!isset($this->_config['driverOptions'][parent::ATTR_STATEMENT_CLASS])) {
0094             parent::setAttribute(
0095                 parent::ATTR_STATEMENT_CLASS,
0096                 array('Flooer_Db_Statement')
0097             );
0098         }
0099         if (!isset($this->_config['driverOptions'][parent::ATTR_DEFAULT_FETCH_MODE])) {
0100             parent::setAttribute(
0101                 parent::ATTR_DEFAULT_FETCH_MODE,
0102                 parent::FETCH_ASSOC
0103             );
0104         }
0105         if (!isset($this->_config['driverOptions'][parent::ATTR_PERSISTENT])) {
0106             parent::setAttribute(parent::ATTR_PERSISTENT, true);
0107         }
0108     }
0109 
0110     /**
0111      * Magic method to create a table
0112      *
0113      * @param   string $key
0114      * @param   array|object $value
0115      * @return  void
0116      */
0117     public function __set($key, $value)
0118     {
0119         if (is_array($value) || is_object($value)) {
0120             $this->__unset($key);
0121             $tableName = $key;
0122             if (isset($this->_config['tableConfig']['prefix'])) {
0123                 $tableName = $this->_config['tableConfig']['prefix'] . $tableName;
0124             }
0125             $fields = array();
0126             foreach ($value as $fieldName => $fieldType) {
0127                 $fields[] = "$fieldName $fieldType";
0128             }
0129             $definition = implode(',', $fields);
0130             $sql = "CREATE TABLE $tableName ($definition);";
0131             $this->_statementLog[] = $sql;
0132             $count = parent::exec($sql);
0133             if ($count !== false) {
0134                 $this->_tableExists[$tableName] = true;
0135             }
0136             return;
0137         }
0138         trigger_error(
0139             "Setting non-array or non-object property ($key) is not allowed",
0140             E_USER_NOTICE
0141         );
0142     }
0143 
0144     /**
0145      * Magic method to get a table object
0146      *
0147      * @param   string $key
0148      * @return  Flooer_Db_Table|null
0149      */
0150     public function __get($key)
0151     {
0152         if ($this->__isset($key)) {
0153             $tableName = $key;
0154             if (isset($this->_config['tableConfig']['prefix'])) {
0155                 $tableName = $this->_config['tableConfig']['prefix'] . $tableName;
0156             }
0157             if (empty($this->_tableCache[$tableName])) {
0158                 $this->_tableCache[$tableName] = new Flooer_Db_Table(
0159                     $this,
0160                     array('name' => $key)
0161                     + $this->_config['tableConfig']
0162                 );
0163             }
0164             return $this->_tableCache[$tableName];
0165         }
0166         return null;
0167     }
0168 
0169     /**
0170      * Magic method to check a table
0171      *
0172      * @param   string $key
0173      * @return  bool
0174      */
0175     public function __isset($key)
0176     {
0177         $tableName = $key;
0178         if (isset($this->_config['tableConfig']['prefix'])) {
0179             $tableName = $this->_config['tableConfig']['prefix'] . $tableName;
0180         }
0181         if (isset($this->_tableExists[$tableName])) {
0182             return $this->_tableExists[$tableName];
0183         }
0184         $driver = parent::getAttribute(parent::ATTR_DRIVER_NAME);
0185         if ($driver == 'sqlite') {
0186             $sql = "SELECT 1"
0187                 . " FROM sqlite_master"
0188                 . " WHERE type = " . parent::quote('table')
0189                 . " AND name = " . parent::quote($tableName)
0190                 . " LIMIT 1;";
0191             $this->_statementLog[] = $sql;
0192             $statement = parent::prepare($sql);
0193             $bool = $statement->execute();
0194             $row = $statement->fetch(parent::FETCH_NUM);
0195             $statement->closeCursor();
0196             if ($bool && $row) {
0197                 if ($row[0]) {
0198                     $this->_tableExists[$tableName] = true;
0199                     return true;
0200                 }
0201                 $this->_tableExists[$tableName] = false;
0202             }
0203         }
0204         else {
0205             $sql = "SELECT COUNT(*) FROM $tableName;";
0206             if ($driver == 'mysql' || $driver == 'pgsql') {
0207                 $sql = "SELECT 1 FROM $tableName LIMIT 1;";
0208             }
0209             else if ($driver == 'sqlsrv') {
0210                 $sql = "SELECT TOP 1 1 FROM $tableName;";
0211             }
0212             $this->_statementLog[] = $sql;
0213             $statement = parent::prepare($sql);
0214             $bool = $statement->execute();
0215             $statement->closeCursor();
0216             if ($bool) {
0217                 $this->_tableExists[$tableName] = true;
0218                 return true;
0219             }
0220             $this->_tableExists[$tableName] = false;
0221         }
0222         return false;
0223     }
0224 
0225     /**
0226      * Magic method to drop a table
0227      *
0228      * @param   string $key
0229      * @return  void
0230      */
0231     public function __unset($key)
0232     {
0233         if ($this->__isset($key)) {
0234             $tableName = $key;
0235             if (isset($this->_config['tableConfig']['prefix'])) {
0236                 $tableName = $this->_config['tableConfig']['prefix'] . $tableName;
0237             }
0238             $sql = "DROP TABLE $tableName;";
0239             $this->_statementLog[] = $sql;
0240             $count = parent::exec($sql);
0241             if ($count !== false) {
0242                 $this->_tableExists[$tableName] = false;
0243                 unset($this->_tableCache[$tableName]);
0244             }
0245         }
0246     }
0247 
0248     /**
0249      * Exception handler
0250      *
0251      * @param   PDOException $exception
0252      * @return  void
0253      */
0254     public static function exceptionHandler($exception)
0255     {
0256         $code = $exception->getCode();
0257         $message = $exception->getMessage();
0258         $file = $exception->getFile();
0259         $line = $exception->getLine();
0260         echo __CLASS__ . ": [$code]: $message; $file($line)\n";
0261     }
0262 
0263     /**
0264      * Set a configuration options for a table class
0265      *
0266      * @param   array $config
0267      * @return  void
0268      */
0269     public function setTableConfig(array $config)
0270     {
0271         $this->_config['tableConfig'] = $config;
0272     }
0273 
0274     /**
0275      * Get a configuration options for a table class
0276      *
0277      * @return  array
0278      */
0279     public function getTableConfig()
0280     {
0281         return $this->_config['tableConfig'];
0282     }
0283 
0284     /**
0285      * Add a SQL statement log
0286      *
0287      * @param $sql
0288      *
0289      * @return  void
0290      */
0291     public function addStatementLog($sql)
0292     {
0293         $this->_statementLog[] = $sql;
0294     }
0295 
0296     /**
0297      * Get a SQL statement log
0298      *
0299      * @return  array
0300      */
0301     public function getStatementLog()
0302     {
0303         return $this->_statementLog;
0304     }
0305 
0306 }