他の言語:
- English documentation
- Русская документация
- Documentation française
- Deutsche Dokumentation
- Documentazione italiana
- Documentación en español
- 한국어 문서
- 简体中文文档
- 繁體中文文件
- Dokumentasi Bahasa Indonesia
- Documentação em Português (BR)
- हिंदी दस्तावेज़
- التوثيق بالعربية
- Türkçe Dokümantasyon
- Tài liệu tiếng Việt
アーカイブとしてダウンロードするか、このサイトからクローンするか、composer経由でインストールできます(packagist.orgへのリンク):
composer require krugozor/database
krugozor/databaseは、PHP拡張機能mysqliを使用したMySQLデータベースでの簡単、便利、高速、安全な作業のためのPHP >= 8.0クラスライブラリです。
PHPでmysqlデータベースを扱うすべてのライブラリの主な欠点は:
- 冗長性
- SQLインジェクションを防ぐために、開発者には2つの方法があります:
- プリペアドステートメントを使用する。
- SQLクエリ本体に入るパラメータを手動でエスケープする。文字列パラメータはmysqli_real_escape_stringを通し、期待される数値パラメータは対応する型 —
intとfloatに変換する。
- 両方のアプローチには大きな欠点があります:
- プリペアドステートメントはひどく冗長です。PDO抽象化やmysqli拡張機能を「箱から出して」使用し、DBMSからデータを取得するすべてのメソッドを集約せずに使用するのは単純に不可能です — テーブルから値を取得するには最低5行のコードを書く必要があります!そして各クエリごとに!
- SQLクエリ本体に入るパラメータを手動でエスケープする — これは議論の余地すらありません。優れたプログラマーは怠惰なプログラマーです。すべてが最大限自動化されるべきです。
- SQLインジェクションを防ぐために、開発者には2つの方法があります:
- デバッグ用のSQLクエリを取得することが不可能
- プログラムでSQLクエリが機能しない理由を理解するには、それをデバッグする必要があります — 論理的または構文エラーを見つける必要があります。エラーを見つけるには、データベースが「文句を言った」SQLクエリ自体を、その本体に代入されたパラメータとともに「見る」必要があります。つまり、完全に形成されたSQLを持つ必要があります。 開発者がプリペアドステートメントを使用したPDOを使用している場合、これは...不可能です!ネイティブライブラリにはこのための最大限便利なメカニズムが提供されていません。無理をするか、データベースログを見るしかありません。
- 冗長性を排除 — 「ネイティブ」ライブラリを使用した場合の1つのクエリを実行するための3行以上のコードの代わりに、たった1行を書くだけです。
- 指定されたプレースホルダータイプに従って、クエリ本体に入るすべてのパラメータをエスケープ — SQLインジェクションからの確実な保護。
- 「ネイティブ」mysqliアダプターの機能を置き換えるのではなく、単にそれを補完します。
- 拡張可能。本質的に、ライブラリはパーサーとSQLインジェクションからの保証された保護を備えたSQLクエリの実行のみを提供します。ライブラリの任意のクラスから継承し、ライブラリのメカニズムと
mysqliおよびmysqli_resultのメカニズムの両方を使用して、必要なメソッドを作成できます。
さまざまなデータベースドライバーのラッパーのほとんどは、ひどいアーキテクチャを持つ無駄なコードの山です。その作者は、自分のラッパーの実用的な目的を理解していないまま、それらをクエリビルダー、ActiveRecordライブラリ、その他のORMソリューションのようなものに変えてしまいます。
krugozor/databaseライブラリはこれらのいずれでもありません。これは、MySQL DBMSの枠組み内で通常のSQLを扱うための便利なツールに過ぎません — それ以上ではありません!
Placeholders(プレースホルダー)は、SQLクエリ文字列内で明示的な値(クエリパラメータ)の代わりに書かれる特殊な型付きマーカーです。値自体は「後で」、SQLクエリを実行する主要メソッドの後続の引数として渡されます:
$result = $db->query(
"SELECT * FROM `users` WHERE `name` = '?s' AND `age` = ?i",
"ダルタニャン", 41
);placeholdersシステムを通過したSQLクエリパラメータは、プレースホルダータイプに応じて特殊なエスケープメカニズムによって処理されます。つまり、以前のように変数をmysqli_real_escape_string()のようなエスケープ関数に入れたり、数値型に変換したりする必要はもうありません:
<?php
// 以前は、各DBMSへのクエリの前に
// だいたいこれを行っていました(そして多くの人は今でも「これ」をしていません):
$id = (int) $_POST['id'];
$value = mysqli_real_escape_string($mysql, $_POST['value']);
$result = mysqli_query($mysql, "SELECT * FROM `t` WHERE `f1` = '$value' AND `f2` = $id");今ではクエリを書くのが簡単で高速になり、何よりもkrugozor/databaseライブラリはすべての可能なSQLインジェクションを完全に防ぎます。
プレースホルダーのタイプとその目的については以下で説明します。プレースホルダーのタイプに慣れる前に、ライブラリのメカニズムがどのように機能するかを理解する必要があります。
PHPは弱い型付けの言語であり、このライブラリの開発中にイデオロギー的なジレンマが生じました。 次の構造を持つテーブルがあるとします:
`name` varchar not null
`flag` tinyint not nullそしてライブラリは(おそらく開発者に依存しない何らかの理由で)次のクエリを実行しなければなりません:
$db->query(
"INSERT INTO `t` SET `name` = '?s', `flag` = ?i",
null, false
);この例では、テキストnot nullフィールドnameにnull値を書き込もうとし、
数値フィールドflagにブール型falseを書き込もうとしています。この状況でどうすべきでしょうか?
- クエリパラメータの検証の責任を誰が負うべきか - クライアントコードかライブラリか?
- この場合、プログラムの実行を中断すべきか、それとも何らかの操作を適用してデータをデータベースに書き込むべきか?
tinyintカラムのfalse値を0値として解釈し、nameカラムのnullを空文字列として解釈できるか?- コード内でこの問題をどのように簡素化または標準化できるか?
これらの質問を考慮して、このライブラリに2つの動作モードを実装することが決定されました。
- Mysql::MODE_STRICT — プレースホルダータイプと引数タイプの厳密な一致モード。
Mysql::MODE_STRICTモードでは、引数タイプはプレースホルダータイプと一致する必要があります。たとえば、整数型プレースホルダー?iに対して引数として55.5または'55.5'の値を渡そうとすると、例外がスローされます:
// 厳密モードを設定
$db->setTypeMode(Mysql::MODE_STRICT);
// この式は実行されず、例外がスローされます:
// クエリテンプレート "SELECT ?i" で "integer" 型のプレースホルダーに "double" 型の値を指定しようとしました
$db->query('SELECT ?i', 55.5);- Mysql::MODE_TRANSFORM — プレースホルダータイプと引数タイプが一致しない場合に引数をプレースホルダータイプに変換するモード。
Mysql::MODE_TRANSFORMモードはデフォルトで設定されており、「寛容な」モードです — プレースホルダータイプと引数タイプが一致しない場合、例外を生成せず、PHP言語自体を使用して引数を必要なプレースホルダータイプに変換しようとします。ちなみに、私はライブラリの作者として、常にこのモードを使用しており、厳密モード(Mysql::MODE_STRICT)は実際の作業では使用したことがありませんが、あなたには必要かもしれません。
Mysql::MODE_TRANSFORMモードでは次の変換が許可されています:
int型(プレースホルダー?i)に変換されますstring型とdouble型の両方で表される浮動小数点数boolTRUEはint(1)に、FALSEはint(0)に変換されますnullはint(0)に変換されます
double型(プレースホルダー?d)に変換されますstring型とint型の両方で表される整数boolTRUEはfloat(1)に、FALSEはfloat(0)に変換されますnullはfloat(0)に変換されます
string型(プレースホルダー?s)に変換されますboolTRUEはstring(1) "1"に、FALSEはstring(1) "0"に変換されます。この動作はPHPでのboolからintへの型変換とは異なります。これは実際には、ブール型がMySQLで数値として書き込まれることが多いためです。numeric型の値はPHPの変換ルールに従って文字列に変換されますnullはstring(0) ""に変換されます
null型(プレースホルダー?n)に変換されます- 任意の引数。
- 配列、オブジェクト、リソースの変換は許可されていません。
$db->query(
'SELECT * FROM `users` WHERE `id` = ?i', 123
);テンプレート変換後のSQLクエリ:
SELECT * FROM `users` WHERE `id` = 123注意! PHP_INT_MAXを超える数値を操作する場合:
- プログラムでは文字列としてのみ操作してください。
- このプレースホルダーを使用せず、文字列プレースホルダー
?sを使用してください(下記参照)。問題は、PHP_INT_MAXを超える数値をPHPが浮動小数点数として解釈することです。ライブラリのパーサーはパラメータをint型に変換しようとしますが、その結果「結果は未定義になります。floatには正しい結果を返すのに十分な精度がないためです。この場合、警告も通知も出力されません!」 — php.net。
$db->query(
'SELECT * FROM `prices` WHERE `cost` IN (?d, ?d)',
12.56, '12.33'
);テンプレート変換後のSQLクエリ:
SELECT * FROM `prices` WHERE `cost` IN (12.56, 12.33)注意! doubleデータ型を扱うためにライブラリを使用する場合は、PHPレベルとDBMSレベルの両方で整数部と小数部の区切り文字が同じになるように適切なロケールを設定してください。
引数の値はmysqli::real_escape_string()メソッドでエスケープされます:
$db->query(
'SELECT "?s"',
"あなたたちは皆馬鹿だ、そして私はダルタニャンだ!"
);テンプレート変換後のSQLクエリ:
SELECT "あなたたちは皆馬鹿だ、そして私はダルタニャンだ!"引数の値はmysqli::real_escape_string()メソッドでエスケープされ + LIKE演算子で使用される特殊文字(%と_)のエスケープが行われます:
$db->query('SELECT "?S"', '% _');テンプレート変換後のSQLクエリ:
SELECT "\% \_"任意の引数の値は無視され、プレースホルダーはSQLクエリ内で文字列NULLに置き換えられます:
$db->query('SELECT ?n', 123);テンプレート変換後のSQLクエリ:
SELECT NULL記号*は次のいずれかのプレースホルダーです:
i(整数のプレースホルダー)d(浮動小数点数のプレースホルダー)s(文字列型のプレースホルダー)
変換とエスケープのルールは、上記で説明した単一のスカラー型と同じです。例:
$db->query(
'INSERT INTO `test` SET ?Ai',
['first' => '123', 'second' => 456]
);テンプレート変換後のSQLクエリ:
INSERT INTO `test` SET `first` = "123", `second` = "456"*は次のタイプのいずれかです:
i(整数のプレースホルダー)d(浮動小数点数のプレースホルダー)s(文字列型のプレースホルダー)
変換とエスケープのルールは、上記で説明した単一のスカラー型と同じです。例:
$db->query(
'SELECT * FROM `test` WHERE `id` IN (?ai)',
[123, 456]
);テンプレート変換後のSQLクエリ:
SELECT * FROM `test` WHERE `id` IN ("123", "456")例:
$db->query(
'INSERT INTO `users` SET ?A[?i, "?s"]',
['age' => 41, 'name' => "ダルタニャン"]
);テンプレート変換後のSQLクエリ:
INSERT INTO `users` SET `age` = 41,`name` = "ダルタニャン"例:
$db->query(
'SELECT * FROM `users` WHERE `name` IN (?a["?s", "?s"])',
["侯爵ダルキアン", "ダルタニャン"]
);テンプレート変換後のSQLクエリ:
SELECT * FROM `users` WHERE `name` IN ("侯爵ダルキアン", "ダルタニャン")このプレースホルダーは、クエリ内でテーブル名またはフィールド名がパラメータとして渡される場合を対象としています。フィールド名とテーブル名は「バッククォート」記号で囲まれます:
$db->query(
'SELECT ?f FROM ?f',
'name',
'database.table_name'
);テンプレート変換後のSQLクエリ:
SELECT `name` FROM `database`.`table_name`ライブラリはプログラマーにSQL構文の遵守を要求します。 これは、次のクエリが機能しないことを意味します:
$db->query(
'SELECT CONCAT("Hello, ", ?s, "!")',
'world'
);— プレースホルダー?sは単一引用符または二重引用符で囲む必要があります:
$db->query(
'SELECT concat("Hello, ", "?s", "!")',
'world'
);テンプレート変換後のSQLクエリ:
SELECT concat("Hello, ", "world", "!")PDOでの作業に慣れている人にとっては、これは奇妙に見えるかもしれませんが、プレースホルダーの値を引用符で囲む必要があるかどうかを決定するメカニズムを実装することは、完全なパーサーを書く必要がある非常に自明ではないタスクです。
ファイル ../console/tests.php を参照してください