Teil 2: Elasticsearch und FSCrawler in Laravel

In unserem letzten Blogbeitrag haben wir darüber gesprochen, wie wir Elasticsearch und FSCrawler mit Docker Compose erfolgreich eingerichtet haben. Nachdem wir die Herausforderungen der Konfiguration gemeistert haben, können wir nun die leistungsstarken OCR-Funktionen nutzen, um unsere Dokumente zu indizieren und durchsuchen.

In diesem Beitrag werden wir einen Schritt weiter gehen und herausfinden, wie wir das gesamte System nahtlos in ein Laravel-Projekt integrieren können.

Schritt 1: Installieren der Elasticsearch PHP-Clientbibliothek

Um Elasticsearch in Laravel zu verwenden, müssen wir die Elasticsearch PHP-Clientbibliothek installieren. Dies können wir mit dem folgenden Befehl in Composer tun:

composer require elasticsearch/elasticsearch

Schritt 2: Hinzufügen von Umgebungsvariablen in .env-Datei

Um eine Verbindung zu Elasticsearch herzustellen, benötigen wir Umgebungsvariablen. Diese können wir in der .env-Datei hinzufügen. Hier ist ein Beispiel, wie das aussehen könnte:

ELASTICSEARCH_HOST=localhost:9200
ELASTICSEARCH_USERNAME=elastic
ELASTICSEARCH_PASSWORD=your_password

Wir ersetzen your_password durch das gleiche Passwort, das wir für die Elasticsearch-Instanz in der Datei docker-compose.yml verwendet haben.

Schritt 3: Erstellung der Elasticsearch Konfiguration

Um die Konfiguration von Elasticsearch besser zu verwalten, erstellen wir eine neue Datei namens elasticsearch.php im Ordner config unseres Laravel-Projekts und fügen den folgenden Code hinzu:

<?php

return [
    'hosts' => [
        env('ELASTICSEARCH_HOST', 'localhost:9200'),
    ],
    'username' => env('ELASTICSEARCH_USERNAME', ''),
    'password' => env('ELASTICSEARCH_PASSWORD', ''),
];

Schritt 4: Erstellung der Elasticsearch Service-Klasse

Als Nächstes erstellen wir eine Service-Klasse, um die Kommunikation zwischen Laravel und Elasticsearch zu verwalten. In dieser Klasse werden wir die grundlegenden Funktionen zum Suchen und Indizieren von Dokumenten implementieren.

Wir erstellen im Ordner app/Services eine neue Datei namens ElasticSearchClient.php. Falls der Services Ordner noch nicht existiert, einfach neu erstellen.

<?php

namespace App\Services;

use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\ClientBuilder;
use Psr\Http\Message\ResponseInterface;

class ElasticsearchService
{
    private $client;

    public function __construct()
    {
        $host = config('elasticsearch.hosts');
        $username = config('elasticsearch.username');
        $password = config('elasticsearch.password');

        $this->client = ClientBuilder::create()
            ->setHosts($host)
            ->setBasicAuthentication($username, $password)
            ->build();
    }

    public function searchDocuments($index, $query)
    {
        $params = [
            'index' => $index,
            'body' => [
                'query' => [
                    'query_string' => [
                        'query' => $query,
                    ],
                ],
            ],
        ];

        $response = $this->client->search($params);

        return $response['hits']['hits'];
    }

    public function getClusterHealth()
    {
        $response = $this->client->cluster()->health();

        return $response;
    }
}

Die Funktion getClusterHealth können wir später zum testen der Verbindung benutzen.

Schritt 5: Funktionstest

Wir erstellen einen neuen Controller:

php artisan make:controller TestController

Und hinterlegen ihn in unsere api.php

Route::get('/test', [TestController::class, 'test']);

Innerhalb des TestControllers hinterlegen wir dann eine Test Funktion um zu schauen ob alles klappt:

<?php

namespace App\Http\Controllers;

use App\Services\ElasticsearchService;

class TestController extends Controller
{
    public function test()
    {
        $query = 'Suchbegriff';
        $elasticsearchService = new ElasticsearchService();
        $results = $elasticsearchService->searchDocuments('documents_job', $query);

        return $results;

        $elasticsearchService = new ElasticsearchService();
        $clusterHealth = $elasticsearchService->getClusterHealth();

        return $clusterHealth;
    }
}

(zum Testen kann man hier dann die obere oder untere Funktion auskommentieren)

Schritt 6: Fine-Tuning der Abfrage

Wenn alles Funktioniert bekommt man alle Dokumente samt Inhalt, Metainformationen, Dateiinformationen und Pfadangabe zurück, bei einer hohen Anzahl an Dokumenten wird das natürlich sehr schnell problematisch. Da wir in unserer Laravel Anwendung nur die Dateipfade haben möchten, um das dann richtig aufzulösen, können wir bei der Abfrage schon Filter setzen:

    public function searchDocuments($index, $query)
    {
        $params = [
            'index' => $index,
            'body' => [
                '_source' => ['file'],
                'query' => [
                    'query_string' => [
                        'query' => $query,
                    ],
                ],

...

Eine weitere sehr brauchbare Funktion ist das Heighlighting. Hier werden einzelne Textpassagen die das Suchwort beinhalten mit geliefert:

$params = [
            'index' => $index,
            'body' => [
                '_source' => ['file'],
                'query' => [
                    'query_string' => [
                        'query' => $query,
                    ],
                ],
                'highlight' => [
                    'fields' => [
                        'content' => new \stdClass()
                    ],
                    'pre_tags' => ['<strong>'],
                    'post_tags' => ['</strong>']
                ]
            ],
        ];

Das Ergebnis sieht dann so aus:

[
    {
        "_index": "documents_job",
        "_id": "10282a61911cbbf119c214b8236b67",
        "_score": 8.568032,
        "_source": {
            "file": {
                "extension": "docx",
                "content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                "created": "2023-04-05T14:47:24.484+00:00",
                "last_modified": "2023-04-05T14:47:24.484+00:00",
                "last_accessed": "2023-04-05T14:47:24.484+00:00",
                "indexing_date": "2023-04-05T14:52:34.451+00:00",
                "filesize": 13429,
                "filename": "Beispiel_Dokument_4.docx",
                "url": "file:///tmp/es/Beispiel_Dokument_4.docx"
            }
        },
        "highlight": {
            "content": [
                "Diese Veränderungen haben weitreichende <strong>Auswirkungen</strong> auf die Natur und die Menschen.",
                "In Deutschland sind die <strong>Auswirkungen</strong> <strong>des</strong> <strong>Klimawandels</strong> bereits spürbar."
            ]
        }
    },

Schritt 7: Daten aufbereiten und dazugehöriges File Model finden

In unserem Konstrukt haben wir „Files“ die hochgeladen werden. Innerhalb der Files gibt es ein Pfad Feld damit die Dateien auch wieder runtergeladen werden können. Da der Dokumente Ordner direkt im Docker Image gemounted wird müssen wir „/documents/“ manuell hinzufügen.

// Initialize an empty array to hold the results
$data = [];

// Loop through each result
foreach ($results as $result) {

    // Get the relevance score of the document
    $score = $result['_score'];

    // Get the highlighted content of the document
    $highlight = $result['highlight']['content'];

    // Construct the path to the document using its filename
    $path = '/documents/' . $result['_source']['file']['filename'];

    // Find the file in the database that has the same path
    $file = File::where('path', $path)->first();

    // Add the document's score, highlight, path, and file to the results array
    $data[] = [
        'score' => $score,
        'highlight' => $highlight,
        'path' => $path,
        'file ' => $file,
    ];
}

// Return the results array
return $data;

Als Ergebnis kommt dann ein JSON in diesem Format raus:

Fazit

In diesem zweiten Teil haben wir erfolgreich Elasticsearch und FSCrawler in ein Laravel-Projekt integriert. Wir haben die Elasticsearch PHP-Clientbibliothek installiert und eine Elasticsearch-Konfigurationsdatei erstellt, um die Verbindung zur Elasticsearch-Instanz herzustellen. Dann haben wir eine Service-Klasse erstellt, um grundlegende Funktionen zum Suchen und Indizieren von Dokumenten zu implementieren.

Um sicherzustellen, dass die Abfrageergebnisse effektiv und sinnvoll sind, haben wir auch Feinabstimmungen vorgenommen, wie die Begrenzung der zurückgegebenen Felder und die Hervorhebung von Suchbegriffen.

Zuletzt haben wir die Ergebnisse aufbereitet und das dazugehörige File Model gefunden, um sicherzustellen, dass die Suchergebnisse in unserer Laravel-Anwendung sinnvoll angezeigt werden.

Sollten Sie noch Fragen haben oder eine Beratung wünschen, können Sie gerne mit uns Kontakt aufnehmen oder unsere Webseite besuchen. Wir lassen Ihnen gerne ein unverbindliches Angebot zukommen.

Gerne können Sie hier auch andere Artikel zum Thema Laravel lesen.