Я пытаюсь управлять своими подключениями к базе данных и запросами через ООП в PHP, и я не очень хорош в этом. Я знаю, что я изобретаю колесо, но так мне нравится 🙂
Я использую три класса, включая синтаксический анализатор SQL, который я сам не сделал. Моя реализация возвращает объект при создании нового соединения. Этот объект базы данных должен создать новый экземпляр запроса (один экземпляр для оператора SQL). Мой вопрос: как я могу заставить свой класс запроса быть только invocable из класса базы данных?
Я вставляю резюме моих классов и реализацию ниже. Не стесняйтесь, дайте мне знать, насколько это плохо. Благодаря!
class genc_db_parser { /* See at http://www.tehuber.com/article.php?story=20081016164856267 returns an array with indexed values ('select','from','where','update',...) when they are available */ } class genc_database { public $db; /* The database connection */ public $signature; /* Unique signature for the connection */ public static $instances = array(); /* Array of references to connection */ public static function error($e,$sql) { /* Errors */ } private static function singleton($cfg,$inst) { $signature = sha1(serialize($cfg)); if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) ) { foreach ( self::$instances as $obj ) { if ( $obj->signature == $signature ) return $obj->db; } try { $db = new PDO($cfg['engine'].':host='.$cfg['host'].';dbname='.$cfg['db'], $cfg['user'], $cfg['pass']); } catch (PDOException $e) { self::error($e); } if ( $db ) { $t = self::$instances; array_push($t,$inst); return $db; } } return false; } function __construct($cfg=array()) { if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db']) ) $cfg['engine'] = isset($cfg['engine']) ? $cfg['engine'] : 'mysql'; else $cfg = array( 'host' => GEN_DB_HOST, 'user' => GEN_DB_USER, 'pass' => GEN_DB_PASS, 'db' => GEN_DATABASE, 'engine' => GEN_DB_ENGINE ); if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) ) { if ( $this->db = self::singleton($cfg,$this) ) { $this->signature = sha1(serialize($cfg)); $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); if ( $cfg['engine'] == 'mysql' ) { $this->db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true); $this->db->exec('SET CHARACTER SET utf8'); } } } } public function query($sql) { return new genc_query($sql,&$this); } } class genc_query { private $sql, $conn, $db, $res, $sequences, $num; function __construct($sql_statement,$db) { $sql_statement = trim($sql_statement); if ( !empty($sql_statement) ) { $this->sql = $sql_statement; $this->conn = &$db; $this->db = &$db->db; $this->analyze(); } } private function analyze() { if ( $this->sql !== null ) { $this->sequences = genc_db_parser::ParseString($this->sql)->getArray(); } } private function execute() { if ( $this->res === null ) { $this->res = false; if ( isset($this->sequences['select']) ) { try { $this->res = $this->db->query($this->sql); } catch (Exception $e) { genc_database::error($e,$this->sql); } } else { try { $this->res = $this->db->exec($this->sql); } catch (Exception $e) { genc_database::error($e,$this->sql); } } } return $this->res; } public function count() { if ( $this->num === null ) { $req = false; $this->num = false; if ( isset($this->sequences['select']) ) { $sql = genc_db_parser::ParseString($this->sql)->getCountQuery(); try { $req = $this->db->query($sql); } catch (Exception $e) { genc_database::error($e,$sql); } if ( $req ) $this->num = $req->fetchColumn(); } } return $this->num; } public function get_result() { if ( $this->execute() ) return $this->res; return false; } public function get_row() { $this->execute(); if ( $this->res && isset($this->sequences['select']) ) return $this->res->fetch(PDO::FETCH_ASSOC); return false; } /* Other functions working on the result... */ }
Реализация
/* db is the database object */ $db = new genc_database(); /* concurrent connections can be opened. However giving twice the same argument will return the same corresponding opened connection */ $db2 = new genc_database(array('host'=>'localhost','user'=>'myname','pass'=>'mypass','db'=>'mydb'); /* $db->query($sql) will create a query object ($q) attached to this database */ $q = $db->query(sprintf(" SELECT id,name,modified FROM users WHERE id_account = %u", $id )); /* $q->count() will return the number of rows returned by the query (through a COUNT), and without taking the limit into account */ echo $q->count(); /* $q->get_row will return the next row of the current recordset indexed by name */ while ( $data = $q->get_row() ) echo $data['id'].': '.$data['name'].'<br />'; /* If we do another action than a select, functions ahead will not return an error but false */ /* On other actions, just to execute the query, use get_result(), which will return the number of affected rows */ $p = $db2->query("UPDATE user2 SET modified = NOW() WHERE id = 1"); echo $p->get_result().'<br />';
Не стесняйтесь, дайте мне знать, насколько это плохо.
Это плохо!
…
Какие?
Вы спросили !
Хорошо, со всей серьезностью, это не так уж плохо, как и глупо . Вы переносите PDO в другой класс. Если вы хотите добавить дополнительные функции в PDO, вы должны расширить его.
Мой вопрос: как я могу заставить свой класс запроса быть только invocable из класса базы данных?
PDO уже делает это во время повседневных операций. Когда вы prepare
запрос, он возвращает объект PDOStatement . Вы можете настроить его для возврата другого объекта ( через PDO::ATTR_STATEMENT_CLASS
), который вместо этого расширяет PDOStatement.
Если вы хотите предварительно обработать запрос, используя ваш синтаксический анализатор, вам необходимо переопределить методы exec
, query
и prepare
в вашем классе, которые расширяют PDO. После того как вы обработали запрос, вы можете вызвать родительский метод и вернуть свой расширенный класс операторов.
Если вы беспокоитесь о том, что люди ссылаются на класс утверждения, не проходя через exec
/ query
/ prepare
, просто имейте в виду, что никакие запросы не могут быть выполнены, если оператор не знает, как обращаться к базе данных, и он не сможет этого сделать без родительского объекта PDO.
Также,
$q = $db->query(sprintf(" SELECT id,name,modified FROM users WHERE id_account = %u", $id ));
Это совершенно абсурдно, учитывая обстоятельства. У вас есть объект PDO здесь, нет причин не использовать здесь подготовленные заявления и заполнители . Если вы не хотите связывать одну переменную за раз (и я не виню вас), для этого используется аргумент произвольного массива execute
.