meiji / pimcore-meilisearch-bundle
Pimcore Meilisearch Bundle
Installs: 178
Dependents: 0
Suggesters: 0
Security: 0
Type:pimcore-bundle
pkg:composer/meiji/pimcore-meilisearch-bundle
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- meilisearch/meilisearch-php: ^v1.9
- nyholm/psr7: ^1.8
- pimcore/pimcore: ^11.1
- symfony/http-client: ^7.1
Requires (Dev)
- ergebnis/phpstan-rules: ^2.6
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- symplify/easy-coding-standard: ^12.5
README
A lightweight integration between Pimcore 11 DataObjects and Meilisearch. It keeps your search index in sync with Pimcore changes and provides simple tools to define index settings and bulk‑reindex content.
Features
- 🔌 Simple configuration via env:
MEILISEARCH_URL
,MEILISEARCH_API_KEY
,MEILISEARCH_INDEX_PREPEND
(optional prefix for index UIDs). - 🧩 Drop‑in interface for Pimcore DataObjects to declare index settings and document payload.
- ♻️ Automatic sync on create/update/delete (Pimcore event subscriber).
- 🛠️ CLI commands: debug, index settings sync, bulk document sync (with batching and class filter).
Requirements
- PHP 8.1–8.3, Pimcore ^11.1
meilisearch/meilisearch-php
^1.9, Symfony HttpClient ^7.1
To star using bundle you must set Mailisearch instance credentials, upgrade specify model (extend with provided interface) and apply index synchronisation:
meilisearch:index-sync
- to sync search index configuration;meilisearch:document-sync
- to sync all objects of implementing classes.$ bin/console meilisearch:document-sync --batch-count=250 --batch-from-number=2 --index-class=Meiji\\GlobalMarketBundle\\Model\\Product\\Product
Bundle automaticaly add, update and delete specular document of DataObject.
Installation
- On your Pimcore 11 root project
$ composer require meiji/pimcore-meilisearch-bundle
- Set environment variables (in
.env
for ex.)
MEILISEARCH_URL=http://127.0.0.1:7700
MEILISEARCH_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MEILISEARCH_INDEX_PREPEND=myapp_
The bundle reads these under the
meilisearch
config namespace; you typically won’t need a custompackages/*.yaml
.
- Register bundle in
config/bundles.php
return [
// ... Other bundles ...
Meiji\MeilisearchBundle\MeilisearchBundle::class => ['all' => true],
];
Configure model
- Add to DataObject implementation of
\Meiji\MeilisearchBundle\Model\DocumentInterface
via UI declaration, or code
use Meiji\MeilisearchBundle\Model\DocumentInterface as MeilisearchDocumentInterface;
// ...
class Product extends \Pimcore\Model\DataObject\Product implements MeilisearchDocumentInterface
{
// ...
- Release interface functions
// Index UID of DataObject model. Keep in mind usable `prefix`, that will autoprepend to this result string
public static function getMeilisearchIndexUid(): string;
// it's must look like
public static function getMeilisearchIndexUid(): string
{
return 'product';
}
// Index Primary Key of DataObject index documents
public static function getMeilisearchIndexPrimaryKey(): string;
// it's must look like
public static function getMeilisearchIndexPrimaryKey(): string
{
return 'id';
}
// You need setup faceting settings for index: maxValuesPerFacet and sortFacetValuesBy
// @see official docs [settings/faceting](https://www.meilisearch.com/docs/reference/api/settings#faceting)
public static function getMeilisearchFaceting() : array
// it's must look like
public static function getMeilisearchFaceting(): array
{
return [
'maxValuesPerFacet' => 100,
'sortFacetValuesBy' => ['*' => 'alpha']
];
}
// Next code show example index configuration with default values. You can use this values or configured for your own rules
// @see official docs [settings](https://www.meilisearch.com/docs/reference/api/settings)
public static function getMeilisearchIndexDisplayedAttributes() : array
{
return ['*'];
}
public static function getMeilisearchIndexFilterableAttributes() : array
{
return [];
}
public static function getMeilisearchIndexSearchableAttributes() : array
{
return ['*'];
}
public static function getMeilisearchIndexSortableAttributes() : array
{
return [];
}
public function getMeilisearchIndexDistinctAttribute() : ?string
{
return null;
}
public static function getMeilisearchIndexTypoTolerance() : array
{
return [
"minWordSizeForTypos" => [
"oneTypo" => 5,
"twoTypos" => 9
],
"disableOnAttributes" => [
]
];
}
public static function getMeilisearchPagination() : array
{
return [
"maxTotalHits" => 1000
];
}
public static function getMeilisearchPagination() : array
{
return [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
];
}
// Define function that do data represent of DataObject for save in Meilisearch
public function getMeilisearchDocumentData() : ?array
{
return [
'id' => $this->getId()
// ... other calculated props
];
// or `return null;` for pass object index process
}
Example making a DataObject indexable
Implement Meiji\MeilisearchBundle\Model\DocumentInterface
on your DataObject class and provide both index settings (static) and document JSON (instance):
<?php
namespace App\Model\Product;
use Meiji\MeilisearchBundle\Model\DocumentInterface as MeiliDoc;
use Pimcore\Model\DataObject\Product as BaseProduct;
final class Product extends BaseProduct implements MeiliDoc
{
public static function getMeilisearchIndexUid(): string { return 'products'; }
public static function getMeilisearchIndexPrimaryKey(): string { return 'id'; }
public static function getMeilisearchIndexDisplayedAttributes(): array {
return ['id','sku','title','slug','price','currency','brand','categories','images','updatedAt'];
}
public static function getMeilisearchIndexFilterableAttributes(): array {
return ['brand','categories','price','currency','isPublished','language','region'];
}
public static function getMeilisearchIndexSearchableAttributes(): array {
return ['title','subtitle','description','sku','altCodes'];
}
public static function getMeilisearchIndexSortableAttributes(): array {
return ['price','updatedAt','popularity'];
}
public static function getMeilisearchIndexDistinctAttribute(): ?string { return 'sku'; }
public static function getMeilisearchIndexTypoTolerance(): array {
return [
'enabled' => true,
'minWordSizeForTypos' => ['oneTypo' => 4, 'twoTypos' => 8],
'disableOnWords' => [],
'disableOnAttributes' => ['sku'],
];
}
public static function getMeilisearchPagination(): array {
return [ 'maxTotalHits' => 20000 ];
}
public static function getMeilisearchRankingRules(): array {
return ['words','typo','proximity','attribute','sort','exact'];
}
public static function getMeilisearchFaceting(): array {
return [ 'maxValuesPerFacet' => 200 ];
}
public function getMeilisearchDocumentData(): ?array
{
if (!$this->isPublished()) { return null; }
return [
'id' => $this->getId(),
'sku' => $this->getSku(),
'title' => $this->getTitle(),
'subtitle' => $this->getSubtitle(),
'description'=> strip_tags((string)$this->getDescription()),
'slug' => $this->getSlug(),
'brand' => $this->getBrand()?->getName(),
'categories' => array_map(fn($c)=>$c->getKey(), (array)$this->getCategories()),
'price' => $this->getPrice(),
'currency' => $this->getCurrency(),
'images' => [$this->getImage()?->getFullPath()],
'language' => $this->getLanguage(),
'region' => $this->getRegion(),
'updatedAt' => (new \DateTimeImmutable('@'.$this->getModificationDate()))->format(DATE_ATOM),
];
}
}
Returning
null
fromgetMeilisearchDocumentData()
skips indexing for that object (useful for drafts/unpublished states).
Syncing indexes & documents
# Create/verify indexes (primary key + settings + faceting)
bin/console meilisearch:index-sync
# Bulk sync all documents (batched)
bin/console meilisearch:document-sync --batch-count=500
# Bulk sync a single class
bin/console meilisearch:document-sync --index-class=App\\Model\\Product\\Product
Automatic syncing happens on Pimcore events (create/update/delete) for classes implementing the interface.
Debug connection
meilisearch:debug
- show some connection and platform vars / or errors
$ bin/console meilisearch:debug
License
MIT © 2025 Meiji