Arama konusu yazılımda herkesin zorlandığı konulardan bir tanesi. Önemli olan veritabanındaki veriyi alıp göstermek değil, büyük ölçekli filtreleme işlemleri ve işlem sürelerini minimuma indirerek kullanıcıya iyi bir arama deneyimi yaşatmak. Buda büyük ölçekli projelerde çokta kolay olmayan bir durum. Bu gibi durumlarda Algolia diyor ki, gönder bana verini ben sana en hızlı şekilde bu hizmeti vereyim.
Ek olarak autocomplete, instantsearch gibi konularda da hizmet veren Algolia’nın ücretli olduğunu söylememe gerek yok sanırım? Hesap açıp 14 gün boyunca ücretsiz deneyebiliyorsunuz. Eğer hoşunuzda giderse (ki adamlar şu sıralar ciddi yatırımlar alıyorlar, belli ki herkesin hoşuna gidiyor) aylık minimum 29$ ödeyerek bu servisi kullanmaya devam edebilirsiniz.
Baktığınızda bu kadar büyük ölçekli işlerde çokta önemsenmeyecek bir rakam diyebiliriz. Elbette bu 29$’lık paketin bir limiti var ancak dediğim gibi, performans istiyorsanız o zaman biraz paraya kıymanız gerek 🙂
Algolia Nasıl Kullanılır?
Algolia’nın bir çok dil için desteği var. Bende sizlere PHP ile kullanımını göstereceğim. Bunun için elbette algolia.com‘a kayıt olduğunuzu varsayıyorum. Giriş yaptığınızda sizi bir dashboard’a gönderiyor, sol menüde Indices
kısmına tıklayıp Create Index
diyerek kendimize bir index yaratalım. Bu index içine verilerimizi aktaracağız öyle düşünün 🙂 Adı herkes tarafından görüleceği için ona göre bir isim verin. Ben kendi örneğimde HOTELS
adında bir Index
oluşturdum.
Artık composer ile algolia’nın paketini kurarak işe başlayalım.
composer require algolia/algoliasearch-client-php
Daha sonra bir index.php
dosyası oluşturun ve vendor/autoload.php
diyerek paketi projenize dahil edin.
Daha sonra Algolia sınıfını appId
ve apiKey
‘lerimizi vererek başlatıyoruz. Ve initIndex()
metodu ile oluşturduğumuz index’e (benim örneğimde adı HOTELS
‘di) bağlanıyoruz.
Not: appId
ve apiKey
değerlerinizi bilmiyorsanız dashboard’da sol menüde API Keys altından bulabilirsiniz.
<?php require __DIR__ . '/vendor/autoload.php'; $client = Algolia\AlgoliaSearch\SearchClient::create( 'APP_ID', 'API_KEY' ); $index = $client->initIndex('INDEX_ADINIZ');
Artık mevcut index’inize veri göndermeye hazırsınız. Bunun için tek bir veri göndereceksek saveObject()
birden fazla göndereceksek saveObjects()
metodlarını kullanacağız. Örnek olarak tek bir veri göndermeyi deneyelim.
try { $data = [ 'id' => 7, 'name' => 'Karavana Otel', 'star' => 5, 'location' => 'Alanya' ]; } catch (\Algolia\AlgoliaSearch\Exceptions\AlgoliaException $e) { echo $e->getMessage(); }
Elbette bunu çalıştırdığımızda şöyle bir hata alacağız;
All objects must have an unique objectID (like a primary key) to be valid. If your batch has a unique identifier but isn’t called objectID, you can map it automatically using `saveObjects($objects, [‘objectIDKey’ => ‘primary’])` Algolia is also able to generate objectIDs automatically but *it’s not recommended*. To do it, use `saveObjects($objects, [‘autoGenerateObjectIDIfNotExist’ => true])`
Bunun sebebide gönderilen veride mutlaka objectID
adında sayıdan oluşan benzersiz bir değer olmalı. Eğer göndereceğiniz veride objectID adında bir key’iniz yoksa ancak benzersiz bir değeriniz farklı bir isimle yer alıyorsa şu şekilde map’leme işlemi yapabilirsiniz;
try {
$data = [
'id' => 7,
'name' => 'Karavana Otel',
'star' => 5,
'location' => 'Alanya'
];
$index->saveObject($data, ['objectIDKey' => 'id']);
} catch (\Algolia\AlgoliaSearch\Exceptions\AlgoliaException $e) {
echo $e->getMessage();
}
Bu sefer çalıştırıp test ederseniz hata almayacaksınız. Çünkü algolia’ya gönderildi 🙂 Bunun için dashboard’da oluşturduğumuz index’in içine bakarak görebiliriz.
Birden fazla veri göndermek içinde bir örnek yapalım;
try {
$data = [
[
'id' => 8,
'name' => 'Test #1 Otel',
'star' => 4,
'location' => 'Adana'
],
[
'id' => 9,
'name' => 'Test #2 Otel',
'star' => 5,
'location' => 'İzmir'
],
[
'id' => 10,
'name' => 'Test #3 Otel',
'star' => 4,
'location' => 'Eskişehir'
]
];
$index->saveObjects($data, ['objectIDKey' => 'id']);
} catch (\Algolia\AlgoliaSearch\Exceptions\AlgoliaException $e) {
echo $e->getMessage();
}
Tek farkı bir array içerisinde birden fazla array tanımladık ve metod olarakta saveObjects()
metodunu kullandık.
Tabi bizim şu an yapacağımız, veritabanına bağlanmak ve oteller tablomdaki tüm veriyi algolia’ya göndermek. Bunun içinse örneğimiz şöyle;
$db = new PDO('mysql:host=localhost;dbname=TEST;charset=utf8', 'KADI', 'ŞİFRE'); try { // index'in içindekileri boşalttık $index->clearObjects(); $query = $db->query('SELECT * FROM hotels')->fetchAll(PDO::FETCH_ASSOC); foreach (array_chunk($query, 1000) as $row){ $index->saveObjects($row, ['objectIDKey' => 'id']); } } catch (\Algolia\AlgoliaSearch\Exceptions\AlgoliaException $e) { echo $e->getMessage(); }
1000’er 1000’er chunk ederek gönderdim. Bir anda hepsini göndermedim 🙂 Sonucunda benim şu an 1987 tane otel verim var algolia’da.
Index içinde arama
Gelelim arama yapmaya, index’imiz yine seçiliyken şu şekilde verilerim içinde arama yapabiliyorum;
try {
$result = $index->search('İstanbul Ephesus Hotel');
print_r($result);
} catch (\Algolia\AlgoliaSearch\Exceptions\AlgoliaException $e) {
echo $e->getMessage();
}
Buda bana şöyle bir çıktı veriyor;
Array ( [hits] => Array ( [0] => Array ( [id] => 1228 [name] => İstanbul Ephesus Hotel [city] => İstanbul [state] => Fatih [email] => [email protected] [phone] => [star] => 3 [image] => https://micegoo.com/upload/hotel/istanbul-ephesus-hotel-istanbul_ZrJT.png [objectID] => 1228 [_highlightResult] => Array ( [id] => Array ( [value] => 1228 [matchLevel] => none [matchedWords] => Array ( ) ) [name] => Array ( [value] => <em>İstanbul</em> <em>Ephesus</em> <em>Hotel</em> [matchLevel] => full [fullyHighlighted] => 1 [matchedWords] => Array ( [0] => istanbul [1] => ephesus [2] => hotel ) ) [city] => Array ( [value] => <em>İstanbul</em> [matchLevel] => partial [fullyHighlighted] => 1 [matchedWords] => Array ( [0] => istanbul ) ) [state] => Array ( [value] => Fatih [matchLevel] => none [matchedWords] => Array ( ) ) [email] => Array ( [value] => [email protected] [matchLevel] => none [matchedWords] => Array ( ) ) [phone] => Array ( [value] => [matchLevel] => none [matchedWords] => Array ( ) ) [star] => Array ( [value] => 3 [matchLevel] => none [matchedWords] => Array ( ) ) [image] => Array ( [value] => https://micegoo.com/upload/<em>hotel</em>/<em>istanbul</em>-<em>ephesus</em>-<em>hotel</em>-<em>istanbul</em>_ZrJT.png [matchLevel] => full [fullyHighlighted] => [matchedWords] => Array ( [0] => istanbul [1] => ephesus [2] => hotel ) ) ) ) ) [nbHits] => 1 [page] => 0 [nbPages] => 1 [hitsPerPage] => 20 [exhaustiveNbHits] => 1 [query] => İstanbul Ephesus Hotel [params] => query=%C4%B0stanbul+Ephesus+Hotel [processingTimeMS] => 1 )
Bu çıktıda gördüğünüz gibi ilk olarak eşleşen verinin detayı geliyor ve _highlightResult
altında ise hangi kolonların ne kadar eşleştiğiyle ilgili bilgiler yer alıyor. Ve eşleşen yerler <em></em> ile vurgulanmış ayrıca matchedWords
altında bize tek tek dönüyor. Ve bunları çok kısa bir sürede yapıyor 🙂
Arama Kriterlerini Kısıtlama
Arama işleminin örneğin sadece otel adında, şehirde ve bölgede yapmasını istersek o zaman şu şekilde ayarımızı güncelliyoruz.
$index->setSettings([
'searchableAttributes' => ['name', 'city', 'state']
]);
Ve örneğin yukarıdaki çıktıda mail adresi olan oteli bulmak istersek artık bulamayız çünkü adında şehir ya da bölgesinde yer almadığı için eşleşen bir veri gelmeyecektir 🙂 Ancak adını, şehrini ya da bölgesini yazarak hala eşleşenleri bulabiliriz. 3 kolonla kısıtladık arama işlemini.
Veri Güncelleme
Olan bir veriyi ya da verileri güncellemek için partialUpdateObject()
ve partialUpdateObjects()
metodları kullanılır. Tahmin edebileceğiniz gibi tekli ya da çoklu güncelleme için 2 metoddur 🙂
// objectID 5 olan otelin name değerini güncelledik $index->partialUpdateObject([ 'name' => 'Yeni Otel Adı', 'objectID' => '5' ]); // birden fazla otel adını objectID'lerine göre güncelledik $index->partialUpdateObjects([ [ 'name' => 'Yeni Otel Adı', 'objectID' => '5' ], [ 'name' => 'Yeni Otel Adı #2', 'objectID' => '6' ] ]);
Veri Silme
Tek bir veriyi silmek için deleteObject() birden fazla veriyi silmek için deleteObjects() metodları kullanılır. Veriler objectID değerine göre silinir.
$index->deleteObject("1"); $index->deleteObjects(["1", "2"]);
Verileri Kategorilemek
Algolia’ya gönderdiğiniz verileri kategorilemek için Facet’ler kullanılır. Örneğin benim otel index’imdeki verileri yıldıza göre, şehre göre ve bölge’ye göre kategorize edebilirim. Bunun için ayarımı şu şekilde güncelliyorum;
$index->setSettings([ 'attributesForFaceting' => ['city', 'state', 'star'] ]);
Böylece artık arama yaparken bir otel adını yazdığımda şunu söyleyebilirim, örneğin Hakan Otel ara ama bölgesi Şişli olan ve Yıldızı 3 olanları bul gibi. Hemen örnek verelim;
$result = $index->search('İstanbul', [
'facetFilters' => [
'state:Şişli',
'star:3'
]
]);
print_r($result);
InstantSearch.js Kullanımı
Algolia bize bir takım widget’lardan oluşan bir yapı veriyor. Kategorize ettiğiniz verilerinizi kolayca filtreleme ve listeleme işlemini bu widget’lar yardımı ile yapıyorsunuz. Tasarımınıza uygun olarak CSS değişikliğide yapmanız mümkün.
Öncelikle bununla ilgili dökümanların tamamına şuradan ulaşabilirsiniz;
https://www.algolia.com/doc/api-reference/widgets/js/
Gelelim biz bunu nasıl kullanabiliriz? İlk olarak html yapımızı belirleyelim.
<aside class="sidebar"> <section class="widget"> <h3>Şehirler</h3> <div id="cities"></div> </section> <section class="widget"> <h3>Bölgeler</h3> <div id="states"></div> </section> </aside> <main class="container"> <div id="search"></div> <div id="stats"></div> <section class="hotels"> <div id="hotels"></div> </section> <div id="pagination"></div> </main>
Ve buna göre de widget’larımızı ekliyoruz.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/algoliasearchLite.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/instantsearch.production.min.js"></script> <script> var searchClient = algoliasearch('APP_ID', 'SEARCH_ONLY_API_KEY'); var search = instantsearch({ indexName: 'INDEX_ADINIZ', searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: '#search', placeholder: 'Otellerde arayın..' }), instantsearch.widgets.refinementList({ container: '#cities', attribute: 'city', }), instantsearch.widgets.refinementList({ container: '#states', attribute: 'state', }), instantsearch.widgets.hits({ container: '#hotels', templates: { item(hit) { return `<div class="hotel-single"> <img src="${hit.image ? hit.image.replace('.png', '_anasayfa.png') : 'https://xxxxxx.com/public/images/no-photo.png'}" alt=""> <div class="hotel-name">${hit.name}</div> <div class="hotel-location">${hit.city} / ${hit.state}</div> </div>`; } }, }), instantsearch.widgets.pagination({ container: '#pagination', }), instantsearch.widgets.stats({ container: '#stats', templates: { text: ` {{#hasNoResults}}Sonuç bulunamadı{{/hasNoResults}} {{#hasOneResult}}1 sonuç{{/hasOneResult}} {{#hasManyResults}}{{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} sonuç bulundu{{/hasManyResults}} ({{processingTimeMS}}ms) `, } }) ]); search.start(); </script>
Client tarafında API Key yerine size verilen Search Only Key’i kullanmanız gerekiyor, bu sadece arama işlemleri için client tarafında verebileceğiniz güvenlik anahtarınız. Yukarıdaki kodlarıma birazda css yazınca demomuz şöyle bir şey oluyor 🙂
http://www.erbilen.net/demo/algolia/
Daha bir çok özelliği var, eğer sizi heyecanladırdı ise döküman sayfasını inceleyerek şaşırmaya devam edebilirsiniz.
WordPress için Algolia
Eğer wordpress altyapısına sahip bir web siteniz varsa, mevcut algolia eklentilerini kullanarak sitenizin arama kısmını algolia’ya çevirmeniz çok kolay. Ben şu an erbilen.net’de şu eklentiyi kullanıyorum;
Tam olarak işini yapan bir eklneti 🙂 En azından wordpress sitelerinizde ufak bir test yapıp farkı görebilirsiniz.
büyük çaplı php projelerinde algolia baya işe yarar bir sistem gibi duruyor, kod örnekleri ve detaylı bilgilendirme için teşekkürler.
harikasınız hocam sizi severek takip ediyorum.