larablog 建構日誌:站內搜尋

larablog 建構日誌:站內搜尋

Google 自訂搜尋:設定簡單,但調性不符

  我使用 Joomla! 系統建置的網站:華燈初上跟部落格都使用 Google 提供的自定義搜尋服務,將 Google 提供的嵌入碼加進佈景主題中,再透過「自訂 HTML」模組將搜尋表單顯示於佈景主題定義的模組位置,站內搜尋服務就完成了。

華燈初上的 Google 自定義搜尋畫面

  讓 Google 處理站內搜尋的好處,是將「搜尋網站內容」這件消耗伺服器效能的事委外處理,對於性能不高的共享型虛擬主機空間來說幫助很大,而且「搜尋及索引」就是 Google 的核心業務。

  在學習 PHP 語法,製作 ffxitoolbox 第一版時也是使用 Google 自定義搜尋作為站內搜尋功能,不過沒過多久我就發現該服務不適合用在 ffxitoolbox 上,主要有兩點:

  1. Google 自定義搜尋的搜尋結果是 Google 索引過的內容,所以沒索引到的部分就找不到。加上搜尋功能的使用是輸入素材關鍵字尋找相關配方,搜尋結果是整頁的搜尋結果,點選後還要再找,不怎麼方便。
  2. 搜尋結果的呈現風格跟網站的設計差別很大,每次使用時很難跟網站畫面聯想在一起,雖然這不是 Google 的錯...

自建搜尋:畫面自行決定,佔用伺服器效能

  2021 年初完成 ffxitoolbox 第一版製作,代表我從零開始學習 PHP 有了初步成就:我有能力使用 PHP 跟 MySQL 將 ffxi 的合成配方資料整理成網站內容提供閱覽。

  在這之後我開始學習 Laravel 框架,設下的目標就是將 ffxitoolbox 以 Laravel 框架重寫:如果能夠完成代表我已經能用 Laravel 做出網站專案。就在作業接近收尾的階段,「站內搜尋」功能的建置浮出檯面,這次實在不想用 Google 自定義搜尋了...

  用 Google 搜尋前人的智慧結晶時,我發現了〈How to add simple search to your Laravel blog/website?〉這篇文章,透過閱讀文章內容逐步操作,我讓 ffxitoolbox 的站內搜尋結果能以想要的方式呈現。

ffxitoolbox 的站內搜尋結果

  建立簡易站內搜尋的流程是:建立搜尋的 Controller,以 LIKE 語法搭配 %{search}% 搜尋資料庫(合成配方、分解及食物效果)中是否有符合的項目。之後將結果送到 Blade template 輸出,並且設置搜尋路由就可以了。以下是 ffxitoolbox 負責搜尋的 Controller 程式碼:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Discompose;
use App\Models\Foodresult;
use App\Models\Recipe;

class SearchController extends Controller
{
        //搜尋功能
        public function search(Request $request){
            // 取得搜尋關鍵字
            $search = $request->input('search');

            // 查詢範圍
            $recipes = Recipe::query()
                ->where('name', 'LIKE', "%{$search}%")
                ->orWhere('material1', 'LIKE', "%{$search}%")
                ->orWhere('material2', 'LIKE', "%{$search}%")
                ->orWhere('material3', 'LIKE', "%{$search}%")
                ->orWhere('material4', 'LIKE', "%{$search}%")
                ->orWhere('material5', 'LIKE', "%{$search}%")
                ->orWhere('material6', 'LIKE', "%{$search}%")
                ->orWhere('material7', 'LIKE', "%{$search}%")
                ->orWhere('material8', 'LIKE', "%{$search}%")
                ->get();

            $discomposes = Discompose::query()
                ->where('material1', 'LIKE', "%{$search}%")
                ->orwhere('name', 'LIKE', "%{$search}%")
                ->orWhere('HQ1', 'LIKE', "%{$search}%")
                ->orWhere('HQ2', 'LIKE', "%{$search}%")
                ->orWhere('HQ3', 'LIKE', "%{$search}%")
                ->get();

            $foodresults = Foodresult::query()
                ->where('Name', 'LIKE', "%{$search}%")
                ->get();

            $binding = [
                'search' => $search,
                'discomposes' => $discomposes,
                'foodresults' => $foodresults,
                'recipes' => $recipes,
            ];
            // 回傳搜尋結果,以搜尋頁面呈現
            return view('frontend.search', $binding);

        }

}

  搜尋結果頁面是自己設計的,所以在搜尋結果中加入編輯功能也不是什麼難事。當配方資料獲得進一步確認時時我可以在前貒登入網站,利用搜尋功能找到編輯項目,選開啟編輯表單更新資料。

  以一個全合成種類都有涉獵,常常會需要搜尋配方資料的人來說,當前提供的搜尋功能已經符合我的需求。雖然每次搜尋都會消耗伺服器效能,不過以使用者大概只有自己的情況下不擔心伺服器會被操掛(笑)。

外部資源加自訂顯示:Laravel Scout + Algolia

  Google 提供的資源省掉運作負擔,但是頁面呈現不愛;自建站內搜尋結果符合預期,擔心使用的人多造成伺服器負載...有沒有集合兩者優點的第三種方案?有的,就是 Laravel Scout 與 Algolia 服務。

  Algolia 以 SaaS 方式提供搜尋服務的,開發者可以將網站資料上傳至 Algolia 製作索引,然後透過自訂的視覺版型呈現搜尋結果,兼具效能與美觀。接下來將透過 Laravel 的 Scout 套件連接 Algolia,製作站內搜尋。

申請 Algolia 服務

  前往 https://www.algolia.com/ 註冊帳號,接著為網站建立應用程式(Application)及索引(Index)名稱,之後點選畫面左下方「設定(Settings,齒輪圖示)」畫面中點選「API Keys」取得 Application ID 及 Admin API Key。

API Keys 畫面

下載 Laravel Scout,設定 Algolia 通訊

  在終端機畫面下載 Laravel Scout 套件

composer require laravel/scout

  發佈套件,會建立 /config/scout.php 設定檔

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

  在 .env 輸入與 Algolia 溝通所需的資料:Application IDAdmin API Key 及索引名稱。

SCOUT_PREFIX=(索引名稱)
SCOUT_QUEUE=true #是否將索引作業排入隊列,建議為 true
ALGOLIA_APP_ID=(Application ID)
ALGOLIA_SECRET=(Admin API Key)

  下載 Algolia 搜尋驅動

composer require algolia/algoliasearch-client-php

Model 加入索引

  在要索引的 Model(本例是 Post)引入命名空間

use Laravel\Scout\Searchable;

  以及在類別中引入 Searchable trait:

use Searchable;

  新增 searchableAs 方法,指定存入索引名

public function searchableAs()
{
    return config('scout.prefix'); //回傳 .env 中 SCOUT_PREFIX 參數值
// return 'blog'; //直接輸入回傳的索引名
}

  新增 toSearchableArray 方法,指定哪些欄位不要納入索引:

public function toSearchableArray()
{
    $array = $this->toArray();

    unset($array['category_id']);
    unset($array['cover_image']);
    unset($array['user_id']);
    unset($array['sort']);
    unset($array['status']);
    unset($array['featured']);

    return $array;
}

  將資料匯入 Algolia:

php artisan scout:import "App\Models\Post"

  回到 Algolia 畫面,會看到索引下已經有記錄了。

索引的網站資料

負責搜尋的 Controller 及方法

  建立負責網站運作的 Controller:SiteController,在其中建立 search 方法負責處理搜尋資料:

php artisan make:controller SiteController
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class SiteController extends Controller
{
    public function search(Request $request)
    {
        $keyword = $request->search;
        $post_results = Post::search($keyword);
        if($post_results == null)
        {
            $post_results = '';
        }else{
            $post_results = $post_results->orderBy('created_at', 'desc')->paginate(20);
        }

        return view('search', compact('keyword', 'post_results'));
    }

}

增加路由

  在 /routes/web.php 增加搜尋路由:

// 搜尋
Route::get('/search', 'App\Http\Controllers\SiteController@search')->name('search');

與搜尋有關的 Blade template

  與搜尋有關的 Blade template 有兩個:提供搜尋輸入欄位的 /resources/views/widgets/search.blade.php

<h3 class="sidebar-title">站內搜尋</h3>
<div class="sidebar-item search-form">
    <form action="{{ route('search') }}" method="GET">
        <input type="text" name="search">
        <button type="submit"><i class="bi bi-search"></i></button>
    </form>
</div>

  負責顯示搜尋結果的 /resources/views/search.blade.php

@extends('layouts.master')
@section('meta_description', '搜尋 '. $keyword . ' 的結果')
@section('title', '搜尋 '. $keyword . ' 的結果')
@section('cover_image', Voyager::image('articles/search.png'))
@section('url', url()->full())
@section('nav_home', 'active')
@section('content')

<!-- 網頁路徑 -->
<section class="breadcrumbs">
    <div class="container">
        <div class="d-flex justify-content-between align-items-center">
            <h2>搜尋</h2>

            <ol>
                {{ Breadcrumbs::render('search') }}&nbsp; {!! $keyword !!} 的結果
            </ol>
        </div>
    </div>
</section>
<!-- 網頁路徑 -->

<section id="blog" class="blog">
    <div class="container">
        <div class="row">
            <div class="col-lg-12 entries">
                <article class="entry entry-single">

                    <div class="entry-content">
                        @if($post_results !== '')
                            <h3>包含:<font color="blue">{{ $keyword }}</font>&nbsp;的文章</h3>
                            @foreach($post_results as $post)
                                <h3><a href="/blog/{{ $post->slug }}">{{ $post->title }}</a></h3>
                                {{ $post->introtext }}
                            @endforeach

                            <div class="blog-pagination">
                                <ul class="justify-content-center">{{ $post_results->links() }}</ul>
                            </div>
                        @else
                            <h3>找不到與 &nbsp;{{ $keyword }} 有關的文章。</h3>
                        @endif
                    </div>

                </article><!-- End blog entry -->
            </div><!-- End blog entries list -->
        </div><!-- End blog sidebar -->

    </div>
</section><!-- End Blog Single Section -->
@stop

  在前端搜尋欄位輸入關鍵字

搜尋欄位輸入關鍵字

  在搜尋結果頁面觀看相關的文章

關鍵字搜尋結果

結語

  站內搜尋是我最後一個實做的主要功能,除了尋求適合的方案之外,想等到網站內容累積到一定程度之後再實做,可以看到多筆資料結果也是原因之一。如果手邊實在沒有資料進行查詢,可以透過 FactoryFaker 的協同運作產生假資料,這樣內容要有幾筆就有幾筆。

  Algolia 另有推出 Scout Extended 套件擴展 Laravel Scout 功能,透過聚合器(aggregator)將多個 Model 內容集中在同一索引中,還有在搜尋列輸入時就能即時顯示的 live search 功能要等到實力有所精進後再實做出來。

  規劃的專案功能皆以到位,該是把網站放上運作空間,讓 larablog 上線的時候了。

後記

  在文章發表之後我繼續尋找有關 live search 的資料想弄清楚運作方式,進而瞭解到:live search 透過 AJAX 方式監聽搜尋欄位的輸入內容,返回找到的結果。

  如果是 AJAX 的話那麼可以透過 livewire 做到嗎?參考網路上找到的資訊後有做出類似的成果,以下是過程記錄。

參考資料

安裝 livewire,導入資源

  安裝 livewire:

composer require livewire/livewire

  在 Blade template 主版檔案嵌入 livewire 資源:在 CSS 引入段落加入:

@livewireStyles

  JavaScript 引入段落加入:

@livewireScripts

建立 livewire 元件

php artisan make:livewire search

  會產生兩個檔案:

  • 類別檔案:/app/Http/Livewire/Search.php
  • 視圖檔案:/resources/views/livewire/search.blade.php

編輯類別及視圖

  在類別檔案加入搜尋事件程式碼:

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Post;

class Search extends Component
{
    public $keyword = "";

    public function render()
    {
        $posts = Post::search($this->keyword)->get();

        return view('livewire.search', compact('posts'));
    }
}

  $keyword 作為接收輸入的變數,初始值是空字串。$posts 儲存搜尋結果(Algolia 索引化後的資料),將結果傳到 search 這個 Blade template。

  編輯視圖檔案:定義搜尋欄位外觀,及搜尋結果呈現。

<div>
    <input type="text" wire:model="keyword" placeholder="請輸入關鍵字"/>

    @if($keyword == "")
        &nbsp;
    @else

    <div style="
    display:block;
    position:absolute;
    z-index:+1;
    width: 280px;
    margin-top:10px;
    padding: 10px 10px 0px 5px;
    background-color: white;
    border: 1px dotted;
    border-radius: 10px;
    ">
        @foreach($posts as $post)
        <ul>
            <li><a href="/blog/{{ $post->slug }}">{{ $post->title}}</a></li>
        </ul>
    @endforeach
    </div>
    @endif
</div>

  這裡的重點在 wire:model="keyword",livewire 會監聽 $keyword 的變化,即時呈現搜尋結果。

  發佈 livewire:

php artisan livewire:publish --config

  在前端要呈現 live search 地方加上以下內容,顯示 live search 欄位:

@livewire('search')

live search 示意

尚須改進之處

  在搜尋欄位輸入關鍵字後會在下方動態產生相關的文章標題,在呈現有符合 live search 的樣子,只是訊息變動的反應有點鈍。其次因為索引內容的範圍關係,顯示項目部分僅做到標題顯示,理想的情況是呈現的內容能再以出處的分類(如:文章/分類/標籤)分別顯示,這部分等到瞭解 Scout Extend 套件的運作細節後再做改良。

文章作者:A-Bo Lee
作者大頭照

居住在臺灣的 Joomler,期望以程式設計、開放原碼推廣活動收入養活一家老小。
35 歲後改姓李,id 作為曾為郭姓的證明。
FFXI:Abokuo@Sylph鯖、よろしくです。

17 篇回應

  1. Image
    comment-1499422025-12-04 04:16:42

    не минует ни одного вечера, чтобы вы не посмотрели пару фильмов?

  2. Image
    bplay2025-12-04 03:12:03

    Among the hotel's many amenities, guests and visitors will find an extensive gaming area, connect with
    popular entertainment, diverse cafes, a beauty, a bplay and a number of various shopping centers.

  3. Image

    VPS сервер, VPS сервер — отличный выбор
    для разработчиков, стремящихся к достойной
    производительности.

  4. Image
    caliente casino login2025-12-03 23:33:08

    for the {most {many years|long time} in the caliente casino login, {all|maximum}
    that I can {afford|provide} to lose in one game ({evening|night} or weekend) is $400.

  5. Image

    besides a customer get the chance to book a more spacious transport, for example, a comfortable bus capable of transporting up https://www.whatsyourhours.com/latvija/health-beauty/melinda-lindas-skaistuma-studija.

  6. Image
    bluechip2025-12-03 23:13:06

    Your deposits and collections are instantaneous.
    gamble at Winz bluechip and resort to our exclusive bonus, which not requires wagering.

  7. Image
    casino plus login2025-12-03 22:22:35

    Casino Security Index compiled on database similar information,
    gives an assessment reflecting the safety and truthfulness of the online
    casino plus login. Casino Plus is owned by Stotsenberg Leisure Park Corporation

  8. Image
    ramblermails.com2025-12-03 22:13:42

    Составляя рейтинг гемов для вас,
    наши сотрудники трудятся еще
    с 2006-го года, https://ramblermails.

  9. Image
    AngelReecy2025-12-03 22:04:02

    https://registraciavsaita.listbb.ru/viewtopic.php?f=3&t=5156

  10. Image
    Michealstecy2025-12-03 22:01:00

    https://www.aicrowd.com/participants/lagodigardacom

  11. Image
    Michealdet2025-12-03 21:58:47

    https://www.kristiesjewels.com/index.php?route=journal2/blog/post&journal_blog_category_id=2&journal_blog_post_id=7

  12. Image
    casino game2025-12-03 21:54:33

    Fishing' Frenzy megaways has the fisherman free casino game game in which
    users have the opportunity get pleasure from interesting occupation fishing
    , and increase own winnings.

  13. Image
    rabby2025-12-03 21:36:05

    this algorithm is embedded in the dapp context using a content-script.
    even if is about to happen, rabby this is not a a
    real problem from the user's side.

  14. Image

    Ищете, где купить VPS дешево,
    virtual private server? Вам стоит обратить внимание на рекомендуемые предложения на рынке.

  15. Image
    JosephnaP2025-12-03 20:18:35

    https://rich513.com/

  16. Image
    JeffreyNet2025-12-03 20:15:38

    Надежная электроника с быстрой доставкой по всему Казахстану и без космических цен. Многие уже давно заказывают в Salemshop.kz: огромный выбор реальные скидки каждый день гарантия на все и отправка в тот же день - https://salemshop.kz/

  17. Image
    1inch2025-12-03 19:54:27

    users it is offered to participate figure in worldwide network tournaments to compete
    for the main prize: 1,500 1inch 1inch tokens. in its current state, 1inch
    supports more than 389 sources of liquidity from 11 blockchain and 6.

看完文章有什麼想法嗎?利用下面表單告訴作者吧

請先閱讀服務條款隱私權政策,送出回應意即同意前述文件。標記 * 欄位請務必填寫,電子郵件信箱僅作驗證使用,不會顯示在回應中。