Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Opensearch Php Laravel Package

opensearch-project/opensearch-php

opensearch-php is a community-driven, open source PHP client for OpenSearch, forked from elasticsearch-php and licensed under Apache 2.0. It lets PHP applications index, search, and manage data in OpenSearch clusters.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Check the constraints before installing. The 2.6.x line requires PHP 8.2+ (2.4/2.5 required 8.1) and uses PSR-18/PSR-17 HTTP abstractions. If you're on PHP 8.0 or below, you need a 1.x/older 2.x pin — verify against COMPATIBILITY.md, which maps client versions to OpenSearch server versions.

composer require opensearch-project/opensearch-php
# plus one HTTP client implementation, e.g.:
composer require guzzlehttp/guzzle

2. Read these three files first, in this order:

File Why
USER_GUIDE.md The actual tutorial — README is just a link hub. It was rewritten in 2.4.x for the new PSR/factory approach; older blog posts and Stack Overflow answers still show the deprecated connection-pool style.
COMPATIBILITY.md Server ↔ client version matrix.
samples/ Runnable examples added in 2.4.0 — fastest way to see idiomatic calls.

3. Smallest working client + first use case (index a document, search it back):

use OpenSearch\ClientBuilder;

$client = ClientBuilder::create()
    ->setHosts(['https://localhost:9200'])
    ->setBasicAuthentication('admin', getenv('OPENSEARCH_PASSWORD'))
    ->setSSLVerification(false) // local dev only
    ->build();

$client->indices()->create([
    'index' => 'posts',
    'body'  => ['mappings' => ['properties' => ['title' => ['type' => 'text']]]],
]);

$client->index([
    'index' => 'posts',
    'id'    => 'p1',
    'body'  => ['title' => 'Hello OpenSearch', 'published_at' => '2026-09-14'],
    'refresh' => true, // dev only
]);

$res = $client->search([
    'index' => 'posts',
    'body'  => ['query' => ['match' => ['title' => 'opensearch']]],
]);

4. Local dev: the repo ships a docker-compose.yml (added in 2.4.0). Note that 2.x images enable the security plugin by default — you'll get https + a generated admin password unless you set DISABLE_SECURITY_PLUGIN=true in the environment. Most "connection refused / 401" first-day problems are this.


Implementation Patterns

Bind it once, configure from an array

Don't scatter ClientBuilder::create() around the codebase. ClientBuilder::fromConfig() takes a plain array, which maps cleanly onto Laravel config and lets you swap hosts per environment:

// config/opensearch.php
return [
    'hosts' => [env('OPENSEARCH_HOST', 'https://localhost:9200')],
    'basicAuthentication' => [env('OPENSEARCH_USER'), env('OPENSEARCH_PASS')],
    'retries' => 3,
    'includePortInHostHeader' => true, // added 2.3.0, matters behind proxies/nonstandard ports
];
// AppServiceProvider::register()
$this->app->singleton(\OpenSearch\Client::class, fn () => \OpenSearch\ClientBuilder::fromConfig(
    config('opensearch')
));

The client is stateless and safe to share; injecting it (or a thin repository wrapper) makes it trivially mockable in tests.

Drive retries/backoff through the HTTP client, not the builder

Connections, connection pools, and selectors are deprecated — the official direction is a PSR-18 client that handles retries itself. Use the shipped factories, which is also where AWS signing lives:

use OpenSearch\ClientBuilder;
use OpenSearch\GuzzleClientFactory;

$client = (new ClientBuilder())
    ->setHttpClientFactory(new GuzzleClientFactory(['auth_aws' => true])) // 2.4.3+
    ->setHosts(['https://search-....eu-west-1.es.amazonaws.com'])
    ->build();

For OpenSearch Serverless / IAM SigV4, the builder also exposes credential-provider, region, and custom service-name options (custom service names landed in 2.3.0 — needed when signing as aoss rather than es).

Add observability by pushing a PSR-3 logger into the HTTP client's middleware stack rather than wrapping every call site.

Bulk ingestion — the one pattern you'll write most often

bulk() takes NDJSON, and every action line must be followed by a newline or you get a 400 with a useless message:

$body = '';
foreach ($rows as $row) {
    $body .= json_encode(['index' => ['_index' => 'posts', '_id' => $row['id']]]) . "\n";
    $body .= json_encode($row) . "\n";
}

$res = $client->bulk(['body' => $body]);

if ($res['errors']) {
    foreach ($res['items'] as $item) {
        // per-document failure — bulk returns HTTP 200 even when items failed
    }
}

Chunk in the low thousands per request; memory and the coordinating node's request buffer are the limits, not the client.

Search: keep the DSL in PHP arrays, and pick the right paginator

  • Simple/compound: $client->search(['index' => ..., 'body' => [...]]) — the whole Query DSL is just nested arrays.
  • Multi-query in one round trip: msearch() — halves latency for dashboards.
  • Deep pagination: do not use from/size past 10k. Use search_after + PIT (point-in-time APIs were fixed in 2.3.0) or scroll() for export jobs, and stream hits rather than collecting into an array.
  • Use the SearchResponseIterator-style helper iterators for unbounded iteration; wrap them in cursor-like generators so controllers never materialize 500k hits.

Raw JSON requests for anything not yet generated

Endpoints are generated from opensearch-api-specification, so new server features can lag. Raw requests (2.3.0+) let you hit them today, and are also the sane escape hatch for SQL/ESQL with format (e.g. format=csv), _cat endpoints, and ad-hoc _cluster/health checks in deploy scripts. Treat them as a temporary bridge — replace with typed methods once the spec catches up, otherwise you lose the array-shape docblocks and static analysis.

Repository wrapper per index

For application code, wrap the client in a domain-level repository (PostIndex) that owns the index name, mapping, and query shapes. Benefits: index naming/aliasing lives in one place, tests can point at a ephemeral index, and you can swap in a fake without touching business logic.

Testing

  • Point at a throwaway index (posts_test_<random>), refresh: true on writes, delete in tearDown().
  • For unit tests, inject a mocked PSR-18 client (Guzzle's MockHandler / Middleware::history) via setHttpClient() — you can assert on the exact serialized request body, which catches NDJSON and query-param bugs that integration tests miss.
  • Use ignore in params to swallow expected 404s (e.g. indices()->delete on a missing index) instead of try/catch noise.

Gotchas and Tips

body vs. params — this is the #1 source of "it silently ignored my option." Query-string parameters (refresh, ignore, routing, request_cache, scroll, format) belong in the params array, not inside body. Conversely, the Query DSL, mappings, and settings go in body. Newly generated endpoints (2.6.x added array-shape docblocks) may take some fields at the top level — let the IDE read the docblock rather than guessing.

Errors are exceptions, and the class names moved. A 404 raises Missing404Exception; a 400 with a malformed query raises a parse exception. In 2.4.0 many \OpenSearch\Common\Exceptions\* classes were deprecated in favour of \OpenSearch\Exception\* — catch the new namespace, and catch broadly on the base OpenSearch exception at integration boundaries. Bulk and msearch are the exception to the rule: they return HTTP 200 with per-item errors, so you must inspect errors/status yourself.

Deprecations you'll hit during a 2.x upgrade:

  • setEndpoint(callable) → setEndpointFactory(new EndpointFactory(...)), and the factory must return a new endpoint object per call (fixed in 2.4.1 after parameter-reuse bugs).
  • Connection pools / selectors → PSR client retries.
  • Namespaces async_search, searchable_snapshots, ssl, monitoring, data_frame_transform_deprecated are slated for removal in 3.0.0.
  • Client's constructor now takes an optional EndpointFactory — relevant only if you subclass.

Async exists again but is niche. Async requests were removed in 2.4.0 as "never actually working" and restored in 2.4.2. It returns promises; only worth it for fan-out reads. Don't build ingestion on it — a synchronous bulk() loop is simpler and easier to backpressure.

Pin and read the changelog before minor bumps. Every minor release regenerates endpoints from the API spec and adds deprecations ("Updated APIs" blocks in each changelog). A ^2.4 constraint can change generated signatures out from under you. Read changelog/<version>.md for the target version and check the Changed/Deprecated/Removed blocks specifically.

Host strings: trailing slashes in host URLs used to break request building (fixed 2.3.0) — normalize config to no trailing slash anyway. includePortInHostHeader (2.3.0) exists because some proxies/LBs drop the port from the Host header and the cluster rejects the request. Basic auth was once overridden by connection params (fixed 2.3.0); if you're on an older pin and see random 401s, that's why.

Debugging a failing query fast:

  1. Log the serialized request via a middleware on the PSR client (you get the exact path, params, and body).
  2. Replay that body with curl against _search?pretty — if it fails there too, the bug is in your DSL, not the client.
  3. _cluster/health and GET / via a raw request before blaming the client — it's usually TLS, auth, or the security plugin.
  4. Set setSSLVerification(false) only in local config; it's silently swallowed in production arrays otherwise.

Extension points worth knowing: setHttpClient()/setHttpClientFactory() (Guzzle or Symfony factories, with auth_aws), setEndpointFactory(), fromConfig() for config-driven construction, and raw request methods for ungenerated endpoints. If you need per-request tracing IDs or custom headers, add a PSR-18 middleware — do not fork the client or subclass Client.

Memory: search() with size: 10000 and hydrating full _source into PHP arrays is the classic production OOM. Prefer _source filtering, search_after, and generators.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json