PHPackages                             sandstorm/kissearch - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. sandstorm/kissearch

ActiveNeos-package[Search &amp; Filtering](/categories/search)

sandstorm/kissearch
===================

Sandstorm KISSearch - search Neos content in your existing database and extend it with custom entities

1.1.2(1mo ago)6187—0%1[2 issues](https://github.com/sandstorm/Sandstorm.KISSearch/issues)MITPHP

Since Feb 19Pushed 1mo ago7 watchersCompare

[ Source](https://github.com/sandstorm/Sandstorm.KISSearch)[ Packagist](https://packagist.org/packages/sandstorm/kissearch)[ RSS](/packages/sandstorm-kissearch/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (9)Dependencies (3)Versions (21)Used By (0)

KISSearch - Simple Full-Text Search for Neos
============================================

[](#kissearch---simple-full-text-search-for-neos)

Search with the power of SQL full text queries. No need for additional infrastructure like ElasticSearch or MySQLite. KISSearch works purely with your existing PostgreSQL, MariaDB or MySQL database.

Search configuration is more or less downwards compatible to Neos.SearchPlugin / SimpleSearch.

Supported Databases:

- MariaDB version &gt;= 10.6
- MySQL version &gt;= 8.0
- PostgreSQL -&gt; supported very soon

Supports Neos 9+

For Neos 8 use the prototype version from branch `neos8-prototype` (this is not actively maintained though).

Installation
------------

[](#installation)

```
composer require sandstorm/kissearch

```

TOC
---

[](#toc)

- [KISSearch - Simple Full-Text Search for Neos](#kissearch---simple-full-text-search-for-neos)
    - [Installation](#installation)
    - [TOC](#toc)
    - [Why KISSearch?](#why-kissearch)
    - [Neos Integration](#neos-integration)
        - [Features](#features)
    - [Brief Architecture Overview](#brief-architecture-overview)
- [Setup and Deployment](#setup-and-deployment)
    - [Deployment](#deployment)
    - [Refresh Search Dependencies](#refresh-search-dependencies)
- [generic Configuration API](#generic-configuration-api)
    - [SQL Schema migrations](#sql-schema-migrations)
    - [Query Configuration API](#query-configuration-api)
        - [Search Endpoints](#search-endpoints)
- [Execute a search query](#execute-a-search-query)
    - [Query parameters](#query-parameters)
    - [Limit input](#limit-input)
    - [Examples](#examples)
        - [PHP API (with Flow dependency injection)](#php-api-with-flow-dependency-injection)
        - [PHP API plain](#php-api-plain)
        - [Fusion API](#fusion-api)
        - [EEL Helpers](#eel-helpers)
            - [KISSearch.input()](#kissearchinput)
            - [KISSearch.search()](#kissearchsearch)
        - [Fusion Objects](#fusion-objects)
            - [Sandstorm.KISSearch:SearchInput](#sandstormkissearchsearchinput)
            - [Sandstorm.KISSearch:ExecuteSearchQuery (search)](#sandstormkissearchexecutesearchquery-search)
- [Sandstorm.KISSearch.Neos](#sandstormkissearchneos)
    - [Configuration](#configuration)
    - [Schema](#schema)
    - [Query](#query)
    - [NodeType search configuration](#nodetype-search-configuration)
        - [Mode: Extract text value into a single bucket.](#mode-extract-text-value-into-a-single-bucket)
        - [Mode: Extract HTML content into specific buckets.](#mode-extract-html-content-into-specific-buckets)
    - [search buckets &amp; score boost](#search-buckets--score-boost)
        - [Score Formular](#score-formular)
        - [Score Aggregator](#score-aggregator)
            - [Example: boost score for individual filters](#example-boost-score-for-individual-filters)
            - [Example: different](#example-different)
- [Extending KISSearch with own search results](#extending-kissearch-with-own-search-results)

Why KISSearch?
--------------

[](#why-kissearch)

- no additional infrastructure required (like ElasticSearch or MySQLite)
- no explicit full-text index building after changing nodes required, full-text indexes are up-to-date on INSERT or UPDATE
- easy to extend with additional search result types (f.e. tables from your database and/or custom flow entities like products, etc.)
- the API package provides generic search query and schema functionality
- the Neos package comes with Neos Content / Neos Documents as default result types
- search multiple sources with a single SQL query
- configure your "search endpoints" plug-and-play style
- good query performance due to full text indexing on database level
    - LIMITS: for now, I tested with 200k nodes which was a bit flaky tbh

Neos Integration
----------------

[](#neos-integration)

The `Sandstorm.KISSearch.Neos` package namespace implements the KISSearch API to provide full-text search for the Neos 9 ContentRepository. It is intended to be used standalone or in combination with other search sources (most likely your custom database entities).

Important note: KISSearch.Neos implements full-text search based on searching nodes in the content repository database. Those nodes are a tree structure.

The default NeosDocumentQuery implementation comes with a default behavior: When finding **content nodes**, they are aggregated to their closest parent document. A.k.a. your search should **not** result in content nodes but rather **document nodes** containing that content. The Idea: you always want to find a whole "web page" with a URL to render the search result.

Example node-tree:

```
 - Homepage (document)
   - BlogOverview (document)
     - BlogPage1 (document)      'default'
    ],
    filters: [
        'your-filter-id': new \Sandstorm\KISSearch\Api\Query\Configuration\ResultFilterConfiguration(
            filterIdentifier: 'your-filter-id',
            resultFilterReference: 'your-filter-ref-id',
            resultType: \Sandstorm\KISSearch\Api\Query\Model\SearchResultTypeName::fromString('your-result'),
            requiredSources: ['your-source-ref-id'],
            defaultParameters: [
                'workspace': \Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName::forLive()
            ],
            filterOptions: [
                  // ...
            ]
        )
    ],
    typeAggregators: [
        'your-result': new \Sandstorm\KISSearch\Api\Query\Configuration\TypeAggregatorConfiguration(
            resultTypeName: \Sandstorm\KISSearch\Api\Query\Model\SearchResultTypeName::fromString('your-result'),
            typeAggregatorRef: 'your-aggregator-ref-id'
            aggregatorOptions: [
                // ...
            ]
        )
    ]
);

```

Execute a search query
======================

[](#execute-a-search-query)

Executing a search query can be done in various ways:

- use the PHP API
- integrate the Fusion API
- call the shipped REST controller (TODO)
- print out the search query SQL and do whatever you feel like with it
- use the backend module
- use the backend search bar in the Document Tree
- use the shipped flow command

Query parameters
----------------

[](#query-parameters)

Each filter defines its set of parameters. Since you can add the same filter more than one time, all parameter names in the `SearchInput` must be **prefixed with the filter ID** in the following general form:

```
[FILTER-ID]__[PARAMETER-NAME]

```

-&gt; The "filter-specific" parameter name: parameter name, prefixed by Filter ID followed by two underscores.

See also some examples.

Limit input
-----------

[](#limit-input)

You need to specify **a limit** value for **each search result type** that is defined in your search endpoint. They are required for each defined result type.

There is also the possibility to add a global limit. This parameter is optional and defaults to the **sum of all result type specific limits**.

A global limit, **less than** the sum of the result type limits will result in a definitive hard limit to the overall result count. Less important items of any type are "cut off".

No global limit means: query the N most important items of each type.

If you only have one result type in your search endpoint, just leave out the global limit, since it is kind of redundant.

HINT: Currently, there are no plans to add **offset** parameters. ... who really looks at the second google result page at all? ;) but it's open for discussion ofc.

Examples
--------

[](#examples)

### PHP API (with Flow dependency injection)

[](#php-api-with-flow-dependency-injection)

Given, you configured a search endpoint with the ID `your-endpoint-id` in the Settings.yaml.

```
#[Scope('singleton')]
class YourSearchService {

    // constructor injection
    public function __construct(
        private readonly FlowSearchEndpoints $searchEndpoints,
        private readonly DatabaseTypeDetector $databaseTypeDetector,
        private readonly FlowCDIObjectInstanceProvider $instanceProvider,
        private readonly DoctrineDatabaseAdapterService $databaseAdapter
    ) {
    }

    /**
     * Your search API...
     *
     * @param string $query
     * @return \Sandstorm\KISSearch\Api\Query\Model\SearchResults
     */
    public function executeMyQuery(string $query): \Sandstorm\KISSearch\Api\Query\Model\SearchResults
    {
        $params = [
           // ...
        ];

        $resultLimits = [
            // ...
        ];

        // global limit
        $limit = 100;

        // ### 0. detect database type
        // may also be hard-coded in your project
        $databaseType = $this->databaseTypeDetector->detectDatabase();

        // ### 1. put user input into a SearchInput instance
        $input = new SearchInput(
            // the search query input
            $query,
            // the additional parameters, f.e. Neos workspace, etc.
            // may override default parameters configured in the endpoint
            $params,
            // limit per result type, f.e. ['neos-document' => 20, 'product' => 40]
            $resultLimits,
            // (optional) global limit of all merged results
            // If not given, the sum of all limits per result type is used
            $limit
        );

        // ### 2. load your endpoint configuration
        // In this case, we use the shipped Flow service.
        $searchEndpointConfiguration = $this->searchEndpoints
            ->getEndpointConfiguration('your-endpoint-id');

        // ### 3. create the search query
        $searchQuery = SearchQuery::create(
            $databaseType,
            $this->instanceProvider,
            $searchEndpointConfiguration,
            // override default query options configured in the endpoint
            $queryOptionsArray
        );

        // ### 4. execute the search query
        $results = QueryTool::executeSearchQuery(
            $databaseType,
            $searchQuery,
            $input,
            $this->databaseAdapter
        );

        return $results;
    }

```

### PHP API plain

[](#php-api-plain)

Given, you use doctrine and have access to the `EntityManagerInterface` instance. If you don't use doctrine, implement your own `SearchQueryDatabaseAdapterInterface`.

```
final readonly class YourSearchService {

    public static function executeSearch(
        string $query,
        \Doctrine\ORM\EntityManagerInterface $entityManager
    ): \Sandstorm\KISSearch\Api\Query\Model\SearchResults
    {
        $params = [
           // ...
        ];

        $resultLimits = [
            // ...
        ];

        // global limit
        $limit = 100;

        // ### 0. create adapter instance
        $databaseType = \Sandstorm\KISSearch\Api\DBAbstraction\DatabaseType::MARIADB;
        $databaseAdapter = new \Sandstorm\KISSearch\Api\DBAbstraction\DoctrineDatabaseAdapter($entityManager);

        // ### 1. put user input into a SearchInput instance
        $input = new SearchInput(
            // the search query input
            $query,
            // the additional parameters, f.e. Neos workspace, etc.
            // may override default parameters configured in the endpoint
            $params,
            // limit per result type, f.e. ['neos-document' => 20, 'product' => 40]
            $resultLimits,
            // (optional) global limit of all merged results
            // If not given, the sum of all limits per result type is used
            $limit
        );

        // ### 2. create your endpoint configuration (this could also be done globally elsewhere)
        $searchEndpointConfiguration = new \Sandstorm\KISSearch\Api\Query\Configuration\SearchEndpointConfiguration(
            endpointIdentifier: 'some-neos-endpoint',
            queryOptions: [
                'contentRepository' => 'default'
            ],
            filters: [
                'neos': new \Sandstorm\KISSearch\Api\Query\Configuration\ResultFilterConfiguration(
                    filterIdentifier: 'neos',
                    resultFilterReference: 'neos-document-filter',
                    resultType: \Sandstorm\KISSearch\Api\Query\Model\SearchResultTypeName::fromString('neos-document'),
                    requiredSources: ['neos-content'],
                    defaultParameters: [
                        'workspace': \Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName::forLive()
                    ]
                )
            ],
            typeAggregators: [
                'neos-document': new \Sandstorm\KISSearch\Api\Query\Configuration\TypeAggregatorConfiguration(
                    resultType: \Sandstorm\KISSearch\Api\Query\Model\SearchResultTypeName::fromString('neos-document'),
                    typeAggregatorRef: 'neos-document-aggregator',
                    aggregatorOptions: [
                        'contentRepository' => 'default'
                    ]
                )
            ]
        )

        // ### 3. create your instance provider
        // (if you use plain PHP, you need to instantiate the query builder classes by yourself)

        // We re-use the $neosDocumentQuery instance, since it implements both the filter and aggregator.
        // This is optional, though. The classes are all stateless.
        $neosDocumentQuery = new \Sandstorm\KISSearch\Neos\Query\NeosDocumentQuery();
        $instanceProvider = new \Sandstorm\KISSearch\Api\FrameworkAbstraction\DefaultQueryObjectInstanceProvider(
            searchSourceInstances: [
                'neos-content': new \Sandstorm\KISSearch\Neos\Query\NeosContentSource()
            ],
            resultFilterInstances: [
                'neos-document-filter': $neosDocumentQuery
            ],
            typeAggregatorInstances: [
                'neos-document-aggregator': $neosDocumentQuery
            ]
        );

        // ### 4. create the search query
        $searchQuery = SearchQuery::create(
            $databaseType,
            $instanceProvider,
            $searchEndpointConfiguration,
            // override default query options configured in the endpoint
            $queryOptionsArray
        );

        // ### 5. execute the search query
        $results = QueryTool::executeSearchQuery(
            $databaseType,
            $searchQuery,
            $input,
            $databaseAdapter
        );

        return $results;
    }

}

```

### Fusion API

[](#fusion-api)

There are two Fusion objects and EEL Helpers that you can use:

### EEL Helpers

[](#eel-helpers)

#### KISSearch.input()

[](#kissearchinput)

Creates an instance of `SearchInput` which is required to fire a search query.

```
input = ${KISSearch.input('your search query', ['neos__workspace' => 'live'], ['neos-document' => 20], 20)}

```

- Helper name: `KISSearch`
- Function name: `input`
- parameters:
    - searchQuery (string) -&gt; the search query input
    - parameters (array&lt;string, mixed&gt;) -&gt; query parameter values
    - resultTypeLimits (array&lt;string, int&gt;) -&gt; limits per result type
    - limit (int, optional) -&gt; global limit

#### KISSearch.search()

[](#kissearchsearch)

Fires a search query.

```
searchResults = ${KISSearch.search('your-endpoint', props.input, ['contentRepository' => 'default'], null)}

```

- Helper name: `KISSearch`
- Function name: `search`
- parameters:
    - endpointId (string) -&gt; which search endpoint to use
    - input (SearchInput) -&gt; the user input
    - queryOptions (array&lt;string, mixed&gt;) -&gt; override query options from endpoint here
    - databaseType (string of enum DatabaseType, optional) -&gt; set explicit database type, auto-detected if null

### Fusion Objects

[](#fusion-objects)

The Fusion objects basically wrap the EEL Helper calls.

#### Sandstorm.KISSearch:SearchInput

[](#sandstormkissearchsearchinput)

Wrapper for EEL Helper `KISSearch.input()`

Usage:

```
input = Sandstorm.KISSearch:SearchInput {
    # the search query user input
    searchQuery = ${request.arguments.q}

    # query parameter values
    parameters {
        # the prefix 'neos__' is the filter identifier configured in the search endpoint
        neos__workspace = 'live'
        neos__dimension_values {
            language = 'en_US'
        }
    }

    # limit per result type
    resultTypeLimits {
        # the result type name 'neos-document' is configured in the search endpoint
        'neos-document' = 20
    }

    # optional global limit parameter
    limit = null
}

```

Evaluates into an instance of `SearchInput`.

#### Sandstorm.KISSearch:ExecuteSearchQuery (search)

[](#sandstormkissearchexecutesearchquery-search)

Wrapper for EEL Helper `KISSearch.search()`

```

prototype(Vendor:SearchResultList) < prototype(Neos.Fusion:Component) {
    searchResults = Sandstorm.KISSearch:ExecuteSearchQuery {
        # reference the default endpoint
        endpoint = 'default-live'

        # see doc for search input
        input = Sandstorm.KISSearch:SearchInput {
            # ...
        }

        # override default query options here
        queryOptions {
            # ...
        }

        # set explicit database type or auto-detect (default)
        databaseType = null
    }

    # IMPORTANT: please cache your server-side search result rendering!!!
    # for an example, see the ExampleDefaultQuery prototype
    @cache {
        mode = 'dynamic'
        # see the example query
        # ...
    }

    renderer = afx`

                    {item.title}

    `
}

```

A full Fusion API example can be seen here: [ExampleDefaultQuery.fusion](Resources/Private/Fusion/_Examples/ExampleDefaultQuery.fusion)

Sandstorm.KISSearch.Neos
========================

[](#sandstormkissearchneos)

The package namespace `Sandstorm.KISSearch.Neos` is the implementation of the KISSearch Core API, which provides full-text search for the Neos **ContentRepository** (Neos 9 ESCR).

Configuration
-------------

[](#configuration)

The configuration that is specific for the Neos integration lives under the Settings.yaml namespace `Sandstorm.KISSearch.Neos`

```
Sandstorm:
  KISSearch:
    Neos:

      # Integration of KISSearch in the Neos Backend search bar.
      backendSearch:
        # Enable/Disable the feature. If disabled, the original search endpoint is used
        enabled: true
        # Which KISSearch endpoint to use for the backend search.
        # Expects a filter with ID "neos"
        endpoint: neos-backend

      # Auto refresh dependencies on node publish events
      refresher:
        autoRefreshEnabled: true
```

Enable or disable the backend search bar integration and the auto-refresher on node publish. You also can customize the search endpoint here, or you change the configuration of the default endpoint `neos-backend`directly.

Schema
------

[](#schema)

The package brings a default schema: `neos-default-cr`.

IMPORTANT: each ContentRepository can only have **one single** KISSearch schema.

Apply/remove/re-create the schema via flow command.

Query
-----

[](#query)

Supported filter parameters for the Neos result filter:

Parameter nameDescriptionSupported typeExampleworkspacethe Neos workspace namestring, `WorkspaceName``"my-workspace"`, `WorkspaceName::forLive()`dimension\_valuesthe Neos dimension valuesarray&lt;string, string&gt;`['language' => 'en']`root\_nodethe root node for a sub-tree searchstring, `NodeAggregateId`some UUIDsite\_nodenode name(s) of the site nodestring, `NodeName`, array, array&lt;`NodeName`&gt;`neosdemo`excluded\_site\_nodeexclude this site(s)string, `NodeName`, array, array&lt;`NodeName`&gt;`['site-a', 'site-b']`content\_node\_typesfilter content NodeTypes (f.e. only search in your Headline nodes)string, `NodeTypeName`, array, array&lt;`NodeTypeName`&gt;`"Vendor.Package:Headline"`document\_node\_typesfilter for document NodeTypesstring, `NodeTypeName`, array, array&lt;`NodeTypeName`&gt;`["Vendor.Package:Document.BlogPage", "Vendor.Package:Document.ArticlePage"]`inherited\_content\_node\_typefilter content NodeTypes with inheritance logic (a node matches if it inherits the given NodeType)string, `NodeTypeName``"Vendor.Package:AbstractSearchableContent"`inherited\_document\_node\_typefilter document NodeTypes with inheritance logic (a node matches if it inherits the given NodeType)string, `NodeTypeName``"Vendor.Package:Document.AbstractSearchablePage"`NodeType search configuration
-----------------------------

[](#nodetype-search-configuration)

Nodes are found by KISSearch by indexing node properties based on the yaml configuration in the NodeType yaml. For properties to be indexed, the property value needs to be extracted. How this is performed is configured in two main ways:

1. indexing a string value node property (extract into single bucket)
2. indexing an HTML-based node property (extract HTML)

### Mode: Extract text value into a single bucket.

[](#mode-extract-text-value-into-a-single-bucket)

The whole property value is put into a single bucket. Intent to be used for **text based** node properties that are not wrapped by HTML. Most likely, this is true for inspector properties that are text or textarea fields. For rich text edited properties, use the HTML extraction mode instead.

```
'Vendor:My.NodeType':
  properties:
    'myProperty':
      search:
        # possible values: 'critical', 'major', 'normal', 'minor'
        bucket: 'major'
```

### Mode: Extract HTML content into specific buckets.

[](#mode-extract-html-content-into-specific-buckets)

The property contains HTML, most likely produced by the rich text editor.

```
'Vendor:My.Headline':
  superTypes:
    'Neos.NodeTypes.BaseMixins:TextMixin': true
  properties:
    'text':
      search:
        # possible values: 'all', 'critical', 'major', 'normal', 'minor'
        # or an array containing multiple values of: 'critical', 'major', 'normal', 'minor'
        extractHtmlInto: [ 'critical', 'major' ]
```

This package is compatible to the fulltext extraction configuration used by Neos.SearchPlugin / Neos.SimpleSearch / Neos.ElasticSearch.

Here you can specify a list of buckets. Text content inside different headline tags are sorted into more important buckets.

BucketExtracted to indexFiltered outcriticalh1, h2h3, h4, h5, h6, text contentmajorh3, h4, h5, h6h1, h2, text contentnormaltext contenth1, h2, h3, h4, h5, h6minortext contenth1, h2, h3, h4, h5, h6Note, that minor and normal have the same fulltext extraction behavior. They just result in different scores.

Use the shortcut `all` for a full-spectrum extraction into all buckets.

```
'Vendor:My.ExampleText':
  superTypes:
    'Neos.NodeTypes.BaseMixins:TextMixin': true
  properties:
    'text':
      search:
        # possible values: 'all', 'critical', 'major', 'normal', 'minor'
        # or an array containing multiple values of: 'critical', 'major', 'normal', 'minor'
        #    'all' is not supported as array value
        #    'all' is equivalent to ['critical', 'major', 'normal', 'minor']
        extractHtmlInto: 'all'
```

search buckets &amp; score boost
--------------------------------

[](#search-buckets--score-boost)

So-called "search buckets" are different stores for full-text searchable content that have different weights in score. A query always searches **all** buckets and calculates an overall score for the result item, based on wich bucket(s) matched.

In detail:

1. The search sources emit rows that contain a column for each bucket score.
2. The result filter then calculates the overall score, based on a configurable formular.
3. The type aggregator finally decides, how aggregate the score for multiple items that gets grouped to a single result (f.e. take the **max** value).

There are four buckets in the Neos KISSearch implementation:

- critical
- major
- normal
- minor

Those buckets can be accessed in the score formular.

### Score Formular

[](#score-formular)

If you customize the formular, it is advised to represent the bucket names in the actual math (critical should boost most, etc...).

The default score formular **SQL expression** is:

```
(20 * n.score_bucket_critical) + (5 * n.score_bucket_major) + (1 * n.score_bucket_normal) + (0.5 * n.score_bucket_minor)

```

Note: The table alias `n` accesses items emitted by the search source. It stands for "node". The column names for the individual bucket scores are `score_bucket_critical`, `score_bucket_major`, `score_bucket_normal` and `score_bucket_minor`.

The default formular is both compatible to MariaDB/MySQL and PostgreSQL.

If you want to customize the **Score Formular**, you can set this via query or filter option in the search endpoint configuration. You can call database-specific functions here as well, as long as your expression returns a **scalar number** value.

### Score Aggregator

[](#score-aggregator)

Finally, when the type aggregator combines multiple matches from filters to a single result item, the scores of those hits are also aggregated. More specific: the Neos filter finds general nodes (Content and Document nodes) and the aggregator groups by closest parent document. Therefore, the aggregator needs to know how to combine the scores of multiple nodes to a single document node.

The default aggregator **SQL expression** is:

```
max(r.score)

```

Note: The table alias `r` accesses items emitted by the result filters. It stands for "result". The column name is `score` and is accessed in a `GROUP BY` select. If you want to customize this behavior via query option, make sure your SQL expression is a **SQL aggregate function** that returns a single **scalar number**.

Examples:

- sum up all result hits: `sum(r.score)` -&gt; The more nodes match below the document, the more important the document becomes.
- take the max score / default: `max(r.score)` -&gt; The most important node below the document sets the overall score.
- f.e. average ...

Query option names are:

Database typeMeaningOption NameWhere to define?MariaDB / MySQLScore Formular`mariadb_scoreFormular`query options (for all filers), filter options for a single filterPostgreSQLScore Formular`pgsql_scoreFormular`query options (for all filers), filter options for a single filterMariaDB / MySQLScore Aggregator`mariadb_scoreAggregator`query options (for all aggregators), aggregator options for single aggregatorPostgreSQLScore Aggregator`pgsql_scoreAggregator`query options (for all aggregators), aggregator options for single aggregator(there is multi-DB-support, in case you want to support both DB types)

```
Sandstorm:
  KISSearch:
    # ...
    query:
      endpoints:
        # ...
        'your-endpoint':
          queryOptions:
            # Here, we want a cubic/square score sum, to boost critical even higher.
            # Also, in this example we do not consider the minor bucket at all.
            'mariadb_scoreFormular': 'power(n.score_bucket_critical, 3) + power(n.score_bucket_major, 2) + n.score_bucket_normal'
            # The more nodes match, the more important the document gets.
            # Also, here we multiply by a constant value, to even out the score with other search results ...
            'mariadb_scoreAggregator': 'sum(n.score) * 0.5'
            # ...
```

or if you want to boost the individual score of filters:

#### Example: boost score for individual filters

[](#example-boost-score-for-individual-filters)

```
Sandstorm:
  KISSearch:
    # ...
    query:
      endpoints:
        # ...
        'your-endpoint':
          # example: boost the score for a specific content dimension
          filters:
            # this filter uses the default score
            'neos-en-uk':
              filter: 'neos-document-filter'
              sources:
                - neos-content-source
              defaultParameters:
                dimension_values:
                  language: en_UK
            'neos-en-us':
              filter: 'neos-document-filter'
              sources:
                - neos-content-source
              defaultParameters:
                dimension_values:
                  language: en_US
              options:
                # We use the default formular, multiplied by 2 -> so we boost US content over UK content
                'mariadb_scoreFormular': '2 * ((20 * n.score_bucket_critical) + (5 * n.score_bucket_major) + (1 * n.score_bucket_normal) + (0.5 * n.score_bucket_minor))'
          # ...
```

#### Example: different

[](#example-different)

TODO more examples

For now, just to mention some:

- Search for the 10 most important blog posts and 20 most important Product pages
    - -&gt; this can be done with two different filters + different search result types
    - then set the result limit per type
-

Extending KISSearch with own search results
===========================================

[](#extending-kissearch-with-own-search-results)

TODO indepth guide coming soon

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance83

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 89.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~32 days

Recently: every ~49 days

Total

9

Last Release

53d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2ced0d63cfdae881c32128c7f66451a013d3e24d9eed210d6a846b6d8e95fa3b?d=identicon)[sandstorm](/maintainers/sandstorm)

---

Top Contributors

[![erickloss](https://avatars.githubusercontent.com/u/16836464?v=4)](https://github.com/erickloss "erickloss (35 commits)")[![skurfuerst](https://avatars.githubusercontent.com/u/190777?v=4)](https://github.com/skurfuerst "skurfuerst (2 commits)")[![t-heuser](https://avatars.githubusercontent.com/u/53174153?v=4)](https://github.com/t-heuser "t-heuser (2 commits)")

### Embed Badge

![Health badge](/badges/sandstorm-kissearch/health.svg)

```
[![Health](https://phpackages.com/badges/sandstorm-kissearch/health.svg)](https://phpackages.com/packages/sandstorm-kissearch)
```

###  Alternatives

[ruflin/elastica

Elasticsearch Client

2.3k50.4M202](/packages/ruflin-elastica)[opensearch-project/opensearch-php

PHP Client for OpenSearch

15024.3M65](/packages/opensearch-project-opensearch-php)[flowpack/searchplugin

Plugin for search integration via content node

24257.5k1](/packages/flowpack-searchplugin)[flowpack/elasticsearch-contentrepositoryadaptor

This package provides functionality for using Elasticsearch on top of Neos.ContentRepository.Search

43385.8k8](/packages/flowpack-elasticsearch-contentrepositoryadaptor)[neos/neos-base-distribution

Neos Base Distribution

4464.9k](/packages/neos-neos-base-distribution)[neos/content-repository-search

Common code and interface for a Neos CR search implementation

10413.3k4](/packages/neos-content-repository-search)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
