SQL Yolu
Nette Database iki çalışma yolu sunar: SQL sorgularını kendiniz yazabilirsiniz (SQL yolu) ya da otomatik üretilmelerini sağlayabilirsiniz (bkz. Explorer). SQL yolu, sorguların güvenli biçimde kurulmasını güvence altına alırken size sorgular üzerinde tam denetim verir.
Veritabanı bağlantısı ve yapılandırmasının ayrıntıları Bağlantı ve yapılandırma bölümünde bulunabilir.
Temel Sorgulama
Veritabanını sorgulamak için query() metodu kullanılır. Sorgu sonucunu temsil eden bir ResultSet nesnesi döndürür. Sorgu başarısız
olursa metot istisna fırlatır. Sorgu sonucunu bir
foreach döngüsüyle dolaşabilir ya da yardımcı metotlardan birini
kullanabilirsiniz.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
Değerleri SQL sorgularına güvenle koymak için parametreli sorgular kullanın. Nette Database bunu son derece basit kılar: SQL sorgusundan sonra yalnızca bir virgül ve değeri ekleyin:
$database->query('SELECT * FROM users WHERE name = ?', $name);
Birden çok parametrede iki seçeneğiniz var: SQL sorgusuyla parametreleri iç içe geçirebilirsiniz:
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
Ya da önce tüm SQL sorgusunu yazıp sonra tüm parametreleri ekleyebilirsiniz:
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
SQL Injection'a Karşı Koruma
Parametreli sorgular kullanmak neden önemli? Çünkü sizi SQL injection denen bir saldırıdan korurlar; bu saldırıda bir saldırgan kendi SQL komutlarını ekleyip veritabanındaki verilere erişebilir ya da onlara zarar verebilir.
Değişkenleri asla doğrudan bir SQL sorgusuna koymayın! Her zaman, sizi SQL injection'dan koruyan parametreli sorgular kullanın.
// ❌ TEHLİKELİ KOD - SQL injection'a açık
$database->query("SELECT * FROM users WHERE name = '$name'");
// ✅ Güvenli parametreli sorgu
$database->query('SELECT * FROM users WHERE name = ?', $name);
Olası güvenlik risklerini öğrenin.
Sorgulama Teknikleri
WHERE Koşulları
WHERE koşullarını, anahtarların sütun adları, değerlerin ise karşılaştırılacak veriler olduğu
ilişkisel bir dizi olarak yazabilirsiniz. Nette Database, değerin türüne göre en uygun SQL operatörünü
otomatik seçer.
$database->query('SELECT * FROM users WHERE', [
'name' => 'John',
'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1
Karşılaştırma operatörünü anahtarda açıkça da belirtebilirsiniz:
$database->query('SELECT * FROM users WHERE', [
'age >' => 25, // > operatörünü kullanır
'name LIKE' => '%John%', // LIKE operatörünü kullanır
'email NOT LIKE' => '%example.com%', // NOT LIKE operatörünü kullanır
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
Nette, null değerler ya da diziler gibi özel durumları otomatik olarak ele alır.
$database->query('SELECT * FROM products WHERE', [
'name' => 'Laptop', // = operatörünü kullanır
'category_id' => [1, 2, 3], // IN kullanır
'description' => null, // IS NULL kullanır
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
Olumsuz koşullar için NOT operatörünü kullanın:
$database->query('SELECT * FROM products WHERE', [
'name NOT' => 'Laptop', // != operatörünü kullanır
'category_id NOT' => [1, 2, 3], // NOT IN kullanır
'description NOT' => null, // IS NOT NULL kullanır
'id NOT' => [], // atlanır
]);
// WHERE `name` != 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
Koşullar varsayılan olarak AND operatörüyle birleştirilir. Bu, ?or yer
tutucusuyla değiştirilebilir.
ORDER BY Kuralları
ORDER BY yan tümcesi bir dizi kullanılarak yazılabilir. Sütunları anahtarlarda belirtin ve artan
(true) ya da azalan (false) sıralamayı boolean bir değerle gösterin:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // artan
'name' => false, // azalan
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
Veri Ekleme (INSERT)
Kayıt eklemek için SQL INSERT komutu kullanılır.
$values = [
'name' => 'John Doe',
'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
getInsertId() metodu, son eklenen satırın ID'sini döndürür. Bazı veritabanlarında (örneğin PostgreSQL),
ID'nin üretileceği dizinin adını $database->getInsertId($sequenceId) biçiminde parametre olarak belirtmek
gerekir.
Parametre olarak dosyalar, DateTime nesneleri ya da enum türleri gibi Özel Değerler de aktarabilirsiniz.
Birden çok kaydı tek seferde ekleme:
$database->query('INSERT INTO users ?', [
['name' => 'User 1', 'email' => 'user1@mail.com'],
['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
Çok kayıtlı bir INSERT çok daha hızlıdır; çünkü pek çok ayrı sorgu yerine yalnızca tek bir veritabanı sorgusu çalıştırılır.
Güvenlik notu: Doğrulanmamış verileri asla $values olarak kullanmayın. Olası riskleri öğrenin.
Veri Güncelleme (UPDATE)
Kayıtları güncellemek için SQL UPDATE komutu kullanılır.
// Tek bir kaydı güncelle
$values = [
'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
Etkilenen satır sayısını $result->getRowCount() döndürür.
UPDATE içinde += ve -= operatörlerini kullanabiliriz:
$database->query('UPDATE users SET ? WHERE id = ?', [
'login_count+=' => 1, // login_count değerini artır
], 1);
Kayıt zaten varsa güncelleyen, yoksa ekleyen bir örnek. ON DUPLICATE KEY UPDATE tekniğini kullanıyoruz:
$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'in, bir dizi parametresinin SQL komutunda hangi bağlamda kullanıldığını tanıdığına ve SQL kodunu buna
göre kurduğuna dikkat edin. Yani ilk diziden (id, name, year) VALUES (123, 'Jim', 1978) kurdu, ikincisini ise
name = 'Jim', year = 1978 biçimine çevirdi. Bunu SQL Kurma İpuçları
bölümünde daha ayrıntılı ele alıyoruz.
Veri Silme (DELETE)
Kayıtları silmek için SQL DELETE komutu kullanılır. Silinen satır sayısını alma örneği:
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
->getRowCount();
SQL Kurma İpuçları
İpucu (hint), SQL sorgusunda parametre değerinin bir SQL ifadesine nasıl dönüştürüleceğini belirten özel bir yer tutucudur:
| İpucu | Açıklama | Otomatik kullanıldığı yer |
|---|---|---|
?name |
Tablo ya da sütun adı eklemek için kullanılır | – |
?values |
(key, ...) VALUES (value, ...) üretir |
INSERT ... ?, REPLACE ... ? |
?set |
key = value, ... atamalarını üretir |
SET ?, KEY UPDATE ? |
?and |
Dizideki koşulları AND ile birleştirir |
WHERE ?, HAVING ? |
?or |
Dizideki koşulları OR ile birleştirir |
– |
?order |
ORDER BY yan tümcesini üretir |
ORDER BY ?, GROUP BY ? |
?name yer tutucusu, sorguya tablo ve sütun adlarını dinamik olarak eklemek için kullanılır. Nette Database,
tanımlayıcıların veritabanı uzlaşımlarına göre doğru tırnaklanmasını üstlenir (örneğin MySQL'de ters tırnak
içine alma).
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (MySQL'de)
Uyarı: ?name yer tutucusunu yalnızca doğrulanmış tablo ve sütun adlarında kullanın. Aksi hâlde güvenlik açıkları riskini alırsınız.
Diğer ipuçlarını genellikle belirtmeye gerek yoktur; çünkü Nette, SQL sorgusunu kurarken akıllı otomatik saptama
kullanır (tablonun üçüncü sütununa bakın). Ama örneğin koşulları AND yerine OR ile
birleştirmek istediğinizde kullanabilirsiniz:
$database->query('SELECT * FROM users WHERE ?or', [
'name' => 'John',
'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
Özel Değerler
Yaygın skaler türlerin (string, int, bool) yanı sıra parametre olarak özel değerler de aktarabilirsiniz:
- dosyalar:
fopen('image.gif', 'r')dosyanın ikili içeriğini ekler - tarih ve saat:
DateTimeInterfacenesneleri veritabanı biçimine dönüştürülür - enum türleri:
enumörnekleri değerlerine dönüştürülür - SQL sabit değerleri:
Connection::literal('NOW()')ile oluşturulanlar doğrudan sorguya eklenir
$database->query('INSERT INTO articles ?', [
'title' => 'My Article',
'published_at' => new DateTimeImmutable, // ya da new DateTime
'content' => fopen('image.png', 'r'),
'state' => Status::Draft,
]);
datetime veri türünü yerel olarak desteklemeyen veritabanlarında (SQLite ve Oracle gibi),
DateTime ve DateTimeImmutable nesneleri, veritabanı yapılandırmasında formatDateTime öğesiyle
belirtilen bir değere dönüştürülür (varsayılan değer U, yani Unix zaman damgasıdır).
SQL Sabit Değerleri
Bazı durumlarda, dize olarak ele alınıp kaçışlanmaması gereken ham SQL kodunu değer olarak aktarmanız gerekir. Bunun
için Nette\Database\SqlLiteral sınıfının nesneleri kullanılır. Connection::literal() metoduyla
oluşturulurlar.
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
Alternatif olarak:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
SQL sabit değerleri parametre içerebilir:
$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)
Bu, ilginç bileşimlere olanak tanır:
$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')
Veri Alma
SELECT Sorguları İçin Kısayollar
Veri almayı kolaylaştırmak için Connection, bir query() çağrısıyla ardından gelen bir
fetch*() çağrısını birleştiren çeşitli kısayollar sunar. Bu metotlar query() ile aynı
parametreleri, yani bir SQL sorgusunu ve isteğe bağlı parametreleri alır. fetch*() metotlarının tam
açıklaması aşağıda bulunabilir.
fetch($sql, ...$params): ?Row |
Sorguyu çalıştırır ve ilk satırı Row nesnesi ya da null olarak döndürür. |
fetchAll($sql, ...$params): array |
Sorguyu çalıştırır ve tüm satırları Row nesnelerinden oluşan bir dizi olarak döndürür. |
fetchPairs($sql, ...$params): array |
Sorguyu çalıştırır ve ilişkisel bir dizi (anahtar ⇒ değer çiftleri) döndürür. |
fetchField($sql, ...$params): mixed |
Sorguyu çalıştırır ve ilk satırdaki ilk sütunun değerini döndürür. |
fetchList($sql, ...$params): ?array |
Sorguyu çalıştırır ve ilk satırı indeksli bir dizi ya da null olarak döndürür. |
Örnek:
// fetchField() - ilk hücrenin değerini döndürür
$count = $database->query('SELECT COUNT(*) FROM articles')
->fetchField();
foreach – Satırlar Üzerinde Yineleme
Bir sorgu çalıştırıldıktan sonra, sonuçları çeşitli yollarla dolaşmayı sağlayan bir ResultSet nesnesi döndürülür. Bir sorguyu
çalıştırıp satırları almanın en kolay yolu, foreach döngüsüyle dolaşmaktır. Bu yöntem bellek
açısından en verimli olanıdır; veriyi satır satır getirir ve sonuç kümesinin tamamını belleğe bir kerede
yüklemez.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
// ...
}
ResultSet yalnızca bir kez dolaşılabilir. Defalarca dolaşmanız gerekiyorsa, veriyi önce
örneğin fetchAll() metoduyla bir diziye yüklemelisiniz.
fetch(): ?Row
Bir satırı Row nesnesi olarak döndürür. Başka satır kalmadıysa null döndürür. İç
işaretçiyi sonraki satıra ilerletir.
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // ilk satırı yükler
if ($row) {
echo $row->name;
}
fetchAll(): array
ResultSet içinde kalan tüm satırları Row nesnelerinden oluşan bir dizi olarak döndürür.
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // tüm satırları yükler
foreach ($rows as $row) {
echo $row->name;
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Sonuç kümesini ilişkisel bir dizi olarak döndürür. İlk argüman anahtar olarak kullanılacak sütunu, ikinci argüman ise değer olarak kullanılacak sütunu belirtir:
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Yalnızca ilk parametre ($key) verilirse, değer olarak satırın tamamı (Row nesnesi)
kullanılır:
$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
Anahtarlar yinelenirse son satırdaki değer kullanılır. Anahtar olarak null kullanmak, sayısal indeksli
(sıfırdan başlayan) bir dizi verir ve anahtar çakışmalarını önler:
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Alternatif olarak, her satırı işleyen bir callback verebilirsiniz. Callback tek bir değer ya da bir anahtar-değer çifti döndürebilir.
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]
// Callback, anahtar ve değer çiftinden oluşan bir dizi de döndürebilir:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
fetchField(): mixed
Geçerli satırdaki ilk sütunun değerini döndürür. Başka satır kalmadıysa null döndürür. İç
işaretçiyi sonraki satıra ilerletir.
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // ilk satırdaki name değerini yükler
fetchList(): ?array
Satırı indeksli bir dizi olarak döndürür. Başka satır kalmadıysa null döndürür. İç işaretçiyi
sonraki satıra ilerletir.
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
getRowCount(): ?int
Son UPDATE ya da DELETE sorgusundan etkilenen satır sayısını döndürür. SELECT
sorgularında sonuç kümesindeki satır sayısını döndürür. Ancak bu her zaman bilinmeyebilir; o durumda metot
null döndürür.
getColumnCount(): ?int
ResultSet içindeki sütun sayısını döndürür.
Sorgu Bilgisi
Hata ayıklama amacıyla, son çalıştırılan sorgu hakkında bilgi alabiliriz:
echo $database->getLastQueryString(); // SQL sorgusunu yazdırır
$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString(); // SQL sorgusunu yazdırır
echo $result->getTime(); // çalışma süresini saniye cinsinden yazdırır
Sonucu bir HTML tablosu olarak göstermek için şunu kullanabilirsiniz:
$result = $database->query('SELECT * FROM articles');
$result->dump();
ResultSet, sütun türleri hakkında bilgi sağlar:
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();
foreach ($types as $column => $type) {
echo "$column is of type $type"; // örneğin 'id is of type int'
}
Sorgu Günlükleme
Kendi sorgu günlüklememizi gerçekleştirebiliriz. onQuery olayı, çalıştırılan her sorgudan sonra
çağrılan callback'lerden oluşan bir dizidir:
$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');
}
};