meiji/pimcore-meilisearch-bundle

Pimcore Meilisearch Bundle

Installs: 178

Dependents: 0

Suggesters: 0

Security: 0

Type:pimcore-bundle

pkg:composer/meiji/pimcore-meilisearch-bundle

v0.4.3 2025-05-20 11:52 UTC

This package is not auto-updated.

Last update: 2025-10-02 09:31:34 UTC


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

  1. On your Pimcore 11 root project
$ composer require meiji/pimcore-meilisearch-bundle
  1. 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 custom packages/*.yaml.

  1. Register bundle in config/bundles.php
return [
    // ... Other bundles ...
    Meiji\MeilisearchBundle\MeilisearchBundle::class => ['all' => true],
];

Configure model

  1. 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
{
// ...
  1. 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 from getMeilisearchDocumentData() 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