romanstruk / solr-scout-engine
Laravel Solr Scout Engine (Solarium driver for Laravel Scout)
Requires
- php: ^8.2
- ext-json: *
- ext-zip: *
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- laravel/scout: ^10.0|^11.0
- solarium/solarium: ^6.2
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-07-02 14:08:39 UTC
README
Laravel Scout driver for Apache Solr, built on top of Solarium.
Requirements
- PHP
^8.2 - Laravel
^10/^11 - Laravel Scout
^10 - Solarium
^6.2
Installation
composer require romanstruk/solr-scout-engine php artisan vendor:publish --tag=solr-config
Set the driver in .env:
SCOUT_DRIVER=solr SOLR_HOST=127.0.0.1 SOLR_PORT=8983 SOLR_PATH=/ SOLR_CONFIGSET="my_configset" # used by solr:index / createIndex
The Solr core is resolved per model from searchableAs().
Indexing a model
Use Solr dynamic field suffixes in toSearchableArray() (_s, _ss, _i,
_is, _f, _b, _txt_xx):
use Laravel\Scout\Searchable; class Product extends Model { use Searchable; public function searchableAs(): string { return 'products'; } public function toSearchableArray(): array { return [ 'name_txt_uk' => $this->name, 'price_f' => (float) $this->price, 'active_b' => (int) $this->active, 'categories_is' => $this->category_ids, ]; } }
Sync as usual: php artisan scout:import "App\Models\Product".
Searching
Simple Scout API:
Product::search('apple') ->where('active_b', 1) ->whereIn('categories_is', [1, 2]) ->orderBy('price_f', 'asc') ->paginate(30);
Full control via the search callback — you receive the Solarium
Select\Query\Query, mutate it (EDisMax, boosts, filter queries, JSON facets…)
and return it:
use Solarium\QueryType\Select\Query\Query; use Solarium\Component\DisMax\BoostQuery; Product::search($term, function (Query $query, string $search) { $query->setQuery("name_txt_uk:({$search})"); $query->getEDisMax()->addBoostQuery( (new BoostQuery())->setQuery('status_s:in_stock')->setKey('status') ); $query->getFacetSet()->createFacetField('brand')->setField('brand_ss'); return $query; })->paginate(30);
Facets
After a search, facets from the last result are available via a Collection
macro:
$products = Product::search('apple', $callback)->paginate(30); $facets = collect()->getFacetSet(); // normalised array (supports pivot facets)
Index management (standalone & SolrCloud)
The package works with both Solr modes. Pick one in .env:
SOLR_MODE="core" # standalone: indexes are cores (CoreAdmin API) # SOLR_MODE=cloud # SolrCloud: indexes are collections (Collections API)
Create the index for a model:
php artisan solr:index "App\Models\Product" # override per call: php artisan solr:index "App\Models\Product" --mode=cloud --configset=products
- core — creates a core via CoreAdmin using the on-disk configset
SOLR_CONFIGSET(must already exist underserver/solr/configsets/). - cloud — creates a collection via the Collections API with
collection.configName = SOLR_CONFIGSET,numShards,replicationFactor(SOLR_NUM_SHARDS,SOLR_REPLICATION_FACTOR, or per-call['num_shards' => .., 'replication_factor' => ..]).
The engine exposes createIndex($name, $options) and
deleteIndex($name, $options). Both honour mode/configset overrides via
$options.
Uploading a configset (SolrCloud only)
In cloud mode configsets live in ZooKeeper and are uploaded over the Configsets
API. The package zips a local directory (files at the zip root, solrconfig.xml
on top) and uploads it:
php artisan solr:configset products # from a model (uses searchableAs as the name, solrConfigsetPath() if defined): php artisan solr:configset --model="App\Models\Product" # explicit folder + overwrite stale files: php artisan solr:configset products --path=/path/to/conf --cleanup
Resolution of the source folder:
--pathoption, else- model
solrConfigsetPath()(when--modelgiven), else SOLR_CONFIGSET_PATHbase dir +/{name}.
SOLR_CONFIGSET_PATH=/var/www/app/solr/configsets
Standalone Solr has no configset upload API —
solr:configsetrefuses to run incoremode. Place the configset underserver/solr/configsets/on the Solr server instead.
A model may pin its own configset directory:
public function solrConfigsetPath(): string { return base_path('solr/configsets/products'); }
Testing
composer install
composer test
Tests run against an in-memory Solarium adapter (tests/Fakes/FakeAdapter) — no live Solr instance required.