Veritabanı Gezgini

Nette Database Explorer, SQL sorguları yazmadan veritabanından veri almayı önemli ölçüde kolaylaştırır.

  • verimli sorgular kullanır
  • hiçbir veri gereksiz yere iletilmez
  • zarif sözdizimine sahiptir

Veritabanı Gezgini'ni kullanmak için bir tablo ile başlayın – Nette\Database\Explorer nesnesi üzerinde table() adresini çağırın. Bir bağlam nesnesi örneği elde etmenin en kolay yolu burada açıklanmıştır veya Nette Database Explorer'ın bağımsız bir araç olarak kullanılması durumunda, manuel olarak oluşturulabilir.

$books = $explorer->table('book'); // db tablo adı 'book'

Çağrı, tüm kitapları almak için üzerinde yinelenebilen bir Selection nesnesi örneği döndürür. Her öğe (bir satır), özelliklerine eşlenen verilerle birlikte bir ActiveRow örneği ile temsil edilir:

foreach ($books as $book) {
	echo $book->title;
	echo $book->author_id;
}

Sadece belirli bir satırı almak, doğrudan bir ActiveRow örneği döndüren get() yöntemiyle yapılır.

$book = $explorer->table('book')->get(2); // kimliği 2 olan kitabı döndürür
echo $book->title;
echo $book->author_id;

Yaygın kullanım durumuna bir göz atalım. Kitapları ve yazarlarını getirmeniz gerekiyor. Bu yaygın bir 1:N ilişkisidir. Sık kullanılan çözüm, tablo birleştirmeleri ile tek bir SQL sorgusu kullanarak veri getirmektir. İkinci olasılık, verileri ayrı ayrı almak, kitapları almak için bir sorgu çalıştırmak ve ardından başka bir sorgu ile her kitap için bir yazar almaktır (örneğin foreach döngünüzde). Bu, biri kitaplar için diğeri de gerekli yazarlar için olmak üzere yalnızca iki sorgu çalıştıracak şekilde kolayca optimize edilebilir – ve Nette Database Explorer'ın yaptığı da tam olarak budur.

Aşağıdaki örneklerde, şekildeki veritabanı şeması ile çalışacağız. Kitap ve etiketleri arasında OneHasMany (1:N) bağlantıları (kitabın yazarı author_id ve olası çevirmeni translator_id, null olabilir) ve ManyHasMany (M:N) bağlantıları vardır.

Şema da dahil olmak üzere bir örnek GitHub'da bulunabilir.

Örneklerde kullanılan veritabanı yapısı

Aşağıdaki kod her kitap için yazarın adını ve tüm etiketlerini listeler. Bunun dahili olarak nasıl çalıştığını birazdan tartışacağız.

$books = $explorer->table('book');

foreach ($books as $book) {
	echo 'başlık: ' . $kitap->başlık;
	echo 'tarafından yazıldı: ' . $book->author->name; // $book->author is row from table 'author'

	echo 'etiketler: ';
	foreach ($book->related('book_tag') as $bookTag) {
		echo $bookTag->tag->name . ', '; // $bookTag->tag 'tag' tablosundan bir satırdır
	}
}

Veritabanı katmanının ne kadar verimli çalıştığını görünce memnun olacaksınız. Yukarıdaki örnek, aşağıdaki gibi görünen sabit sayıda istek yapar:

SELECT * FROM `book`
SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12))
SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3))
SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))

Önbellek kullanırsanız (varsayılan olarak açık), hiçbir sütun gereksiz yere sorgulanmayacaktır. İlk sorgudan sonra, önbellek kullanılan sütun adlarını depolayacak ve Nette Database Explorer yalnızca gerekli sütunlarla sorguları çalıştıracaktır:

SELECT `id`, `title`, `author_id` FROM `book`
SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12))
SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3))
SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))

Seçmeler

Satırların nasıl filtreleneceği ve kısıtlanacağı ile ilgili olasılıklara bakın Nette\Database\Table\Selection:

$table->where($where[, $param[, ...]]) İki veya daha fazla koşul sağlandığında AND öğesini yapıştırıcı olarak kullanarak WHERE öğesini ayarlayın
$table->whereOr($where) İki veya daha fazla koşul sağlandığında VEYA'yı yapıştırıcı olarak kullanarak WHERE'i ayarlayın
$table->order($columns) ORDER BY ayarla, ifade olabilir ('column DESC, id DESC')
$table->select($columns) Alınan sütunları ayarlayın, ifade olabilir ('col, MD5(col) AS hash')
$table->limit($limit[, $offset]) LIMIT ve OFFSET'i Ayarla
$table->page($page, $itemsPerPage[, &$lastPage]) Sayfalamayı etkinleştirir
$table->group($columns) GROUP BY ayarla
$table->having($having) Set HAVING

Akıcı arayüz olarak adlandırılan bir arayüz kullanabiliriz, örneğin $table->where(...)->order(...)->limit(...). Birden fazla where veya whereOr koşulu AND operatörü ile birbirine bağlanır.

nerede()

Nette Database Explorer, geçirilen değerler için gerekli operatörleri otomatik olarak ekleyebilir:

$table->where('field', $value) alan = $değer
$table->where('field', null) alan NULL
$table->where('field > ?', $val) alan > $val
$table->where('field', [1, 2]) alan IN (1, 2)
$table->where('id = ? OR name = ?', 1, $name) id = 1 OR name = ‘Jon Snow’
$table->where('field', $explorer->table($tableName)) field IN (SELECT $primary FROM $tableName)
$table->where('field', $explorer->table($tableName)->select('col')) field IN (SELECT col FROM $tableName)

Sütun operatörü olmadan da yer tutucu sağlayabilirsiniz. Bu çağrılar aynıdır.

$table->where('id = ? OR id = ?', 1, 2);
$table->where('id ? OR id ?', 1, 2);

Bu özellik, değere bağlı olarak doğru operatörün oluşturulmasını sağlar:

$table->where('id ?', 2);    // id = 2
$table->where('id ?', null); // id IS NULL
$table->where('id', $ids);   // id IN (...)

Seçim, negatif koşulları da doğru bir şekilde işler, boş diziler için de çalışır:

$table->where('id', []);       // id IS NULL AND FALSE
$table->where('id NOT', []);   // id IS NULL OR TRUE
$table->where('NOT (id ?)', $ids);  // NOT (id IS NULL AND FALSE)

// this will throws an exception, this syntax is not supported
$table->where('NOT id ?', $ids);

whereOr()

Parametresiz kullanım örneği:

// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`))
$table->whereOr([
	'user_id IS NULL',
	'SUM(field1) > SUM(field2)',
]);

Parametreleri kullanırız. Bir operatör belirtmezseniz, Nette Database Explorer uygun olanı otomatik olarak ekleyecektir:

// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11)
$table->whereOr([
	'field1' => null,
	'field2' => [3, 5],
	'amount >' => 11,
]);

Anahtar, joker soru işaretleri içeren bir ifade içerebilir ve ardından değerde parametreleri iletebilir:

// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3)
$table->whereOr([
	'id > ?' => 12,
	'ROUND(id, ?) = ?' => [5, 3],
]);

order()

Kullanım örnekleri:

$table->order('field1');               // ORDER BY `field1`
$table->order('field1 DESC, field2');  // ORDER BY `field1` DESC, `field2`
$table->order('field = ? DESC', 123);  // ORDER BY `field` = 123 DESC

select()

Kullanım örnekleri:

$table->select('field1');                 // SELECT `field1`
$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc
$table->select('SUBSTR(title, ?)', 3);    // SELECT SUBSTR(`title`, 3)

limit()

Kullanım örnekleri:

$table->limit(1);     // LIMIT 1
$table->limit(1, 10); // LIMIT 1 OFFSET 10

page()

Limit ve ofseti ayarlamak için alternatif bir yol:

$page = 5;
$itemsPerPage = 10;
$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40

$lastPage değişkenine aktarılan son sayfa numarasını alır:

$table->page($page, $itemsPerPage, $lastPage);

grup()

Kullanım örnekleri:

$table->group('field1');          // GROUP BY `field1`
$table->group('field1, field2');  // GROUP BY `field1`, `field2`

sahip olmak()

Kullanım örnekleri:

$table->having('COUNT(items) >', 100);  // HAVING COUNT(`items`) > 100

Başka Bir Tablo Değerine Göre Filtreleme

Çoğu zaman, sonuçları başka bir veritabanı tablosunu içeren bazı koşullara göre filtrelemeniz gerekir. Bu tür koşullar tablo birleştirme gerektirir. Ancak, artık bunları yazmanıza gerek yoktur.

Diyelim ki yazarının adı ‘Jon’ olan tüm kitapları almanız gerekiyor. Yazmanız gereken tek şey ilişkinin birleştirme anahtarı ve birleştirilen tablodaki sütun adıdır. Birleştirme anahtarı, birleştirmek istediğiniz tabloyu ifade eden sütundan türetilir. Örneğimizde (db şemasına bakın) bu author_id sütunudur ve sadece ilk kısmını kullanmak yeterlidir – author ( _id son eki atlanabilir). name, kullanmak istediğimiz author tablosundaki bir sütundur. Kitap çevirmeni için bir koşul ( translator_id sütunu ile bağlantılı olan) aynı kolaylıkla oluşturulabilir.

$books = $explorer->table('book');
$books->where('author.name LIKE ?', '%Jon%');
$books->where('translator.name', 'David Grudl');

Birleştirme anahtarı mantığı, Conventions uygulaması tarafından yönlendirilir. Yabancı anahtarlarınızı analiz eden ve bu ilişkilerle kolayca çalışmanıza olanak tanıyan DiscoveredConventions'ı kullanmanızı öneririz.

Kitap ve yazarı arasındaki ilişki 1:N'dir. Ters ilişki de mümkündür. Biz buna backjoin diyoruz. Başka bir örneğe bakalım. 3'ten fazla kitap yazmış olan tüm yazarları getirmek istiyoruz. Birleştirmeyi tersine çevirmek için : (colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide a GROUP BY deyimini kullanırız, ayrıca koşul HAVING deyimi şeklinde yazılmalıdır.

$authors = $explorer->table('author');
$authors->group('author.id')
	->having('COUNT(:book.id) > 3');

Birleştirme ifadesinin kitaba atıfta bulunduğunu fark etmiş olabilirsiniz, ancak author_id üzerinden mi yoksa translator_id üzerinden mi birleştirdiğimiz net değildir. Yukarıdaki örnekte, Selection author_id sütunu üzerinden birleştirir çünkü kaynak tablo ile bir eşleşme bulunmuştur – author tablosu. Böyle bir eşleşme olmasaydı ve daha fazla olasılık olsaydı, Nette AmbiguousReferenceKeyException atardı.

translator_id sütunu aracılığıyla bir birleştirme yapmak için, birleştirme ifadesinde isteğe bağlı bir parametre sağlayın.

$authors = $explorer->table('author');
$authors->group('author.id')
	->having('COUNT(:book(translator).id) > 3');

Şimdi biraz daha zor birleştirme ifadelerine bir göz atalım.

PHP hakkında bir şeyler yazmış olan tüm yazarları bulmak istiyoruz. Tüm kitapların etiketleri vardır, bu nedenle PHP etiketi ile herhangi bir kitap yazmış olan yazarları seçmeliyiz.

$authors = $explorer->table('author');
$authors->where(':book:book_tags.tag.name', 'PHP')
	->group('author.id')
	->having('COUNT(:book:book_tags.tag.id) > 0');

Toplu Sorgular

$table->count('*') Satır sayısını al
$table->count("DISTINCT $column") Farklı değerlerin sayısını al
$table->min($column) Minimum değeri al
$table->max($column) Maksimum değeri al
$table->sum($column) Tüm değerlerin toplamını alın
$table->aggregation("GROUP_CONCAT($column)") Herhangi bir toplama işlevini çalıştırın

Herhangi bir parametre belirtilmeyen count() yöntemi tüm kayıtları seçer ve dizi boyutunu döndürür, bu da çok verimsizdir. Örneğin, sayfalama için satır sayısını hesaplamanız gerekiyorsa, her zaman ilk bağımsız değişkeni belirtin.

Kaçış ve Alıntı

Veritabanı Gezgini akıllıdır ve parametreleri ve tırnak tanımlayıcılarını sizin için kaçar. Yine de bu temel kurallara uyulması gerekir:

  • anahtar kelimeler, fonksiyonlar, prosedürler büyük harfle yazılmalıdır
  • sütunlar ve tablolar küçük harfle yazılmalıdır
  • değişkenleri parametre olarak geçirin, birleştirme yapmayın
->where('name like ?', 'John'); // WRONG! generates: `isim` `gibi` ?
->where('name LIKE ?', 'John'); // DOĞRU

->where('KEY = ?', $value); // YANLIŞ! KEY bir anahtar kelimedir
->where('key = ?', $value); // DOĞRU. üretir: anahtar` = ?

->where('name = ' . $name); // YANLIŞ! sql enjeksiyonu!
->where('name = ?', $name); // DOĞRU

->select('DATE_FORMAT(created, "%d.%m.%Y")'); // YANLIŞ! değişkenleri parametre olarak geçirin, birleştirmeyin
->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // DOĞRU

Yanlış kullanım güvenlik açıkları oluşturabilir

Veri Getirme

foreach ($table as $id => $row) Sonuçtaki tüm satırlar üzerinde yinele
$row = $table->get($id) Tablodan $id kimliğine sahip tek bir satır al
$row = $table->fetch() Sonuçtan bir sonraki satırı al
$array = $table->fetchPairs($key, $value) Tüm değerleri ilişkisel diziye getir
$array = $table->fetchPairs($key) Tüm satırları ilişkisel diziye getir
count($table) Sonuç kümesindeki satır sayısını al

Ekle, Güncelle ve Sil

insert() yöntemi Traversable nesneleri dizisini kabul eder (örneğin formları döndüren ArrayHash ):

$row = $explorer->table('users')->insert([
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)

Tabloda birincil anahtar tanımlanmışsa, eklenen satırı içeren bir ActiveRow nesnesi döndürülür.

Çoklu ekleme:

$explorer->table('users')->insert([
	[
		'name' => 'Jim',
		'year' => 1978,
	], [
		'name' => 'Jack',
		'year' => 1987,
	],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)

Dosyalar veya DateTime nesneleri parametre olarak geçirilebilir:

$explorer->table('users')->insert([
	'name' => $name,
	'created' => new DateTime, // or $explorer::literal('NOW()')
	'avatar' => fopen('image.gif', 'r'), // inserts the file
]);

Güncelleme (etkilenen satırların sayısını döndürür):

$count = $explorer->table('users')
	->where('id', 10) // update() işlevinden önce çağrılmalıdır
	->update([
		'name' => 'Ned Stark'
	]);
// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10)

Güncelleme için += a -= operatörlerini kullanabiliriz:

$explorer->table('users')
	->update([
		'age+=' => 1, // see +=
	]);
// UPDATE users SET `age` = `age` + 1

Silme (silinen satırların sayısını döndürür):

$count = $explorer->table('users')
	->where('id', 10)
	->delete();
// DELETE FROM `users` WHERE (`id` = 10)

İlişkilerle Çalışmak

Bir İlişkisi Var

Bir ilişkiye sahip olmak yaygın bir kullanım durumudur. Kitabın bir yazarı vardır. Kitabın bir çevirmeni vardır. İlgili satırı almak esas olarak ref() yöntemi ile yapılır. İki bağımsız değişken kabul eder: hedef tablo adı ve kaynak birleştirme sütunu. Örneğe bakınız:

$book = $explorer->table('book')->get(1);
$book->ref('author', 'author_id');

Yukarıdaki örnekte, author tablosundan ilgili yazar girdisini getiriyoruz, yazar birincil anahtarı book.author_id sütunu tarafından aranır. Ref() metodu ActiveRow örneğini döndürür veya uygun bir girdi yoksa null döndürür. Dönen satır bir ActiveRow örneğidir, bu nedenle kitap girişiyle aynı şekilde çalışabiliriz.

$author = $book->ref('author', 'author_id');
$author->name;
$author->born;

// veya doğrudan
$book->ref('author', 'author_id')->name;
$book->ref('author', 'author_id')->born;

Kitapta ayrıca bir çevirmen var, bu nedenle çevirmen adını almak oldukça kolay.

$book->ref('author', 'translator_id')->name

Tüm bunlar iyi, ancak biraz zahmetli, öyle değil mi? Veritabanı Gezgini zaten yabancı anahtar tanımlarını içeriyor, o halde neden bunları otomatik olarak kullanmayalım? Hadi bunu yapalım!

Eğer var olmayan bir özelliği çağırırsak, ActiveRow çağırılan özellik adını ‘has one’ ilişkisi olarak çözümlemeye çalışır. Bu özelliği almak, ref() metodunu sadece bir argümanla çağırmakla aynıdır. Tek argümanı key olarak adlandıracağız. Anahtar, belirli bir yabancı anahtar ilişkisine çözümlenecektir. İletilen anahtar satır sütunlarıyla eşleştirilir ve eşleşirse, eşleşen sütunda tanımlanan yabancı anahtar ilgili hedef tablodan veri almak için kullanılır. Örneğe bakınız:

$book->author->name;
// same as
$book->ref('author')->name;

ActiveRow örneğinin yazar sütunu yoktur. Tüm kitap sütunları key ile eşleşme için aranır. Bu durumda eşleştirme, sütun adının anahtarı içermesi gerektiği anlamına gelir. Yukarıdaki örnekte, author_id sütunu ‘author’ dizesini içerir ve bu nedenle ‘author’ anahtarıyla eşleştirilir. Kitap çevirmenini almak istiyorsanız, anahtar olarak örneğin ‘translator’ kullanabilirsiniz, çünkü ‘translator’ anahtarı translator_id sütunuyla eşleşecektir. Anahtar eşleştirme mantığı hakkında daha fazla bilgiyi Joining expressions bölümünde bulabilirsiniz.

echo $book->title . ': ';
echo $book->author->name;
if ($book->translator) {
	echo ' (translated by ' . $book->translator->name . ')';
}

Birden fazla kitap getirmek istiyorsanız, aynı yaklaşımı kullanmalısınız. Nette Database Explorer, getirilen tüm kitaplar için yazarları ve çevirmenleri bir kerede getirecektir.

$books = $explorer->table('book');
foreach ($books as $book) {
	echo $book->title . ': ';
	echo $book->author->name;
	if ($book->translator) {
		echo ' (translated by ' . $book->translator->name . ')';
	}
}

Kod yalnızca bu 3 sorguyu çalıştıracaktır:

SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column
SELECT * FROM `author` WHERE (`id` IN (2, 3));    -- ids of fetched books from translator_id column

Birçok İlişkisi Var

‘Has many’ ilişkisi ‘has one’ ilişkisinin tersine çevrilmiş halidir. Yazar çok kitap yazmıştır. Yazar çok kitap çevirmiştir. Gördüğünüz gibi, bu tür bir ilişki biraz daha zordur çünkü ilişki ‘adlandırılmıştır’ (‘yazılmıştır’, ‘çevrilmiştir’). ActiveRow örneği, ilgili girdilerin dizisini döndürecek olan related() yöntemine sahiptir. Girişler de ActiveRow örnekleridir. Aşağıdaki örneğe bakınız:

$author = $explorer->table('author')->get(11);
echo $author->name . ' has written:';

foreach ($author->related('book.author_id') as $book) {
	echo $book->title;
}

echo 'and translated:';
foreach ($author->related('book.translator_id') as $book) {
	echo $book->title;
}

Yöntem related() yöntemi, iki bağımsız değişken olarak veya nokta ile birleştirilmiş bir bağımsız değişken olarak aktarılan tam birleştirme açıklamasını kabul eder. İlk bağımsız değişken hedef tablo, ikincisi ise hedef sütundur.

$author->related('book.translator_id');
// ile aynı
$author->related('book', 'translator_id');

Yabancı anahtarlara dayalı Nette Database Explorer buluşsal yöntemlerini kullanabilir ve yalnızca key bağımsız değişkenini sağlayabilirsiniz. Anahtar, geçerli tabloya işaret eden tüm yabancı anahtarlarla eşleştirilecektir (author tablo). Bir eşleşme varsa, Nette Database Explorer bu yabancı anahtarı kullanacaktır, aksi takdirde Nette\InvalidArgumentException veya AmbiguousReferenceKeyException atacaktır. Anahtar eşleştirme mantığı hakkında daha fazla bilgiyi Joining expressions bölümünde bulabilirsiniz.

Elbette, getirilen tüm yazarlar için ilgili yöntemleri çağırabilirsiniz, Nette Database Explorer uygun kitapları bir kerede tekrar getirecektir.

$authors = $explorer->table('author');
foreach ($authors as $author) {
	echo $author->name . ' has written:';
	foreach ($author->related('book') as $book) {
		$book->title;
	}
}

Yukarıdaki örnek yalnızca iki sorgu çalıştıracaktır:

SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors

Explorer'ı Manuel Olarak Oluşturma

Uygulama yapılandırması kullanılarak bir veritabanı bağlantısı oluşturulabilir. Bu gibi durumlarda bir Nette\Database\Explorer hizmeti oluşturulur ve DI konteyneri kullanılarak bir bağımlılık olarak aktarılabilir.

Ancak, Nette Database Explorer bağımsız bir araç olarak kullanılıyorsa, Nette\Database\Explorer nesnesinin bir örneğinin manuel olarak oluşturulması gerekir.

// $storage Nette\Caching\Storage uygular:
$storage = new Nette\Caching\Storages\FileStorage($tempDir);
$connection = new Nette\Database\Connection($dsn, $user, $password);
$structure = new Nette\Database\Structure($connection, $storage);
$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure);
$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage);
versiyon: 4.0