From 6ba7a5834c4191c2d197b8e5e710fc2d50787354 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 27 Sep 2026 18:45:08 +0530 Subject: [PATCH 1/4] Upgrade plugin to CakePHP 6 Dependencies: - cakephp/cakephp ^6.0, php >=8.4, cakephp-codesniffer ^6.0, phpunit ^13.0, rector ~2.6.0 (plus minimum-stability dev). Source changes (cakephp60 rector set + manual follow-up): - Entity access API renamed by Cake 6: setAccess()/accessibleFields became setPatchable()/patchableFields (Marshaller option included). - QueryInterface methods now declare `static` return types; Query::order() removed, applyOptions() maps `order` to orderBy(). - Finder option arrays are rejected with an InvalidArgumentException pointing at named arguments instead of being deprecated. - FixtureInterface::insert()/truncate() return void. - Document stores the Elastica result in $searchResult, and its clean path writes values through setRawValue() so declared fields stay initialised. - Index::saveMany() assigns the ES id/version with set() instead of direct property writes (Property PHPStan error on EntityInterface). - ValidationRule is readonly in Cake 6: DocumentContext reads ->name/->pass. - Plugin classes drop the underscore prefix convention (Query, Index, Connection, Marshaller, DocumentContext, IsUnique, ElasticLogger, ...); dynamic ES fields such as `_version` are left untouched. - Test plugin classes added for TestPlugin/TestPluginTwo: Cake 6 removed the BasePlugin fallback in PluginCollection::create(). Tooling: - rector.php targets PHP 8.4 and skips rector 2.5/2.6 docblock removal rules, mirroring debug_kit, so `composer rector-check` stays green without losing `@return $this` that PHPStan needs for fluent interfaces. CI/docs: - ci.yml pushes to 6.x, PHP matrix 8.4/8.5, prefer-lowest on 8.4. - docs-validation uses the cakephp/.github 6.x workflow. - deploy_docs_6x.yml added alongside the other major-version deployers. - README, docs version references and a new 6.0 upgrade guide. Tests: 472 tests, 1375 assertions, 0 failures/errors against Elasticsearch 9.1.5; phpcs and phpstan are clean. --- .github/workflows/ci.yml | 6 +- .github/workflows/deploy_docs_6x.yml | 28 ++ .github/workflows/docs-validation.yml | 6 +- README.md | 13 +- composer.json | 13 +- docs/.vitepress/toc_en.json | 1 + docs/en/6-0-upgrade-guide.md | 114 +++++++ docs/en/index.md | 4 +- docs/en/installation.md | 2 +- docs/ja/index.md | 2 +- rector.php | 16 +- src/Association/Embedded.php | 8 +- src/Datasource/Connection.php | 48 ++- src/Datasource/IndexLocator.php | 4 +- src/Datasource/IndexLocatorAwareTrait.php | 12 +- src/Datasource/Log/ElasticLogger.php | 18 +- src/Document.php | 28 +- src/Exception/MissingDocumentException.php | 2 +- src/Exception/MissingIndexClassException.php | 2 +- src/Exception/NotImplementedException.php | 2 +- src/Index.php | 88 +++--- src/Marshaller.php | 58 ++-- src/Query.php | 292 +++++++++--------- src/QueryBuilder.php | 53 ++-- src/Rule/IsUnique.php | 8 +- src/TestSuite/TestFixture.php | 10 +- src/View/Form/DocumentContext.php | 56 ++-- tests/Fixture/ArticlesFixture.php | 3 + tests/Fixture/ProfilesFixture.php | 3 + .../Datasource/IndexLocatorAwareTraitTest.php | 6 +- tests/TestCase/MarshallerTest.php | 15 +- tests/TestCase/ResultSetTest.php | 1 - tests/TestCase/Rule/IsUniqueTest.php | 9 +- .../Fixture/DeleteQueryStrategyTest.php | 8 - tests/TestCase/TestSuite/TestFixtureTest.php | 18 +- tests/bootstrap.php | 4 +- .../TestPlugin/src/TestPluginPlugin.php | 26 ++ .../TestPluginTwo/src/TestPluginTwoPlugin.php | 26 ++ .../src/Model/Document/ProtectedArticle.php | 2 +- .../TestApp/src/Model/Index/MyUsersIndex.php | 2 +- 40 files changed, 592 insertions(+), 425 deletions(-) create mode 100644 .github/workflows/deploy_docs_6x.yml create mode 100644 docs/en/6-0-upgrade-guide.md create mode 100644 tests/testapp/Plugin/TestPlugin/src/TestPluginPlugin.php create mode 100644 tests/testapp/Plugin/TestPluginTwo/src/TestPluginTwoPlugin.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 681124be..247685fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - 5.x + - 6.x pull_request: branches: - '*' @@ -18,10 +18,10 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1', '8.2', '8.3', '8.4', '8.5'] + php-version: ['8.4', '8.5'] prefer-lowest: [''] include: - - php-version: '8.1' + - php-version: '8.4' prefer-lowest: 'prefer-lowest' services: diff --git a/.github/workflows/deploy_docs_6x.yml b/.github/workflows/deploy_docs_6x.yml new file mode 100644 index 00000000..9280ba8e --- /dev/null +++ b/.github/workflows/deploy_docs_6x.yml @@ -0,0 +1,28 @@ +--- +name: 'deploy_docs_6x' + +on: + push: + branches: + - 6.x + workflow_dispatch: + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Cloning repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Push to dokku + uses: dokku/github-action@cc7dec1d2b9fed249a14ae462bc953bba436f78c # v1.10.0 + with: + git_remote_url: 'ssh://dokku@apps.cakephp.org:22/elasticsearch-docs-6' + git_push_flags: '-f' + ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }} + branch: '6.x' diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml index 439a52c1..6043b1f6 100644 --- a/.github/workflows/docs-validation.yml +++ b/.github/workflows/docs-validation.yml @@ -3,7 +3,7 @@ name: Documentation Validation on: push: branches: - - 5.x + - 6.x paths: - 'docs/**' - '.github/**' @@ -14,7 +14,7 @@ on: jobs: validate: - uses: cakephp/.github/.github/workflows/docs-validation.yml@5.x + uses: cakephp/.github/.github/workflows/docs-validation.yml@6.x with: docs-path: 'docs' vitepress-path: 'docs/.vitepress' @@ -24,4 +24,4 @@ jobs: enable-spell-check: true enable-markdown-lint: true enable-link-check: true - tools-ref: '5.x' + tools-ref: '6.x' diff --git a/README.md b/README.md index 22c57819..d5653953 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Elasticsearch Datasource for CakePHP -![Build Status](https://github.com/cakephp/elastic-search/actions/workflows/ci.yml/badge.svg?branch=5.x) +![Build Status](https://github.com/cakephp/elastic-search/actions/workflows/ci.yml/badge.svg?branch=6.x) [![Latest Stable Version](https://img.shields.io/github/v/release/cakephp/elastic-search?sort=semver&style=flat-square)](https://packagist.org/packages/cakephp/elastic-search) [![Total Downloads](https://img.shields.io/packagist/dt/cakephp/elastic-search?style=flat-square)](https://packagist.org/packages/cakephp/elastic-search/stats) -[![codecov](https://codecov.io/gh/cakephp/elastic-search/branch/5.x/graph/badge.svg?token=G3Tcg116OX)](https://app.codecov.io/gh/cakephp/elastic-search/tree/5.x) +[![codecov](https://codecov.io/gh/cakephp/elastic-search/branch/6.x/graph/badge.svg?token=G3Tcg116OX)](https://app.codecov.io/gh/cakephp/elastic-search/tree/6.x) [![PHPStan](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg?style=flat-square)](https://phpstan.org/) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -Use [Elastic Search](https://www.elastic.co/) as an alternative ORM backend in CakePHP 5.2+. +Use [Elastic Search](https://www.elastic.co/) as an alternative ORM backend in CakePHP 6.0+. You can [find the documentation for the plugin in the Cake Book](https://book.cakephp.org/elasticsearch). @@ -17,7 +17,7 @@ You can install Elasticsearch into your project using [composer](https://getcomposer.org). For existing applications you can run: ```bash -composer require cakephp/elastic-search:^5.0 +composer require cakephp/elastic-search:^6.0 ``` ### Versions Table @@ -30,8 +30,9 @@ composer require cakephp/elastic-search:^5.0 | [>=3.4.0](https://github.com/cakephp/elastic-search/tree/3.x) | 4.0+ | 7.x | 7.x | | [4.x](https://github.com/cakephp/elastic-search/tree/4.x) | 5.0+ | 7.x | 7.x | | [5.x](https://github.com/cakephp/elastic-search/tree/5.x) | 5.2+ | 9.x | 9.x | +| [6.x](https://github.com/cakephp/elastic-search/tree/6.x) | 6.0+ | 9.x | 9.x | -You are seeing the 5.x version. +You are seeing the 6.x version. ## Connecting the Plugin to your Application @@ -42,7 +43,7 @@ use Cake\ElasticSearch\Plugin as ElasticSearchPlugin; class Application extends BaseApplication { - public function bootstrap() + public function bootstrap(): void { $this->addPlugin(ElasticSearchPlugin::class); diff --git a/composer.json b/composer.json index 5b1e6bf2..307e2aea 100644 --- a/composer.json +++ b/composer.json @@ -18,12 +18,13 @@ "source": "https://github.com/cakephp/elastic-search" }, "require": { - "cakephp/cakephp": "^5.2.0", + "php": ">=8.4", + "cakephp/cakephp": "^6.0", "ruflin/elastica": "^9.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^5.0", - "phpunit/phpunit": "^10.5.5 || ^11.5.3 || ^12.1.3", + "cakephp/cakephp-codesniffer": "^6.0", + "phpunit/phpunit": "^13.0", "nyholm/psr7": "^1.8" }, "autoload": { @@ -56,8 +57,10 @@ "test-coverage": "phpunit --coverage-clover=clover.xml", "phpstan": "vendor/bin/phpstan analyse", "phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^2.0 --no-update && composer update && mv composer.backup composer.json", - "rector-setup": "cp composer.json composer.backup && composer require --dev rector/rector:\"^2.2\" && mv composer.backup composer.json", + "rector-setup": "cp composer.json composer.backup && composer require --dev rector/rector:\"~2.6.0\" && mv composer.backup composer.json", "rector-check": "vendor/bin/rector process --dry-run", "rector-fix": "vendor/bin/rector process" - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/docs/.vitepress/toc_en.json b/docs/.vitepress/toc_en.json index 3915b418..847157b4 100644 --- a/docs/.vitepress/toc_en.json +++ b/docs/.vitepress/toc_en.json @@ -21,6 +21,7 @@ "text": "Migration", "collapsed": false, "items": [ + { "text": "6.0 Upgrade Guide", "link": "/6-0-upgrade-guide" }, { "text": "5.0 Upgrade Guide", "link": "/5-0-upgrade-guide" }, { "text": "4.0 Upgrade Guide", "link": "/4-0-upgrade-guide" }, { "text": "3.0 Upgrade Guide", "link": "/3-0-upgrade-guide" } diff --git a/docs/en/6-0-upgrade-guide.md b/docs/en/6-0-upgrade-guide.md new file mode 100644 index 00000000..5a190fb3 --- /dev/null +++ b/docs/en/6-0-upgrade-guide.md @@ -0,0 +1,114 @@ +# 6.0 Upgrade Guide + +::: warning Requirements +CakePHP ElasticSearch `6.x` requires CakePHP `6.0+`, Elasticsearch `9.x`, Elastica `9.x`, and PHP `8.4+`. +::: + +## Requirements + +- CakePHP `6.0+` +- Elasticsearch `9.x` +- Elastica `9.x` +- PHP `8.4+` + +## Breaking Changes + +Version `6.x` ports the plugin to CakePHP `6.x` and drops all CakePHP `5.x` support. +The Elasticsearch, Elastica and PHP version requirements are unchanged from `5.x`, +apart from the PHP version bump required by CakePHP `6`. + +### Updated dependencies + +Update your application requirements: + +```bash +composer require cakephp/elastic-search:^6.0 +``` + +- `cakephp/cakephp` is now `^6.0`. +- `php` is now `>=8.4`. + +### Entity access rules + +`Cake\Datasource\EntityTrait` renamed its access control API: + +| CakePHP 5.x | CakePHP 6.x | +| ------------------------------- | ------------------------------- | +| `$entity->setAccess(...)` | `$entity->setPatchable(...)` | +| `$entity->getAccessible()` | `$entity->getPatchable()` | +| `$entity->isAccessible(...)` | `$entity->isPatchable(...)` | +| `'accessibleFields' => [...]` | `'patchableFields' => [...]` | + +The `Marshaller` options array follows the same rename: + +```php +// Before (5.x) +$entity = $this->Articles->marshallOne($data, ['accessibleFields' => ['title' => false]]); + +// After (6.x) +$entity = $this->Articles->marshallOne($data, ['patchableFields' => ['title' => false]]); +``` + +### Query changes + +- `Query::order()` has been removed. Use `Query::orderBy()` instead. +- Finders no longer accept a positional options array. Pass finder options as + named arguments; passing an array now throws an `InvalidArgumentException`: + +```php +// Before (5.x, deprecated) +$this->Articles->find('all', ['limit' => 10]); + +// After (6.x) +$this->Articles->find('all', limit: 10); +``` + +- Fluent query methods (`select()`, `where()`, `limit()`, `offset()`, `page()`, + `orderBy()`, `applyOptions()`, `setRepository()`, ...) now declare a `static` + return type, as required by `Cake\Datasource\QueryInterface`. + +### Fixtures + +`Cake\Datasource\FixtureInterface::insert()` and `truncate()` now return `void`. +If you have custom fixtures that implement or extend `Cake\ElasticSearch\TestSuite\TestFixture`, +update their signatures accordingly: + +```php +public function insert(ConnectionInterface $connection): void +{ +} +``` + +### Subclassing `Cake\ElasticSearch\Document` + +CakePHP `6` removed the underscore prefix convention. Plugin classes follow the +same rule, so protected members that custom subclasses may rely on were renamed: + +| 5.x | 6.x | +| -------------------- | ---------------- | +| `Document::$_result` | `Document::$searchResult` | + +`$searchResult` holds the `Elastica\Result` the document was hydrated from and +backs `index()`, `version()`, `highlights()` and `explanation()`. + +The same convention applies to `Query`, `Index`, `Connection`, `Marshaller` and +friends - for example `Query::$_queryParts` is now `Query::$queryParts`. + +### Test suite + +- `Index` and `Connection` fluent setters (`setConnection()`, `setName()`, + `setEntityClass()`, `setCacher()`, `setLogger()`, `enableQueryLogging()`, ...) + now declare a `static` return type. + +## Recommended Migration Steps + +1. Update `composer.json` to require `cakephp/elastic-search:^6.0` and run `composer update`. +2. Upgrade your application to CakePHP `6.0` and PHP `8.4` by following the + [CakePHP 6 migration guide](https://book.cakephp.org/6.x/appendices/6-0-migration-guide.html). +3. Replace `setAccess()`/`getAccessible()`/`isAccessible()` calls with their + `setPatchable()`/`getPatchable()`/`isPatchable()` equivalents. +4. Rename the `accessibleFields` marshaller option to `patchableFields`. +5. Replace `Query::order()` with `Query::orderBy()` and convert finder option + arrays to named arguments. +6. Update custom fixture `insert()`/`truncate()` signatures to `void`. +7. Re-run your test suite against Elasticsearch `9.x`. diff --git a/docs/en/index.md b/docs/en/index.md index 5c56f874..f618f8cb 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -2,7 +2,7 @@ The ElasticSearch plugin provides an ORM-like abstraction for working with [Elasticsearch](https://www.elastic.co/products/elasticsearch) indexes in CakePHP. It helps with indexing, searching, validation, embedded documents, and fixture-backed tests. -Version `5.x` targets CakePHP `5.2+`, Elasticsearch `9.x`, Elastica `9.x`, and PHP `8.1+`. +Version `6.x` targets CakePHP `6.0+`, Elasticsearch `9.x`, Elastica `9.x`, and PHP `8.4+`. ## Documentation Map @@ -11,4 +11,4 @@ Version `5.x` targets CakePHP `5.2+`, Elasticsearch `9.x`, Elastica `9.x`, and P - [Searching and Saving](/searching-and-saving) covers finding, validating, saving, updating, bulk operations, and deleting documents. - [Embedded Documents](/embedded-documents) explains `embedOne()` and `embedMany()` for nested document objects. - [Testing](/testing) shows how to load mappings and fixtures for integration tests. -- [3.0 Upgrade Guide](/3-0-upgrade-guide), [4.0 Upgrade Guide](/4-0-upgrade-guide), and [5.0 Upgrade Guide](/5-0-upgrade-guide) describe version-specific migration changes. +- [6.0 Upgrade Guide](/6-0-upgrade-guide), [5.0 Upgrade Guide](/5-0-upgrade-guide), [4.0 Upgrade Guide](/4-0-upgrade-guide), and [3.0 Upgrade Guide](/3-0-upgrade-guide) describe version-specific migration changes. diff --git a/docs/en/installation.md b/docs/en/installation.md index 55ec6afe..11b7f2df 100644 --- a/docs/en/installation.md +++ b/docs/en/installation.md @@ -3,7 +3,7 @@ Install the plugin with Composer from your application's root directory: ```bash -composer require cakephp/elastic-search:^5.0 +composer require cakephp/elastic-search:^6.0 ``` Load the plugin in your application bootstrap: diff --git a/docs/ja/index.md b/docs/ja/index.md index b1fae161..624778d8 100644 --- a/docs/ja/index.md +++ b/docs/ja/index.md @@ -7,7 +7,7 @@ ElasticSearch プラグインは、[Elasticsearch](https://www.elastic.co/produc アプリケーションのルートディレクトリーで Composer を使ってインストールします。 ```bash -composer require cakephp/elastic-search:^5.0 +composer require cakephp/elastic-search:^6.0 ``` `src/Application.php` でプラグインを読み込みます。 diff --git a/rector.php b/rector.php index e728216d..f2b3d594 100644 --- a/rector.php +++ b/rector.php @@ -1,24 +1,28 @@ withPhpVersion(PhpVersion::PHP_83) + ->withPhpVersion(PhpVersion::PHP_84) ->withPaths([ __DIR__ . '/src', __DIR__ . '/tests', ]) ->withSkip([ - // CakePHP coding standards compatibility - DisallowedEmptyRuleFixerRector::class, - SimplifyIfElseToTernaryRector::class, // Skip visibility changes that might break inheritance MakeInheritedMethodVisibilitySameAsParentRector::class, + // Docblock removal rules added in rector 2.5/2.6. They are skipped to keep + // the diff behavior-neutral, and because `@return $this` is still needed for + // PHPStan to track the fluent interfaces declared by CakePHP interfaces. + RemoveDuplicatedReturnSelfDocblockRector::class, + RemoveUselessReturnTagRector::class, + RemoveUselessUnionReturnDocblockRector::class, ]) ->withParallel() ->withPreparedSets( diff --git a/src/Association/Embedded.php b/src/Association/Embedded.php index 6dd80929..d6599145 100644 --- a/src/Association/Embedded.php +++ b/src/Association/Embedded.php @@ -101,7 +101,7 @@ public function getProperty(): string * @param string|null $name The property name to set. * @return $this */ - public function setProperty(?string $name = null) + public function setProperty(?string $name = null): static { if ($name !== null) { $this->property = $name; @@ -149,7 +149,7 @@ public function getEntityClass(): string * @param string $name The name of the class to use * @return $this */ - public function setEntityClass(string $name) + public function setEntityClass(string $name): static { $class = App::className($name, 'Model/Document'); $this->entityClass = $class ?? Document::class; @@ -186,7 +186,7 @@ public function getIndexClass(): string * @throws \InvalidArgumentException In case the class name is set after the target index has been * resolved, and it doesn't match the target index's class name. */ - public function setIndexClass(string|Index|null $className) + public function setIndexClass(string|Index|null $className): static { if ($className instanceof Index) { $this->index = $className; @@ -219,7 +219,7 @@ public function setIndexClass(string|Index|null $className) * @param \Cake\ElasticSearch\Index $index the instance to be assigned as target side * @return $this */ - public function setIndex(Index $index) + public function setIndex(Index $index): static { $this->index = $index; diff --git a/src/Datasource/Connection.php b/src/Datasource/Connection.php index 6a289222..dce22d88 100644 --- a/src/Datasource/Connection.php +++ b/src/Datasource/Connection.php @@ -35,12 +35,12 @@ */ class Connection implements ConnectionInterface { - protected array $_config; + protected array $config; /** * Compatibility shim for ConnectionInterface */ - public const ROLE_WRITE = 'write'; + public const string ROLE_WRITE = 'write'; /** * Whether or not query logging is enabled. @@ -60,17 +60,17 @@ class Connection implements ConnectionInterface /** * Elastica client instance */ - protected ElasticaClient $_client; + protected ElasticaClient $client; /** * Logger object instance. */ - protected QueryLogger|LoggerInterface $_logger; + protected QueryLogger|LoggerInterface $logger; /** * Instance of ElasticLogger */ - protected ElasticLogger $_esLogger; + protected ElasticLogger $esLogger; protected CacheInterface $cacher; @@ -104,9 +104,9 @@ public function __construct(array $config = []) ); } - $this->_config = $config; + $this->config = $config; $logger = $this->logQueries ? $this->getEsLogger() : null; - $this->_client = new ElasticaClient($config, $logger); + $this->client = new ElasticaClient($config, $logger); } /** @@ -118,8 +118,8 @@ public function __construct(array $config = []) */ public function __call(string $name, array $attributes): mixed { - if (method_exists($this->_client, $name)) { - $callback = [$this->_client, $name]; + if (method_exists($this->client, $name)) { + $callback = [$this->client, $name]; assert(is_callable($callback)); return call_user_func_array($callback, $attributes); @@ -151,7 +151,7 @@ public function configName(): string * @param bool $enable Enable/disable query logging * @return $this */ - public function enableQueryLogging(bool $enable = true) + public function enableQueryLogging(bool $enable = true): static { $this->logQueries = $enable; @@ -160,10 +160,8 @@ public function enableQueryLogging(bool $enable = true) /** * Disable query logging - * - * @return $this */ - public function disableQueryLogging() + public function disableQueryLogging(): static { $this->logQueries = false; @@ -199,7 +197,7 @@ public function disableConstraints(callable $operation) */ public function config(): array { - return $this->_config; + return $this->config; } /** @@ -208,9 +206,9 @@ public function config(): array * @param \Cake\Database\Log\QueryLogger|\Psr\Log\LoggerInterface $logger Logger instance * @return $this */ - public function setLogger(QueryLogger|LoggerInterface $logger) + public function setLogger(QueryLogger|LoggerInterface $logger): static { - $this->_logger = $logger; + $this->logger = $logger; $this->getEsLogger()->setLogger($logger); return $this; @@ -225,7 +223,7 @@ public function setLogger(QueryLogger|LoggerInterface $logger) */ public function getLogger(): LoggerInterface { - if (!isset($this->_logger)) { + if (!isset($this->logger)) { $loggerName = $this->loggerName; $engine = Log::engine($loggerName) ?: Log::engine('debug'); @@ -236,7 +234,7 @@ public function getLogger(): LoggerInterface $this->setLogger($engine); } - return $this->_logger; + return $this->logger; } /** @@ -244,17 +242,17 @@ public function getLogger(): LoggerInterface */ public function getEsLogger(): ElasticLogger { - if (!isset($this->_esLogger)) { - $this->_esLogger = new ElasticLogger($this->getLogger(), $this); + if (!isset($this->esLogger)) { + $this->esLogger = new ElasticLogger($this->getLogger(), $this); } - return $this->_esLogger; + return $this->esLogger; } /** * @inheritDoc */ - public function setCacher(CacheInterface $cacher) + public function setCacher(CacheInterface $cacher): static { $this->cacher = $cacher; @@ -270,7 +268,7 @@ public function getCacher(): CacheInterface return $this->cacher; } - $configName = $this->_config['cacheMetadata'] ?? '_cake_model_'; + $configName = $this->config['cacheMetadata'] ?? '_cake_model_'; if (!is_string($configName)) { $configName = '_cake_model_'; } @@ -292,7 +290,7 @@ public function getCacher(): CacheInterface */ public function getDriver(string $role = self::ROLE_WRITE): ElasticaClient { - return $this->_client; + return $this->client; } /** @@ -306,6 +304,6 @@ public function getIndex(?string $name = null): Index { $defaultIndex = $this->config()['index'] ?? $this->configName; - return $this->_client->getIndex($name ?: $defaultIndex); + return $this->client->getIndex($name ?: $defaultIndex); } } diff --git a/src/Datasource/IndexLocator.php b/src/Datasource/IndexLocator.php index 6981ecf9..2524284e 100644 --- a/src/Datasource/IndexLocator.php +++ b/src/Datasource/IndexLocator.php @@ -53,7 +53,7 @@ class IndexLocator extends AbstractLocator * @return $this * @psalm-param class-string<\Cake\ElasticSearch\Index> $className */ - public function setFallbackClassName(string $className) + public function setFallbackClassName(string $className): static { $this->fallbackClassName = $className; @@ -69,7 +69,7 @@ public function setFallbackClassName(string $className) * @param bool $allow Flag to enable or disable fallback * @return $this */ - public function allowFallbackClass(bool $allow = true) + public function allowFallbackClass(bool $allow = true): static { $this->allowFallbackClass = $allow; diff --git a/src/Datasource/IndexLocatorAwareTrait.php b/src/Datasource/IndexLocatorAwareTrait.php index 58f69a81..f337b17e 100644 --- a/src/Datasource/IndexLocatorAwareTrait.php +++ b/src/Datasource/IndexLocatorAwareTrait.php @@ -34,7 +34,7 @@ trait IndexLocatorAwareTrait /** * Index locator instance */ - protected ?LocatorInterface $_indexLocator = null; + protected ?LocatorInterface $indexLocator = null; /** * Sets the index locator. @@ -42,9 +42,9 @@ trait IndexLocatorAwareTrait * @param \Cake\Datasource\Locator\LocatorInterface $indexLocator LocatorInterface instance. * @return $this */ - public function setIndexLocator(LocatorInterface $indexLocator) + public function setIndexLocator(LocatorInterface $indexLocator): static { - $this->_indexLocator = $indexLocator; + $this->indexLocator = $indexLocator; return $this; } @@ -54,8 +54,8 @@ public function setIndexLocator(LocatorInterface $indexLocator) */ public function getIndexLocator(): LocatorInterface { - if ($this->_indexLocator !== null) { - return $this->_indexLocator; + if ($this->indexLocator !== null) { + return $this->indexLocator; } $locator = FactoryLocator::get('Elastic'); @@ -64,7 +64,7 @@ public function getIndexLocator(): LocatorInterface '`FactoryLocator` must return an instance of Cake\Datasource\Locator\LocatorInterface for type `Elastic`.', ); - return $this->_indexLocator = $locator; + return $this->indexLocator = $locator; } /** diff --git a/src/Datasource/Log/ElasticLogger.php b/src/Datasource/Log/ElasticLogger.php index 373f484a..c0f5242b 100644 --- a/src/Datasource/Log/ElasticLogger.php +++ b/src/Datasource/Log/ElasticLogger.php @@ -34,12 +34,12 @@ class ElasticLogger extends AbstractLogger /** * Holds the logger instance */ - protected QueryLogger|LoggerInterface $_logger; + protected QueryLogger|LoggerInterface $logger; /** * Holds the connection instance */ - protected Connection $_connection; + protected Connection $connection; /** * Constructor, set the QueryLogger instance @@ -50,7 +50,7 @@ class ElasticLogger extends AbstractLogger public function __construct(QueryLogger|LoggerInterface $logger, Connection $connection) { $this->setLogger($logger); - $this->_connection = $connection; + $this->connection = $connection; } /** @@ -59,9 +59,9 @@ public function __construct(QueryLogger|LoggerInterface $logger, Connection $con * @param \Cake\Database\Log\QueryLogger|\Psr\Log\LoggerInterface $logger Set logger instance to pass logging data to * @return $this */ - public function setLogger(QueryLogger|LoggerInterface $logger) + public function setLogger(QueryLogger|LoggerInterface $logger): static { - $this->_logger = $logger; + $this->logger = $logger; return $this; } @@ -71,7 +71,7 @@ public function setLogger(QueryLogger|LoggerInterface $logger) */ public function getLogger(): QueryLogger|LoggerInterface { - return $this->_logger; + return $this->logger; } /** @@ -85,8 +85,8 @@ public function getLogger(): QueryLogger|LoggerInterface */ public function log(mixed $level, Stringable|string $message, array $context = []): void { - if ($this->_connection->isQueryLoggingEnabled() && $level === LogLevel::DEBUG) { - $this->_log($level, (string)$message, $context); + if ($this->connection->isQueryLoggingEnabled() && $level === LogLevel::DEBUG) { + $this->logQuery($level, (string)$message, $context); } } @@ -107,7 +107,7 @@ public function log(mixed $level, Stringable|string $message, array $context = [ * @param string $message The log message * @param array $context log context */ - protected function _log(string $level, string $message, array $context = []): void + protected function logQuery(string $level, string $message, array $context = []): void { // Only process if we have request data if (!isset($context['request']) || !is_array($context['request'])) { diff --git a/src/Document.php b/src/Document.php index 8b567a88..6de969c6 100644 --- a/src/Document.php +++ b/src/Document.php @@ -35,7 +35,7 @@ class Document implements EntityInterface, InvalidPropertyInterface * from a search query. It can contain extra information about this document * concerning the search operation, such as highlights, score and version. */ - protected Result $_result; + protected Result $searchResult; /** * Takes either an array or a Result object form a search and constructs @@ -75,14 +75,20 @@ public function __construct(Result|array $data = [], array $options = []) } if ($options['result'] !== null) { - $this->_result = $options['result']; + $this->searchResult = $options['result']; } if (count($data) > 0) { $this->setOriginalField(array_keys($data)); if ($options['markClean'] && !$options['useSetters']) { - $this->_fields = $data; + foreach ($data as $field => $value) { + $field = (string)$field; + $this->setRawValue($field, $value); + if (!array_key_exists($field, $this->dynamicFields)) { + $this->assignedProps[$field] = true; + } + } return; } @@ -106,8 +112,8 @@ public function __construct(Result|array $data = [], array $options = []) */ public function index(): ?string { - if (isset($this->_result)) { - return $this->_result->getIndex(); + if (isset($this->searchResult)) { + return $this->searchResult->getIndex(); } return null; @@ -120,8 +126,8 @@ public function index(): ?string */ public function version(): int { - if (isset($this->_result)) { - return intval($this->_result->getVersion()); + if (isset($this->searchResult)) { + return intval($this->searchResult->getVersion()); } return 1; @@ -136,8 +142,8 @@ public function version(): int */ public function highlights(): array { - if (isset($this->_result)) { - return $this->_result->getHighlights(); + if (isset($this->searchResult)) { + return $this->searchResult->getHighlights(); } return []; @@ -151,8 +157,8 @@ public function highlights(): array */ public function explanation(): array { - if (isset($this->_result)) { - return $this->_result->getExplanation(); + if (isset($this->searchResult)) { + return $this->searchResult->getExplanation(); } return []; diff --git a/src/Exception/MissingDocumentException.php b/src/Exception/MissingDocumentException.php index 1412c971..758a8af8 100644 --- a/src/Exception/MissingDocumentException.php +++ b/src/Exception/MissingDocumentException.php @@ -25,5 +25,5 @@ */ class MissingDocumentException extends MissingEntityException { - protected string $_messageTemplate = 'Document class %s could not be found.'; + protected string $messageTemplate = 'Document class %s could not be found.'; } diff --git a/src/Exception/MissingIndexClassException.php b/src/Exception/MissingIndexClassException.php index ea7cf53a..f0b65712 100644 --- a/src/Exception/MissingIndexClassException.php +++ b/src/Exception/MissingIndexClassException.php @@ -25,5 +25,5 @@ */ class MissingIndexClassException extends CakeException { - protected string $_messageTemplate = 'Index class %s could not be found.'; + protected string $messageTemplate = 'Index class %s could not be found.'; } diff --git a/src/Exception/NotImplementedException.php b/src/Exception/NotImplementedException.php index 5e34c21e..a1f7c79f 100644 --- a/src/Exception/NotImplementedException.php +++ b/src/Exception/NotImplementedException.php @@ -25,5 +25,5 @@ */ class NotImplementedException extends CakeException { - protected string $_messageTemplate = 'Method %s has not been implemented in this context.'; + protected string $messageTemplate = 'Method %s has not been implemented in this context.'; } diff --git a/src/Index.php b/src/Index.php index a0e44e3a..db28f60f 100644 --- a/src/Index.php +++ b/src/Index.php @@ -32,7 +32,6 @@ use Cake\Event\EventDispatcherTrait; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; -use Cake\Event\EventManagerInterface; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareTrait; use Closure; @@ -40,7 +39,6 @@ use InvalidArgumentException; use Psr\SimpleCache\CacheInterface; use RuntimeException; -use function Cake\Core\deprecationWarning; use function Cake\Core\namespaceSplit; /** @@ -48,14 +46,9 @@ * * A index in elastic search is approximately equivalent to a table or collection * in a relational datastore. This ODM maps each index to a class. - * - * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Index implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface { - /** - * @use \Cake\Event\EventDispatcherTrait<\Cake\ElasticSearch\Index> - */ use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; @@ -84,24 +77,24 @@ class Index implements RepositoryInterface, EventListenerInterface, EventDispatc /** * Connection instance */ - protected Connection $_connection; + protected Connection $connection; /** * The name of the Elasticsearch index this class represents */ - protected string $_name; + protected string $name; /** * Registry key used to create this index object */ - protected string $_registryAlias; + protected string $registryAlias; /** * The name of the class that represent a single document for this type * * @var class-string<\Cake\ElasticSearch\Document> */ - protected string $_documentClass; + protected string $documentClass; /** * Collection of Embedded sub documents this type has. @@ -146,11 +139,9 @@ public function __construct(array $config = []) $eventManager = $config['eventManager']; } - $this->_eventManager = $eventManager ?: new EventManager(); + $this->eventManager = $eventManager ?: new EventManager(); $this->initialize($config); - if ($this->_eventManager instanceof EventManagerInterface) { - $this->_eventManager->on($this); - } + $this->eventManager->on($this); $this->dispatchEvent('Model.initialize'); } @@ -218,9 +209,9 @@ public function embedded(): array * @param \Cake\ElasticSearch\Datasource\Connection $conn the new connection instance * @return $this */ - public function setConnection(Connection $conn) + public function setConnection(Connection $conn): static { - $this->_connection = $conn; + $this->connection = $conn; return $this; } @@ -230,7 +221,7 @@ public function setConnection(Connection $conn) */ public function getConnection(): Connection { - return $this->_connection; + return $this->connection; } /** @@ -239,9 +230,9 @@ public function getConnection(): Connection * @param string $registryAlias The key used to access this object. * @return $this */ - public function setRegistryAlias(string $registryAlias) + public function setRegistryAlias(string $registryAlias): static { - $this->_registryAlias = $registryAlias; + $this->registryAlias = $registryAlias; return $this; } @@ -251,11 +242,11 @@ public function setRegistryAlias(string $registryAlias) */ public function getRegistryAlias(): string { - if (!isset($this->_registryAlias)) { - $this->_registryAlias = $this->getAlias(); + if (!isset($this->registryAlias)) { + $this->registryAlias = $this->getAlias(); } - return $this->_registryAlias; + return $this->registryAlias; } /** @@ -264,9 +255,9 @@ public function getRegistryAlias(): string * @param string $name Index name * @return $this */ - public function setName(string $name) + public function setName(string $name): static { - $this->_name = $name; + $this->name = $name; return $this; } @@ -278,13 +269,13 @@ public function setName(string $name) */ public function getName(): string { - if (!isset($this->_name)) { + if (!isset($this->name)) { $name = namespaceSplit(static::class); $name = substr(end($name), 0, -5); - $this->_name = Inflector::underscore($name); + $this->name = Inflector::underscore($name); } - return $this->_name; + return $this->name; } /** @@ -303,7 +294,7 @@ public function getTable(): string * @param string $alias Index alias * @return $this */ - public function setAlias(string $alias) + public function setAlias(string $alias): static { return $this->setName($alias); } @@ -364,18 +355,15 @@ public function callFinder(string $type, Query $query, mixed ...$args): Query ); } - // Handle backward compatibility for array-based options - if (count($args) === 1 && isset($args[0]) && is_array($args[0])) { - deprecationWarning( - '5.0.0', - 'Calling finder methods with options arrays is deprecated. ' . - 'Use named arguments instead.', - ); - $options = $args[0]; - $query->applyOptions($options); - $options = $query->getOptions(); - - return $this->{$finder}($query, $options); + // A single positional array used to be the legacy options array, + // removed in 6.0 in favor of named arguments. + if (count($args) === 1 && array_key_exists(0, $args) && is_array($args[0])) { + throw new InvalidArgumentException(sprintf( + 'Passing an options array to the "%s" finder is not supported. ' . + "Use named arguments instead, for example find('%s', limit: 10).", + $type, + $type, + )); } // Convert named arguments to options array for the finder method @@ -593,8 +581,8 @@ public function saveMany(array $entities, array $options = []): bool $esIndex->addDocuments($documents, $requestParams); foreach ($documents as $key => $doc) { - $entities[$key]->id = $doc->getId(); - $entities[$key]->_version = $doc->getVersion(); + $entities[$key]->set('id', $doc->getId()); + $entities[$key]->set('_version', $doc->getVersion()); $entities[$key]->setNew(false); $entities[$key]->setSource($this->getRegistryAlias()); $entities[$key]->clean(); @@ -806,19 +794,19 @@ public function newEntities(array $data, array $options = []): array */ public function getEntityClass(): string { - if (!isset($this->_documentClass)) { + if (!isset($this->documentClass)) { $default = Document::class; $self = static::class; $parts = explode('\\', $self); if ($self === self::class || count($parts) < 3) { - return $this->_documentClass = $default; + return $this->documentClass = $default; } $alias = Inflector::classify(Inflector::underscore(substr(array_pop($parts), 0, -5))); $name = implode('\\', array_slice($parts, 0, -1)) . '\\Document\\' . $alias; if (!class_exists($name)) { - return $this->_documentClass = $default; + return $this->documentClass = $default; } /** @var class-string<\Cake\ElasticSearch\Document>|null $class */ @@ -828,10 +816,10 @@ public function getEntityClass(): string } /** @var class-string<\Cake\ElasticSearch\Document> $class */ - return $this->_documentClass = $class; + return $this->documentClass = $class; } - return $this->_documentClass; + return $this->documentClass; } /** @@ -841,7 +829,7 @@ public function getEntityClass(): string * @throws \Cake\ElasticSearch\Exception\MissingDocumentException when the entity class cannot be found * @return $this */ - public function setEntityClass(string $name) + public function setEntityClass(string $name): static { $class = App::className($name, 'Model/Document'); if (!$class) { @@ -849,7 +837,7 @@ public function setEntityClass(string $name) } /** @var class-string<\Cake\ElasticSearch\Document> $class */ - $this->_documentClass = $class; + $this->documentClass = $class; return $this; } diff --git a/src/Marshaller.php b/src/Marshaller.php index 6023508c..2445cdab 100644 --- a/src/Marshaller.php +++ b/src/Marshaller.php @@ -53,7 +53,7 @@ public function __construct(Index $index) * * - fieldList: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. - * - accessibleFields: A list of fields to allow or deny in entity accessible fields. + * - patchableFields: A list of fields to allow or deny in entity accessible fields. * - associated: A list of embedded documents you want to marshal. * * @param array $data The data to hydrate. @@ -63,17 +63,17 @@ public function one(array $data, array $options = []): Document { $options += ['associated' => []]; - [$data, $options] = $this->_prepareDataAndOptions($data, $options); + [$data, $options] = $this->prepareDataAndOptions($data, $options); $entity = $this->index->newEmptyEntity(); assert($entity instanceof Document); - if (isset($options['accessibleFields'])) { - foreach ((array)$options['accessibleFields'] as $key => $value) { - $entity->setAccess($key, $value); + if (isset($options['patchableFields'])) { + foreach ((array)$options['patchableFields'] as $key => $value) { + $entity->setPatchable($key, $value); } } - $errors = $this->_validate($data, $options, true); + $errors = $this->validate($data, $options, true); $entity->setErrors($errors); $properties = []; @@ -180,30 +180,30 @@ protected function mergeNested(Embedded $embed, Document|array|null $existing, a $existing->patch($data); return $existing; - } else { - if (!is_array($existing)) { - $existing = []; - } + } - foreach ($existing as $i => $row) { - if (isset($data[$i])) { - $row->patch($data[$i]); - } + if (!is_array($existing)) { + $existing = []; + } - unset($data[$i]); + foreach ($existing as $i => $row) { + if (isset($data[$i])) { + $row->patch($data[$i]); } - foreach ($data as $row) { - if (is_array($row)) { - $new = $index->newEmptyEntity(); - assert($new instanceof Document); - $new->patch($row); - $existing[] = $new; - } - } + unset($data[$i]); + } - return $existing; + foreach ($data as $row) { + if (is_array($row)) { + $new = $index->newEmptyEntity(); + assert($new instanceof Document); + $new->patch($row); + $existing[] = $new; + } } + + return $existing; } /** @@ -213,7 +213,7 @@ protected function mergeNested(Embedded $embed, Document|array|null $existing, a * * - fieldList: A whitelist of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. - * - accessibleFields: A list of fields to allow or deny in entity accessible fields. + * - patchableFields: A list of fields to allow or deny in entity accessible fields. * * @param array $data The data to hydrate. * @param array $options List of options @@ -250,10 +250,10 @@ public function many(array $data, array $options = []): array public function merge(EntityInterface $entity, array $data, array $options = []): EntityInterface { $options += ['associated' => []]; - [$data, $options] = $this->_prepareDataAndOptions($data, $options); + [$data, $options] = $this->prepareDataAndOptions($data, $options); $isNew = $entity->isNew(); - $errors = $this->_validate($data, $options, $isNew); + $errors = $this->validate($data, $options, $isNew); $entity->setErrors($errors); // Handle invalid fields @@ -357,7 +357,7 @@ public function mergeMany(iterable $entities, array $data, array $options = []): * @return array The list of validation errors. * @throws \RuntimeException If no validator can be created. */ - protected function _validate(array $data, array $options, bool $isNew): array + protected function validate(array $data, array $options, bool $isNew): array { if (!$options['validate']) { return []; @@ -390,7 +390,7 @@ protected function _validate(array $data, array $options, bool $isNew): array * @param array $options The options passed to this marshaller. * @return array An array containing prepared data and options. */ - protected function _prepareDataAndOptions(array $data, array $options): array + protected function prepareDataAndOptions(array $data, array $options): array { $options += ['validate' => true]; $data = new ArrayObject($data); diff --git a/src/Query.php b/src/Query.php index acebfe78..0100e054 100644 --- a/src/Query.php +++ b/src/Query.php @@ -28,6 +28,7 @@ use Elastica\Collapse; use Elastica\Query as ElasticaQuery; use Elastica\Query\AbstractQuery; +use Elastica\Query\BoolQuery; use InvalidArgumentException; use IteratorAggregate; use Psr\SimpleCache\CacheInterface; @@ -67,13 +68,13 @@ class Query implements IteratorAggregate, QueryInterface * * @var \Elastica\Query */ - protected ElasticaQuery $_elasticQuery; + protected ElasticaQuery $elasticQuery; /** * The various query builder parts that will * be transferred to the elastica query. */ - protected array $_queryParts = [ + protected array $queryParts = [ 'fields' => [], 'limit' => null, 'offset' => null, @@ -90,19 +91,19 @@ class Query implements IteratorAggregate, QueryInterface /** * Internal state to track whether or not the query has been modified. */ - protected bool $_dirty = false; + protected bool $dirty = false; /** * Additional options for Elastica\Index::search() * * @see \Elastica\Search::OPTION_SEARCH_* constants */ - protected array $_searchOptions = []; + protected array $searchOptions = []; /** * Instance of a repository object this query is bound to. */ - protected Index $_repository; + protected Index $repository; /** * A ResultSet. @@ -111,13 +112,13 @@ class Query implements IteratorAggregate, QueryInterface * * @see \Cake\Datasource\QueryTrait::setResult() */ - protected ?iterable $_results = null; + protected ?iterable $results = null; /** * List of map-reduce routines that should be applied over the query * result */ - protected array $_mapReduce = []; + protected array $mapReduce = []; /** * List of formatter classes or callbacks that will post-process the @@ -125,18 +126,18 @@ class Query implements IteratorAggregate, QueryInterface * * @var array<\Closure> */ - protected array $_formatters = []; + protected array $formatters = []; /** * A query cacher instance if this query has caching enabled. */ - protected ?QueryCacher $_cache = null; + protected ?QueryCacher $cache = null; /** * Holds any custom options passed using applyOptions that could not be processed * by any method in this class. */ - protected array $_options = []; + protected array $options = []; /** * Query constructor @@ -146,7 +147,7 @@ class Query implements IteratorAggregate, QueryInterface public function __construct(Index $repository) { $this->setRepository($repository); - $this->_elasticQuery = new ElasticaQuery(); + $this->elasticQuery = new ElasticaQuery(); } /** @@ -162,10 +163,10 @@ public function __construct(Index $repository) * @param bool $overwrite Whether or not to replace previous selections. * @return $this */ - public function select(Closure|array|string|int|float $fields, bool $overwrite = false) + public function select(Closure|array|string|int|float $fields, bool $overwrite = false): static { if (!$overwrite) { - $currentFields = $this->_queryParts['fields']; + $currentFields = $this->queryParts['fields']; if (!is_array($currentFields)) { $currentFields = []; } @@ -177,7 +178,7 @@ public function select(Closure|array|string|int|float $fields, bool $overwrite = $fields = array_merge($currentFields, $fields); } - $this->_queryParts['fields'] = $fields; + $this->queryParts['fields'] = $fields; return $this; } @@ -189,9 +190,9 @@ public function select(Closure|array|string|int|float $fields, bool $overwrite = * @param ?int $limit The number of documents to return. * @return $this */ - public function limit(?int $limit) + public function limit(?int $limit): static { - $this->_queryParts['limit'] = (int)$limit; + $this->queryParts['limit'] = (int)$limit; return $this; } @@ -203,9 +204,9 @@ public function limit(?int $limit) * @param ?int $offset The number of records to be skipped * @return $this */ - public function offset(?int $offset) + public function offset(?int $offset): static { - $this->_queryParts['offset'] = (int)$offset; + $this->queryParts['offset'] = (int)$offset; return $this; } @@ -224,7 +225,7 @@ public function offset(?int $offset) * the current limit clause will be used. * @return $this */ - public function page(int $num, ?int $limit = null) + public function page(int $num, ?int $limit = null): static { if ($limit !== null) { $this->limit($limit); @@ -268,7 +269,7 @@ public function page(int $num, ?int $limit = null) */ public function clause(string $name): mixed { - return $this->_queryParts[$name]; + return $this->queryParts[$name]; } /** @@ -283,36 +284,17 @@ public function clause(string $name): mixed * @param \Closure|array|string $fields The sorting order to use. * @param bool $overwrite Whether or not to replace previous sorting. * @return $this - * @deprecated 5.0.0 Use orderBy() instead now that CollectionInterface methods are no longer proxied. */ - public function order(Closure|array|string $fields, bool $overwrite = false) - { - return $this->orderBy($fields, $overwrite); - } - - /** - * Sets the sorting options for the result set. - * - * The accepted format for the $order parameter is: - * - * - [['name' => ['order'=> 'asc', ...]], ['price' => ['order'=> 'asc', ...]]] - * - ['name' => 'asc', 'price' => 'desc'] - * - 'field1' (defaults to order => 'desc') - * - * @param \Closure|array|string $fields The sorting order to use. - * @param bool $overwrite Whether or not to replace previous sorting. - * @return $this - */ - public function orderBy(array|Closure|string $fields, bool $overwrite = false) + public function orderBy(array|Closure|string $fields, bool $overwrite = false): static { if (is_array($fields) && is_numeric(key($fields))) { if ($overwrite) { - $this->_queryParts['order'] = $fields; + $this->queryParts['order'] = $fields; return $this; } - $this->_queryParts['order'] = array_merge($fields, $this->_queryParts['order']); + $this->queryParts['order'] = array_merge($fields, $this->queryParts['order']); return $this; } @@ -337,10 +319,10 @@ public function orderBy(array|Closure|string $fields, bool $overwrite = false) $fields = collection($fields)->map($normalizer)->toList(); if (!$overwrite) { - $fields = array_merge($this->_queryParts['order'], $fields); + $fields = array_merge($this->queryParts['order'], $fields); } - $this->_queryParts['order'] = $fields; + $this->queryParts['order'] = $fields; return $this; // [['field' => [...]], ['field2' => [...]]] } @@ -355,7 +337,7 @@ public function orderBy(array|Closure|string $fields, bool $overwrite = false) */ public function find(string $finder, mixed ...$args): static { - return $this->_repository->callFinder($finder, $this, ...$args); + return $this->repository->callFinder($finder, $this, ...$args); } /** @@ -408,13 +390,13 @@ public function where( Closure|array|string|null $conditions = null, array $types = [], bool $overwrite = false, - ) { + ): static { // Convert string conditions to proper format for _buildBoolQuery if (is_string($conditions)) { $conditions = [$conditions]; } - return $this->_buildBoolQuery('filter', $conditions, $overwrite); + return $this->buildBoolQuery('filter', $conditions, $overwrite); } /** @@ -467,15 +449,19 @@ public function where( * * `WHERE (title = 'Foo') AND (author_id = 1 OR author_id = 2)` * - * @param \Elastica\Query\AbstractQuery|callable|array|null $conditions The list of conditions. + * @param \Elastica\Query\AbstractQuery|\Closure|array|string|null $conditions The list of conditions. * @param array $types Not used, required to comply with QueryInterface. * @see \Cake\ElasticSearch\Query::where() * @see \Cake\ElasticSearch\QueryBuilder * @return $this */ - public function andWhere(array|callable|AbstractQuery|null $conditions, array $types = []) + public function andWhere(array|Closure|AbstractQuery|string|null $conditions, array $types = []): static { - return $this->_buildBoolQuery('filter', $conditions, false, 'addMust'); + if (is_string($conditions)) { + $conditions = [$conditions]; + } + + return $this->buildBoolQuery('filter', $conditions, false, 'addMust'); } /** @@ -485,13 +471,13 @@ public function andWhere(array|callable|AbstractQuery|null $conditions, array $t * This method can be used in the same way the `where()` method is used. Please refer to * its documentation for more details. * - * @param \Elastica\Query\AbstractQuery|callable|array $conditions The list of conditions + * @param \Elastica\Query\AbstractQuery|\Closure|array $conditions The list of conditions * @param bool $overwrite Whether or not to replace previous queries. * @return $this */ - public function queryMust(array|callable|AbstractQuery $conditions, bool $overwrite = false) + public function queryMust(array|Closure|AbstractQuery $conditions, bool $overwrite = false): static { - return $this->_buildBoolQuery('query', $conditions, $overwrite); + return $this->buildBoolQuery('query', $conditions, $overwrite); } /** @@ -501,13 +487,13 @@ public function queryMust(array|callable|AbstractQuery $conditions, bool $overwr * This method can be used in the same way the `where()` method is used. Please refer to * its documentation for more details. * - * @param \Elastica\Query\AbstractQuery|callable|array $conditions The list of conditions + * @param \Elastica\Query\AbstractQuery|\Closure|array $conditions The list of conditions * @param bool $overwrite Whether or not to replace previous queries. * @return $this */ - public function queryShould(array|callable|AbstractQuery $conditions, bool $overwrite = false) + public function queryShould(array|Closure|AbstractQuery $conditions, bool $overwrite = false): static { - return $this->_buildBoolQuery('query', $conditions, $overwrite, 'addShould'); + return $this->buildBoolQuery('query', $conditions, $overwrite, 'addShould'); } /** @@ -517,14 +503,14 @@ public function queryShould(array|callable|AbstractQuery $conditions, bool $over * This method can be used in the same way the `where()` method is used. Please refer to * its documentation for more details. * - * @param \Elastica\Query\AbstractQuery|callable|array $conditions The list of conditions. + * @param \Elastica\Query\AbstractQuery|\Closure|array $conditions The list of conditions. * @param bool $overwrite Whether or not to replace previous filters. * @return $this * @see \Cake\ElasticSearch\Query::where() */ - public function postFilter(array|callable|AbstractQuery $conditions, bool $overwrite = false) + public function postFilter(array|Closure|AbstractQuery $conditions, bool $overwrite = false): static { - return $this->_buildBoolQuery('postFilter', $conditions, $overwrite); + return $this->buildBoolQuery('postFilter', $conditions, $overwrite); } /** @@ -533,9 +519,9 @@ public function postFilter(array|callable|AbstractQuery $conditions, bool $overw * @param \Elastica\Query\AbstractQuery $query Set the query * @return $this */ - public function setFullQuery(AbstractQuery $query) + public function setFullQuery(AbstractQuery $query): static { - $this->_queryParts['query'] = $query; + $this->queryParts['query'] = $query; return $this; } @@ -546,13 +532,13 @@ public function setFullQuery(AbstractQuery $query) * @param \Elastica\Collapse|string $collapse Collapse field or elastic collapse object * @return $this */ - public function collapse(Collapse|string $collapse) + public function collapse(Collapse|string $collapse): static { if (is_string($collapse)) { $collapse = (new Collapse())->setFieldname($collapse); } - $this->_queryParts['collapse'] = $collapse; + $this->queryParts['collapse'] = $collapse; return $this; } @@ -563,14 +549,14 @@ public function collapse(Collapse|string $collapse) * @param \Elastica\Aggregation\AbstractAggregation|array $aggregation One or multiple facets * @return $this */ - public function aggregate(AbstractAggregation|array $aggregation) + public function aggregate(AbstractAggregation|array $aggregation): static { if (is_array($aggregation)) { foreach ($aggregation as $aggregationItem) { $this->aggregate($aggregationItem); } } else { - $this->_queryParts['aggregations'][] = $aggregation; + $this->queryParts['aggregations'][] = $aggregation; } return $this; @@ -584,10 +570,10 @@ public function aggregate(AbstractAggregation|array $aggregation) public function searchOptions(?array $options = null): array|self { if ($options === null) { - return $this->_searchOptions; + return $this->searchOptions; } - $this->_searchOptions = $options; + $this->searchOptions = $options; return $this; } @@ -597,19 +583,19 @@ public function searchOptions(?array $options = null): array|self * variable. * * @param string $partType The name of the part in which the bool query will be stored - * @param \Elastica\Query\AbstractQuery|callable|array $conditions The list of conditions. + * @param \Elastica\Query\AbstractQuery|\Closure|array $conditions The list of conditions. * @param bool $overwrite Whether or not to replace previous query. * @param string $type The method to use for appending the conditions to the Query * @return $this */ - protected function _buildBoolQuery( + protected function buildBoolQuery( string $partType, - AbstractQuery|callable|array|null $conditions, + AbstractQuery|Closure|array|null $conditions, bool $overwrite, string $type = 'addMust', - ) { - if (!isset($this->_queryParts[$partType]) || $overwrite) { - $this->_queryParts[$partType] = new ElasticaQuery\BoolQuery(); + ): static { + if (!isset($this->queryParts[$partType]) || $overwrite) { + $this->queryParts[$partType] = new BoolQuery(); } if ($conditions === null) { @@ -617,13 +603,13 @@ protected function _buildBoolQuery( } if ($conditions instanceof AbstractQuery) { - $this->_queryParts[$partType]->{$type}($conditions); + $this->queryParts[$partType]->{$type}($conditions); return $this; } - if (is_callable($conditions)) { - $conditions = $conditions(new QueryBuilder(), $this->_queryParts[$partType], $this); + if ($conditions instanceof Closure) { + $conditions = $conditions(new QueryBuilder(), $this->queryParts[$partType], $this); } if ($conditions === null) { @@ -634,14 +620,14 @@ protected function _buildBoolQuery( $conditions = (new QueryBuilder())->parse($conditions); if (is_array($conditions)) { foreach ($conditions as $condition) { - $this->_queryParts[$partType]->{$type}($condition); + $this->queryParts[$partType]->{$type}($condition); } } return $this; } - $this->_queryParts[$partType]->{$type}($conditions); + $this->queryParts[$partType]->{$type}($conditions); return $this; } @@ -652,7 +638,7 @@ protected function _buildBoolQuery( * * - fields: Maps to the select method * - conditions: Maps to the where method - * - order: Maps to the order method + * - order: Maps to the orderBy method * - limit: Maps to the limit method * - offset: Maps to the offset method * - page: Maps to the page method @@ -681,12 +667,12 @@ protected function _buildBoolQuery( * @param array $options list of query clauses to apply new parts to. * @return $this */ - public function applyOptions(array $options) + public function applyOptions(array $options): static { $valid = [ 'fields' => 'select', 'conditions' => 'where', - 'order' => 'order', + 'order' => 'orderBy', 'limit' => 'limit', 'offset' => 'offset', 'page' => 'page', @@ -697,7 +683,7 @@ public function applyOptions(array $options) if (isset($valid[$option]) && isset($values)) { $this->{$valid[$option]}($values); } else { - $this->_options[$option] = $values; + $this->options[$option] = $values; } } @@ -710,9 +696,9 @@ public function applyOptions(array $options) * @param array $highlight The highlight options to use. * @return $this */ - public function highlight(array $highlight) + public function highlight(array $highlight): static { - $this->_queryParts['highlight'] = $highlight; + $this->queryParts['highlight'] = $highlight; return $this; } @@ -724,9 +710,9 @@ public function highlight(array $highlight) * @param float $score The minimum score to observe * @return $this */ - public function withMinScore(float $score) + public function withMinScore(float $score): static { - $this->_elasticQuery->setMinScore($score); + $this->elasticQuery->setMinScore($score); return $this; } @@ -745,7 +731,7 @@ public function withMinScore(float $score) * @throws \InvalidArgumentException When a negative integer value is provided * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-track-total-hits */ - public function trackTotalHits(int|bool|null $trackTotalHits) + public function trackTotalHits(int|bool|null $trackTotalHits): static { if (is_int($trackTotalHits) && $trackTotalHits < 0) { throw new InvalidArgumentException( @@ -753,8 +739,8 @@ public function trackTotalHits(int|bool|null $trackTotalHits) ); } - $this->_queryParts['trackTotalHits'] = $trackTotalHits; - $this->_dirty = true; + $this->queryParts['trackTotalHits'] = $trackTotalHits; + $this->dirty = true; return $this; } @@ -764,15 +750,15 @@ public function trackTotalHits(int|bool|null $trackTotalHits) * * @return \Cake\ElasticSearch\ResultSet The results of the query */ - protected function _execute(): ResultSetInterface + protected function execute(): ResultSetInterface { - $connection = $this->_repository->getConnection(); - $index = $this->_repository->getName(); + $connection = $this->repository->getConnection(); + $index = $this->repository->getName(); $esIndex = $connection->getIndex($index); $query = $this->compileQuery(); - return new ResultSet($esIndex->search($query, $this->_searchOptions), $this); + return new ResultSet($esIndex->search($query, $this->searchOptions), $this); } /** @@ -782,58 +768,58 @@ protected function _execute(): ResultSetInterface */ public function compileQuery(): ElasticaQuery { - if ($this->_queryParts['fields']) { - $this->_elasticQuery->setSource($this->_queryParts['fields']); + if ($this->queryParts['fields']) { + $this->elasticQuery->setSource($this->queryParts['fields']); } - if (isset($this->_queryParts['limit'])) { - $this->_elasticQuery->setSize($this->_queryParts['limit']); + if (isset($this->queryParts['limit'])) { + $this->elasticQuery->setSize($this->queryParts['limit']); } - if (isset($this->_queryParts['offset'])) { - $this->_elasticQuery->setFrom($this->_queryParts['offset']); + if (isset($this->queryParts['offset'])) { + $this->elasticQuery->setFrom($this->queryParts['offset']); } - if ($this->_queryParts['order']) { - $this->_elasticQuery->setSort($this->_queryParts['order']); + if ($this->queryParts['order']) { + $this->elasticQuery->setSort($this->queryParts['order']); } - if ($this->_queryParts['highlight']) { - $this->_elasticQuery->setHighlight($this->_queryParts['highlight']); + if ($this->queryParts['highlight']) { + $this->elasticQuery->setHighlight($this->queryParts['highlight']); } - if ($this->_queryParts['collapse']) { - $this->_elasticQuery->setCollapse($this->_queryParts['collapse']); + if ($this->queryParts['collapse']) { + $this->elasticQuery->setCollapse($this->queryParts['collapse']); } - if ($this->_queryParts['aggregations']) { - foreach ($this->_queryParts['aggregations'] as $aggregation) { - $this->_elasticQuery->addAggregation($aggregation); + if ($this->queryParts['aggregations']) { + foreach ($this->queryParts['aggregations'] as $aggregation) { + $this->elasticQuery->addAggregation($aggregation); } } - if ($this->_queryParts['trackTotalHits'] !== null) { - $this->_elasticQuery->setTrackTotalHits($this->_queryParts['trackTotalHits']); + if ($this->queryParts['trackTotalHits'] !== null) { + $this->elasticQuery->setTrackTotalHits($this->queryParts['trackTotalHits']); } - if (!isset($this->_queryParts['query'])) { - $this->_queryParts['query'] = new ElasticaQuery\BoolQuery(); + if (!isset($this->queryParts['query'])) { + $this->queryParts['query'] = new BoolQuery(); } /** @var \Elastica\Query\AbstractQuery $query */ - $query = clone $this->_queryParts['query']; + $query = clone $this->queryParts['query']; - if ($query instanceof ElasticaQuery\BoolQuery && isset($this->_queryParts['filter'])) { - $query->addFilter($this->_queryParts['filter']); + if ($query instanceof BoolQuery && isset($this->queryParts['filter'])) { + $query->addFilter($this->queryParts['filter']); } - if (isset($this->_queryParts['postFilter'])) { - $this->_elasticQuery->setPostFilter($this->_queryParts['postFilter']); + if (isset($this->queryParts['postFilter'])) { + $this->elasticQuery->setPostFilter($this->queryParts['postFilter']); } - $this->_elasticQuery->setQuery($query); + $this->elasticQuery->setQuery($query); - return $this->_elasticQuery; + return $this->elasticQuery; } /** @@ -867,8 +853,8 @@ public function aliasFields(array $fields, ?string $defaultAlias = null): array */ public function count(): int { - $connection = $this->_repository->getConnection(); - $index = $this->_repository->getName(); + $connection = $this->repository->getConnection(); + $index = $this->repository->getName(); $esIndex = $connection->getIndex($index); $query = clone $this->compileQuery(); @@ -884,10 +870,10 @@ public function count(): int * @param \Cake\Datasource\RepositoryInterface $repository The default repository object to use. * @return $this */ - public function setRepository(RepositoryInterface $repository) + public function setRepository(RepositoryInterface $repository): static { assert($repository instanceof Index, 'ElasticSearch\Query requires an Index subclass'); - $this->_repository = $repository; + $this->repository = $repository; return $this; } @@ -898,7 +884,7 @@ public function setRepository(RepositoryInterface $repository) */ public function getRepository(): Index { - return $this->_repository; + return $this->repository; } /** @@ -948,15 +934,15 @@ public function getIterator(): Traversable * a cache engine instance. * @return $this */ - public function cache(Closure|string|false $key, CacheInterface|string $config = 'default') + public function cache(Closure|string|false $key, CacheInterface|string $config = 'default'): static { if ($key === false) { - $this->_cache = null; + $this->cache = null; return $this; } - $this->_cache = new QueryCacher($key, $config); + $this->cache = new QueryCacher($key, $config); return $this; } @@ -976,29 +962,29 @@ public function cache(Closure|string|false $key, CacheInterface|string $config = */ public function all(): ResultSetInterface { - if ($this->_results !== null) { - if (!($this->_results instanceof ResultSetInterface)) { - $this->_results = $this->decorateResults($this->_results); + if ($this->results !== null) { + if (!($this->results instanceof ResultSetInterface)) { + $this->results = $this->decorateResults($this->results); } - return $this->_results; + return $this->results; } $results = null; - if ($this->_cache instanceof QueryCacher) { - $results = $this->_cache->fetch($this); + if ($this->cache instanceof QueryCacher) { + $results = $this->cache->fetch($this); } if ($results === null) { - $results = $this->decorateResults($this->_execute()); - if ($this->_cache instanceof QueryCacher) { - $this->_cache->store($this, $results); + $results = $this->decorateResults($this->execute()); + if ($this->cache instanceof QueryCacher) { + $this->cache->store($this, $results); } } - $this->_results = $results; + $this->results = $results; - return $this->_results; + return $this->results; } /** @@ -1024,10 +1010,10 @@ public function toArray(): array * @return $this * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. */ - public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false) + public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false): static { if ($overwrite) { - $this->_mapReduce = []; + $this->mapReduce = []; } if (!$mapper instanceof Closure) { @@ -1038,7 +1024,7 @@ public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, boo return $this; } - $this->_mapReduce[] = ['mapper' => $mapper, 'reducer' => $reducer]; + $this->mapReduce[] = ['mapper' => $mapper, 'reducer' => $reducer]; return $this; } @@ -1048,7 +1034,7 @@ public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, boo */ public function getMapReducers(): array { - return $this->_mapReduce; + return $this->mapReduce; } /** @@ -1094,10 +1080,10 @@ public function getMapReducers(): array * @return $this * @throws \InvalidArgumentException */ - public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND) + public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND): static { if ($mode === self::OVERWRITE) { - $this->_formatters = []; + $this->formatters = []; } if (!$formatter instanceof Closure) { @@ -1109,12 +1095,12 @@ public function formatResults(?Closure $formatter = null, int|bool $mode = self: } if ($mode === self::PREPEND) { - array_unshift($this->_formatters, $formatter); + array_unshift($this->formatters, $formatter); return $this; } - $this->_formatters[] = $formatter; + $this->formatters[] = $formatter; return $this; } @@ -1126,7 +1112,7 @@ public function formatResults(?Closure $formatter = null, int|bool $mode = self: */ public function getResultFormatters(): array { - return $this->_formatters; + return $this->formatters; } /** @@ -1143,7 +1129,7 @@ public function getResultFormatters(): array */ public function first(): mixed { - if ($this->_dirty) { + if ($this->dirty) { $this->limit(1); } @@ -1187,7 +1173,7 @@ public function firstOrFail(): mixed */ public function getOptions(): array { - return $this->_options; + return $this->options; } /** @@ -1199,8 +1185,8 @@ protected function decorateResults(iterable $result): ResultSetInterface { $decorator = $this->decoratorClass(); - if ($this->_mapReduce !== []) { - foreach ($this->_mapReduce as $functions) { + if ($this->mapReduce !== []) { + foreach ($this->mapReduce as $functions) { $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); } @@ -1211,8 +1197,8 @@ protected function decorateResults(iterable $result): ResultSetInterface $result = new $decorator($result); } - if ($this->_formatters !== []) { - foreach ($this->_formatters as $formatter) { + if ($this->formatters !== []) { + foreach ($this->formatters as $formatter) { $result = $formatter($result, $this); } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index e5c636e2..36460c99 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -3,7 +3,6 @@ namespace Cake\ElasticSearch; -use Elastica; use Elastica\Query; use Elastica\Query\AbstractQuery; use Elastica\Query\BoolQuery; @@ -56,7 +55,7 @@ public function between(string $field, mixed $from, mixed $to): Range */ public function bool(): BoolQuery { - return new Elastica\Query\BoolQuery(); + return new BoolQuery(); } /** @@ -68,7 +67,7 @@ public function bool(): BoolQuery */ public function exists(string $field): Exists { - return new Elastica\Query\Exists($field); + return new Exists($field); } /** @@ -96,7 +95,7 @@ public function exists(string $field): Exists */ public function geoBoundingBox(string $field, array|string $topLeft, array|string $bottomRight): GeoBoundingBox { - return new Elastica\Query\GeoBoundingBox($field, [$topLeft, $bottomRight]); + return new GeoBoundingBox($field, [$topLeft, $bottomRight]); } /** @@ -118,7 +117,7 @@ public function geoBoundingBox(string $field, array|string $topLeft, array|strin */ public function geoDistance(string $field, array|string $location, string $distance): GeoDistance { - return new Elastica\Query\GeoDistance($field, $location, $distance); + return new GeoDistance($field, $location, $distance); } /** @@ -147,7 +146,7 @@ public function geoDistance(string $field, array|string $location, string $dista */ public function geoPolygon(string $field, array $geoPoints): GeoPolygon { - return new Elastica\Query\GeoPolygon($field, $geoPoints); + return new GeoPolygon($field, $geoPoints); } /** @@ -176,9 +175,9 @@ public function geoPolygon(string $field, array $geoPoints): GeoPolygon public function geoShape( string $field, array $geoPoints, - string $type = Elastica\Query\GeoShapeProvided::TYPE_ENVELOPE, + string $type = GeoShapeProvided::TYPE_ENVELOPE, ): GeoShapeProvided { - return new Elastica\Query\GeoShapeProvided($field, $geoPoints, $type); + return new GeoShapeProvided($field, $geoPoints, $type); } /** @@ -203,7 +202,7 @@ public function geoShapeIndex( string $index = 'shapes', string $path = 'shape', ): GeoShapePreIndexed { - return new Elastica\Query\GeoShapePreIndexed($field, $id, $index, $path); + return new GeoShapePreIndexed($field, $id, $index, $path); } /** @@ -242,7 +241,7 @@ public function gte(string $field, mixed $value): Range */ public function hasChild(Query|AbstractQuery|string $query, string $type): HasChild { - return new Elastica\Query\HasChild($query, $type); + return new HasChild($query, $type); } /** @@ -254,7 +253,7 @@ public function hasChild(Query|AbstractQuery|string $query, string $type): HasCh */ public function hasParent(Query|AbstractQuery|string $query, string $type): HasParent { - return new Elastica\Query\HasParent($query, $type); + return new HasParent($query, $type); } /** @@ -265,7 +264,7 @@ public function hasParent(Query|AbstractQuery|string $query, string $type): HasP */ public function ids(array $ids = []): Ids { - return new Elastica\Query\Ids($ids); + return new Ids($ids); } /** @@ -276,7 +275,7 @@ public function ids(array $ids = []): Ids */ public function limit(int $limit): Limit { - return new Elastica\Query\Limit($limit); + return new Limit($limit); } /** @@ -286,7 +285,7 @@ public function limit(int $limit): Limit */ public function matchAll(): MatchAll { - return new Elastica\Query\MatchAll(); + return new MatchAll(); } /** @@ -330,7 +329,7 @@ public function lte(string $field, mixed $value): Range */ public function nested(string $path, AbstractQuery $query): Nested { - $nested = new Elastica\Query\Nested(); + $nested = new Nested(); $nested->setPath($path); $nested->setQuery($query); @@ -346,7 +345,7 @@ public function nested(string $path, AbstractQuery $query): Nested */ public function not(AbstractQuery|array $query): BoolQuery { - $boolQuery = new Elastica\Query\BoolQuery(); + $boolQuery = new BoolQuery(); $boolQuery->addMustNot($query); return $boolQuery; @@ -363,7 +362,7 @@ public function not(AbstractQuery|array $query): BoolQuery */ public function prefix(string $field, string $prefix, float $boost = 1.0): Prefix { - $prefixQuery = new Elastica\Query\Prefix(); + $prefixQuery = new Prefix(); $prefixQuery->setPrefix($field, $prefix, $boost); return $prefixQuery; @@ -386,7 +385,7 @@ public function prefix(string $field, string $prefix, float $boost = 1.0): Prefi */ public function range(string $field, array $args): Range { - return new Elastica\Query\Range($field, $args); + return new Range($field, $args); } /** @@ -405,7 +404,7 @@ public function range(string $field, array $args): Range */ public function regexp(string $field, string $regexp, float $boost = 1.0): Regexp { - return new Elastica\Query\Regexp($field, $regexp, $boost); + return new Regexp($field, $regexp, $boost); } /** @@ -422,7 +421,7 @@ public function regexp(string $field, string $regexp, float $boost = 1.0): Regex */ public function script(AbstractScript|array|string $script): Script { - return new Elastica\Query\Script($script); + return new Script($script); } /** @@ -440,7 +439,7 @@ public function script(AbstractScript|array|string $script): Script */ public function simpleQueryString(array|string $fields, string $string): SimpleQueryString { - return new Elastica\Query\SimpleQueryString($string, (array)$fields); + return new SimpleQueryString($string, (array)$fields); } /** @@ -458,7 +457,7 @@ public function simpleQueryString(array|string $fields, string $string): SimpleQ */ public function match(string $field, string $value): MatchQuery { - return new Elastica\Query\MatchQuery($field, $value); + return new MatchQuery($field, $value); } /** @@ -476,7 +475,7 @@ public function match(string $field, string $value): MatchQuery */ public function term(string $field, string|float|int|bool $value): Term { - return new Elastica\Query\Term([$field => $value]); + return new Term([$field => $value]); } /** @@ -502,7 +501,7 @@ public function terms(string $field, array $values): Terms } } - return new Elastica\Query\Terms($field, $scalarValues); + return new Terms($field, $scalarValues); } /** @@ -701,7 +700,7 @@ public function parse(array|AbstractQuery $conditions): AbstractQuery|array continue; } - $result[] = $this->_parseQuery($k, $c); + $result[] = $this->parseQuery($k, $c); } return $result; @@ -713,7 +712,7 @@ public function parse(array|AbstractQuery $conditions): AbstractQuery|array * @param string $field The filed name containing the operator * @param mixed $value The value to pass to the query */ - protected function _parseQuery(string $field, mixed $value): AbstractQuery + protected function parseQuery(string $field, mixed $value): AbstractQuery { $operator = '='; $parts = explode(' ', trim($field), 2); @@ -740,7 +739,7 @@ protected function _parseQuery(string $field, mixed $value): AbstractQuery return $this->lte($field, $value); } - if (in_array($operator, ['in', 'not in'])) { + if (in_array($operator, ['in', 'not in'], true)) { $value = (array)$value; } diff --git a/src/Rule/IsUnique.php b/src/Rule/IsUnique.php index 9f8406af..94f97520 100644 --- a/src/Rule/IsUnique.php +++ b/src/Rule/IsUnique.php @@ -31,7 +31,7 @@ class IsUnique /** * The list of fields to check */ - protected array $_fields; + protected array $fields; /** * Constructor. @@ -45,7 +45,7 @@ class IsUnique */ public function __construct(array $fields) { - $this->_fields = $fields; + $this->fields = $fields; } /** @@ -62,11 +62,11 @@ public function __construct(array $fields) */ public function __invoke(EntityInterface $entity, array $options): bool { - if (!$entity->extract($this->_fields, true)) { + if (!$entity->extract($this->fields, true)) { return true; } - $fields = $entity->extract($this->_fields); + $fields = $entity->extract($this->fields); $conditions = []; foreach ($fields as $field => $value) { diff --git a/src/TestSuite/TestFixture.php b/src/TestSuite/TestFixture.php index f6b98414..65656070 100644 --- a/src/TestSuite/TestFixture.php +++ b/src/TestSuite/TestFixture.php @@ -161,11 +161,11 @@ public function create(ConnectionInterface $db): bool * * @param \Cake\Datasource\ConnectionInterface $connection The Elasticsearch connection */ - public function insert(ConnectionInterface $connection): bool + public function insert(ConnectionInterface $connection): void { assert($connection instanceof Connection, 'Requires an elasticsearch connection'); if ($this->records === []) { - return false; + return; } $documents = []; @@ -183,8 +183,6 @@ public function insert(ConnectionInterface $connection): bool $esIndex->addDocuments($documents); $esIndex->refresh(); - - return true; } /** @@ -211,7 +209,7 @@ public function drop(ConnectionInterface $db): bool * * @param \Cake\Datasource\ConnectionInterface $connection The Elasticsearch connection */ - public function truncate(ConnectionInterface $connection): bool + public function truncate(ConnectionInterface $connection): void { $query = new MatchAll(); assert($connection instanceof Connection, 'Requires an elasticsearch connection'); @@ -219,8 +217,6 @@ public function truncate(ConnectionInterface $connection): bool $esIndex = $connection->getIndex($this->getIndex()->getName()); $esIndex->deleteByQuery($query); $esIndex->refresh(); - - return true; } /** diff --git a/src/View/Form/DocumentContext.php b/src/View/Form/DocumentContext.php index 7f8cf71f..7b9c60ea 100644 --- a/src/View/Form/DocumentContext.php +++ b/src/View/Form/DocumentContext.php @@ -37,23 +37,23 @@ class DocumentContext implements ContextInterface /** * The request object. */ - protected ServerRequest $_request; + protected ServerRequest $request; /** * The context data */ - protected array $_context; + protected array $context; /** * The name of the top level entity/index object. */ - protected string $_rootName; + protected string $rootName; /** * Boolean to track whether or not the entity is a * collection. */ - protected bool $_isCollection = false; + protected bool $isCollection = false; /** * Constructor. @@ -63,14 +63,14 @@ class DocumentContext implements ContextInterface */ public function __construct(ServerRequest $request, array $context) { - $this->_request = $request; + $this->request = $request; $context += [ 'entity' => null, 'index' => null, 'validator' => 'default', ]; - $this->_context = $context; - $this->_prepare(); + $this->context = $context; + $this->prepare(); } /** @@ -87,10 +87,10 @@ public function __construct(ServerRequest $request, array $context) * * @throws \RuntimeException When a table object cannot be located/inferred. */ - protected function _prepare(): void + protected function prepare(): void { - $index = $this->_context['index']; - $entity = $this->_context['entity']; + $index = $this->context['index']; + $entity = $this->context['entity']; if (empty($index)) { if (is_array($entity) || $entity instanceof Traversable) { $entity = (new Collection($entity))->first(); @@ -118,13 +118,13 @@ protected function _prepare(): void ); } - $this->_isCollection = ( + $this->isCollection = ( is_array($entity) || $entity instanceof Traversable ); assert($index instanceof Index); - $this->_rootName = $index->getName(); - $this->_context['index'] = $index; + $this->rootName = $index->getName(); + $this->context['index'] = $index; } /** @@ -150,7 +150,7 @@ public function isPrimaryKey(string $field): bool */ public function isCreate(): bool { - $entity = $this->_context['entity']; + $entity = $this->context['entity']; if (is_array($entity) || $entity instanceof Traversable) { $entity = (new Collection($entity))->first(); } @@ -167,12 +167,12 @@ public function isCreate(): bool */ public function val(string $field, array $options = []): mixed { - $val = $this->_request->getData($field); + $val = $this->request->getData($field); if ($val !== null) { return $val; } - if (empty($this->_context['entity'])) { + if (empty($this->context['entity'])) { return null; } @@ -183,8 +183,8 @@ public function val(string $field, array $options = []): mixed return $entity->get(array_pop($parts)); } - if ($this->_context['entity'] instanceof Document) { - return Hash::get($this->_context['entity'], $field); + if ($this->context['entity'] instanceof Document) { + return Hash::get($this->context['entity'], $field); } return null; @@ -200,16 +200,16 @@ public function val(string $field, array $options = []): mixed protected function entity(array $path): object|array|false { $oneElement = count($path) === 1; - if ($oneElement && $this->_isCollection) { + if ($oneElement && $this->isCollection) { return false; } - $entity = $this->_context['entity']; + $entity = $this->context['entity']; if ($oneElement) { return $entity; } - if ($path[0] === $this->_rootName) { + if ($path[0] === $this->rootName) { $path = array_slice($path, 1); } @@ -349,8 +349,8 @@ public function getMaxLength(string $field): ?int } foreach ($validator->field($fieldName)->rules() as $rule) { - if ($rule->get('rule') === 'maxLength') { - return $rule->get('pass')[0]; + if ($rule->name === 'maxLength' && isset($rule->pass[0])) { + return $rule->pass[0]; } } @@ -364,7 +364,7 @@ public function getMaxLength(string $field): ?int */ protected function getValidator(): Validator { - return $this->_context['index']->getValidator($this->_context['validator']); + return $this->context['index']->getValidator($this->context['validator']); } /** @@ -372,7 +372,7 @@ protected function getValidator(): Validator */ public function fieldNames(): array { - $schema = $this->_context['index']->schema(); + $schema = $this->context['index']->schema(); return $schema->fields(); } @@ -382,7 +382,7 @@ public function fieldNames(): array */ public function type(string $field): ?string { - $schema = $this->_context['index']->schema(); + $schema = $this->context['index']->schema(); return $schema->fieldType($field); } @@ -413,8 +413,8 @@ public function error(string $field): array $entityErrors = []; $errors = []; - if ($this->_context['entity'] instanceof Document) { - $entityErrors = $this->_context['entity']->getErrors(); + if ($this->context['entity'] instanceof Document) { + $entityErrors = $this->context['entity']->getErrors(); } $tailField = array_pop($parts); diff --git a/tests/Fixture/ArticlesFixture.php b/tests/Fixture/ArticlesFixture.php index 1a16f358..d75cbfc8 100644 --- a/tests/Fixture/ArticlesFixture.php +++ b/tests/Fixture/ArticlesFixture.php @@ -1,4 +1,7 @@ _indexLocator instanceof LocatorInterface) { - return $this->_indexLocator; + if ($this->indexLocator instanceof LocatorInterface) { + return $this->indexLocator; } // Return a mock IndexLocator instead of using FactoryLocator - return $this->_indexLocator = new IndexLocator(); + return $this->indexLocator = new IndexLocator(); } }; diff --git a/tests/TestCase/MarshallerTest.php b/tests/TestCase/MarshallerTest.php index 29a8cf1e..765c9878 100644 --- a/tests/TestCase/MarshallerTest.php +++ b/tests/TestCase/MarshallerTest.php @@ -140,7 +140,7 @@ public function testOneFieldList(): void } /** - * test marshalling with accessibleFields + * test marshalling with patchableFields */ public function testOneAccessibleFields(): void { @@ -158,7 +158,7 @@ public function testOneAccessibleFields(): void $this->assertNull($result->body); $this->assertNull($result->user_id); - $result = $marshaller->one($data, ['accessibleFields' => ['body' => true]]); + $result = $marshaller->one($data, ['patchableFields' => ['body' => true]]); $this->assertSame($data['title'], $result->title); $this->assertSame($data['body'], $result->body); @@ -475,7 +475,7 @@ public function testMergeValidationErrorsPreserveInvalidFields(): void public function testMergeFieldList(): void { $doc = $this->index->get(1); - $doc->setAccess('*', false); + $doc->setPatchable('*', false); $data = [ 'title' => 'New title', @@ -866,13 +866,13 @@ public function testMarshallOneMultiLevelEmbedWithAccessibleFields(): void ], ]; $options = [ - 'accessibleFields' => ['remove_this' => false], + 'patchableFields' => ['remove_this' => false], 'associated' => [ 'User' => [ - 'accessibleFields' => ['last_name' => false], + 'patchableFields' => ['last_name' => false], 'associated' => [ 'UserType' => [ - 'accessibleFields' => ['level' => false], + 'patchableFields' => ['level' => false], ], ], ], @@ -1235,7 +1235,6 @@ public function testFieldValueWithExistingField(): void $reflection = new ReflectionClass($marshaller); $method = $reflection->getMethod('fieldValue'); - $method->setAccessible(true); $result = $method->invoke($marshaller, $entity, 'title'); $this->assertSame('Test', $result); @@ -1254,7 +1253,6 @@ public function testFieldValueWithMissingField(): void $reflection = new ReflectionClass($marshaller); $method = $reflection->getMethod('fieldValue'); - $method->setAccessible(true); $result = $method->invoke($marshaller, $entity, 'nonexistent'); $this->assertNull($result, 'Should return null for missing fields'); @@ -1270,7 +1268,6 @@ public function testFieldValueWithNullValue(): void $reflection = new ReflectionClass($marshaller); $method = $reflection->getMethod('fieldValue'); - $method->setAccessible(true); $result = $method->invoke($marshaller, $entity, 'title'); $this->assertNull($result); diff --git a/tests/TestCase/ResultSetTest.php b/tests/TestCase/ResultSetTest.php index d061f5d3..709d6367 100644 --- a/tests/TestCase/ResultSetTest.php +++ b/tests/TestCase/ResultSetTest.php @@ -107,7 +107,6 @@ public function testDecoratedMethods(): void // Read the wrapped results so we can compare method outputs. // This is not ideal but better than using mocks. $reflect = new ReflectionProperty($results, 'resultSet'); - $reflect->setAccessible(true); $elasticResult = $reflect->getValue($results); diff --git a/tests/TestCase/Rule/IsUniqueTest.php b/tests/TestCase/Rule/IsUniqueTest.php index 00e4c18e..ec4fa620 100644 --- a/tests/TestCase/Rule/IsUniqueTest.php +++ b/tests/TestCase/Rule/IsUniqueTest.php @@ -46,8 +46,7 @@ public function testConstructor(): void // Use reflection to verify fields were stored correctly $reflection = new ReflectionClass($rule); - $fieldsProperty = $reflection->getProperty('_fields'); - $fieldsProperty->setAccessible(true); + $fieldsProperty = $reflection->getProperty('fields'); $storedFields = $fieldsProperty->getValue($rule); @@ -62,8 +61,7 @@ public function testConstructorWithEmptyArray(): void $rule = new IsUnique([]); $reflection = new ReflectionClass($rule); - $fieldsProperty = $reflection->getProperty('_fields'); - $fieldsProperty->setAccessible(true); + $fieldsProperty = $reflection->getProperty('fields'); $storedFields = $fieldsProperty->getValue($rule); @@ -80,8 +78,7 @@ public function testConstructorWithSingleField(): void $rule = new IsUnique([$field]); $reflection = new ReflectionClass($rule); - $fieldsProperty = $reflection->getProperty('_fields'); - $fieldsProperty->setAccessible(true); + $fieldsProperty = $reflection->getProperty('fields'); $storedFields = $fieldsProperty->getValue($rule); diff --git a/tests/TestCase/TestSuite/Fixture/DeleteQueryStrategyTest.php b/tests/TestCase/TestSuite/Fixture/DeleteQueryStrategyTest.php index acbefe96..e7aff552 100644 --- a/tests/TestCase/TestSuite/Fixture/DeleteQueryStrategyTest.php +++ b/tests/TestCase/TestSuite/Fixture/DeleteQueryStrategyTest.php @@ -40,7 +40,6 @@ public function testConstructor(): void // Use reflection to verify helper was initialized $reflection = new ReflectionClass($strategy); $helperProperty = $reflection->getProperty('helper'); - $helperProperty->setAccessible(true); $helper = $helperProperty->getValue($strategy); @@ -48,7 +47,6 @@ public function testConstructor(): void // Test that fixtures array is initialized empty $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); @@ -76,7 +74,6 @@ public function testSetupTest(): void // Verify fixtures are stored internally $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); @@ -96,7 +93,6 @@ public function testSetupTestWithEmptyArray(): void // Verify fixtures array remains empty $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); @@ -132,7 +128,6 @@ public function testMultipleSetupTestCalls(): void // Verify it doesn't crash and fixtures are updated $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); @@ -173,7 +168,6 @@ public function testTeardownTestWithoutSetup(): void // Verify fixtures array is still empty $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); @@ -294,7 +288,6 @@ public function testSetupTestMethodCoverage(): void // Verify fixtures are loaded $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); $this->assertNotEmpty($fixtures); @@ -319,7 +312,6 @@ public function testTeardownTestMethodCoverage(): void // Verify fixtures were cleared $reflection = new ReflectionClass($strategy); $fixturesProperty = $reflection->getProperty('fixtures'); - $fixturesProperty->setAccessible(true); $fixtures = $fixturesProperty->getValue($strategy); $this->assertEmpty($fixtures, 'Fixtures should be cleared after teardown'); diff --git a/tests/TestCase/TestSuite/TestFixtureTest.php b/tests/TestCase/TestSuite/TestFixtureTest.php index d3eaeb94..69de7dd0 100644 --- a/tests/TestCase/TestSuite/TestFixtureTest.php +++ b/tests/TestCase/TestSuite/TestFixtureTest.php @@ -175,11 +175,12 @@ public function testCreateWithEmptySchema(): void } /** - * Test insert method with empty records returns false + * Test insert method with empty records does not touch the connection */ public function testInsertWithEmptyRecords(): void { $connection = $this->createMock(Connection::class); + $connection->expects($this->never())->method('getIndex'); $fixture = new class extends TestFixture { public string $connection = 'test_empty'; @@ -187,8 +188,7 @@ public function testInsertWithEmptyRecords(): void public array $records = []; }; - $result = $fixture->insert($connection); - $this->assertFalse($result); + $fixture->insert($connection); } /** @@ -581,12 +581,12 @@ public function testInsertWithRealConnectionAndRecords(): void // Create the index first $fixture->create($connection); - $result = $fixture->insert($connection); - $this->assertTrue($result); + $fixture->insert($connection); // Verify documents were inserted $esIndex = $connection->getIndex($fixture->getIndex()->getName()); $esIndex->refresh(); + $this->assertSame(2, $fixture->getIndex()->find()->count()); // Clean up $esIndex->delete(); @@ -618,8 +618,8 @@ public function testInsertWithRecordsWithoutIds(): void // Create the index first $fixture->create($connection); - $result = $fixture->insert($connection); - $this->assertTrue($result); + $fixture->insert($connection); + $this->assertSame(2, $fixture->getIndex()->find()->count()); // Clean up $esIndex = $connection->getIndex($fixture->getIndex()->getName()); @@ -709,8 +709,8 @@ public function testTruncate(): void $fixture->create($connection); $fixture->insert($connection); - $result = $fixture->truncate($connection); - $this->assertTrue($result); + $fixture->truncate($connection); + $this->assertSame(0, $fixture->getIndex()->find()->count()); // Verify index still exists but is empty $esIndex = $connection->getIndex($fixture->getIndex()->getName()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b0b5201f..ee8635d9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,9 +14,9 @@ * @since 0.0.1 * @license https://www.opensource.org/licenses/mit-license.php MIT License */ -require dirname(__DIR__) . '/vendor/autoload.php'; +require __DIR__ . '/../vendor/autoload.php'; -define('CAKE', dirname(__DIR__) . '/vendor/cakephp/cakephp/src/'); +define('CAKE', __DIR__ . '/../vendor/cakephp/cakephp/src/'); define('ROOT', dirname(__DIR__)); if (!defined('DS')) { diff --git a/tests/testapp/Plugin/TestPlugin/src/TestPluginPlugin.php b/tests/testapp/Plugin/TestPlugin/src/TestPluginPlugin.php new file mode 100644 index 00000000..e2f2dba5 --- /dev/null +++ b/tests/testapp/Plugin/TestPlugin/src/TestPluginPlugin.php @@ -0,0 +1,26 @@ + true, ]; } diff --git a/tests/testapp/TestApp/src/Model/Index/MyUsersIndex.php b/tests/testapp/TestApp/src/Model/Index/MyUsersIndex.php index b3dc8b52..8a0dce59 100644 --- a/tests/testapp/TestApp/src/Model/Index/MyUsersIndex.php +++ b/tests/testapp/TestApp/src/Model/Index/MyUsersIndex.php @@ -26,5 +26,5 @@ class MyUsersIndex extends Index /** * Overrides default table name */ - protected string $_name = 'users'; + protected string $name = 'users'; } From a54ba115c45d9524c379f090b6f0f9b3674d4b61 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Oct 2026 12:26:03 +0530 Subject: [PATCH 2/4] Fix CI config --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 247685fa..57e5d6ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ name: CI on: push: branches: + - 5.x - 6.x pull_request: branches: From ebea35335facad7457a21da8e2ada514835e1bf4 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 2 Oct 2026 12:26:38 +0530 Subject: [PATCH 3/4] Update phpcs config --- phpcs.xml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index abf9ae9f..4434c0a8 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -4,13 +4,8 @@ tests/ - + - - - - 0 - From 5130f25f756f248fba38f76f8b25fe8ec1419c29 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 4 Oct 2026 09:10:13 +0530 Subject: [PATCH 4/4] Update docs/en/6-0-upgrade-guide.md Co-authored-by: Mark Story --- docs/en/6-0-upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/6-0-upgrade-guide.md b/docs/en/6-0-upgrade-guide.md index 5a190fb3..c1a2dcfe 100644 --- a/docs/en/6-0-upgrade-guide.md +++ b/docs/en/6-0-upgrade-guide.md @@ -13,7 +13,7 @@ CakePHP ElasticSearch `6.x` requires CakePHP `6.0+`, Elasticsearch `9.x`, Elasti ## Breaking Changes -Version `6.x` ports the plugin to CakePHP `6.x` and drops all CakePHP `5.x` support. +Version `6.x` requires CakePHP `6.x`. The Elasticsearch, Elastica and PHP version requirements are unchanged from `5.x`, apart from the PHP version bump required by CakePHP `6`.