A single PHP file you put on your server and call as search.php?q=solar+panels&page=1&lang=en. It runs the flow of Build your own search UI: the meaning vector of the search, one {!hybrid} request to Solr, and words alone when there is no vector. It answers the JSON of Solr with highlights (hl.encoder=html), language facets and Did you mean.
Keep the API key and the password of the index on your server. A web page shows its JavaScript to every visitor, and the password of a new index is your API key: the username and password of your index.
<?php // search.php?q=solar+panels&page=1&lang=en : hybrid search on an Opensolr Index, answers JSON. // Needs PHP 7.4 or newer with the curl extension. Keep this file on your server only. const OPENSOLR_EMAIL = 'YOUR_EMAIL'; const OPENSOLR_API_KEY = 'YOUR_API_KEY'; const INDEX_NAME = 'YOUR_INDEX'; const INDEX_URL = 'https://YOUR_INDEX_HOST/solr/YOUR_INDEX/select'; const INDEX_USER = 'opensolr'; const INDEX_PASSWORD = 'YOUR_API_KEY'; const ROWS = 10; const QF = 'title^0.2 description^0.05 uri^0.01 text^0.01 text_t^0.01'; const MM = '2<65% 4<50% 8<40%'; header('Content-Type: application/json'); $q = mb_substr(trim((string)($_GET['q'] ?? '')), 0, 200); $page = max(1, min(100, (int)($_GET['page'] ?? 1))); $lang = (string)($_GET['lang'] ?? ''); if ($q === '') { http_response_code(400); echo json_encode(['error' => 'Missing q']); exit; } if (!preg_match('/^[a-z]{2,3}(-[a-z0-9]{2,8})?$/i', $lang)) { $lang = ''; } // Step 1: the meaning vector of the search, or null when there is none function embed_query(string $text): ?array { if (mb_strlen($text) < 2) { return null; } $url = 'https://api.opensolr.com/solr_manager/api/embed?' . http_build_query([ 'email' => OPENSOLR_EMAIL, 'api_key' => OPENSOLR_API_KEY, 'index_name' => INDEX_NAME, 'is_query' => 1, 'payload' => $text, ]); $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($body === false || $code !== 200) { return null; } $vector = json_decode($body, true); if (!is_array($vector) || count($vector) !== 1024 || !isset($vector[0]) || !is_numeric($vector[0])) { return null; } return array_map('floatval', $vector); } // Step 2: one Solr request, hybrid when there is a vector, words only when there is not $lexical = '{!edismax qf="' . QF . '" mm="' . MM . '" v=$uq}'; $params = [ ['uq', $q], ['fl', 'id,uri,title,description,og_image,creation_date,score'], ['rows', ROWS], ['start', ($page - 1) * ROWS], ['fq', 'content_type:text*'], ['hl', 'true'], ['hl.method', 'unified'], ['hl.encoder', 'html'], ['hl.q', '{!edismax qf="title description uri text" mm=0 v=$uq}'], ['hl.fl', 'title,description,text'], ['hl.fragsize', 300], ['hl.snippets', 1], ['hl.defaultSummary', 'true'], ['f.title.hl.fragsize', 0], ['facet', 'true'], ['facet.field', '{!ex=lang}meta_detected_language'], ['facet.mincount', 1], ['spellcheck', 'true'], ['spellcheck.q', $q], ['spellcheck.count', 5], ['spellcheck.collate', 'true'], ['spellcheck.maxCollations', 3], ['spellcheck.maxCollationTries', 15], ['echoParams', 'none'], ['wt', 'json'], ]; if ($lang !== '') { $params[] = ['fq', '{!tag=lang}{!term f=meta_detected_language}' . $lang]; } $vector = embed_query($q); if ($vector !== null) { $params[] = ['q', '{!hybrid lexical=$lexicalQuery vector=$vectorQuery mode=union alpha=0.85 topN=500}']; $params[] = ['lexicalQuery', $lexical]; $params[] = ['vectorQuery', '{!knn f=embeddings topK=500}[' . implode(',', $vector) . ']']; } else { $params[] = ['q', $lexical]; } // POST, because the vector makes the request too long for a URL $body = implode('&', array_map(function ($p) { return rawurlencode($p[0]) . '=' . rawurlencode((string)$p[1]); }, $params)); $ch = curl_init(INDEX_URL); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], CURLOPT_USERPWD => INDEX_USER . ':' . INDEX_PASSWORD, CURLOPT_TIMEOUT => 10, ]); $result = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($result === false || $code !== 200) { http_response_code(502); echo json_encode(['error' => 'Search failed']); exit; } echo $result;
| Replace | With |
|---|---|
YOUR_EMAIL | The email of your Opensolr account. |
YOUR_API_KEY | Your API key (Account › API Keys). The examples also use it as the password of the index, which is what a new index starts with; if you changed the username or password of the index, put yours: the username and password of your index. |
YOUR_INDEX | The name of your Opensolr Index. |
YOUR_INDEX_HOST | The host shown in the Control Panel of the index. |
The field weights, mm and alpha are the defaults; the Query Inspector of your hosted search page shows the values of your own index. The complete search page shows the results in a browser.