SQLアクセス

Nette Databaseは2つの方法を提供します:SQLクエリを自分で記述する(SQLアクセス)、または自動的に生成させる(Explorerを参照)。SQLアクセスはクエリを完全に制御でき、同時に安全な構築を保証します。

データベース接続と設定の詳細については、接続と設定の章を参照してください。

基本的なクエリ

データベースにクエリを実行するには、query()メソッドを使用します。これは、クエリの結果を表すResultSetオブジェクトを返します。失敗した場合、メソッドは例外をスローします。 クエリの結果はforeachループを使用して反復処理するか、ヘルパー関数のいずれかを使用できます。

$result = $database->query('SELECT * FROM users'); foreach ($result as $row) {	echo $row->id;	echo $row->name; } 

SQLクエリに値を安全に挿入するには、パラメータ化されたクエリを使用します。Nette Databaseはこれを最大限に簡単にします – SQLクエリの後にカンマと値を追加するだけです:

$database->query('SELECT * FROM users WHERE name = ?', $name); 

複数のパラメータがある場合、2つの記述方法があります。SQLクエリにパラメータを「散りばめる」ことができます:

$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); 

または、まず完全なSQLクエリを記述し、次にすべてのパラメータを追加します:

$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); 

SQLインジェクションからの保護

パラメータ化されたクエリを使用することが重要なのはなぜでしょうか? なぜなら、SQLインジェクションと呼ばれる攻撃から保護してくれるからです。この攻撃では、攻撃者が独自のSQLコマンドを挿入し、それによってデータベース内のデータを取得または破損させる可能性があります。

変数をSQLクエリに直接挿入しないでください! SQLインジェクションから保護するために、常にパラメータ化されたクエリを使用してください。

// ❌ 危険なコード - SQLインジェクションに対して脆弱 $database->query("SELECT * FROM users WHERE name = '$name'"); // ✅ 安全なパラメータ化されたクエリ $database->query('SELECT * FROM users WHERE name = ?', $name); 

潜在的なセキュリティリスクについて理解してください

クエリ技術

WHERE条件

WHERE条件は連想配列として記述でき、キーはカラム名、値は比較データです。Nette Databaseは、値の型に基づいて最適なSQL演算子を自動的に選択します。

$database->query('SELECT * FROM users WHERE', [	'name' => 'John',	'active' => true, ]); // WHERE `name` = 'John' AND `active` = 1 

キーで比較演算子を明示的に指定することもできます:

$database->query('SELECT * FROM users WHERE', [	'age >' => 25, // 演算子 > を使用	'name LIKE' => '%John%', // 演算子 LIKE を使用	'email NOT LIKE' => '%example.com%', // 演算子 NOT LIKE を使用 ]); // WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' 

Netteは、null値や配列などの特殊なケースを自動的に処理します。

$database->query('SELECT * FROM products WHERE', [	'name' => 'Laptop', // 演算子 = を使用	'category_id' => [1, 2, 3], // IN を使用	'description' => null, // IS NULL を使用 ]); // WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL 

否定条件には演算子 NOT を使用します:

$database->query('SELECT * FROM products WHERE', [	'name NOT' => 'Laptop', // 演算子 <> を使用	'category_id NOT' => [1, 2, 3], // NOT IN を使用	'description NOT' => null, // IS NOT NULL を使用	'id' => [], // 省略されます ]); // WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL 

条件を結合するには演算子 AND が使用されます。これはプレースホルダ ?orを使用して変更できます。

ORDER BYルール

ORDER BYソートは配列を使用して記述できます。キーにカラムを指定し、値は昇順でソートするかどうかを示すブール値になります:

$database->query('SELECT id FROM author ORDER BY', [	'id' => true, // 昇順	'name' => false, // 降順 ]); // SELECT id FROM author ORDER BY `id`, `name` DESC 

データの挿入 (INSERT)

レコードを挿入するには、SQLコマンド INSERT を使用します。

$values = [	'name' => 'John Doe',	'email' => 'john@example.com', ]; $database->query('INSERT INTO users ?', $values); $userId = $database->getInsertId(); 

getInsertId()メソッドは、最後に挿入された行のIDを返します。一部のデータベース(例:PostgreSQL)では、$database->getInsertId($sequenceId)を使用してIDを生成するシーケンス名をパラメータとして指定する必要があります。

パラメータとして、ファイル、DateTimeオブジェクト、または列挙型などの特別な値を渡すこともできます。

複数のレコードを一度に挿入する:

$database->query('INSERT INTO users ?', [	['name' => 'User 1', 'email' => 'user1@mail.com'],	['name' => 'User 2', 'email' => 'user2@mail.com'], ]); 

複数INSERTは、多くの個別のクエリではなく単一のデータベースクエリが実行されるため、はるかに高速です。

セキュリティ警告: $valuesとして検証されていないデータを使用しないでください。潜在的なリスクについて理解してください

データの更新 (UPDATE)

レコードを更新するには、SQLコマンド UPDATE を使用します。

// 1つのレコードの更新 $values = [	'name' => 'John Smith', ]; $result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); 

影響を受けた行数は $result->getRowCount() で返されます。

UPDATEには演算子 += および -= を使用できます:

$database->query('UPDATE users SET ? WHERE id = ?', [	'login_count+=' => 1, // login_count をインクリメント ], 1); 

レコードが存在する場合は挿入、存在しない場合は更新する例。ON DUPLICATE KEY UPDATEテクニックを使用します:

$values = [	'name' => $name,	'year' => $year, ]; $database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',	$values + ['id' => $id],	$values, ); // INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) // ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 

Nette Databaseが、SQLコマンドのどのコンテキストに配列パラメータを挿入するかを認識し、それに応じてSQLコードを構築することに注意してください。したがって、最初の配列から(id, name, year) VALUES (123, 'Jim', 1978)を構築し、2番目の配列をname = 'Jim', year = 1978の形式に変換しました。これについては、SQL構築のヒントセクションで詳しく説明します。

データの削除 (DELETE)

レコードを削除するには、SQLコマンド DELETE を使用します。削除された行数を取得する例:

$count = $database->query('DELETE FROM users WHERE id = ?', 1)	->getRowCount(); 

SQL構築のヒント

ヒントは、SQLクエリ内の特別なプレースホルダーであり、パラメータ値をSQL式にどのように書き換えるかを示します:

ヒント 説明 自動的に使用される
?name テーブル名またはカラム名の挿入に使用します
?values (key, ...) VALUES (value, ...) を生成します INSERT ... ?, REPLACE ... ?
?set 割り当て key = value, ... を生成します SET ?, KEY UPDATE ?
?and 配列内の条件を AND 演算子で結合します WHERE ?, HAVING ?
?or 配列内の条件を OR 演算子で結合します
?order ORDER BY 句を生成します ORDER BY ?, GROUP BY ?

テーブル名とカラム名をクエリに動的に挿入するには、プレースホルダ ?name を使用します。Nette Databaseは、特定のデータベースの規則に従って識別子を正しく処理します(たとえば、MySQLではバッククォートで囲む)。

$table = 'users'; $column = 'name'; $database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); // SELECT `name` FROM `users` WHERE id = 1 (MySQLの場合) 

警告: シンボル ?name は、検証された入力からのテーブル名とカラム名にのみ使用してください。そうしないと、セキュリティリスクにさらされます

他のヒントは通常、NetteがSQLクエリを構築する際に賢い自動検出を使用するため(表の3番目の列を参照)、指定する必要はありません。ただし、たとえば AND の代わりに OR を使用して条件を結合したい場合などに使用できます:

$database->query('SELECT * FROM users WHERE ?or', [	'name' => 'John',	'email' => 'john@example.com', ]); // SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' 

特別な値

通常のスカラ型(string、int、bool)に加えて、パラメータとして特別な値を渡すこともできます:

  • ファイル:fopen('image.gif', 'r') はファイルのバイナリコンテンツを挿入します
  • 日付と時刻:DateTimeオブジェクトはデータベース形式に変換されます
  • 列挙型:enumインスタンスはその値に変換されます
  • SQLリテラル:Connection::literal('NOW()')を使用して作成されたものは、クエリに直接挿入されます
$database->query('INSERT INTO articles ?', [	'title' => 'My Article',	'published_at' => new DateTime,	'content' => fopen('image.png', 'r'),	'state' => Status::Draft, ]); 

datetimeデータ型をネイティブにサポートしていないデータベース(SQLiteやOracleなど)の場合、DateTimeデータベース設定formatDateTime項目で指定された値(デフォルト値はU – Unixタイムスタンプ)に変換されます。

SQLリテラル

場合によっては、値として直接SQLコードを指定する必要がありますが、これは文字列として解釈されず、エスケープされるべきではありません。この目的のために、Nette\Database\SqlLiteralクラスのオブジェクトが使用されます。これらはConnection::literal()メソッドによって作成されます。

$result = $database->query('SELECT * FROM users WHERE', [	'name' => $name,	'year >' => $database::literal('YEAR()'), ]); // SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) 

または代替案:

$result = $database->query('SELECT * FROM users WHERE', [	'name' => $name,	$database::literal('year > YEAR()'), ]); // SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) 

SQLリテラルにはパラメータを含めることができます:

$result = $database->query('SELECT * FROM users WHERE', [	'name' => $name,	$database::literal('year > ? AND year < ?', $min, $max), ]); // SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017) 

これにより、興味深い組み合わせを作成できます:

$result = $database->query('SELECT * FROM users WHERE', [	'name' => $name,	$database::literal('?or', [	'active' => true,	'role' => $role,	]), ]); // SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin') 

データの取得

SELECTクエリのショートカット

データ取得を簡略化するために、Connectionquery()の呼び出しとそれに続くfetch*()を組み合わせたいくつかのショートカットを提供します。これらのメソッドはquery()と同じパラメータ、つまりSQLクエリとオプションのパラメータを受け入れます。fetch*()メソッドの完全な説明は以下にあります。

fetch($sql, ...$params): ?Row クエリを実行し、最初の行をRowオブジェクトとして返します
fetchAll($sql, ...$params): array クエリを実行し、すべての行をRowオブジェクトの配列として返します
fetchPairs($sql, ...$params): array クエリを実行し、最初のカラムがキー、2番目のカラムが値である連想配列を返します
fetchField($sql, ...$params): mixed クエリを実行し、最初の行の最初のフィールドの値を返します
fetchList($sql, ...$params): ?array クエリを実行し、最初の行をインデックス付き配列として返します

例:

// fetchField() - 最初のセルの値を返します $count = $database->query('SELECT COUNT(*) FROM articles')	->fetchField(); 

foreach – 行の反復処理

クエリを実行した後、ResultSetオブジェクトが返され、これにより結果をいくつかの方法で反復処理できます。クエリを実行して行を取得する最も簡単な方法は、foreachループで反復処理することです。この方法は、データを段階的に返し、一度にメモリに保存しないため、メモリ効率が最も高くなります。

$result = $database->query('SELECT * FROM users'); foreach ($result as $row) {	echo $row->id;	echo $row->name;	// ... } 

ResultSetは一度しか反復処理できません。繰り返し反復処理する必要がある場合は、まずfetchAll()メソッドなどを使用してデータを配列に読み込む必要があります。

fetch(): ?Row

行をRowオブジェクトとして返します。これ以上行がない場合はnullを返します。内部ポインタを次の行に進めます。

$result = $database->query('SELECT * FROM users'); $row = $result->fetch(); // 最初の行を読み込みます if ($row) {	echo $row->name; } 

fetchAll(): array

ResultSetから残りのすべての行をRowオブジェクトの配列として返します。

$result = $database->query('SELECT * FROM users'); $rows = $result->fetchAll(); // すべての行を読み込みます foreach ($rows as $row) {	echo $row->name; } 

fetchPairs (string|int|null $key = null, string|int|null $value = null)array

結果を連想配列として返します。最初の引数は配列のキーとして使用されるカラム名を指定し、2番目の引数は値として使用されるカラム名を指定します:

$result = $database->query('SELECT id, name FROM users'); $names = $result->fetchPairs('id', 'name'); // [1 => 'John Doe', 2 => 'Jane Doe', ...] 

最初のパラメータのみを指定した場合、値は行全体、つまりRowオブジェクトになります:

$rows = $result->fetchPairs('id'); // [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] 

キーが重複する場合、最後の行の値が使用されます。キーとしてnullを使用すると、配列はゼロから数値でインデックス付けされます(衝突は発生しません):

$names = $result->fetchPairs(null, 'name'); // [0 => 'John Doe', 1 => 'Jane Doe', ...] 

fetchPairs (Closure $callback)array

あるいは、パラメータとしてコールバックを指定できます。これは、各行に対して値自体、またはキーと値のペアのいずれかを返します。

$result = $database->query('SELECT * FROM users'); $items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); // ['1 - John', '2 - Jane', ...] // コールバックはキーと値のペアを持つ配列を返すこともできます: $names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); // ['John' => 46, 'Jane' => 21, ...] 

fetchField(): mixed

現在の行の最初のフィールドの値を返します。これ以上行がない場合はnullを返します。内部ポインタを次の行に進めます。

$result = $database->query('SELECT name FROM users'); $name = $result->fetchField(); // 最初の行から名前を読み込みます 

fetchList(): ?array

行をインデックス付き配列として返します。これ以上行がない場合はnullを返します。内部ポインタを次の行に進めます。

$result = $database->query('SELECT name, email FROM users'); $row = $result->fetchList(); // ['John', 'john@example.com'] 

getRowCount(): ?int

最後のUPDATEまたはDELETEクエリによって影響を受けた行数を返します。SELECTの場合、これは返された行数ですが、これは不明な場合があり、その場合メソッドはnullを返します。

getColumnCount(): ?int

ResultSet内のカラム数を返します。

クエリ情報

デバッグ目的で、最後に実行されたクエリに関する情報を取得できます:

echo $database->getLastQueryString(); // SQLクエリを出力します $result = $database->query('SELECT * FROM articles'); echo $result->getQueryString(); // SQLクエリを出力します echo $result->getTime(); // 実行時間を秒単位で出力します 

結果をHTMLテーブルとして表示するには、次を使用できます:

$result = $database->query('SELECT * FROM articles'); $result->dump(); 

ResultSetはカラムの型に関する情報を提供します:

$result = $database->query('SELECT * FROM articles'); $types = $result->getColumnTypes(); foreach ($types as $column => $type) {	echo "$column は型 $type->type です"; // 例:'id は型 int です' } 

クエリのロギング

独自のクエリロギングを実装できます。イベントonQueryは、実行された各クエリの後に呼び出されるコールバックの配列です:

$database->onQuery[] = function ($database, $result) use ($logger) {	$logger->info('Query: ' . $result->getQueryString());	$logger->info('Time: ' . $result->getTime());	if ($result->getRowCount() > 1000) {	$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');	} };