ホームページ >バックエンド開発 >PHPチュートリアル >セルフコンパイル PHP フレームワーク 1 (データベース操作のカプセル化)
もしあなたが荒らしで、ホイールを押してから終了し、私の記事をチェックしてください。なぜ独自のフレームワークを作成する必要があるのですか?フレームワークのコードはすべて作者のGithubに公開されており、在庫管理情報システムの例もGithubアカウントでご覧いただけます。
これは、の最初の記事です。ルーティング記事、ウィジェット記事、ファクトリー記事などを書く予定です。
Web アプリケーションの核となるのはデータベース操作です。タイトルを見ると、これが PDO パッケージ化に関する記事であることがわかります。デザイン パターンにインスピレーションを与えてくれた Yii2.0 フレームワークにもう一度感謝します。さっそく始めましょう
カプセル化は 2 つのクラスに分かれています: 接続クラス | コマンド クラス
まず、PHP として、リクエストはPHP スレッド、この環境では、1 つのスレッドに複数のデータベース接続があるのは無駄ではないでしょうか。そのため、リクエストのライフサイクル全体を通じてデータベース接続を共有するためにシングルトン モードを使用します
//Connection类private static $_instance;private function __construct() {}private function __clone() {}//单例public static function getinstance() { if (!(self::$_instance instanceof self)) { self::$_instance = new self(); } return self::$_instance;}//类被回收的时候调用,此时关闭数据库连接public function __destruct() { $this->close();}
シングルトン モードの場合 その後、config を通じてデータベースを構成し、PDO の独自の構文 dsn を使用する必要があります。
return [ 'dsn' => 'mysql:host=127.0.0.1;dbname=dbname', 'user' => 'root', 'password' => 'pass', 'charset' => 'utf8', 'slaves' => [ [ 'dsn' => 'mysql:host=127.0.0.1;dbname=dbname', 'user' => 'root', 'password' => 'pass', 'charset' => 'utf8', ], ],];
この設計ではすべての接続変数がインスタンス化されます。サーバーからの接続はプライベート変数に保存され、均一に管理されます。
//实例化后数据库连接属性public $connect;private $db; //数据库连接信息//服务器信息,私有属性private $dsn;private $user;private $pass;private $charset;private $rightNowDb; //当前服务器信息私有属性的服务器名private $PDOSlaves; //从服务器实例化后数据库连接属性
データベース接続情報を取得します。
private function getInfo() { $this->db = require (dirname(__FILE__).'/../../config/db.php'); if ($this->db['dsn'] && $this->db['user'] && $this->db['password']) { $this->dsn = $this->db['dsn']; $this->user = $this->db['user']; $this->pass = $this->db['password']; $this->charset = $this->db['charset']?$this->db['charset']:'utf8'; $this->rightNowDb = 'master'; } else { $this->err('One of the PDO::DB Parameter is empty!'); }}private function err($err) { $err = 'ErrInfo: '.$err; LogWrite::getinstance()->IntoWhere('errordb')->Info($err)->execute(); throw new Exception($err);}
PDO 接続メソッド。これは PDO の接続メソッドです。新しい PDO を使用して操作します。
private function nowConnect() { try { $connect = new PDO($this->dsn, $this->user, $this->pass); } catch (PDOException $e) { $this->err($e->getMessage()); } if (!$connect) { $this->err('PDO connect error'); } $connect->exec('SET NAMES '.$this->charset); $connect->getAttribute(constant("PDO::ATTR_SERVER_VERSION")); return $connect;}
データベースを閉じる方法とデータベースがアクティブであるかどうか (簡単、説明なしでコードを直接貼り付けます)
public function getConnect() { $this->getInfo(); if ($this->isActive()) { return $this->connect; } else { $this->connect = $this->nowConnect(); return $this->connect; }}//if there is activepublic function isActive() { return $this->connect;}//close connectionpublic function close() { if ($this->isActive()) { $this->connect = null; }}
実際にどのように行うかは次のとおりですデータベース接続を使用する ここで、プログラマがデータベース接続を使用する場合、これらのメソッドが最も一般的に使用されます。つまり、接続
public function getConnect() { $this->getInfo(); if ($this->isActive()) { return $this->connect; } else { $this->connect = $this->nowConnect(); return $this->connect; }}
の connect 属性 以下はサーバーから接続するコードです
public function getSlavesConnect($num) { $this->setToSlaves($num); $key = 'slave'.$num; if ($this->PDOSlaves[$key]) { return $this->PDOSlaves[$key]; } else { $connect = $this->nowConnect(); $this->PDOSlaves[$key] = $connect; return $this->PDOSlaves[$key]; }}//Serval attributes change to slaver DataBasepublic function setToSlaves($num) { if ($this->db['slaves'][$num]['dsn'] && $this->db['slaves'][$num]['user'] && $this->db['slaves'][$num]['password']) { $this->dsn = $this->db['slaves'][$num]['dsn']; $this->user = $this->db['slaves'][$num]['user']; $this->pass = $this->db['slaves'][$num]['password']; $this->rightNowDb = 'slaves'.$num; } else { $this->err('slaves '.$num.':: missing info!'); }}public function setMaster() { $this->getInfo(); return $this;}public function getRightNowDb() { return $this->rightNowDb;}
これを書いた後で多くの人が疑問を感じますが、そのすべてがデータベースに接続されているだけでは意味がありません。まだデータベースに対して操作を行う必要はなく、現在の導入によれば、無関係な接続を作成するには 2 つの新しいクラスが必要です。これはそれほど低いことではありません。一つにまとめてもいいかもしれませんね!
このアイデアを持っている人は素晴らしいです!それは、Yii2.0 のソースコードを読みながら開いたときに同じ考えを持っていたため、私が考えていたことを意味しますが、その後、この問題に対する彼の解決策を見て、突然理解しました
public function createCommand($sql = null) { //first connect the db $command = new Command([ 'db' => $this->getConnect(), 'sql' => $sql, ]); return $command;}public function createSlavesComm($num = 0, $sql = null) { $command = new Command([ 'db' => $this->getSlavesConnect($num), 'sql' => $sql, ]); return $command;}
これはすべてのデータベース接続コードです。 まず、Command クラスを以下に紹介します。構築メソッドと一部の属性およびデータベース接続は、明らかに
//this save sql wordprivate $sql;//pdo connectprivate $pdo;//the pdo statementprivate $pdoStmt;//the last db select is in hereprivate $lastCommandDb;private $dataType = PDO::FETCH_ASSOC; //从数据库内取出数据的默认属性//Connection.php to new a commandpublic function __construct($arr) { $this->sql = $arr['sql']?$arr['sql']:''; $this->pdo = $arr['db'];}
クラスの pdo の属性に保存されます。データベース SQL 検索は、準備と実行の 2 つのステップに分かれています。PDO メソッドが作成されたら、それを使用します。エラーをスローし、準備なしで実行すると、システムは throw new PDOException('PDO は準備前に実行できません!') をスローします。 error
//Must handleprivate function prepare() { //if there have stmt if ($this->pdoStmt) { $this->lastCommandDb = $this->pdoStmt; } $this->pdoStmt = $this->pdo->prepare($this->sql);}//execute it and returnprivate function execute($method) { if ($this->pdoStmt) { $pdoStmt = $this->pdoStmt; $pdoStmt->execute(); $res = $pdoStmt->$method($this->dataType); if (!$res) { $msg = 'The result is empty, The sql word is :: '.$this->sql; LogWrite::getinstance()->IntoWhere('errordb')->Info($msg)->execute(); return false; } return $res; } else { throw new PDOException('PDO is Fail to use execute before prepare!'); }}
データベース運用としては必ず必要な事務です、事務がないと致命的なミスがよく起こります 突然事務の話をするのはちょっと唐突かもしれません
銀行内のさまざまな人々が異なる口座に対応し、各口座はデータベースの記録です。 以下は送金業務をシミュレートします: A が B に 100 元を送金します
A が B に送金すると、そのお金は B に送金されます。 A のアカウントから 100 が減算されます。これは成功しました。B のアカウントに 100 元を追加したところ、何らかの理由 (デッドロックによる実行タイムアウト、内部システム エラーなど) により、B のアカウントには 100 元がなくなりました。この転送トランザクションは実際には失敗しましたが、トランザクション制約がなくなるとエラーのみがスローされ、記録もロールバックも行われません。これは実際、転送ビジネスにとって致命的です。 ! ! !
これに基づいてトランザクションの概念が生まれ、PDO にもそれに対する完全なソリューションがあるので、それを以下にカプセル化しましょう
//transction handleprivate function transction() { try { $this->pdo->beginTransaction(); $res = $this->pdo->exec($this->sql); if ($this->pdo->errorInfo()[0] != '00000') { throw new PDOException('DB Error::Fail to change the database!! The sql is: '.$this->sql.' The Error is :: '.$this->pdo->errorInfo()[2]); } $this->pdo->commit(); return true; } catch (PDOException $e) { $this->pdo->rollback(); LogWrite::getinstance()->IntoWhere('errordb')->Info($e)->execute(); return false; }}//change it latelyprivate function transctions(array $sqlArr = array()) { try { $this->pdo->beginTransaction(); foreach ($sqlArr as $value) { $res = $this->pdo->exec($value); print_r($this->pdo->errorInfo()); if ($this->pdo->errorInfo()[0] != '00000') { throw new PDOException('DB Error::Fail to change the database!! The sql is: '.$value.' The Error is :: '.$this->pdo->errorInfo()[2]); } } $this->pdo->commit(); return true; } catch (PDOException $e) { $this->pdo->rollback(); LogWrite::getinstance()->IntoWhere('errordb')->Info($e)->execute(); return false; }}
以上就是基础的一些方法的封装,那应该如何使用这些方法呐,当然就是增删改查咯!
public function queryAll($fetchMode = null) { return $this->queryInit('fetchAll', $fetchMode);}public function queryOne($fetchMode = null) { return $this->queryInit('fetch', $fetchMode);}//insert into databasepublic function insert($table, $arr) { $this->sql = Merj::sql()->insert($table)->value($arr)->sqlVal(); return $this->transction();}//insert serval databasepublic function insertSomeVal($table, array $key, array $arr) { $this->sql = Merj::sql()->insert($table)->servalValue($key, $arr)->sqlVal(); return $this->transction();}//update the databasepublic function update($table, $arr, $where) { $this->sql = Merj::sql()->update($table)->set($arr)->where($where)->sqlVal(); return $this->transction();}public function updateTrans(array $sqlArr = array()) { return $this->transctions($sqlArr);}//delete one recordpublic function delete($table, $whereArr) { $this->sql = Merj::sql()->delete($table)->where($whereArr)->sqlVal(); return $this->transction();}private function queryInit($method, $fetchMode = null) { if ($fetchMode) { $this->dataType = $fetchMode; } if ($this->sql && $this->pdo) { $this->prepare(); $result = $this->execute($method); return $result?$result:''; } else { $err = 'Sql or PDO is empty; The sql is '.$this->sql; LogWrite::getinstance()->IntoWhere('errordb')->Info($this->sql)->execute(); throw new PDOException($err); return false; }}
方法很多,其实是有规律的
public static function sql() { return new QueryBuilder();}没错,框架运用到的就是sql语句的链式调用拼接方法
$connect = Connection::getinstance();$connect->createCommand($sql)->queryOne();$connect->createCommand($sql)->queryAll();$connect::db()->createCommand()->insert('tableName', [ 'Val Key1' => 'Val1', 'Val Key2' => 'Val2', ]);Merj::db()->createSlavesComm(0, 'SELECT * FROM content')->queryAll(); //从服务器操作数据库
数据库的模型类方法部分展示,所有模型继承至此类:
/** * 返回一条纪录内的一个值 * @param 想要取出的值 * @param 主键对应的值 * @return 返回一个值 **/public function findOnlyOne($target, $idVal) { $sql = Merj::sql()->select($target)->from($this->tableName)->where([ $this->primKey => $idVal, ])->sqlVal(); $rows = Merj::db()->createCommand($sql)->queryOne(); if ($rows) { return $rows[$target]; } else { return false; }}/** * 返回一条纪录 * @param 主键属性 * @param 主键对应的值 * @return 返回这条纪录 **/public function findOneRecord($userIdVal) { $sql = Merj::sql()->select()->from($this->tableName)->where([ $this->primKey => $userIdVal, ])->sqlVal(); $rows = Merj::db()->createCommand($sql)->queryOne(); if ($rows) { return $rows; } else { return false; }}/** * 通过sql语句查找 * @param sql words * @return results **/public function findBySql($sql) { return Merj::db()->createCommand($sql)->queryAll();}/** * @param Insert info * @return success or not **/ public function insertOne($arr) { return Merj::db()->createCommand()->insert($this->tableName, $arr);}/** * @param Insert infos * @return success or not **/public function insertNum(array $key = array(), array $arr = array()) { return Merj::db()->createCommand()->insertSomeVal($this->tableName, $key, $arr);}/** * 更新一条记录 * @param * @param * @return success or not **/public function updateOneRec($arrUpDate, $idVal) { return Merj::db()->createCommand()->update($this->tableName, $arrUpDate, [ $this->primKey => $idVal, ]);}/** * 多个sql语句更新 * @param sql array * @return success or not **/public function updateTrans($sqlArr) { return Merj::db()->createCommand()->updateTrans($sqlArr);}/** * 删除一条记录 * @param where $arr * @return success or not **/public function deleteOne($arr) { return Merj::db()->createCommand()->delete($this->tableName, $arr);}/** * object to array * @param object * @return array **/public function obj_arr($obj) { if (is_object($obj)) { $array = (array) $obj; }if (is_array($obj)) { foreach ($obj as $key => $value) { $array[$key] = $this->obj_arr($value); } } return $array;}public function jsonUtf8Out($arr) { foreach ($arr as $key => $value) { if (is_array($value)) { $arr[$key] = $this->jsonUtf8Out($value); } else { $arr[$key] = urlencode($value); } } return $arr;}
最后两个方法为两个递归方法
好了!说了这么多,笔者要去吃饭了!明天还有一天的高考,祝大家高考顺利!