Skip to content

Commit 518eabe

Browse files
committed
Changelog
1 parent ee23147 commit 518eabe

16 files changed

Lines changed: 67 additions & 41 deletions

CHANGELOG.md

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@
22

33
All notable changes to this `laravel-elasticsearch` package will be documented in this file.
44

5+
## v5.7.0 - 2026-05-24
6+
7+
This release is compatible with Laravel 11, 12 & 13
8+
9+
### Added
10+
11+
- **`whereNestedFieldExists($path)`** ([docs](https://elasticsearch.pdphilip.com/eloquent/nested-queries#where-nested-field-exists)) - explicit, zero-overhead check for "this nested path has at least one document." Emits the idiomatic `nested + exists` form directly, no closure required. Variants: `whereNestedFieldDoesntExist`, `orWhereNestedFieldExists`, `orWhereNestedFieldDoesntExist`.
12+
- **`whereFieldExists($field)`** ([docs](https://elasticsearch.pdphilip.com/eloquent/eloquent-queries#wherefieldexists)) - properly named alias for the Elasticsearch `exists` query. Variants: `whereFieldDoesntExist`, `orWhereFieldExists`, `orWhereFieldDoesntExist`.
13+
- **`nestedQuery($column)`** ([docs](https://elasticsearch.pdphilip.com/eloquent/nested-queries#building-the-inner-query-programmatically)) - returns a pre-configured Builder you can build up across conditionals and functions, then pass straight to `whereNestedObject()`. The closure form still works for simple cases; this is the escape hatch for programmatic composition.
14+
- `whereNestedObject()` now accepts a pre-built Builder (from `nestedQuery()`) in addition to a Closure or raw DSL string. Invalid input now throws `BuilderException` instead of silently being stored.
15+
16+
### Changed
17+
18+
- **Exists queries on nested-mapped fields auto-wrap in `nested`** ([docs](https://elasticsearch.pdphilip.com/eloquent/eloquent-queries#wherenotnull)) - `whereFieldExists('comments.country')`, `whereNotNull('comments.country')`, and `whereNull('comments.country')` now emit the idiomatic `nested { path: comments, query: { exists: ... } }` form when the target field lives under a nested path. Detection only fires for dotted field names - bare-field calls (`whereNotNull('title')`) pay no overhead.
19+
- **IDE/PHPDoc improvements** - `find`, `first`, `firstOrCreate` now return `static` (not the abstract base `Model`), so subclass property hints resolve correctly. Collection-returning methods (`get`, `rawSearch`, `distinct`, `bulkDistinct`) annotated as `array<int, static>` so `foreach` typing works in PhpStorm. `ElasticCollection` template hygiene tightened (FQN parent in `@extends`, dropped name-colliding `use Model as TModel` alias).
20+
21+
### Deprecated
22+
23+
- **`whereTermExists` family** - replaced by `whereFieldExists`. The `Term` prefix was historical (the underlying ES query isn't term-related) and the old name avoided collision with Laravel's base `whereExists(Closure)`. Old methods still work; will be removed in v6:
24+
- `whereTermExists``whereFieldExists`
25+
- `whereNotTermExists``whereFieldDoesntExist`
26+
- `orWhereTermExists``orWhereFieldExists`
27+
- `orWhereNotTermsExists``orWhereFieldDoesntExist` (also fixes the typo'd plural "Terms")
28+
29+
**Full Changelog**: https://github.com/pdphilip/laravel-elasticsearch/compare/v5.6.1...v5.7.0
30+
531
## v5.6.1 - 2026-04-06
632

733
> **Future-proofing note:** After GitHub incorrectly shadow-banned my account (since reinstated with no
@@ -13,7 +39,7 @@ This release is compatible with Laravel 11, 12 & 13
1339

1440
### Fixed
1541

16-
- **Laravel 13.3 compatibility** `newCollection()` override on base Model to prevent `HasCollection`
42+
- **Laravel 13.3 compatibility** - `newCollection()` override on base Model to prevent `HasCollection`
1743
trait from attempting to instantiate the abstract parent class (`Cannot instantiate abstract class`
1844
error introduced in Laravel 13.3's new `resolveCollectionFromAttribute` grandchild resolution)
1945
- **QueryException** crash on `Undefined array key "error"` when Elasticsearch returns responses
@@ -41,7 +67,7 @@ This release is compatible with Laravel 11, 12 & 13
4167
### Refactored
4268

4369
- Collapsed Laravel version compatibility layer from 12 files (4 dispatchers + 4 v11 traits + 4 v12 traits) into 4 self-contained traits. Version checks now happen inside each method with spread operators for different constructor
44-
signatures no more file-level conditional trait loading. Removed phpstan bootstrap `class_alias` hacks that were needed for the old pattern.
70+
signatures - no more file-level conditional trait loading. Removed phpstan bootstrap `class_alias` hacks that were needed for the old pattern.
4571

4672
**Full Changelog**: https://github.com/pdphilip/laravel-elasticsearch/compare/v5.5.3...v5.6.0
4773

@@ -50,7 +76,7 @@ This release is compatible with Laravel 11, 12 & 13
5076
### Fixed
5177

5278
- `distinct()`, `bulkDistinct()`, and `groupBy()` now work on nested fields (e.g., `distinct('tags.key', true)`). Previously these returned empty results because the compiled DSL lacked the required `nested` aggregation wrapper. The package
53-
now auto-detects nested mappings and wraps aggregations accordingly no changes needed in userland code.
79+
now auto-detects nested mappings and wraps aggregations accordingly - no changes needed in userland code.
5480
- When `whereNestedObject()` is combined with `distinct()` on the same nested path, the nested filter is injected inside the aggregation context so that only matching sub-documents are aggregated.
5581

5682
**Full Changelog**: https://github.com/pdphilip/laravel-elasticsearch/compare/v5.5.2...v5.5.3
@@ -67,8 +93,8 @@ This release is compatible with Laravel 11, 12 & 13
6793

6894
### Added
6995

70-
- `Schema::compileMapping()` compile a Blueprint callback into its resulting ES mapping structure without creating the index. Useful for debugging and previewing what `mappingDefinition()` will produce.
71-
- `Grammar::compileMapping()` public access to the Blueprint-to-properties compilation pipeline.
96+
- `Schema::compileMapping()` - compile a Blueprint callback into its resulting ES mapping structure without creating the index. Useful for debugging and previewing what `mappingDefinition()` will produce.
97+
- `Grammar::compileMapping()` - public access to the Blueprint-to-properties compilation pipeline.
7298

7399
### Fixed
74100

@@ -86,7 +112,7 @@ This release is compatible with Laravel 10, 11 & 12
86112

87113
#### Automated Re-indexing Command
88114

89-
New `elastic:re-index` command that automates the entire re-indexing process when your field mappings change. Pass a model name and the command handles the rest creating a temp index, copying data, verifying counts, swapping, and cleaning
115+
New `elastic:re-index` command that automates the entire re-indexing process when your field mappings change. Pass a model name and the command handles the rest - creating a temp index, copying data, verifying counts, swapping, and cleaning
90116
up across 9 interactive phases with confirmation prompts between each step. - [Docs](https://elasticsearch.pdphilip.com/schema/artisan-commands/#elasticre-index)
91117

92118
```bash
@@ -97,7 +123,7 @@ php artisan elastic:re-index "App\Models\ES\UserLog"
97123
Features include:
98124

99125
- Smart mapping analysis that detects type mismatches and sub-field changes (e.g., adding `hasKeyword: true`)
100-
- Resume capability interrupted runs pick up where they left off
126+
- Resume capability - interrupted runs pick up where they left off
101127
- Configurable tolerance and retry settings
102128
- `--force` flag to skip all confirmation prompts
103129

@@ -286,7 +312,7 @@ Why: Massive performance gains vs running sequential distinct queries.
286312
`groupByRanges()` performs a [range aggregation](https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-range-aggregation) on the specified
287313
field. - [Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-ranges)
288314

289-
`groupByRanges()->get()` return bucketed results - [Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-ranges)
315+
`groupByRanges()->get()` - return bucketed results - [Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-ranges)
290316

291317
`groupByRanges()->agg()` - apply metric aggregations per bucket -[Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-ranges-with-aggregations)
292318

@@ -295,9 +321,9 @@ field. - [Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-ra
295321
`groupByDateRanges()` performs a [date range aggregation](https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-daterange-aggregation) on the specified
296322
field. - [Docs](https://elasticsearch.pdphilip.com/eloquent/distinct/#groupby-date-ranges)
297323

298-
`groupByDateRanges()->get()` bucketed date ranges
324+
`groupByDateRanges()->get()` - bucketed date ranges
299325

300-
`groupByDateRanges()->agg()` metrics per date bucket
326+
`groupByDateRanges()->agg()` - metrics per date bucket
301327

302328
#### Model Meta Accessor
303329

@@ -334,7 +360,7 @@ This release is compatible with Laravel 10, 11 & 12
334360
This release introduces Query String Queries, bringing full Elasticsearch `query_string` syntax support directly into your Eloquent-style queries.
335361

336362
- Method: `searchQueryString(query, $fields = null, $options = [])` and related methods (`orSearchQueryString`, `searchNotQueryString`, etc.)
337-
- Supports all `query_string` features logical operators, wildcards, fuzziness, ranges, regex, boosting, field scoping, and more
363+
- Supports all `query_string` features - logical operators, wildcards, fuzziness, ranges, regex, boosting, field scoping, and more
338364
- Includes a dedicated `QueryStringOptions` class for fluent option configuration or array-based parameters
339365
- [See Tests](https://github.com/pdphilip/laravel-elasticsearch/blob/main/tests/QueryStringTest.php)
340366
- [Full documentation](https://elasticsearch.pdphilip.com/eloquent/query-string-queries/)

src/Data/QueryMeta.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function __construct(?MetaDTO $meta = null)
2020
}
2121

2222
// ----------------------------------------------------------------------
23-
// Getters delegate to MetaDTO
23+
// Getters - delegate to MetaDTO
2424
// ----------------------------------------------------------------------
2525

2626
public function getIndex(): string

src/Eloquent/ElasticCollection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* @template TKey of array-key
1212
* @template TModel of \PDPhilip\Elasticsearch\Eloquent\Model
1313
*
14-
* @extends \Illuminate\Database\Eloquent\Collection<TKey, TModel>
14+
* @extends Collection<TKey, TModel>
1515
*/
1616
class ElasticCollection extends Collection
1717
{

src/Eloquent/GeneratesTimeOrderedIds.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* Generates time-ordered, sortable IDs for Elasticsearch models.
1313
*
1414
* Use this trait when you need IDs that sort chronologically across
15-
* multiple processes/workers ideal for high-volume APIs where
15+
* multiple processes/workers - ideal for high-volume APIs where
1616
* time-sequenced analytics matter.
1717
*
1818
* IDs are 20 characters, URL-safe, and sort lexicographically in

src/Query/Concerns/BuildsFieldQueries.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
* ES-specific field queries: term, match, phrase, fuzzy, regex, prefix.
99
*
1010
* Each query type provides four variants:
11-
* where{Type}() AND match
12-
* orWhere{Type}() OR match
13-
* whereNot{Type}() AND NOT match
14-
* orWhereNot{Type}() OR NOT match
11+
* where{Type}() - AND match
12+
* orWhere{Type}() - OR match
13+
* whereNot{Type}() - AND NOT match
14+
* orWhereNot{Type}() - OR NOT match
1515
*
1616
* Aliases:
1717
* whereExact* → whereTerm*
@@ -85,7 +85,7 @@ public function orWhereNotExact($column, $value, $options = []): self
8585
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html
8686
//
8787
// Named whereFieldExists (not whereExists) to avoid colliding with
88-
// Laravel's base Builder::whereExists(Closure $callback) the SQL
88+
// Laravel's base Builder::whereExists(Closure $callback) - the SQL
8989
// EXISTS subquery clause, which has entirely different semantics.
9090
//
9191
// Works on top-level fields and on nested-mapped fields: the compiler

src/Query/Concerns/BuildsNestedQueries.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public function filterNested($column, $query, $options = [])
139139
public function whereNestedFieldExists(string $path, string $boolean = 'and', bool $not = false): self
140140
{
141141
// Stored as 'path' (not 'column') to bypass the where-loop's automatic
142-
// parent-field prepending the path here is the nested path itself,
142+
// parent-field prepending - the path here is the nested path itself,
143143
// not a field inside a nested context.
144144
$this->wheres[] = [
145145
'type' => 'NestedFieldExists',

src/Query/Concerns/BuildsSearchQueries.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
* Multi-field search queries for Elasticsearch (multi_match + query_string).
1313
*
1414
* All convenience methods delegate to search() with different multi_match types:
15-
* searchTerm* best_fields (highest score from best-matching field)
16-
* searchTermMost* most_fields (combined score from all matching fields)
17-
* searchTermCross* cross_fields (treats fields as one combined field)
18-
* searchPhrase* phrase (exact phrase match)
19-
* searchPhrasePrefix* phrase_prefix (phrase + prefix on last term)
20-
* searchBoolPrefix* bool_prefix (search-as-you-type)
21-
* searchFuzzy* best_fields + fuzziness:AUTO
22-
* searchFuzzyPrefix* bool_prefix + fuzziness:AUTO
23-
* searchQueryString* query_string (Lucene query syntax)
15+
* searchTerm* - best_fields (highest score from best-matching field)
16+
* searchTermMost* - most_fields (combined score from all matching fields)
17+
* searchTermCross* - cross_fields (treats fields as one combined field)
18+
* searchPhrase* - phrase (exact phrase match)
19+
* searchPhrasePrefix* - phrase_prefix (phrase + prefix on last term)
20+
* searchBoolPrefix* - bool_prefix (search-as-you-type)
21+
* searchFuzzy* - best_fields + fuzziness:AUTO
22+
* searchFuzzyPrefix* - bool_prefix + fuzziness:AUTO
23+
* searchQueryString* - query_string (Lucene query syntax)
2424
*
2525
* Each type has four variants: search{Type}, orSearch{Type}, searchNot{Type}, orSearchNot{Type}
2626
*

src/Query/Grammar/Concerns/FieldUtilities.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ protected function resolveNestedPaths(Builder $builder): array
171171
}
172172
}
173173
} catch (\Exception) {
174-
// Index may not exist yet no nested paths to detect
174+
// Index may not exist yet - no nested paths to detect
175175
}
176176

177177
$builder->options()->add($cacheKey, $nestedPaths);

src/Query/ManagesOptions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ protected function parseOptions($value, ?string $type = null)
162162

163163
return tap(new $class, $value)->toArray();
164164
} catch (Exception $e) {
165-
// Callable didn't match expected option signature return empty options
165+
// Callable didn't match expected option signature - return empty options
166166
return [];
167167
}
168168
}

src/Query/Processor/Processor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public function processAggregations(Builder $query, $result)
6767
$response = $this->getRawResponse();
6868
$this->rawAggregations = $response['aggregations'] ?? [];
6969

70-
// Extract after_key may be inside a nested agg wrapper
70+
// Extract after_key - may be inside a nested agg wrapper
7171
$groupByAggs = $this->unwrapNestedAggregation($this->rawAggregations, 'group_by');
7272
if (! empty($groupByAggs['group_by']['after_key'])) {
7373
$this->query->getMetaTransfer()->set('after_key', $groupByAggs['group_by']['after_key']);

0 commit comments

Comments
 (0)