diff --git a/CHANGELOG.md b/CHANGELOG.md
index cef1e203d..acbb038e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,10 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Solarium\Core\Query\Result\QueryType::getStatus() and getQueryTime(), inherited by all Solarium\QueryType Results
- Solarium\QueryType\CoreAdmin\Result\Result::getInitFailureResults()
- Solarium\QueryType\Ping\Result::getPingStatus() and getZkConnected()
+- Fluent interface methods for adding/removing excludes in Solarium\Component\Facet\AbstractFacet
+- Fluent interface methods for adding/removing terms in Solarium\Component\Facet\Field
### Fixed
- JSON serialization of arrays with non-consecutive indices in multivalue fields
- PHP 8.2 deprecations
+- Handling of escaped literal commas in local parameters for faceting
### Changed
- Update queries use the JSON request format by default
@@ -27,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Solarium\QueryType\Server\Collections\Result\CreateResult::getStatus(), use getCreateStatus() instead
- Solarium\QueryType\Server\Collections\Result\DeleteResult::getStatus(), use getDeleteStatus() instead
- Solarium\QueryType\Server\Collections\Result\ReloadResult::getStatus(), use getReloadStatus() instead
+- LocalParameters::removeTerms(), use removeTerm() instead
## [6.2.8]
diff --git a/docs/queries/select-query/building-a-select-query/components/facetset-component/facet-field.md b/docs/queries/select-query/building-a-select-query/components/facetset-component/facet-field.md
index df17a5895..baefe24d2 100644
--- a/docs/queries/select-query/building-a-select-query/components/facetset-component/facet-field.md
+++ b/docs/queries/select-query/building-a-select-query/components/facetset-component/facet-field.md
@@ -7,9 +7,10 @@ The options below can be set as query option values, but also by using the set/g
Only the facet-type specific options are listed. See [FacetSet component](facetset-component.md) for the options shared by all facet types.
-| Name | Type | Default value | Description |
-|-------|--------|---------------|--------------------------------|
-| field | string | id | The index field for the facet. |
+| Name | Type | Default value | Description |
+|-------------|--------|---------------|-----------------------------------------------------------------------------------------------------|
+| field | string | id | The index field for the facet. |
+| local_terms | string | null | Limit field facet to specified terms. Specify a comma separated list. Use `\,` for a literal comma. |
||
Example
diff --git a/docs/queries/select-query/building-a-select-query/components/facetset-component/facetset-component.md b/docs/queries/select-query/building-a-select-query/components/facetset-component/facetset-component.md
index 9e9ac8fa6..14592a339 100644
--- a/docs/queries/select-query/building-a-select-query/components/facetset-component/facetset-component.md
+++ b/docs/queries/select-query/building-a-select-query/components/facetset-component/facetset-component.md
@@ -32,10 +32,10 @@ Standard facet options
All facet types available in the facetset extend a base class that offers a standard set of options. The following options are available for ALL facet types:
-| Name | Type | Default value | Description |
-|----------|--------|---------------|-------------------------------------------------------------|
-| key | string | null | Key to identify the facet (mandatory) |
-| excludes | string | null | Add one or multiple filterquery tags to exclude for a facet |
+| Name | Type | Default value | Description |
+|---------------|--------|---------------|--------------------------------------------------------------|
+| local_key | string | null | Key to identify the facet (mandatory). |
+| local_exclude | string | null | Add one or multiple filterquery tags to exclude for a facet. |
||
Pivot facet options
diff --git a/examples/2.1.5.1.1.1-facet-field-filters.php b/examples/2.1.5.1.1.1-facet-field-filters.php
index 01ac8ce62..61f43c8b5 100644
--- a/examples/2.1.5.1.1.1-facet-field-filters.php
+++ b/examples/2.1.5.1.1.1-facet-field-filters.php
@@ -29,12 +29,16 @@
$facetSet->createFacetField('electronicsAndMore')->setField('cat')->setMatches('electronics.+');
// setExcludeTerms takes a comma separated list of terms to exclude from the facet
-// escape the comma for a literal match e.g. 'yes\, this facet with be excluded'
+// escape the comma for a literal match e.g. 'yes\, this term will be excluded'
$facetSet->createFacetField('electronicsExclude')->setField('cat')->setExcludeTerms('electronics,music');
// all three restriction types can also be used on the facetset as a whole and will affect all (non json) facets
// e.g. $facetSet->setExcludeTerms('search');
+// setTerms takes a comma separated list of terms to exclude from the facet
+// escape the comma for a literal match e.g. 'yes\, this term will be included'
+$facetSet->createFacetField('electronicsTerms')->setField('cat')->setTerms('electronics,music');
+
// this executes the query and returns the result
$resultset = $client->select($query);
@@ -76,4 +80,11 @@
echo $value . ' [' . $count . ']
';
}
+// display facet counts
+echo '
Facet counts for field "cat"; terms limited to "electronics" and "music":
';
+$facet = $resultset->getFacetSet()->getFacet('electronicsTerms');
+foreach ($facet as $value => $count) {
+ echo $value . ' [' . $count . ']
';
+}
+
htmlFooter();
diff --git a/examples/2.1.5.1.1.2-facet-field-excludes.php b/examples/2.1.5.1.1.2-facet-field-excludes.php
index 20085117c..02d61914f 100644
--- a/examples/2.1.5.1.1.2-facet-field-excludes.php
+++ b/examples/2.1.5.1.1.2-facet-field-excludes.php
@@ -22,7 +22,7 @@
$facetSet->createFacetField('category')->setField('cat');
// addExcludes will exclude filters by tag
-$facetSet->createFacetField('unfiltered')->setField('cat')->getLocalParameters()->addExcludes(['electronics']);
+$facetSet->createFacetField('unfiltered')->setField('cat')->addExcludes(['electronics']);
// this executes the query and returns the result
$resultset = $client->select($query);
diff --git a/examples/2.1.5.1.4.3-facet-range-excludes.php b/examples/2.1.5.1.4.3-facet-range-excludes.php
index f587a0e62..fe36ea8b2 100644
--- a/examples/2.1.5.1.4.3-facet-range-excludes.php
+++ b/examples/2.1.5.1.4.3-facet-range-excludes.php
@@ -32,7 +32,7 @@
$facet->setStart(1);
$facet->setGap(100);
$facet->setEnd(1000);
-$facet->getLocalParameters()->addExcludes(['budget']);
+$facet->addExcludes(['budget']);
// this executes the query and returns the result
$resultset = $client->select($query);
diff --git a/src/Component/Facet/AbstractFacet.php b/src/Component/Facet/AbstractFacet.php
index 639652458..86f472207 100644
--- a/src/Component/Facet/AbstractFacet.php
+++ b/src/Component/Facet/AbstractFacet.php
@@ -47,10 +47,94 @@ public function getKey(): ?string
*
* @return self Provides fluent interface
*/
- public function setKey(string $key): FacetInterface
+ public function setKey(string $key): self
{
$this->getLocalParameters()->setKey($key);
return $this;
}
+
+ /**
+ * Add an exclude tag.
+ *
+ * @param string $exclude
+ *
+ * @return self Provides fluent interface
+ */
+ public function addExclude(string $exclude)
+ {
+ $this->getLocalParameters()->setExclude($exclude);
+
+ return $this;
+ }
+
+ /**
+ * Add multiple exclude tags.
+ *
+ * @param array|string $excludes array or string with comma separated exclude tags
+ *
+ * @return self Provides fluent interface
+ */
+ public function addExcludes($excludes)
+ {
+ if (\is_string($excludes)) {
+ $excludes = preg_split('/(?getLocalParameters()->addExcludes($excludes);
+
+ return $this;
+ }
+
+ /**
+ * Set the list of exclude tags.
+ *
+ * This overwrites any existing exclude tags.
+ *
+ * @param array|string $excludes
+ *
+ * @return self Provides fluent interface
+ */
+ public function setExcludes($excludes)
+ {
+ $this->clearExcludes()->addExcludes($excludes);
+
+ return $this;
+ }
+
+ /**
+ * Remove a single exclude tag.
+ *
+ * @param string $exclude
+ *
+ * @return self Provides fluent interface
+ */
+ public function removeExclude(string $exclude)
+ {
+ $this->getLocalParameters()->removeExclude($exclude);
+
+ return $this;
+ }
+
+ /**
+ * Remove all exclude tags.
+ *
+ * @return self Provides fluent interface
+ */
+ public function clearExcludes()
+ {
+ $this->getLocalParameters()->clearExcludes();
+
+ return $this;
+ }
+
+ /**
+ * Get the list of exclude tags.
+ *
+ * @return array
+ */
+ public function getExcludes(): array
+ {
+ return $this->getLocalParameters()->getExcludes();
+ }
}
diff --git a/src/Component/Facet/AbstractRange.php b/src/Component/Facet/AbstractRange.php
index 508dd8702..e5b18a8e7 100644
--- a/src/Component/Facet/AbstractRange.php
+++ b/src/Component/Facet/AbstractRange.php
@@ -9,8 +9,6 @@
namespace Solarium\Component\Facet;
-use Solarium\Core\Configurable;
-
/**
* Facet range.
*
@@ -272,11 +270,13 @@ public function getInclude(): array
/**
* @param \Solarium\Component\Facet\Pivot|array $pivot
*
- * @return \Solarium\Core\Configurable
+ * @return self Provides fluent interface
*/
- public function setPivot($pivot): Configurable
+ public function setPivot($pivot): self
{
- return $this->setOption('pivot', $pivot);
+ $this->setOption('pivot', $pivot);
+
+ return $this;
}
/**
diff --git a/src/Component/Facet/FacetInterface.php b/src/Component/Facet/FacetInterface.php
index f8cb0e964..b4dd14963 100644
--- a/src/Component/Facet/FacetInterface.php
+++ b/src/Component/Facet/FacetInterface.php
@@ -37,7 +37,59 @@ public function getKey(): ?string;
*
* @param string $key
*
- * @return self
+ * @return self Provides fluent interface
*/
- public function setKey(string $key): self;
+ public function setKey(string $key);
+
+ /**
+ * Add an exclude tag.
+ *
+ * @param string $exclude
+ *
+ * @return self Provides fluent interface
+ */
+ public function addExclude(string $exclude);
+
+ /**
+ * Add multiple exclude tags.
+ *
+ * @param array|string $excludes array or string with comma separated exclude tags
+ *
+ * @return self Provides fluent interface
+ */
+ public function addExcludes($excludes);
+
+ /**
+ * Set the list of exclude tags.
+ *
+ * This overwrites any existing exclude tags.
+ *
+ * @param array|string $excludes
+ *
+ * @return self Provides fluent interface
+ */
+ public function setExcludes($excludes);
+
+ /**
+ * Remove a single exclude tag.
+ *
+ * @param string $exclude
+ *
+ * @return self Provides fluent interface
+ */
+ public function removeExclude(string $exclude);
+
+ /**
+ * Remove all exclude tags.
+ *
+ * @return self Provides fluent interface
+ */
+ public function clearExcludes();
+
+ /**
+ * Get the list of exclude tags.
+ *
+ * @return array
+ */
+ public function getExcludes();
}
diff --git a/src/Component/Facet/Field.php b/src/Component/Facet/Field.php
index 9f2df10d2..8a1928530 100644
--- a/src/Component/Facet/Field.php
+++ b/src/Component/Facet/Field.php
@@ -62,4 +62,88 @@ public function getField(): ?string
{
return $this->getOption('field');
}
+
+ /**
+ * Add a term.
+ *
+ * @param string $term
+ *
+ * @return self Provides fluent interface
+ */
+ public function addTerm(string $term): self
+ {
+ $this->getLocalParameters()->setTerm($term);
+
+ return $this;
+ }
+
+ /**
+ * Add multiple terms.
+ *
+ * @param array|string $terms array or string with comma separated terms
+ *
+ * @return self Provides fluent interface
+ */
+ public function addTerms($terms): self
+ {
+ if (\is_string($terms)) {
+ $terms = preg_split('/(?getLocalParameters()->addTerms($terms);
+
+ return $this;
+ }
+
+ /**
+ * Set the list of terms.
+ *
+ * This overwrites any existing terms.
+ *
+ * @param array|string $terms
+ *
+ * @return self Provides fluent interface
+ */
+ public function setTerms($terms): self
+ {
+ $this->clearTerms()->addTerms($terms);
+
+ return $this;
+ }
+
+ /**
+ * Remove a single term.
+ *
+ * @param string $term
+ *
+ * @return self Provides fluent interface
+ */
+ public function removeTerm(string $term): self
+ {
+ $this->getLocalParameters()->removeTerm($term);
+
+ return $this;
+ }
+
+ /**
+ * Remove all terms.
+ *
+ * @return self Provides fluent interface
+ */
+ public function clearTerms(): self
+ {
+ $this->getLocalParameters()->clearTerms();
+
+ return $this;
+ }
+
+ /**
+ * Get the list of terms.
+ *
+ * @return array
+ */
+ public function getTerms(): array
+ {
+ return $this->getLocalParameters()->getTerms();
+ }
}
diff --git a/src/Component/Facet/FieldValueParametersInterface.php b/src/Component/Facet/FieldValueParametersInterface.php
index 3b0ebc260..4687f9a39 100644
--- a/src/Component/Facet/FieldValueParametersInterface.php
+++ b/src/Component/Facet/FieldValueParametersInterface.php
@@ -217,7 +217,7 @@ public function getMethod(): ?string;
*
* @return self Provides fluent interface
*/
- public function setEnumCacheMinimumDocumentFrequency($frequency);
+ public function setEnumCacheMinimumDocumentFrequency(int $frequency);
/**
* Get the minimum document frequency for which the filterCache should be used.
@@ -229,7 +229,7 @@ public function getEnumCacheMinimumDocumentFrequency(): ?int;
/**
* Set to true to cap facet counts by 1.
*
- * @param int $exists
+ * @param bool $exists
*
* @return self Provides fluent interface
*/
@@ -267,7 +267,7 @@ public function getExcludeTerms(): ?string;
*
* @return self Provides fluent interface
*/
- public function setOverrequestCount($count);
+ public function setOverrequestCount(int $count);
/**
* Get the facet overrequest count.
@@ -283,7 +283,7 @@ public function getOverrequestCount(): ?int;
*
* @return self Provides fluent interface
*/
- public function setOverrequestRatio($ratio);
+ public function setOverrequestRatio(float $ratio);
/**
* Get the facet overrequest ratio.
diff --git a/src/Component/Facet/FieldValueParametersTrait.php b/src/Component/Facet/FieldValueParametersTrait.php
index d270e105b..9ac04b167 100644
--- a/src/Component/Facet/FieldValueParametersTrait.php
+++ b/src/Component/Facet/FieldValueParametersTrait.php
@@ -267,7 +267,7 @@ public function getMethod(): ?string
*
* @return self Provides fluent interface
*/
- public function setEnumCacheMinimumDocumentFrequency($frequency): self
+ public function setEnumCacheMinimumDocumentFrequency(int $frequency): self
{
$this->setOption('enum.cache.minDf', $frequency);
@@ -287,7 +287,7 @@ public function getEnumCacheMinimumDocumentFrequency(): ?int
/**
* Set to true to cap facet counts by 1.
*
- * @param int $exists
+ * @param bool $exists
*
* @return self Provides fluent interface
*/
@@ -341,9 +341,11 @@ public function getExcludeTerms(): ?string
*
* @return self Provides fluent interface
*/
- public function setOverrequestCount($count): self
+ public function setOverrequestCount(int $count): self
{
- return $this->setOption('overrequest.count', $count);
+ $this->setOption('overrequest.count', $count);
+
+ return $this;
}
/**
@@ -363,9 +365,11 @@ public function getOverrequestCount(): ?int
*
* @return self Provides fluent interface
*/
- public function setOverrequestRatio($ratio): self
+ public function setOverrequestRatio(float $ratio): self
{
- return $this->setOption('overrequest.ratio', $ratio);
+ $this->setOption('overrequest.ratio', $ratio);
+
+ return $this;
}
/**
diff --git a/src/Component/Facet/JsonFacetTrait.php b/src/Component/Facet/JsonFacetTrait.php
index a594cc063..f7083ec2b 100644
--- a/src/Component/Facet/JsonFacetTrait.php
+++ b/src/Component/Facet/JsonFacetTrait.php
@@ -59,9 +59,9 @@ public function getDomainFilter()
* @param string $query
* @param array $bind Bind values for placeholders in the query string
*
- * @return \Solarium\Component\FacetSetInterface
+ * @return self Provides fluent interface
*/
- public function setDomainFilterQuery(string $query, array $bind = null): FacetSetInterface
+ public function setDomainFilterQuery(string $query, array $bind = null): self
{
if (null !== $bind) {
$helper = new Helper();
@@ -70,14 +70,18 @@ public function setDomainFilterQuery(string $query, array $bind = null): FacetSe
$filter = $this->getDomainFilter();
if (!$filter || \is_string($filter)) {
- return $this->setOption('domain', ['filter' => $query]);
+ $this->setOption('domain', ['filter' => $query]);
+
+ return $this;
}
foreach ($filter as &$paramOrQuery) {
if (\is_string($paramOrQuery)) {
$paramOrQuery = $query;
- return $this->setOption('domain', ['filter' => $filter]);
+ $this->setOption('domain', ['filter' => $filter]);
+
+ return $this;
}
}
unset($paramOrQuery);
@@ -85,7 +89,9 @@ public function setDomainFilterQuery(string $query, array $bind = null): FacetSe
/* @noinspection UnsupportedStringOffsetOperationsInspection */
$filter[] = $query;
- return $this->setOption('domain', ['filter' => $filter]);
+ $this->setOption('domain', ['filter' => $filter]);
+
+ return $this;
}
/**
@@ -95,15 +101,19 @@ public function setDomainFilterQuery(string $query, array $bind = null): FacetSe
*
* @return self Provides fluent interface
*/
- public function addDomainFilterParameter(string $param): FacetSetInterface
+ public function addDomainFilterParameter(string $param): self
{
$filter = $this->getDomainFilter();
if (!$filter) {
- return $this->setOption('domain', ['filter' => ['param' => $param]]);
+ $this->setOption('domain', ['filter' => ['param' => $param]]);
+
+ return $this;
}
if (\is_string($filter) || 1 === \count($filter)) {
- return $this->setOption('domain', ['filter' => [$filter, ['param' => $param]]]);
+ $this->setOption('domain', ['filter' => [$filter, ['param' => $param]]]);
+
+ return $this;
}
foreach ($filter as &$paramOrQuery) {
@@ -116,7 +126,9 @@ public function addDomainFilterParameter(string $param): FacetSetInterface
/* @noinspection UnsupportedStringOffsetOperationsInspection */
$filter[] = ['param' => $param];
- return $this->setOption('domain', ['filter' => $filter]);
+ $this->setOption('domain', ['filter' => $filter]);
+
+ return $this;
}
/**
@@ -155,7 +167,7 @@ public function serialize()
*
* @return self Provides fluent interface
*/
- public function addFacet($facet): FacetSetInterface
+ public function addFacet($facet): self
{
if ($facet instanceof JsonFacetInterface) {
$this->facetSetAddFacet($facet);
@@ -176,7 +188,7 @@ public function addFacet($facet): FacetSetInterface
*
* @return self Provides fluent interface
*/
- public function removeFacet($facet): FacetSetInterface
+ public function removeFacet($facet): self
{
$this->facetSetRemoveFacet($facet);
$this->serialize();
@@ -189,7 +201,7 @@ public function removeFacet($facet): FacetSetInterface
*
* @return self Provides fluent interface
*/
- public function clearFacets(): FacetSetInterface
+ public function clearFacets(): self
{
$this->facetSetClearFacets();
$this->serialize();
diff --git a/src/Component/Facet/MultiQuery.php b/src/Component/Facet/MultiQuery.php
index 2ad3667e2..f333a2fb3 100644
--- a/src/Component/Facet/MultiQuery.php
+++ b/src/Component/Facet/MultiQuery.php
@@ -203,7 +203,7 @@ public function setQueries(array $facetQueries): self
/**
* Add an exclude tag.
*
- * Excludes added to the MultiQuery facet a shared by all underlying
+ * Excludes added to the MultiQuery facet are shared by all underlying
* FacetQueries, so they must be forwarded to any existing instances.
*
* If you don't want to share an exclude use the addExclude method of a
@@ -211,14 +211,12 @@ public function setQueries(array $facetQueries): self
*
* @param string $exclude
*
- * @throws OutOfBoundsException
- *
* @return self Provides fluent interface
*/
- public function addExclude(string $exclude): AbstractFacet
+ public function addExclude(string $exclude): self
{
foreach ($this->facetQueries as $facetQuery) {
- $facetQuery->getLocalParameters()->setExclude($exclude);
+ $facetQuery->addExclude($exclude);
}
$this->getLocalParameters()->setExclude($exclude);
@@ -226,10 +224,66 @@ public function addExclude(string $exclude): AbstractFacet
return $this;
}
+ /**
+ * Add exclude tags.
+ *
+ * Excludes added to the MultiQuery facet are shared by all underlying
+ * FacetQueries, so they must be forwarded to any existing instances.
+ *
+ * If you don't want to share excludes use the addExcludes method of a
+ * specific FacetQuery instance instead.
+ *
+ * @param array|string $excludes array or string with comma separated exclude tags
+ *
+ * @return self Provides fluent interface
+ */
+ public function addExcludes($excludes): self
+ {
+ if (\is_string($excludes)) {
+ $excludes = preg_split('/(?facetQueries as $facetQuery) {
+ $facetQuery->addExcludes($excludes);
+ }
+
+ $this->getLocalParameters()->addExcludes($excludes);
+
+ return $this;
+ }
+
+ /**
+ * Set the list of exclude tags.
+ *
+ * Excludes added to the MultiQuery facet are shared by all underlying
+ * FacetQueries, so they must be forwarded to any existing instances.
+ *
+ * If you don't want to share excludes use the setExcludes method of a
+ * specific FacetQuery instance instead.
+ *
+ * @param array|string $excludes array or string with comma separated exclude tags
+ *
+ * @return self Provides fluent interface
+ */
+ public function setExcludes($excludes): self
+ {
+ if (\is_string($excludes)) {
+ $excludes = preg_split('/(?facetQueries as $facetQuery) {
+ $facetQuery->setExcludes($excludes);
+ }
+
+ $this->getLocalParameters()->setExcludes($excludes);
+
+ return $this;
+ }
+
/**
* Remove a single exclude tag.
*
- * Excludes added to the MultiQuery facet a shared by all underlying
+ * Excludes added to the MultiQuery facet are shared by all underlying
* FacetQueries, so changes must be forwarded to any existing instances.
*
* If you don't want this use the removeExclude method of a
@@ -237,14 +291,12 @@ public function addExclude(string $exclude): AbstractFacet
*
* @param string $exclude
*
- * @throws OutOfBoundsException
- *
* @return self Provides fluent interface
*/
- public function removeExclude(string $exclude): AbstractFacet
+ public function removeExclude(string $exclude): self
{
foreach ($this->facetQueries as $facetQuery) {
- $facetQuery->getLocalParameters()->removeExclude($exclude);
+ $facetQuery->removeExclude($exclude);
}
$this->getLocalParameters()->removeExclude($exclude);
@@ -253,22 +305,20 @@ public function removeExclude(string $exclude): AbstractFacet
}
/**
- * Remove all excludes.
+ * Remove all exclude tags.
*
- * Excludes added to the MultiQuery facet a shared by all underlying
+ * Excludes added to the MultiQuery facet are shared by all underlying
* FacetQueries, so changes must be forwarded to any existing instances.
*
* If you don't want this use the clearExcludes method of a
* specific FacetQuery instance instead.
*
- * @throws OutOfBoundsException
- *
* @return self Provides fluent interface
*/
- public function clearExcludes(): AbstractFacet
+ public function clearExcludes(): self
{
foreach ($this->facetQueries as $facetQuery) {
- $facetQuery->getLocalParameters()->clearExcludes();
+ $facetQuery->clearExcludes();
}
$this->getLocalParameters()->clearExcludes();
@@ -276,6 +326,22 @@ public function clearExcludes(): AbstractFacet
return $this;
}
+ /**
+ * Get the list of exclude tags.
+ *
+ * Excludes added to the MultiQuery facet are shared by all underlying
+ * FacetQueries, so they must be forwarded to any existing instances.
+ *
+ * If you don't want to share excludes use the getExcludes method of a
+ * specific FacetQuery instance instead.
+ *
+ * @return array
+ */
+ public function getExcludes(): array
+ {
+ return $this->getLocalParameters()->getExcludes();
+ }
+
/**
* Initialize options.
*
diff --git a/src/Component/Facet/Pivot.php b/src/Component/Facet/Pivot.php
index f0d1aa682..8773ccd27 100644
--- a/src/Component/Facet/Pivot.php
+++ b/src/Component/Facet/Pivot.php
@@ -158,9 +158,11 @@ public function getSort(): ?string
*
* @return self Provides fluent interface
*/
- public function setOverrequestCount($count): self
+ public function setOverrequestCount(int $count): self
{
- return $this->setOption('overrequest.count', $count);
+ $this->setOption('overrequest.count', $count);
+
+ return $this;
}
/**
@@ -180,9 +182,11 @@ public function getOverrequestCount(): ?int
*
* @return self Provides fluent interface
*/
- public function setOverrequestRatio($ratio): self
+ public function setOverrequestRatio(float $ratio): self
{
- return $this->setOption('overrequest.ratio', $ratio);
+ $this->setOption('overrequest.ratio', $ratio);
+
+ return $this;
}
/**
diff --git a/src/Component/Facet/Range.php b/src/Component/Facet/Range.php
index 9ba8566e9..2cb11092d 100644
--- a/src/Component/Facet/Range.php
+++ b/src/Component/Facet/Range.php
@@ -65,10 +65,18 @@ protected function init()
foreach ($this->options as $name => $value) {
switch ($name) {
case 'exclude':
+ if (!\is_array($value)) {
+ $value = preg_split('/(?getLocalParameters()->addExcludes($value);
+ unset($this->options[$name]);
+
+ trigger_error('Setting local parameter using the "exclude" option is deprecated in Solarium 7 and will be removed in Solarium 8. Use "local_exclude" instead.', \E_USER_DEPRECATED);
break;
case 'pivot':
$this->setPivot(new Pivot($value));
+ break;
}
}
}
diff --git a/src/Component/FacetSetInterface.php b/src/Component/FacetSetInterface.php
index 72ba39934..ac3eba5fc 100644
--- a/src/Component/FacetSetInterface.php
+++ b/src/Component/FacetSetInterface.php
@@ -75,18 +75,18 @@ interface FacetSetInterface
*
* @throws InvalidArgumentException
*
- * @return \Solarium\Component\FacetSet
+ * @return self Provides fluent interface
*/
- public function addFacet($facet): self;
+ public function addFacet($facet);
/**
* Add multiple facets.
*
* @param array $facets
*
- * @return \Solarium\Component\FacetSet
+ * @return self Provides fluent interface
*/
- public function addFacets(array $facets): self;
+ public function addFacets(array $facets);
/**
* Get a facet.
@@ -111,16 +111,16 @@ public function getFacets(): array;
*
* @param string|FacetInterface $facet
*
- * @return \Solarium\Component\FacetSet
+ * @return self Provides fluent interface
*/
- public function removeFacet($facet): self;
+ public function removeFacet($facet);
/**
* Remove all facets.
*
- * @return \Solarium\Component\FacetSet
+ * @return self Provides fluent interface
*/
- public function clearFacets(): self;
+ public function clearFacets();
/**
* Set multiple facets.
@@ -129,9 +129,9 @@ public function clearFacets(): self;
*
* @param FacetInterface[] $facets
*
- * @return \Solarium\Component\FacetSet
+ * @return self Provides fluent interface
*/
- public function setFacets(array $facets): self;
+ public function setFacets(array $facets);
/**
* Create a facet instance.
diff --git a/src/Component/RequestBuilder/FacetSet.php b/src/Component/RequestBuilder/FacetSet.php
index c5bc68f82..52e9cdcfe 100644
--- a/src/Component/RequestBuilder/FacetSet.php
+++ b/src/Component/RequestBuilder/FacetSet.php
@@ -146,8 +146,10 @@ public function addFacetField(Request $request, FacetField $facet, bool $useLoca
$field = $facet->getField();
if ($useLocalParams) {
- $localParams = ['key' => $facet->getKey(),
+ $localParams = [
+ 'key' => $facet->getKey(),
'ex' => $facet->getLocalParameters()->getExcludes(),
+ 'terms' => $facet->getLocalParameters()->getTerms(),
'facet.prefix' => $facet->getPrefix(),
'facet.contains' => $facet->getContains(),
'facet.contains.ignoreCase' => $facet->getContainsIgnoreCase(),
diff --git a/src/Core/Query/AbstractRequestBuilder.php b/src/Core/Query/AbstractRequestBuilder.php
index 7f84d68e6..7115e9f9e 100644
--- a/src/Core/Query/AbstractRequestBuilder.php
+++ b/src/Core/Query/AbstractRequestBuilder.php
@@ -10,6 +10,7 @@
namespace Solarium\Core\Query;
use Solarium\Core\Client\Request;
+use Solarium\Core\Query\LocalParameters\LocalParameter;
use Solarium\QueryType\Server\AbstractServerQuery;
/**
@@ -88,7 +89,13 @@ public function renderLocalParams(string $value, array $localParams = []): strin
$paramValue = $paramValue ? 'true' : 'false';
}
- $params .= $paramName.'='.$helper->escapeLocalParamValue($paramValue).' ';
+ if (LocalParameter::isSplitSmart($paramName)) {
+ $paramValue = $helper->escapeLocalParamValue($paramValue, ',');
+ } else {
+ $paramValue = $helper->escapeLocalParamValue($paramValue);
+ }
+
+ $params .= $paramName.'='.$paramValue.' ';
}
if ('' !== $params = trim($params)) {
diff --git a/src/Core/Query/Helper.php b/src/Core/Query/Helper.php
index 11d61697e..4746be5c5 100644
--- a/src/Core/Query/Helper.php
+++ b/src/Core/Query/Helper.php
@@ -108,18 +108,30 @@ public function escapePhrase(string $input): string
* a single quote, a double quote, or a right curly bracket. It backslash
* escapes single quotes and backslashes within that quoted string.
*
+ * If an optional pre-escaped separator character is passed, a backslash
+ * preceding this character will not be escaped with another backslash.
+ * {@internal Based on splitSmart() in org.apache.solr.common.util.StrUtils}
+ *
* A value that doesn't require quoting is returned as is.
*
* @see https://solr.apache.org/guide/local-parameters-in-queries.html#basic-syntax-of-local-parameters
*
* @param string $value
+ * @param string $preEscapedSeparator Separator character that is already escaped with a backslash
*
* @return string
*/
- public function escapeLocalParamValue(string $value): string
+ public function escapeLocalParamValue(string $value, string $preEscapedSeparator = null): string
{
if (preg_match('/[ \'"}]/', $value)) {
- $value = "'".preg_replace("/('|\\\\)/", '\\\$1', $value)."'";
+ $pattern = "/('|\\\\)/";
+
+ if (null !== $preEscapedSeparator) {
+ $char = preg_quote(substr($preEscapedSeparator, 0, 1), '/');
+ $pattern = "/('|\\\\(?!$char))/";
+ }
+
+ $value = "'".preg_replace($pattern, '\\\$1', $value)."'";
}
return $value;
diff --git a/src/Core/Query/LocalParameters/LocalParameter.php b/src/Core/Query/LocalParameters/LocalParameter.php
index 76c42637a..968f17cdf 100644
--- a/src/Core/Query/LocalParameters/LocalParameter.php
+++ b/src/Core/Query/LocalParameters/LocalParameter.php
@@ -69,6 +69,15 @@ class LocalParameter implements LocalParameterInterface
self::TYPE_COST => 'local_cost',
];
+ public const IS_SPLIT_SMART = [
+ self::TYPE_EXCLUDE,
+ self::TYPE_TAG,
+ self::TYPE_RANGE,
+ self::TYPE_STAT,
+ self::TYPE_TERM,
+ self::TYPE_QUERY,
+ ];
+
/**
* @var string
*/
@@ -174,4 +183,21 @@ public function removeValue($value): LocalParameterInterface
return $this;
}
+
+ /**
+ * Will a parameter of this type be "split smart" by Solr?
+ *
+ * A local parameter is "split smart" if a literal comma in a value can be escaped
+ * with backslash when a comma is normally used to separate multiple values.
+ *
+ * {@internal Method name inspired by splitSmart() in org.apache.solr.common.util.StrUtils}
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ public static function isSplitSmart(string $type): bool
+ {
+ return \in_array($type, self::IS_SPLIT_SMART);
+ }
}
diff --git a/src/Core/Query/LocalParameters/LocalParameters.php b/src/Core/Query/LocalParameters/LocalParameters.php
index ec6eae92b..013353fdb 100644
--- a/src/Core/Query/LocalParameters/LocalParameters.php
+++ b/src/Core/Query/LocalParameters/LocalParameters.php
@@ -329,10 +329,24 @@ public function addTerms(array $terms): self
* @throws OutOfBoundsException
*
* @return $this
+ *
+ * @deprecated Will be removed in Solarium 8. Use {@see removeTerm()} instead.
*/
public function removeTerms(string $terms): self
{
- return $this->removeValue(LocalParameter::TYPE_TERM, $terms);
+ return $this->removeTerm($terms);
+ }
+
+ /**
+ * @param string $term
+ *
+ * @throws OutOfBoundsException
+ *
+ * @return $this
+ */
+ public function removeTerm(string $term): self
+ {
+ return $this->removeValue(LocalParameter::TYPE_TERM, $term);
}
/**
diff --git a/src/Core/Query/LocalParameters/LocalParametersTrait.php b/src/Core/Query/LocalParameters/LocalParametersTrait.php
index d0ade9b7f..37587689e 100644
--- a/src/Core/Query/LocalParameters/LocalParametersTrait.php
+++ b/src/Core/Query/LocalParameters/LocalParametersTrait.php
@@ -48,7 +48,7 @@ protected function initLocalParameters(): void
switch ($name) {
case LocalParameter::PARAMETER_MAP[LocalParameter::TYPE_EXCLUDE]:
if (!\is_array($value)) {
- $value = explode(',', $value);
+ $value = preg_split('/(?getLocalParameters()->addExcludes($value);
@@ -62,7 +62,7 @@ protected function initLocalParameters(): void
break;
case LocalParameter::PARAMETER_MAP[LocalParameter::TYPE_TAG]:
if (!\is_array($value)) {
- $value = explode(',', $value);
+ $value = preg_split('/(?getLocalParameters()->addTags($value);
@@ -71,7 +71,7 @@ protected function initLocalParameters(): void
break;
case LocalParameter::PARAMETER_MAP[LocalParameter::TYPE_RANGE]:
if (!\is_array($value)) {
- $value = explode(',', $value);
+ $value = preg_split('/(?getLocalParameters()->addRanges($value);
@@ -80,7 +80,7 @@ protected function initLocalParameters(): void
break;
case LocalParameter::PARAMETER_MAP[LocalParameter::TYPE_STAT]:
if (!\is_array($value)) {
- $value = explode(',', $value);
+ $value = preg_split('/(?getLocalParameters()->addStats($value);
@@ -88,7 +88,7 @@ protected function initLocalParameters(): void
break;
case LocalParameter::PARAMETER_MAP[LocalParameter::TYPE_TERM]:
if (!\is_array($value)) {
- $value = explode(',', $value);
+ $value = preg_split('/(?getLocalParameters()->addTerms($value);
diff --git a/tests/Component/Facet/FacetTest.php b/tests/Component/Facet/FacetTest.php
index e59acb19b..cc7163556 100644
--- a/tests/Component/Facet/FacetTest.php
+++ b/tests/Component/Facet/FacetTest.php
@@ -22,12 +22,14 @@ public function testConfigMode()
{
$this->facet->setOptions(['local_key' => 'myKey', 'local_exclude' => ['e1', 'e2']]);
$this->assertSame('myKey', $this->facet->getKey());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
$this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
}
public function testConfigModeWithSingleValueExclude()
{
$this->facet->setOptions(['local_exclude' => 'e1']);
+ $this->assertEquals(['e1'], $this->facet->getExcludes());
$this->assertEquals(['e1'], $this->facet->getLocalParameters()->getExcludes());
}
@@ -39,27 +41,61 @@ public function testSetAndGetKey()
public function testAddExclude()
{
- $this->facet->getLocalParameters()->setExclude('e1');
+ $this->facet->addExclude('e1');
+ $this->assertEquals(['e1'], $this->facet->getExcludes());
$this->assertEquals(['e1'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExclude('e2');
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
}
public function testAddExcludes()
{
- $this->facet->getLocalParameters()->addExcludes(['e1', 'e2']);
+ $this->facet->addExcludes(['e1', 'e2']);
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExcludes('e3,e4');
+ $this->assertEquals(['e1', 'e2', 'e3', 'e4'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2', 'e3', 'e4'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testSetExcludes()
+ {
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
$this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->setExcludes('e3,e4');
+ $this->assertEquals(['e3', 'e4'], $this->facet->getExcludes());
+ $this->assertEquals(['e3', 'e4'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testSetAndAddTermsWithEscapedSeparator()
+ {
+ $this->facet->setExcludes('e1\,e2,e3');
+ $this->assertEquals(['e1\,e2', 'e3'], $this->facet->getExcludes());
+ $this->assertEquals(['e1\,e2', 'e3'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExcludes('e4\,e5,e6');
+ $this->assertEquals(['e1\,e2', 'e3', 'e4\,e5', 'e6'], $this->facet->getExcludes());
+ $this->assertEquals(['e1\,e2', 'e3', 'e4\,e5', 'e6'], $this->facet->getLocalParameters()->getExcludes());
}
public function testRemoveExclude()
{
- $this->facet->getLocalParameters()->addExcludes(['e1', 'e2']);
- $this->facet->getLocalParameters()->removeExclude('e1');
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->facet->removeExclude('e1');
+ $this->assertEquals(['e2'], $this->facet->getExcludes());
$this->assertEquals(['e2'], $this->facet->getLocalParameters()->getExcludes());
}
public function testClearExcludes()
{
- $this->facet->getLocalParameters()->addExcludes(['e1', 'e2']);
- $this->facet->getLocalParameters()->clearExcludes();
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->facet->clearExcludes();
+ $this->assertEquals([], $this->facet->getExcludes());
$this->assertEquals([], $this->facet->getLocalParameters()->getExcludes());
}
}
diff --git a/tests/Component/Facet/FieldTest.php b/tests/Component/Facet/FieldTest.php
index 4c8c9e8ad..a2b8aa4e9 100644
--- a/tests/Component/Facet/FieldTest.php
+++ b/tests/Component/Facet/FieldTest.php
@@ -23,6 +23,7 @@ public function testConfigMode()
$options = [
'local_key' => 'myKey',
'local_exclude' => ['e1', 'e2'],
+ 'local_terms' => ['t1', 't2'],
'field' => 'text',
'prefix' => 'xyz',
'contains' => 'foobar',
@@ -45,7 +46,8 @@ public function testConfigMode()
$this->facet->setOptions($options);
$this->assertSame($options['local_key'], $this->facet->getKey());
- $this->assertSame($options['local_exclude'], $this->facet->getLocalParameters()->getExcludes());
+ $this->assertSame($options['local_exclude'], $this->facet->getExcludes());
+ $this->assertSame($options['local_terms'], $this->facet->getTerms());
$this->assertSame($options['field'], $this->facet->getField());
$this->assertSame($options['prefix'], $this->facet->getPrefix());
$this->assertSame($options['contains'], $this->facet->getContains());
@@ -79,6 +81,66 @@ public function testSetAndGetField()
$this->assertSame('category', $this->facet->getField());
}
+ public function testAddTerm()
+ {
+ $this->facet->addTerm('t1');
+ $this->assertEquals(['t1'], $this->facet->getTerms());
+ $this->assertEquals(['t1'], $this->facet->getLocalParameters()->getTerms());
+
+ $this->facet->addTerm('t2');
+ $this->assertEquals(['t1', 't2'], $this->facet->getTerms());
+ $this->assertEquals(['t1', 't2'], $this->facet->getLocalParameters()->getTerms());
+ }
+
+ public function testAddTerms()
+ {
+ $this->facet->addTerms(['t1', 't2']);
+ $this->assertEquals(['t1', 't2'], $this->facet->getTerms());
+ $this->assertEquals(['t1', 't2'], $this->facet->getLocalParameters()->getTerms());
+
+ $this->facet->addTerms('t3,t4');
+ $this->assertEquals(['t1', 't2', 't3', 't4'], $this->facet->getTerms());
+ $this->assertEquals(['t1', 't2', 't3', 't4'], $this->facet->getLocalParameters()->getTerms());
+ }
+
+ public function testSetTerms()
+ {
+ $this->facet->setTerms(['t1', 't2']);
+ $this->assertEquals(['t1', 't2'], $this->facet->getTerms());
+ $this->assertEquals(['t1', 't2'], $this->facet->getLocalParameters()->getTerms());
+
+ $this->facet->setTerms('t3,t4');
+ $this->assertEquals(['t3', 't4'], $this->facet->getTerms());
+ $this->assertEquals(['t3', 't4'], $this->facet->getLocalParameters()->getTerms());
+ }
+
+ public function testSetAndAddTermsWithEscapedSeparator()
+ {
+ $this->facet->setTerms('t1\,t2,t3');
+ $this->assertEquals(['t1\,t2', 't3'], $this->facet->getTerms());
+ $this->assertEquals(['t1\,t2', 't3'], $this->facet->getLocalParameters()->getTerms());
+
+ $this->facet->addTerms('t4\,t5,t6');
+ $this->assertEquals(['t1\,t2', 't3', 't4\,t5', 't6'], $this->facet->getTerms());
+ $this->assertEquals(['t1\,t2', 't3', 't4\,t5', 't6'], $this->facet->getLocalParameters()->getTerms());
+ }
+
+ public function testRemoveTerm()
+ {
+ $this->facet->setTerms(['t1', 't2']);
+ $this->facet->removeTerm('t1');
+ $this->assertEquals(['t2'], $this->facet->getTerms());
+ $this->assertEquals(['t2'], $this->facet->getLocalParameters()->getTerms());
+ }
+
+ public function testClearTerms()
+ {
+ $this->facet->setTerms(['t1', 't2']);
+ $this->facet->clearTerms();
+ $this->assertEquals([], $this->facet->getTerms());
+ $this->assertEquals([], $this->facet->getLocalParameters()->getTerms());
+ }
+
public function testSetAndGetPrefix()
{
$this->facet->setPrefix('xyz');
diff --git a/tests/Component/Facet/MultiQueryTest.php b/tests/Component/Facet/MultiQueryTest.php
index 5273b19c3..cd2028506 100644
--- a/tests/Component/Facet/MultiQueryTest.php
+++ b/tests/Component/Facet/MultiQueryTest.php
@@ -293,6 +293,66 @@ public function testSetQueries()
);
}
+ public function testAddExclude()
+ {
+ $this->facet->addExclude('e1');
+ $this->assertEquals(['e1'], $this->facet->getExcludes());
+ $this->assertEquals(['e1'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExclude('e2');
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testAddExcludes()
+ {
+ $this->facet->addExcludes(['e1', 'e2']);
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExcludes('e3,e4');
+ $this->assertEquals(['e1', 'e2', 'e3', 'e4'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2', 'e3', 'e4'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testSetExcludes()
+ {
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->assertEquals(['e1', 'e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e1', 'e2'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->setExcludes('e3,e4');
+ $this->assertEquals(['e3', 'e4'], $this->facet->getExcludes());
+ $this->assertEquals(['e3', 'e4'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testSetAndAddTermsWithEscapedSeparator()
+ {
+ $this->facet->setExcludes('e1\,e2,e3');
+ $this->assertEquals(['e1\,e2', 'e3'], $this->facet->getExcludes());
+ $this->assertEquals(['e1\,e2', 'e3'], $this->facet->getLocalParameters()->getExcludes());
+
+ $this->facet->addExcludes('e4\,e5,e6');
+ $this->assertEquals(['e1\,e2', 'e3', 'e4\,e5', 'e6'], $this->facet->getExcludes());
+ $this->assertEquals(['e1\,e2', 'e3', 'e4\,e5', 'e6'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testRemoveExclude()
+ {
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->facet->removeExclude('e1');
+ $this->assertEquals(['e2'], $this->facet->getExcludes());
+ $this->assertEquals(['e2'], $this->facet->getLocalParameters()->getExcludes());
+ }
+
+ public function testClearExcludes()
+ {
+ $this->facet->setExcludes(['e1', 'e2']);
+ $this->facet->clearExcludes();
+ $this->assertEquals([], $this->facet->getExcludes());
+ $this->assertEquals([], $this->facet->getLocalParameters()->getExcludes());
+ }
+
public function testAddExcludeForwarding()
{
$facetQuery = new Query();
@@ -304,7 +364,37 @@ public function testAddExcludeForwarding()
$this->assertSame(
['fq1'],
- $facetQuery->getLocalParameters()->getExcludes()
+ $facetQuery->getExcludes()
+ );
+ }
+
+ public function testAddExcludesForwarding()
+ {
+ $facetQuery = new Query();
+ $facetQuery->setKey('k1');
+ $facetQuery->setQuery('category:1');
+ $this->facet->addQuery($facetQuery);
+
+ $this->facet->addExcludes(['fq1', 'fq2']);
+
+ $this->assertSame(
+ ['fq1', 'fq2'],
+ $facetQuery->getExcludes()
+ );
+ }
+
+ public function testSetExcludesForwarding()
+ {
+ $facetQuery = new Query();
+ $facetQuery->setKey('k1');
+ $facetQuery->setQuery('category:1');
+ $this->facet->addQuery($facetQuery);
+
+ $this->facet->setExcludes(['fq1', 'fq2']);
+
+ $this->assertSame(
+ ['fq1', 'fq2'],
+ $facetQuery->getExcludes()
);
}
@@ -319,14 +409,14 @@ public function testRemoveExcludeForwarding()
$this->assertSame(
['fq1'],
- $facetQuery->getLocalParameters()->getExcludes()
+ $facetQuery->getExcludes()
);
$this->facet->removeExclude('fq1');
$this->assertSame(
[],
- $facetQuery->getLocalParameters()->getExcludes()
+ $facetQuery->getExcludes()
);
}
diff --git a/tests/Component/Facet/RangeTest.php b/tests/Component/Facet/RangeTest.php
index 89e8705d3..fb4a32e3f 100644
--- a/tests/Component/Facet/RangeTest.php
+++ b/tests/Component/Facet/RangeTest.php
@@ -37,7 +37,7 @@ public function testConfigMode()
$this->facet->setOptions($options);
$this->assertSame($options['local_key'], $this->facet->getKey());
- $this->assertSame($options['local_exclude'], $this->facet->getLocalParameters()->getExcludes());
+ $this->assertSame($options['local_exclude'], $this->facet->getExcludes());
$this->assertSame($options['field'], $this->facet->getField());
$this->assertSame((string) $options['start'], $this->facet->getStart());
$this->assertSame((string) $options['end'], $this->facet->getEnd());
@@ -47,6 +47,33 @@ public function testConfigMode()
$this->assertSame([$options['include']], $this->facet->getInclude());
}
+ public function testConfigModeWithExclude()
+ {
+ $options = [
+ 'exclude' => 'e1\,e2,e3',
+ ];
+
+ @$this->facet->setOptions($options);
+
+ $this->assertSame(['e1\,e2', 'e3'], $this->facet->getExcludes());
+ }
+
+ public function testConfigModeWithExcludeThrowsDeprecation()
+ {
+ set_error_handler(static function (int $errno, string $errstr): never {
+ throw new \Exception($errstr, $errno);
+ }, \E_USER_DEPRECATED);
+
+ $options = [
+ 'exclude' => 'e1\,e2,e3',
+ ];
+
+ $this->expectExceptionCode(\E_USER_DEPRECATED);
+ $this->facet->setOptions($options);
+
+ restore_error_handler();
+ }
+
public function testGetType()
{
$this->assertSame(
diff --git a/tests/Component/RequestBuilder/FacetSetTest.php b/tests/Component/RequestBuilder/FacetSetTest.php
index e2bc9e8af..fda7f2074 100644
--- a/tests/Component/RequestBuilder/FacetSetTest.php
+++ b/tests/Component/RequestBuilder/FacetSetTest.php
@@ -47,7 +47,7 @@ public function testBuildEmptyFacetSet()
{
$request = $this->builder->buildComponent($this->component, $this->request);
- static::assertEquals(
+ $this->assertEquals(
[],
$request->getParams()
);
@@ -55,8 +55,8 @@ public function testBuildEmptyFacetSet()
public function testBuildWithFacets()
{
- $this->component->addFacet(new FacetField(['local_key' => 'f1', 'field' => 'owner']));
- $this->component->addFacet(new FacetQuery(['local_key' => 'f2', 'query' => 'category:23']));
+ $this->component->addFacet(new FacetField(['local_key' => 'f1', 'local_exclude' => 'e11,e12', 'local_terms' => 't1,t2', 'field' => 'owner']));
+ $this->component->addFacet(new FacetQuery(['local_key' => 'f2', 'local_exclude' => 'e21,e22', 'query' => 'category:23']));
$this->component->addFacet(
new FacetMultiQuery(['local_key' => 'f3', 'query' => ['f4' => ['query' => 'category:40']]])
);
@@ -65,7 +65,20 @@ public function testBuildWithFacets()
$this->assertNull($request->getRawData());
$this->assertEquals(
- '?facet.field={!key=f1}owner&facet.query={!key=f2}category:23&facet.query={!key=f4}category:40&facet=true',
+ '?facet.field={!key=f1 ex=e11,e12 terms=t1,t2}owner&facet.query={!key=f2 ex=e21,e22}category:23&facet.query={!key=f4}category:40&facet=true',
+ urldecode($request->getUri())
+ );
+ }
+
+ public function testBuildWithFacetFieldWithCommaAndQuoteInTerm()
+ {
+ $this->component->addFacet(new FacetField(['local_key' => 'f1', 'local_terms' => ['yes\, it is', 'no\, it isn\'t'], 'field' => 'isit']));
+
+ $request = $this->builder->buildComponent($this->component, $this->request);
+
+ $this->assertNull($request->getRawData());
+ $this->assertEquals(
+ "?facet.field={!key=f1 terms='yes\\, it is,no\\, it isn\\'t'}isit&facet=true",
urldecode($request->getUri())
);
}
@@ -134,6 +147,21 @@ public function testBuildWithJsonFacetFilterQueryAndParams()
);
}
+ public function testBuildWithJsonFacetFilterQueryWithPlaceholders()
+ {
+ $terms = new JsonTerms(['local_key' => 'f1', 'field' => 'owner']);
+ $terms->setDomainFilterQuery('popularity:[%1% TO %2%]', [5, 10]);
+ $this->component->addFacet($terms);
+
+ $request = $this->builder->buildComponent($this->component, $this->request);
+
+ $this->assertNull($request->getRawData());
+ $this->assertEquals(
+ '?json.facet={"f1":{"field":"owner","domain":{"filter":"popularity:[5 TO 10]"},"type":"terms"}}',
+ urldecode($request->getUri())
+ );
+ }
+
public function testBuildWithJsonFacetFilterParamsAndQuery()
{
$terms = new JsonTerms(['local_key' => 'f1', 'field' => 'owner']);
@@ -151,6 +179,24 @@ public function testBuildWithJsonFacetFilterParamsAndQuery()
);
}
+ public function testBuildWithJsonFacetFilterParamsAndQueryOverwrite()
+ {
+ $terms = new JsonTerms(['local_key' => 'f1', 'field' => 'owner']);
+ $terms->setDomainFilterQuery('popularity:[5 TO 10]');
+ $terms->addDomainFilterParameter('myparam1');
+ $terms->addDomainFilterParameter('myparam2');
+ $terms->setDomainFilterQuery('popularity:[15 TO 20]');
+ $this->component->addFacet($terms);
+
+ $request = $this->builder->buildComponent($this->component, $this->request);
+
+ $this->assertNull($request->getRawData());
+ $this->assertEquals(
+ '?json.facet={"f1":{"field":"owner","domain":{"filter":["popularity:[15 TO 20]",{"param":"myparam1"},{"param":"myparam2"}]},"type":"terms"}}',
+ urldecode($request->getUri())
+ );
+ }
+
public function testBuildWithFacetsAndJsonFacets()
{
$this->component->addFacet(new FacetField(['local_key' => 'f1', 'field' => 'owner']));
@@ -208,6 +254,44 @@ public function testBuildWithNestedJsonFacets()
);
}
+ public function testBuildWithNestedJsonFacetRemoved()
+ {
+ $terms = new JsonTerms(['local_key' => 'f1', 'field' => 'owner']);
+ $query = new JsonQuery(['local_key' => 'f2', 'query' => 'category:23']);
+ $query->addFacet(new JsonAggregation(['local_key' => 'f1', 'function' => 'avg(mul(price,popularity))']));
+ $query->addFacet(new JsonAggregation(['local_key' => 'f2', 'function' => 'unique(popularity)']));
+ $query->removeFacet('f1');
+ $terms->addFacet($query);
+ $this->component->addFacet($terms);
+
+ $request = $this->builder->buildComponent($this->component, $this->request);
+
+ $this->assertNull($request->getRawData());
+ $this->assertEquals(
+ '?json.facet={"f1":{"field":"owner","type":"terms","facet":{"f2":{"type":"query","facet":{"f2":"unique(popularity)"},"q":"category:23"}}}}',
+ urldecode($request->getUri())
+ );
+ }
+
+ public function testBuildWithNestedJsonFacetsCleared()
+ {
+ $terms = new JsonTerms(['local_key' => 'f1', 'field' => 'owner']);
+ $query = new JsonQuery(['local_key' => 'f2', 'query' => 'category:23']);
+ $query->addFacet(new JsonAggregation(['local_key' => 'f1', 'function' => 'avg(mul(price,popularity))']));
+ $query->addFacet(new JsonAggregation(['local_key' => 'f2', 'function' => 'unique(popularity)']));
+ $query->clearFacets();
+ $terms->addFacet($query);
+ $this->component->addFacet($terms);
+
+ $request = $this->builder->buildComponent($this->component, $this->request);
+
+ $this->assertNull($request->getRawData());
+ $this->assertEquals(
+ '?json.facet={"f1":{"field":"owner","type":"terms","facet":{"f2":{"type":"query","q":"category:23"}}}}',
+ urldecode($request->getUri())
+ );
+ }
+
public function testBuildWithRangeFacet()
{
$this->component->addFacet(new FacetRange(
@@ -366,7 +450,7 @@ public function testBuildWithPivotFacet()
'overrequest.ratio' => 2.2,
]
);
- $facet->getLocalParameters()->setExclude('owner');
+ $facet->addExclude('owner');
$this->component->addFacet($facet);
$this->component->setPivotMinCount(5);
$this->component->setLimit(-1);
diff --git a/tests/Core/Query/HelperTest.php b/tests/Core/Query/HelperTest.php
index 1583309da..71178c961 100644
--- a/tests/Core/Query/HelperTest.php
+++ b/tests/Core/Query/HelperTest.php
@@ -496,6 +496,34 @@ public function escapeLocalParamValueProvider(): array
];
}
+ /**
+ * @dataProvider escapeLocalParamValuePreEscapedSeparatorProvider
+ */
+ public function testEscapeLocalParamValuePreEscapedSeparator(string $value, string $separator, string $expectedWithoutSeparator, string $expectedWithSeparator)
+ {
+ $this->assertSame(
+ $expectedWithoutSeparator,
+ $this->helper->escapeLocalParamValue($value)
+ );
+
+ $this->assertSame(
+ $expectedWithSeparator,
+ $this->helper->escapeLocalParamValue($value, $separator)
+ );
+ }
+
+ public function escapeLocalParamValuePreEscapedSeparatorProvider(): array
+ {
+ return [
+ 'no other escapes needed' => ['a\\,b', ',', 'a\\,b', 'a\\,b'],
+ 'other escapes needed' => ['a b\\,c', ',', "'a b\\\\,c'", "'a b\\,c'"],
+ 'unescaped separator left alone' => ['a b\\,c,d', ',', "'a b\\\\,c,d'", "'a b\\,c,d'"],
+ 'multiple escaped separators' => ['a b\\,c\\,d', ',', "'a b\\\\,c\\\\,d'", "'a b\\,c\\,d'"],
+ 'separator can be only 1 char' => ['a b\\,\\;c', ',;', "'a b\\\\,\\\\;c'", "'a b\\,\\\\;c'"],
+ 'separator is also regex syntax' => ['a b\\|c', '|', "'a b\\\\|c'", "'a b\\|c'"],
+ ];
+ }
+
/**
* @testWith ["ab"]
* ["a\\b"]
diff --git a/tests/Core/Query/LocalParameters/LocalParameterTest.php b/tests/Core/Query/LocalParameters/LocalParameterTest.php
index debfc3166..ecb64477b 100644
--- a/tests/Core/Query/LocalParameters/LocalParameterTest.php
+++ b/tests/Core/Query/LocalParameters/LocalParameterTest.php
@@ -86,4 +86,10 @@ public function testToString(): void
$this->parameter->setValues(['value1', 'value2']);
$this->assertSame('key=value1,value2', (string) $this->parameter);
}
+
+ public function testIsSplitSmart(): void
+ {
+ $this->assertTrue(LocalParameter::isSplitSmart(LocalParameter::IS_SPLIT_SMART[0]));
+ $this->assertFalse(LocalParameter::isSplitSmart('other.type'));
+ }
}
diff --git a/tests/Core/Query/LocalParameters/LocalParametersTest.php b/tests/Core/Query/LocalParameters/LocalParametersTest.php
index 79a4ba674..a9bb464df 100644
--- a/tests/Core/Query/LocalParameters/LocalParametersTest.php
+++ b/tests/Core/Query/LocalParameters/LocalParametersTest.php
@@ -37,19 +37,19 @@ public function testInitLocalParameters(): void
{
$options = [
'local_key' => 'key',
- 'local_exclude' => '',
- 'local_tag' => '',
- 'local_range' => '',
- 'local_stats' => '',
- 'local_terms' => '',
- 'local_type' => '',
- 'local_query' => '',
- 'local_query_field' => '',
- 'local_default_field' => '',
- 'local_max' => '',
- 'local_mean' => '',
- 'local_min' => '',
- 'local_value' => '',
+ 'local_exclude' => 'ex1\,ex2,ex3',
+ 'local_tag' => 'tag1\,tag2,tag3',
+ 'local_range' => 'r1\,r2,r3',
+ 'local_stats' => 'stat1\,stat2,stat3',
+ 'local_terms' => 't1\,t2,t3',
+ 'local_type' => 'mytype',
+ 'local_query' => 'myquery',
+ 'local_query_field' => 'myfield',
+ 'local_default_field' => 'deffield',
+ 'local_max' => 'max',
+ 'local_mean' => 'mean',
+ 'local_min' => 'min',
+ 'local_value' => 'value',
'local_cache' => true,
'local_cost' => 0,
];
@@ -59,21 +59,37 @@ public function testInitLocalParameters(): void
$parameters = $query->getLocalParameters();
$this->assertArrayHasKey(LocalParameter::TYPE_KEY, $parameters);
+ $this->assertSame(['key'], $parameters->getKeys());
$this->assertArrayHasKey(LocalParameter::TYPE_EXCLUDE, $parameters);
+ $this->assertSame(['ex1\,ex2', 'ex3'], $parameters->getExcludes());
$this->assertArrayHasKey(LocalParameter::TYPE_TAG, $parameters);
+ $this->assertSame(['tag1\,tag2', 'tag3'], $parameters->getTags());
$this->assertArrayHasKey(LocalParameter::TYPE_RANGE, $parameters);
+ $this->assertSame(['r1\,r2', 'r3'], $parameters->getRanges());
$this->assertArrayHasKey(LocalParameter::TYPE_STAT, $parameters);
+ $this->assertSame(['stat1\,stat2', 'stat3'], $parameters->getStats());
$this->assertArrayHasKey(LocalParameter::TYPE_TERM, $parameters);
+ $this->assertSame(['t1\,t2', 't3'], $parameters->getTerms());
$this->assertArrayHasKey(LocalParameter::TYPE_TYPE, $parameters);
+ $this->assertSame(['mytype'], $parameters->getTypes());
$this->assertArrayHasKey(LocalParameter::TYPE_QUERY, $parameters);
+ $this->assertSame(['myquery'], $parameters->getQueries());
$this->assertArrayHasKey(LocalParameter::TYPE_QUERY_FIELD, $parameters);
+ $this->assertSame(['myfield'], $parameters->getQueryFields());
$this->assertArrayHasKey(LocalParameter::TYPE_DEFAULT_FIELD, $parameters);
+ $this->assertSame(['deffield'], $parameters->getDefaultFields());
$this->assertArrayHasKey(LocalParameter::TYPE_MAX, $parameters);
- $this->assertArrayHasKey(LocalParameter::TYPE_MAX, $parameters);
+ $this->assertSame(['max'], $parameters->getMax());
+ $this->assertArrayHasKey(LocalParameter::TYPE_MEAN, $parameters);
+ $this->assertSame(['mean'], $parameters->getMean());
$this->assertArrayHasKey(LocalParameter::TYPE_MIN, $parameters);
+ $this->assertSame(['min'], $parameters->getMin());
$this->assertArrayHasKey(LocalParameter::TYPE_VALUE, $parameters);
+ $this->assertSame(['value'], $parameters->getLocalValues());
$this->assertArrayHasKey(LocalParameter::TYPE_CACHE, $parameters);
+ $this->assertSame(['true'], $parameters->getCache());
$this->assertArrayHasKey(LocalParameter::TYPE_COST, $parameters);
+ $this->assertSame([0], $parameters->getCost());
$keys = $parameters[LocalParameter::TYPE_KEY];
$this->assertInstanceOf(LocalParameter::class, $keys);
@@ -96,6 +112,12 @@ public function testIllegalParameterType(): void
$parameters->getKeys();
}
+ public function testGetUninitedLocalParameters(): void
+ {
+ $localParameters = (new DummyTraitUse())->getLocalParameters();
+ $this->assertEquals(new LocalParameters(), $localParameters);
+ }
+
/**
* @throws \PHPUnit\Framework\ExpectationFailedException
* @throws \Solarium\Exception\OutOfBoundsException
@@ -132,6 +154,9 @@ public function testExclude(): void
$this->parameters->removeExclude('excludeOne');
$this->assertSame(['excludeTwo'], $this->parameters->getExcludes());
+
+ $this->parameters->setExclude('excludeThree');
+ $this->assertSame(['excludeTwo', 'excludeThree'], $this->parameters->getExcludes());
}
/**
@@ -164,6 +189,9 @@ public function testRange(): void
$this->parameters->removeRange('rangeOne');
$this->assertSame(['rangeTwo'], $this->parameters->getRanges());
+
+ $this->parameters->setRange('rangeThree');
+ $this->assertSame(['rangeTwo', 'rangeThree'], $this->parameters->getRanges());
}
/**
@@ -196,6 +224,9 @@ public function testTag(): void
$this->parameters->removeTag('tagOne');
$this->assertSame(['tagTwo'], $this->parameters->getTags());
+
+ $this->parameters->setTag('tagThree');
+ $this->assertSame(['tagTwo', 'tagThree'], $this->parameters->getTags());
}
/**
@@ -226,8 +257,11 @@ public function testTerms(): void
$this->parameters->addTerms(['termsOne', 'termsTwo']);
$this->assertSame(['termsOne', 'termsTwo'], $this->parameters->getTerms());
- $this->parameters->removeTerms('termsOne');
+ $this->parameters->removeTerm('termsOne');
$this->assertSame(['termsTwo'], $this->parameters->getTerms());
+
+ $this->parameters->setTerm('termsThree');
+ $this->assertSame(['termsTwo', 'termsThree'], $this->parameters->getTerms());
}
/**
@@ -243,6 +277,21 @@ public function testReplaceTerms(): void
$this->assertSame(['termsOne', 'termsTwo'], $this->parameters->getTerms());
}
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \Solarium\Exception\OutOfBoundsException
+ *
+ * @deprecated Will be removed in Solarium 8
+ */
+ public function testRemoveTerms(): void
+ {
+ $this->parameters->setTerms(['termsOne', 'termsTwo']);
+ $this->assertSame(['termsOne', 'termsTwo'], $this->parameters->getTerms());
+
+ $this->parameters->removeTerms('termsOne');
+ $this->assertSame(['termsTwo'], $this->parameters->getTerms());
+ }
+
/**
* @throws \PHPUnit\Framework\ExpectationFailedException
* @throws \Solarium\Exception\OutOfBoundsException
@@ -260,6 +309,9 @@ public function testQuery(): void
$this->parameters->removeQuery('queryOne');
$this->assertSame(['queryTwo'], $this->parameters->getQueries());
+
+ $this->parameters->setQuery('queryThree');
+ $this->assertSame(['queryTwo', 'queryThree'], $this->parameters->getQueries());
}
/**
@@ -292,6 +344,9 @@ public function testStats(): void
$this->parameters->removeStat('statsOne');
$this->assertSame(['statsTwo'], $this->parameters->getStats());
+
+ $this->parameters->setStat('statsThree');
+ $this->assertSame(['statsTwo', 'statsThree'], $this->parameters->getStats());
}
/**
@@ -477,3 +532,11 @@ class DummyQuery extends Configurable
{
use LocalParametersTrait;
}
+
+class DummyTraitUse
+{
+ use LocalParametersTrait;
+
+ // trait assumes this is inherited from Configurable
+ protected $options = [];
+}
diff --git a/tests/Core/Query/RequestBuilderTest.php b/tests/Core/Query/RequestBuilderTest.php
index 6b0874655..bcea5cbfa 100644
--- a/tests/Core/Query/RequestBuilderTest.php
+++ b/tests/Core/Query/RequestBuilderTest.php
@@ -5,6 +5,7 @@
use PHPUnit\Framework\TestCase;
use Solarium\Core\Query\AbstractRequestBuilder;
use Solarium\Core\Query\Helper;
+use Solarium\Core\Query\LocalParameters\LocalParameter;
use Solarium\QueryType\Select\Query\Query as SelectQuery;
class RequestBuilderTest extends TestCase
@@ -175,6 +176,21 @@ public function testRenderLocalParamsWithEscapes()
);
}
+ public function testRenderLocalParamsWithSplitSmart()
+ {
+ $splitSmartParam = LocalParameter::IS_SPLIT_SMART[0];
+
+ $myParams = [
+ 'no.split.smart' => 'a\, b,c',
+ $splitSmartParam => 'd\, e,f',
+ ];
+
+ $this->assertSame(
+ "{!no.split.smart='a\\\\, b,c' $splitSmartParam='d\\, e,f'}myValue",
+ $this->builder->renderLocalParams('myValue', $myParams)
+ );
+ }
+
public function testRenderLocalParamsWithoutParams()
{
$this->assertSame(