From accd8f82fb29066c1d35ed85e9fee7f14ab4e326 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 22 Jul 2022 15:06:21 -0400 Subject: [PATCH 01/38] Scout 9 Support Update the test Matrix. Added Laravel 9 and Scout 9 packages. Implemented new abstract methods needed for Scout 9. Added tests for 2 of the 3 new methods. The lazyMap method returns a lazyCollection but not completely implemeted yet. --- .github/workflows/tests.yml | 34 ++++++++++++++++++++++----------- composer.json | 10 +++++----- src/PostgresEngine.php | 37 ++++++++++++++++++++++++++++++++++++ tests/PostgresEngineTest.php | 20 +++++++++++++++++++ 4 files changed, 85 insertions(+), 16 deletions(-) mode change 100644 => 100755 .github/workflows/tests.yml mode change 100644 => 100755 composer.json mode change 100644 => 100755 src/PostgresEngine.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml old mode 100644 new mode 100755 index 3991ffa..eb3ba65 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,21 +11,33 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: - php: [7.2, 7.3, 7.4] - laravel: [^6.0, ^7.0, ^8.0] - scout: [^7.0, ^8.0] + php: [7.3, 7.4, 8.0, 8.1] + laravel: [^6.0, ^7.0, ^8.0, ^9.0] + scout: [^7.0, ^8.0, ^9.0] exclude: - - php: 7.2 - laravel: ^8.0 + - php: 7.3 + laravel: ^9.0 + - php: 7.4 + laravel: ^9.0 + - php: 8.0 + scout: ^7.0 + - php: 8.1 + scout: ^7.0 + - php: 8.1 + scout: ^8.0 + - laravel: ^6.0 + scout: ^9.0 + - laravel: ^7.0 + scout: ^9.0 - laravel: ^8.0 scout: ^7.0 - include: - - php: 8.0 - laravel: ^8.0 + - laravel: ^9.0 + scout: ^7.0 + - laravel: ^9.0 scout: ^8.0 - + name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }} steps: @@ -49,4 +61,4 @@ jobs: composer update --prefer-dist --no-interaction --no-progress - name: Run tests - run: vendor/bin/phpunit --verbose \ No newline at end of file + run: vendor/bin/phpunit --verbose diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index 305a86c..0c51981 --- a/composer.json +++ b/composer.json @@ -23,11 +23,11 @@ } ], "require": { - "php": "^7.2|^8.0", - "illuminate/contracts": "~6.0|~7.0|~8.0", - "illuminate/database": "~6.0|~7.0|~8.0", - "illuminate/support": "~6.0|~7.0|~8.0", - "laravel/scout": "~7.0|~8.0" + "php": "^7.3|^8.0", + "illuminate/contracts": "~6.0|~7.0|~8.0|~9.0", + "illuminate/database": "~6.0|~7.0|~8.0|~9.0", + "illuminate/support": "~6.0|~7.0|~8.0|~9.0", + "laravel/scout": "~7.0|~8.0|~9.0" }, "require-dev": { "phpunit/phpunit": "^8.3", diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php old mode 100644 new mode 100755 index b92b3e0..f4552a5 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -2,6 +2,7 @@ namespace ScoutEngines\Postgres; +use Exception; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; @@ -339,6 +340,42 @@ public function map(Builder $builder, $results, $model) }); } + /** + * Map the given results to instances of the given model via a lazy collection. + * + * @param \Laravel\Scout\Builder $builder + * @param mixed $results + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Support\LazyCollection + */ + public function lazyMap(Builder $builder, $results, $model) + { + return LazyCollection::make($model->newCollection()); + } + + /** + * Create a search index. + * + * @param string $name + * @param array $options + * @return mixed + */ + public function createIndex($name, array $options = []) + { + throw new Exception('PostgreSQL indexes should be created through Laravel database migrations.'); + } + + /** + * Delete a search index. + * + * @param string $name + * @return mixed + */ + public function deleteIndex($name) + { + throw new Exception('PostgreSQL indexes should be deleted through Laravel database migrations.'); + } + /** * Connect to the database. */ diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index f3a060e..7a7f284 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -282,6 +282,26 @@ public function test_map_ids_returns_right_key() $this->assertEquals([1, 2], $ids->all()); } + public function test_create_index() + { + [$engine, $db] = $this->getEngine(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('PostgreSQL indexes should be created through Laravel database migrations.'); + + $engine->createIndex('bad_index'); + } + + public function test_delete_index() + { + [$engine, $db] = $this->getEngine(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('PostgreSQL indexes should be deleted through Laravel database migrations.'); + + $engine->deleteIndex('bad_index'); + } + protected function getEngine($config = []) { $resolver = Mockery::mock(ConnectionResolverInterface::class); From 86c376945e87603dba276397220894824c11a902 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Tue, 7 Feb 2023 17:03:51 -0500 Subject: [PATCH 02/38] Limiting Supported Versions Removing versions that are no longer supported. Added in Laravel 10 packages. --- .github/workflows/tests.yml | 40 ++++---------- composer.json | 106 ++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 82 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index eb3ba65..ba8baf4 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,40 +4,22 @@ on: push: branches: - master + - laravel-10-compatibility pull_request: jobs: tests: - runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: [7.3, 7.4, 8.0, 8.1] - laravel: [^6.0, ^7.0, ^8.0, ^9.0] - scout: [^7.0, ^8.0, ^9.0] + php: [8.0, 8.1, 8.2] + laravel: [^9.0, ^10] + scout: [^9.0] exclude: - - php: 7.3 - laravel: ^9.0 - - php: 7.4 - laravel: ^9.0 - php: 8.0 - scout: ^7.0 - - php: 8.1 - scout: ^7.0 - - php: 8.1 - scout: ^8.0 - - laravel: ^6.0 - scout: ^9.0 - - laravel: ^7.0 - scout: ^9.0 - - laravel: ^8.0 - scout: ^7.0 - - laravel: ^9.0 - scout: ^7.0 - - laravel: ^9.0 - scout: ^8.0 - + laravel: ^10 + name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }} steps: @@ -54,11 +36,11 @@ jobs: - name: Install dependencies run: | - composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update - composer require "illuminate/database=${{ matrix.laravel }}" --no-update - composer require "illuminate/support=${{ matrix.laravel }}" --no-update - composer require "laravel/scout=${{ matrix.scout }}" --no-update - composer update --prefer-dist --no-interaction --no-progress + composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update + composer require "illuminate/database=${{ matrix.laravel }}" --no-update + composer require "illuminate/support=${{ matrix.laravel }}" --no-update + composer require "laravel/scout=${{ matrix.scout }}" --no-update + composer update --prefer-dist --no-interaction --no-progress - name: Run tests run: vendor/bin/phpunit --verbose diff --git a/composer.json b/composer.json index 0c51981..193b594 100755 --- a/composer.json +++ b/composer.json @@ -1,56 +1,56 @@ { - "name": "pmatseykanets/laravel-scout-postgres", - "description": "PostgreSQL Full Text Search Driver for Laravel Scout", - "keywords": [ - "laravel", - "laravel scout", - "search", - "postgresql", - "full text search", - "FTS" - ], - "homepage": "https://github.com/pmatseykanets/laravel-scout-postgres", - "license": "MIT", - "support": { - "issues": "https://github.com/pmatseykanets/laravel-scout-postgres/issues", - "source": "https://github.com/pmatseykanets/laravel-scout-postgres" - }, - "authors": [ - { - "name": "Peter Matseykanets", - "email": "pmatseykanets@gmail.com", - "homepage": "https://github.com/pmatseykanets" - } - ], - "require": { - "php": "^7.3|^8.0", - "illuminate/contracts": "~6.0|~7.0|~8.0|~9.0", - "illuminate/database": "~6.0|~7.0|~8.0|~9.0", - "illuminate/support": "~6.0|~7.0|~8.0|~9.0", - "laravel/scout": "~7.0|~8.0|~9.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.3", - "mockery/mockery": "^1.2.3" - }, - "autoload": { - "psr-4": { - "ScoutEngines\\Postgres\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "ScoutEngines\\Postgres\\Test\\": "tests" - } - }, - "scripts": { - "test": "vendor/bin/phpunit" - }, - "extra": { - "laravel": { - "providers": [ - "ScoutEngines\\Postgres\\PostgresEngineServiceProvider" - ] - } + "name": "pmatseykanets/laravel-scout-postgres", + "description": "PostgreSQL Full Text Search Driver for Laravel Scout", + "keywords": [ + "laravel", + "laravel scout", + "search", + "postgresql", + "full text search", + "FTS" + ], + "homepage": "https://github.com/pmatseykanets/laravel-scout-postgres", + "license": "MIT", + "support": { + "issues": "https://github.com/pmatseykanets/laravel-scout-postgres/issues", + "source": "https://github.com/pmatseykanets/laravel-scout-postgres" + }, + "authors": [ + { + "name": "Peter Matseykanets", + "email": "pmatseykanets@gmail.com", + "homepage": "https://github.com/pmatseykanets" } + ], + "require": { + "php": "^8.0|^8.1|^8.2", + "illuminate/contracts": "^9.0|^10", + "illuminate/database": "^9.0|^10", + "illuminate/support": "^9.0|^10", + "laravel/scout": "^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "mockery/mockery": "^1.5" + }, + "autoload": { + "psr-4": { + "ScoutEngines\\Postgres\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "ScoutEngines\\Postgres\\Test\\": "tests" + } + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "extra": { + "laravel": { + "providers": [ + "ScoutEngines\\Postgres\\PostgresEngineServiceProvider" + ] + } + } } From 67dfeee3bab5bc0304c507e9f717be507838ee89 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 8 Feb 2023 16:33:06 -0500 Subject: [PATCH 03/38] PostgresEngine Update Added the LazyCollection use statement. Updated some returns in the DocBlock to reflect the actual return values. --- src/PostgresEngine.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index f4552a5..9690f96 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; use Laravel\Scout\Engines\Engine; use ScoutEngines\Postgres\TsQuery\PhraseToTsQuery; @@ -76,7 +77,7 @@ public function update($models) * Perform update of the given model. * * @param \Illuminate\Database\Eloquent\Model $model - * @return bool + * @return bool|int */ protected function performUpdate(Model $model) { @@ -426,7 +427,7 @@ protected function rankingExpression(Model $model, $indexColumn) * Get rank function. * * @param \Illuminate\Database\Eloquent\Model $model - * @return int + * @return string */ protected function rankFunction(Model $model) { From 02a5c4bd33e885854432778624ca23493f286c2d Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Tue, 14 Feb 2023 13:17:10 -0500 Subject: [PATCH 04/38] Laravel v10 Update --- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ba8baf4..e668a0e 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,8 @@ jobs: fail-fast: false matrix: php: [8.0, 8.1, 8.2] - laravel: [^9.0, ^10] - scout: [^9.0] + laravel: [^9, ^10] + scout: [^9, ^10] exclude: - php: 8.0 laravel: ^10 @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 193b594..b388c4e 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "illuminate/contracts": "^9.0|^10", "illuminate/database": "^9.0|^10", "illuminate/support": "^9.0|^10", - "laravel/scout": "^9.0" + "laravel/scout": "^9.0|^10" }, "require-dev": { "phpunit/phpunit": "^9.5", From fc6ac480ba7deddb401ea4bb41196f79f74b265d Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Tue, 14 Feb 2023 22:58:00 -0500 Subject: [PATCH 05/38] No Laravel Scout v10 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e668a0e..a563b1e 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: matrix: php: [8.0, 8.1, 8.2] laravel: [^9, ^10] - scout: [^9, ^10] + scout: [^9] exclude: - php: 8.0 laravel: ^10 diff --git a/composer.json b/composer.json index b388c4e..193b594 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "illuminate/contracts": "^9.0|^10", "illuminate/database": "^9.0|^10", "illuminate/support": "^9.0|^10", - "laravel/scout": "^9.0|^10" + "laravel/scout": "^9.0" }, "require-dev": { "phpunit/phpunit": "^9.5", From 8297ab81085a42ab4aaa3d32aee94decd7161860 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 24 Feb 2023 16:34:52 -0500 Subject: [PATCH 06/38] Forked It Changing the composer.json to reflect that this package has been forked and to be published on packagist.org --- composer.json | 78 +++++++++++++++++---------------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/composer.json b/composer.json index 0c51981..b6389d4 100755 --- a/composer.json +++ b/composer.json @@ -1,56 +1,30 @@ { - "name": "pmatseykanets/laravel-scout-postgres", - "description": "PostgreSQL Full Text Search Driver for Laravel Scout", - "keywords": [ - "laravel", - "laravel scout", - "search", - "postgresql", - "full text search", - "FTS" - ], - "homepage": "https://github.com/pmatseykanets/laravel-scout-postgres", - "license": "MIT", - "support": { - "issues": "https://github.com/pmatseykanets/laravel-scout-postgres/issues", - "source": "https://github.com/pmatseykanets/laravel-scout-postgres" + "name": "devnoiseconsulting/laravel-scout-postgres-tsvector", + "description": "PostgreSQL Full Text Search Driver for Laravel Scout", + "keywords": [ + "laravel", + "laravel scout", + "search", + "postgresql", + "full text search", + "FTS" + ], + "homepage": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector", + "license": "MIT", + "support": { + "issues": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector/issues", + "source": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector" + }, + "authors": [ + { + "name": "Michael Flynn", + "email": "flynnmj@devnoise.com.com", + "homepage": "https://github.com/devNoiseConsulting" }, - "authors": [ - { - "name": "Peter Matseykanets", - "email": "pmatseykanets@gmail.com", - "homepage": "https://github.com/pmatseykanets" - } - ], - "require": { - "php": "^7.3|^8.0", - "illuminate/contracts": "~6.0|~7.0|~8.0|~9.0", - "illuminate/database": "~6.0|~7.0|~8.0|~9.0", - "illuminate/support": "~6.0|~7.0|~8.0|~9.0", - "laravel/scout": "~7.0|~8.0|~9.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.3", - "mockery/mockery": "^1.2.3" - }, - "autoload": { - "psr-4": { - "ScoutEngines\\Postgres\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "ScoutEngines\\Postgres\\Test\\": "tests" - } - }, - "scripts": { - "test": "vendor/bin/phpunit" - }, - "extra": { - "laravel": { - "providers": [ - "ScoutEngines\\Postgres\\PostgresEngineServiceProvider" - ] - } + { + "name": "Peter Matseykanets", + "email": "pmatseykanets@gmail.com", + "homepage": "https://github.com/pmatseykanets" } + ] } From 33d7000cf0bf6a9c9c79293c5888c75afe47ae1e Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 24 Feb 2023 17:20:37 -0500 Subject: [PATCH 07/38] Update README.md --- README.md | 73 ++++++++----------------------------------------------- 1 file changed, 10 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 065c6ac..511a028 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,15 @@ # PostgreSQL Full Text Search Engine for Laravel Scout -[![Latest Version on Packagist](https://img.shields.io/packagist/v/pmatseykanets/laravel-scout-postgres.svg?style=flat-square)](https://packagist.org/packages/pmatseykanets/laravel-scout-postgres) -[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -![tests](https://github.com/pmatseykanets/laravel-scout-postgres/workflows/tests/badge.svg) -[![StyleCI](https://styleci.io/repos/67233265/shield)](https://styleci.io/repos/67233265) -[![Total Downloads](https://img.shields.io/packagist/dt/pmatseykanets/laravel-scout-postgres.svg?style=flat-square)](https://packagist.org/packages/pmatseykanets/laravel-scout-postgres) -[![License](https://poser.pugx.org/pmatseykanets/laravel-scout-postgres/license)](https://github.com/pmatseykanets/laravel-scout-postgres/blob/master/LICENSE.md) +![Build Status](https://github.com/devnoiseconsulting/laravel-scout-postgres-tsvector/workflows/tests/badge.svg) +[![Latest Stable Version](https://img.shields.io/packagist/v/devnoiseconsulting/laravel-scout-postgres-tsvector.svg)](https://packagist.org/packages/devnoiseconsulting/laravel-scout-postgres-tsvector) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) This package makes it easy to use native PostgreSQL Full Text Search capabilities with [Laravel Scout](http://laravel.com/docs/master/scout). -If you find this package usefull, please consider bying me a coffee. - -Buy Me a Coffee at ko-fi.com - ## Contents - [Installation](#installation) - [Laravel](#laravel) - - [Lumen](#lumen) - [Configuration](#configuration) - [Configuring the Engine](#configuring-the-engine) - [Configuring PostgreSQL](#configuring-postgresql) @@ -36,8 +28,8 @@ If you find this package usefull, please consider bying me a coffee. You can install the package via composer: -``` bash -composer require pmatseykanets/laravel-scout-postgres +```bash +composer require devnoiseconsulting/laravel-scout-postgres-tsvector ``` ### Laravel @@ -52,52 +44,6 @@ If you're using Laravel < 5.5 or if you have package auto-discovery turned off y ], ``` -### Lumen - -Scout service provider uses `config_path` helper that is not included in Lumen. -To fix this include the following snippet either directly in `bootstrap.app` or in your autoloaded helpers file i.e. `app/helpers.php`. - -```php -if (! function_exists('config_path')) { - /** - * Get the configuration path. - * - * @param string $path - * @return string - */ - function config_path($path = '') - { - return app()->basePath() . '/config'.($path ? DIRECTORY_SEPARATOR.$path : $path); - } -} -``` - -Create the `scout.php` config file in `app/config` folder with the following contents - -```php - env('SCOUT_DRIVER', 'pgsql'), - 'prefix' => env('SCOUT_PREFIX', ''), - 'queue' => false, - 'pgsql' => [ - 'connection' => 'pgsql', - 'maintain_index' => true, - 'config' => 'english', - ], -]; -``` - -Register service providers: - -```php -// bootstrap/app.php -$app->register(Laravel\Scout\ScoutServiceProvider::class); -$app->configure('scout'); -$app->register(ScoutEngines\Postgres\PostgresEngineServiceProvider::class); -``` - ## Configuration ### Configuring the Engine @@ -126,7 +72,7 @@ Specify the database connection that should be used to access indexed documents ### Configuring PostgreSQL -Make sure that an appropriate [default text search configuration](https://www.postgresql.org/docs/9.5/static/runtime-config-client.html#GUC-DEFAULT-TEXT-SEARCH-CONFIG) is set globbaly (in `postgresql.conf`), for a particular database (`ALTER DATABASE ... SET default_text_search_config TO ...`) or alternatively set `default_text_search_config` in each session. +Make sure that an appropriate [default text search configuration](https://www.postgresql.org/docs/14/runtime-config-client.html#GUC-DEFAULT-TEXT-SEARCH-CONFIG) is set globbaly (in `postgresql.conf`), for a particular database (`ALTER DATABASE ... SET default_text_search_config TO ...`) or alternatively set `default_text_search_config` in each session. To check the current value @@ -265,7 +211,7 @@ $posts = App\Post::search('fat & (cat | rat)') ->usingTsQuery()->get() // websearch_to_tsquery() -// uses web search syntax +// uses web search syntax $posts = App\Post::search('"sad cat" or "fat rat" -mouse') ->usingWebSearchQuery()->get() @@ -281,13 +227,13 @@ Please see the [official documentation](http://laravel.com/docs/master/scout) on ## Testing -``` bash +```bash composer test ``` ## Security -If you discover any security related issues, please email pmatseykanets@gmail.com instead of using the issue tracker. +If you discover any security related issues, please email flynnmj@devnoise.com instead of using the issue tracker. ## Changelog @@ -299,6 +245,7 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ## Credits +- [Michael Flynn](https://github.com/devNoiseConsulting) - [Peter Matseykanets](https://github.com/pmatseykanets) - [All Contributors](../../contributors) From a18db80660902a75dab232307b0064a114320ba5 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 26 Feb 2023 17:40:50 -0500 Subject: [PATCH 08/38] Starting Static Analysis Added Larastan to the package and analyzing the code. Going with level 5 to start with. Adding type information. Casting some variable to match the return type. Added a baseline for errors not fixing at this point. Mostly calls to methods that come from traits. Left 2 errors that I want to get fixed. --- composer.json | 4 +++- phpstan-baseline.neon | 26 ++++++++++++++++++++++++++ phpstan.neon | 14 ++++++++++++++ src/PostgresEngine.php | 22 +++++++++++++--------- src/PostgresEngineServiceProvider.php | 13 +++++++++++++ src/TsQuery/BaseTsQueryable.php | 2 +- src/TsQuery/TsQueryable.php | 2 +- 7 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 2a449f6..78baf92 100755 --- a/composer.json +++ b/composer.json @@ -36,7 +36,9 @@ }, "require-dev": { "phpunit/phpunit": "^9.5", - "mockery/mockery": "^1.5" + "mockery/mockery": "^1.5", + "nunomaduro/larastan": "^2.0", + "orchestra/testbench": "^8.0" }, "autoload": { "psr-4": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..acab0c8 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,26 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getDriverName\\(\\)\\.$#" + count: 1 + path: src/PostgresEngine.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getDeletedAtColumn\\(\\)\\.$#" + count: 1 + path: src/PostgresEngine.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:searchableAs\\(\\)\\.$#" + count: 3 + path: src/PostgresEngine.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:toSearchableArray\\(\\)\\.$#" + count: 1 + path: src/PostgresEngine.php + + - + message: "#^Access to an undefined property ScoutEngines\\\\Postgres\\\\PostgresEngineServiceProvider\\:\\:\\$callback\\.$#" + count: 1 + path: src/PostgresEngineServiceProvider.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..966655f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,14 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon + - phpstan-baseline.neon + +parameters: + + paths: + - src/ + + checkGenericClassInNonGenericObjectType: false + + # Level 9 is the highest level + level: 5 + diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 9690f96..a617956 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -33,7 +33,7 @@ class PostgresEngine extends Engine /** * Config values. * - * @var array + * @var array */ protected $config = []; @@ -46,7 +46,7 @@ class PostgresEngine extends Engine * Create a new instance of PostgresEngine. * * @param \Illuminate\Database\ConnectionResolverInterface $resolver - * @param $config + * @param array $config */ public function __construct(ConnectionResolverInterface $resolver, $config) { @@ -213,7 +213,7 @@ public function getTotalCount($results) * @param \Laravel\Scout\Builder $builder * @param int|null $perPage * @param int $page - * @return array + * @return array */ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) { @@ -305,7 +305,7 @@ public function defaultQueryMethod($query, $config) */ public function mapIds($results) { - $keyName = $this->model ? $this->model->getKeyName() : 'id'; + $keyName = $this->model !== null ? $this->model->getKeyName() : 'id'; return collect($results) ->pluck($keyName) @@ -358,10 +358,10 @@ public function lazyMap(Builder $builder, $results, $model) * Create a search index. * * @param string $name - * @param array $options + * @param array $options * @return mixed */ - public function createIndex($name, array $options = []) + public function createIndex($name, $options = []) { throw new Exception('PostgreSQL indexes should be created through Laravel database migrations.'); } @@ -379,6 +379,7 @@ public function deleteIndex($name) /** * Connect to the database. + * @return void */ protected function connect() { @@ -415,7 +416,7 @@ protected function rankingExpression(Model $model, $indexColumn) } if ($norm = $this->rankNormalization($model)) { - $args->push($norm); + $args->push((string) $norm); } $fn = $this->rankFunction($model); @@ -494,8 +495,10 @@ protected function shouldMaintainIndex(Model $model = null) } if ($model !== null) { - return $this->option($model, 'maintain_index', true); + return (bool) $this->option($model, 'maintain_index', true); } + + return false; } /** @@ -553,6 +556,7 @@ protected function config($key, $default = null) /** * @param \Illuminate\Database\Eloquent\Model $model + * @return void */ protected function preserveModel(Model $model) { @@ -563,7 +567,7 @@ protected function preserveModel(Model $model) * Returns a search config name for a model. * * @param \Illuminate\Database\Eloquent\Model $model - * @return string + * @return string|null */ protected function searchConfig(Model $model) { diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index 3946ffd..9899811 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -12,6 +12,9 @@ class PostgresEngineServiceProvider extends ServiceProvider { + /** + * @return array + */ public static function builderMacros() { return [ @@ -22,6 +25,11 @@ public static function builderMacros() ]; } + /** + * Bootstrap the application events. + * + * @return void + */ public function boot() { $this->app->make(EngineManager::class)->extend('pgsql', function () { @@ -36,6 +44,11 @@ public function boot() } } + /** + * @param string $name + * @param string $class + * @return void + */ protected function registerBuilderMacro($name, $class) { if (! Builder::hasMacro($name)) { diff --git a/src/TsQuery/BaseTsQueryable.php b/src/TsQuery/BaseTsQueryable.php index 48dddad..c4b9b88 100644 --- a/src/TsQuery/BaseTsQueryable.php +++ b/src/TsQuery/BaseTsQueryable.php @@ -50,7 +50,7 @@ public function sql() /** * Return value bindings for the SQL representation. * - * @return array + * @return array */ public function bindings() { diff --git a/src/TsQuery/TsQueryable.php b/src/TsQuery/TsQueryable.php index 836a340..4f53439 100644 --- a/src/TsQuery/TsQueryable.php +++ b/src/TsQuery/TsQueryable.php @@ -14,7 +14,7 @@ public function sql(); /** * Return value bindings for the SQL representation. * - * @return array + * @return array */ public function bindings(); } From 24e6aaba8d9dd1d0d47945e03aaa7d95148d198d Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Mon, 20 Mar 2023 22:21:27 -0400 Subject: [PATCH 09/38] Laravel Scout v10 Laravel Scout v10 is now out. --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a563b1e..e668a0e 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: matrix: php: [8.0, 8.1, 8.2] laravel: [^9, ^10] - scout: [^9] + scout: [^9, ^10] exclude: - php: 8.0 laravel: ^10 diff --git a/composer.json b/composer.json index 2a449f6..1f4331c 100755 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "illuminate/contracts": "^9.0|^10", "illuminate/database": "^9.0|^10", "illuminate/support": "^9.0|^10", - "laravel/scout": "^9.0" + "laravel/scout": "^9.0|^10" }, "require-dev": { "phpunit/phpunit": "^9.5", From aa29b68589134c9624a4543a398c8910766002cb Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Mon, 20 Mar 2023 23:46:17 -0400 Subject: [PATCH 10/38] Duster and Pint Installed tighten/duster and laravel/pint as dev dependencies. Just using the lint option with Duster. Added a pint.json file so Pint will follow the Duster formatting standard. Formatting the code. --- composer.json | 4 +- pint.json | 45 ++++++++++ src/PostgresEngine.php | 76 ++++++---------- src/PostgresEngineServiceProvider.php | 6 +- src/TsQuery/BaseTsQueryable.php | 4 +- tests/PostgresEngineTest.php | 121 +++++++++++++++++++------- 6 files changed, 170 insertions(+), 86 deletions(-) create mode 100644 pint.json diff --git a/composer.json b/composer.json index 13875ed..9895ac5 100755 --- a/composer.json +++ b/composer.json @@ -38,7 +38,9 @@ "phpunit/phpunit": "^9.5", "mockery/mockery": "^1.5", "nunomaduro/larastan": "^2.0", - "orchestra/testbench": "^8.0" + "orchestra/testbench": "^8.0", + "tightenco/duster": "^1.1", + "laravel/pint": "^1.6" }, "autoload": { "psr-4": { diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..2964efd --- /dev/null +++ b/pint.json @@ -0,0 +1,45 @@ +{ + "preset": "laravel", + "rules": { + "blank_line_between_import_groups": true, + "concat_space": { + "spacing": "one" + }, + "class_attributes_separation": { + "elements": { + "method": "one" + } + }, + "curly_braces_position": { + "control_structures_opening_brace": "same_line", + "functions_opening_brace": "next_line_unless_newline_at_signature_end", + "anonymous_functions_opening_brace": "same_line", + "classes_opening_brace": "next_line_unless_newline_at_signature_end", + "anonymous_classes_opening_brace": "next_line_unless_newline_at_signature_end", + "allow_single_line_empty_anonymous_classes": true, + "allow_single_line_anonymous_functions": false + }, + "explicit_string_variable": true, + "global_namespace_import": { + "import_classes": true, + "import_constants": true, + "import_functions": true + }, + "new_with_braces": { + "named_class": false, + "anonymous_class": false + }, + "ordered_imports": { + "sort_algorithm": "alpha", + "imports_order": [ + "const", + "class", + "function" + ] + }, + "php_unit_test_annotation": { + "style": "annotation" + }, + "simple_to_complex_string_variable": true + } +} diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index a617956..ae9f798 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\LazyCollection; +use InvalidArgumentException; use Laravel\Scout\Builder; use Laravel\Scout\Engines\Engine; use ScoutEngines\Postgres\TsQuery\PhraseToTsQuery; @@ -45,8 +46,7 @@ class PostgresEngine extends Engine /** * Create a new instance of PostgresEngine. * - * @param \Illuminate\Database\ConnectionResolverInterface $resolver - * @param array $config + * @param array $config */ public function __construct(ConnectionResolverInterface $resolver, $config) { @@ -59,7 +59,7 @@ public function __construct(ConnectionResolverInterface $resolver, $config) /** * Update the given models in the index. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ public function update($models) @@ -76,7 +76,6 @@ public function update($models) /** * Perform update of the given model. * - * @param \Illuminate\Database\Eloquent\Model $model * @return bool|int */ protected function performUpdate(Model $model) @@ -105,7 +104,6 @@ protected function performUpdate(Model $model) /** * Get the indexed value for a given model. * - * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function toVector(Model $model) @@ -129,7 +127,7 @@ protected function toVector(Model $model) // Set a field weight if it was specified in Model's searchableOptions() if ($label = $this->rankFieldWeightLabel($model, $key)) { - $vector = "setweight($vector, ?)"; + $vector = "setweight({$vector}, ?)"; $bindings->push($label); } @@ -138,14 +136,14 @@ protected function toVector(Model $model) return $this->database ->query() - ->selectRaw("$select AS tsvector", $bindings->all()) + ->selectRaw("{$select} AS tsvector", $bindings->all()) ->value('tsvector'); } /** * Remove the given model from the index. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ public function delete($models) @@ -170,7 +168,6 @@ public function delete($models) /** * Perform the given search on the engine. * - * @param \Laravel\Scout\Builder $builder * @return mixed */ public function search(Builder $builder) @@ -181,9 +178,8 @@ public function search(Builder $builder) /** * Perform the given search on the engine. * - * @param \Laravel\Scout\Builder $builder - * @param int $perPage - * @param int $page + * @param int $perPage + * @param int $page * @return mixed */ public function paginate(Builder $builder, $perPage, $page) @@ -194,7 +190,7 @@ public function paginate(Builder $builder, $perPage, $page) /** * Get the total count from a raw result returned by the engine. * - * @param mixed $results + * @param mixed $results * @return int */ public function getTotalCount($results) @@ -210,9 +206,8 @@ public function getTotalCount($results) /** * Perform the given search on the engine. * - * @param \Laravel\Scout\Builder $builder - * @param int|null $perPage - * @param int $page + * @param int|null $perPage + * @param int $page * @return array */ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) @@ -230,7 +225,7 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) ->select($builder->model->getKeyName()) ->selectRaw("{$this->rankingExpression($builder->model, $indexColumn)} AS rank") ->selectRaw('COUNT(*) OVER () AS total_count') - ->whereRaw("$indexColumn @@ \"tsquery\""); + ->whereRaw("{$indexColumn} @@ \"tsquery\""); // Apply where clauses that were set on the builder instance if any foreach ($builder->wheres as $key => $value) { @@ -269,7 +264,7 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) ? call_user_func($builder->callback, $builder, $this->searchConfig($builder->model), $query) : $this->defaultQueryMethod($builder->query, $this->searchConfig($builder->model)); - $query->crossJoin($this->database->raw($tsQuery->sql().' AS "tsquery"')); + $query->crossJoin($this->database->raw($tsQuery->sql() . ' AS "tsquery"')); // Add TS bindings to the query $query->addBinding($tsQuery->bindings(), 'join'); @@ -280,8 +275,8 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) /** * Returns the default query method. * - * @param string $query - * @param string $config + * @param string $query + * @param string $config * @return \ScoutEngines\Postgres\TsQuery\TsQueryable */ public function defaultQueryMethod($query, $config) @@ -300,7 +295,7 @@ public function defaultQueryMethod($query, $config) /** * Pluck and return the primary keys of the given results. * - * @param mixed $results + * @param mixed $results * @return \Illuminate\Support\Collection */ public function mapIds($results) @@ -315,7 +310,6 @@ public function mapIds($results) /** * Map the given results to instances of the given model. * - * @param \Laravel\Scout\Builder $builder * @param mixed $results * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Database\Eloquent\Collection @@ -344,7 +338,6 @@ public function map(Builder $builder, $results, $model) /** * Map the given results to instances of the given model via a lazy collection. * - * @param \Laravel\Scout\Builder $builder * @param mixed $results * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Support\LazyCollection @@ -379,6 +372,7 @@ public function deleteIndex($name) /** * Connect to the database. + * * @return void */ protected function connect() @@ -392,7 +386,7 @@ protected function connect() ->connection($this->config('connection')); if ($connection->getDriverName() !== 'pgsql') { - throw new \InvalidArgumentException('Connection should use pgsql driver.'); + throw new InvalidArgumentException('Connection should use pgsql driver.'); } $this->database = $connection; @@ -403,8 +397,7 @@ protected function connect() * ts_rank([ weights, ] vector, query [, normalization ]) * ts_rank_cd([ weights, ] vector, query [, normalization ]). * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $indexColumn + * @param string $indexColumn * @return string */ protected function rankingExpression(Model $model, $indexColumn) @@ -412,7 +405,7 @@ protected function rankingExpression(Model $model, $indexColumn) $args = collect([$indexColumn, '"tsquery"']); if ($weights = $this->rankWeights($model)) { - $args->prepend("'$weights'"); + $args->prepend("'{$weights}'"); } if ($norm = $this->rankNormalization($model)) { @@ -421,13 +414,12 @@ protected function rankingExpression(Model $model, $indexColumn) $fn = $this->rankFunction($model); - return "$fn({$args->implode(',')})"; + return "{$fn}({$args->implode(',')})"; } /** * Get rank function. * - * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function rankFunction(Model $model) @@ -442,13 +434,12 @@ protected function rankFunction(Model $model) /** * Get the rank weight label for a given field. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $field + * @param string $field * @return string */ protected function rankFieldWeightLabel(Model $model, $field) { - $label = $this->option($model, "rank.fields.$field"); + $label = $this->option($model, "rank.fields.{$field}"); return collect(['A', 'B', 'C', 'D']) ->contains($label) ? $label : ''; @@ -457,7 +448,6 @@ protected function rankFieldWeightLabel(Model $model, $field) /** * Get rank weights. * - * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function rankWeights(Model $model) @@ -468,13 +458,12 @@ protected function rankWeights(Model $model) return ''; } - return '{'.implode(',', $weights).'}'; + return '{' . implode(',', $weights) . '}'; } /** * Get rank normalization. * - * @param \Illuminate\Database\Eloquent\Model $model * @return int */ protected function rankNormalization(Model $model) @@ -485,7 +474,6 @@ protected function rankNormalization(Model $model) /** * See if the index should be maintained for a given model. * - * @param \Illuminate\Database\Eloquent\Model|null $model * @return bool */ protected function shouldMaintainIndex(Model $model = null) @@ -504,7 +492,6 @@ protected function shouldMaintainIndex(Model $model = null) /** * Get the name of the column that holds indexed documents. * - * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function getIndexColumn(Model $model) @@ -515,7 +502,6 @@ protected function getIndexColumn(Model $model) /** * See if indexed documents are stored in a external table. * - * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ protected function isExternalIndex(Model $model) @@ -526,9 +512,8 @@ protected function isExternalIndex(Model $model) /** * Get the model specific option value or a default. * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $key - * @param mixed $default + * @param string $key + * @param mixed $default * @return mixed */ protected function option(Model $model, $key, $default = null) @@ -545,8 +530,8 @@ protected function option(Model $model, $key, $default = null) /** * Get the config value or a default. * - * @param string $key - * @param mixed $default + * @param string $key + * @param mixed $default * @return mixed */ protected function config($key, $default = null) @@ -555,7 +540,6 @@ protected function config($key, $default = null) } /** - * @param \Illuminate\Database\Eloquent\Model $model * @return void */ protected function preserveModel(Model $model) @@ -566,7 +550,6 @@ protected function preserveModel(Model $model) /** * Returns a search config name for a model. * - * @param \Illuminate\Database\Eloquent\Model $model * @return string|null */ protected function searchConfig(Model $model) @@ -577,7 +560,6 @@ protected function searchConfig(Model $model) /** * Checks if the model uses the SoftDeletes trait. * - * @param \Illuminate\Database\Eloquent\Model $model * @return bool */ protected function usesSoftDeletes(Model $model) @@ -588,7 +570,7 @@ protected function usesSoftDeletes(Model $model) /** * Flush all of the model's records from the engine. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function flush($model) diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index 9899811..1628c96 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -25,7 +25,7 @@ public static function builderMacros() ]; } - /** + /** * Bootstrap the application events. * * @return void @@ -45,8 +45,8 @@ public function boot() } /** - * @param string $name - * @param string $class + * @param string $name + * @param string $class * @return void */ protected function registerBuilderMacro($name, $class) diff --git a/src/TsQuery/BaseTsQueryable.php b/src/TsQuery/BaseTsQueryable.php index c4b9b88..fb02a75 100644 --- a/src/TsQuery/BaseTsQueryable.php +++ b/src/TsQuery/BaseTsQueryable.php @@ -28,8 +28,8 @@ abstract class BaseTsQueryable implements TsQueryable /** * Create a new instance. * - * @param string $query - * @param string $config + * @param string $query + * @param string $config */ public function __construct($query, $config = null) { diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 7a7f284..722dd68 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -2,6 +2,7 @@ namespace ScoutEngines\Postgres\Test; +use Exception; use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Collection; @@ -13,14 +14,20 @@ class PostgresEngineTest extends TestCase { - public function test_it_can_be_instantiated() + /** + * @test + */ + public function it_can_be_instantiated() { [$engine] = $this->getEngine(); $this->assertInstanceOf(PostgresEngine::class, $engine); } - public function test_update_adds_object_to_index() + /** + * @test + */ + public function update_adds_object_to_index() { [$engine, $db] = $this->getEngine(); @@ -45,17 +52,23 @@ public function test_update_adds_object_to_index() $table->shouldReceive('update') ->with(['searchable' => 'foo']); - $engine->update(Collection::make([new TestModel()])); + $engine->update(Collection::make([new TestModel])); } - public function test_update_do_nothing_if_index_maintenance_turned_off_globally() + /** + * @test + */ + public function update_do_nothing_if_index_maintenance_turned_off_globally() { [$engine] = $this->getEngine(['maintain_index' => false]); - $engine->update(Collection::make([new TestModel()])); + $engine->update(Collection::make([new TestModel])); } - public function test_delete_removes_object_from_index() + /** + * @test + */ + public function delete_removes_object_from_index() { [$engine, $db] = $this->getEngine(); @@ -67,19 +80,25 @@ public function test_delete_removes_object_from_index() $table->shouldReceive('update') ->with(['searchable' => null]); - $engine->delete(Collection::make([new TestModel()])); + $engine->delete(Collection::make([new TestModel])); } - public function test_delete_do_nothing_if_index_maintenance_turned_off_globally() + /** + * @test + */ + public function delete_do_nothing_if_index_maintenance_turned_off_globally() { [$engine, $db] = $this->getEngine(['maintain_index' => false]); $db->shouldNotReceive('table'); - $engine->delete(Collection::make([new TestModel()])); + $engine->delete(Collection::make([new TestModel])); } - public function test_flush_removes_all_objects_from_index() + /** + * @test + */ + public function flush_removes_all_objects_from_index() { [$engine, $db] = $this->getEngine(); @@ -90,19 +109,25 @@ public function test_flush_removes_all_objects_from_index() ->once() ->with(['searchable' => null]); - $engine->flush(new TestModel()); + $engine->flush(new TestModel); } - public function test_flush_does_nothing_if_index_maintenance_turned_off_globally() + /** + * @test + */ + public function flush_does_nothing_if_index_maintenance_turned_off_globally() { [$engine, $db] = $this->getEngine(['maintain_index' => false]); $db->shouldNotReceive('table'); - $engine->flush(new TestModel()); + $engine->flush(new TestModel); } - public function test_search() + /** + * @test + */ + public function search() { [$engine, $db] = $this->getEngine(); @@ -119,7 +144,7 @@ public function test_search() $db->shouldReceive('select') ->with(null, $table->getBindings()); - $builder = new Builder(new TestModel(), 'foo'); + $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1) ->where('baz', 'qux') ->take(5); @@ -127,7 +152,10 @@ public function test_search() $engine->search($builder); } - public function test_search_with_order_by() + /** + * @test + */ + public function search_with_order_by() { [$engine, $db] = $this->getEngine(); @@ -140,14 +168,17 @@ public function test_search_with_order_by() $db->shouldReceive('select') ->with(null, $table->getBindings()); - $builder = new Builder(new TestModel(), 'foo'); + $builder = new Builder(new TestModel, 'foo'); $builder->orderBy('bar', 'desc') ->orderBy('baz', 'asc'); $engine->search($builder); } - public function test_search_with_global_config() + /** + * @test + */ + public function search_with_global_config() { [$engine, $db] = $this->getEngine(['config' => 'simple']); @@ -162,13 +193,16 @@ public function test_search_with_global_config() $db->shouldReceive('select')->with(null, $table->getBindings()); - $builder = new Builder(new TestModel(), 'foo'); + $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1)->take(5); $engine->search($builder); } - public function test_search_with_model_config() + /** + * @test + */ + public function search_with_model_config() { [$engine, $db] = $this->getEngine(['config' => 'simple']); @@ -183,7 +217,7 @@ public function test_search_with_model_config() $db->shouldReceive('select')->with(null, $table->getBindings()); - $model = new TestModel(); + $model = new TestModel; $model->searchableOptions['config'] = 'english'; $builder = new Builder($model, 'foo'); @@ -192,7 +226,10 @@ public function test_search_with_model_config() $engine->search($builder); } - public function test_search_with_soft_deletes() + /** + * @test + */ + public function search_with_soft_deletes() { [$engine, $db] = $this->getEngine(); @@ -206,20 +243,23 @@ public function test_search_with_soft_deletes() $db->shouldReceive('select')->with(null, $table->getBindings()); - $builder = new Builder(new SoftDeletableTestModel(), 'foo'); + $builder = new Builder(new SoftDeletableTestModel, 'foo'); $builder->where('bar', 1)->take(5); $engine->search($builder); } - public function test_maps_results_to_models() + /** + * @test + */ + public function maps_results_to_models() { [$engine] = $this->getEngine(); $model = Mockery::mock('StdClass'); $model->shouldReceive('getKeyName')->andReturn('id'); $model->shouldReceive('whereIn')->once()->with('id', [1])->andReturn($model); - $model->shouldReceive('get')->once()->andReturn(Collection::make([new TestModel()])); + $model->shouldReceive('get')->once()->andReturn(Collection::make([new TestModel])); $results = $engine->map( new Builder(new TestModel, 'foo'), @@ -230,7 +270,10 @@ public function test_maps_results_to_models() $this->assertCount(1, $results); } - public function test_map_filters_out_no_longer_existing_models() + /** + * @test + */ + public function map_filters_out_no_longer_existing_models() { [$engine] = $this->getEngine(); @@ -238,7 +281,7 @@ public function test_map_filters_out_no_longer_existing_models() $model->shouldReceive('getKeyName')->andReturn('id'); $model->shouldReceive('whereIn')->once()->with('id', [1, 2])->andReturn($model); - $expectedModel = new SoftDeletableTestModel(); + $expectedModel = new SoftDeletableTestModel; $expectedModel->id = 2; $model->shouldReceive('get')->once()->andReturn(Collection::make([$expectedModel])); @@ -253,7 +296,10 @@ public function test_map_filters_out_no_longer_existing_models() $this->assertEquals(2, $models->first()->id); } - public function test_it_returns_total_count() + /** + * @test + */ + public function it_returns_total_count() { [$engine] = $this->getEngine(); @@ -264,7 +310,10 @@ public function test_it_returns_total_count() $this->assertEquals(100, $count); } - public function test_map_ids_returns_right_key() + /** + * @test + */ + public function map_ids_returns_right_key() { [$engine, $db] = $this->getEngine(); @@ -282,21 +331,27 @@ public function test_map_ids_returns_right_key() $this->assertEquals([1, 2], $ids->all()); } - public function test_create_index() + /** + * @test + */ + public function create_index() { [$engine, $db] = $this->getEngine(); - $this->expectException(\Exception::class); + $this->expectException(Exception::class); $this->expectExceptionMessage('PostgreSQL indexes should be created through Laravel database migrations.'); $engine->createIndex('bad_index'); } - public function test_delete_index() + /** + * @test + */ + public function delete_index() { [$engine, $db] = $this->getEngine(); - $this->expectException(\Exception::class); + $this->expectException(Exception::class); $this->expectExceptionMessage('PostgreSQL indexes should be deleted through Laravel database migrations.'); $engine->deleteIndex('bad_index'); From 73e303b84d6331937fc272d8247136d306a942a0 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Mon, 20 Mar 2023 23:59:52 -0400 Subject: [PATCH 11/38] PostgresConnection fix Refactoring code to remove the getDriverName call so that the "Call to an undefined method" error is fixed. Checking if the connection is an instance of PostgessConnection. --- phpstan-baseline.neon | 5 ----- src/PostgresEngine.php | 9 +++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index acab0c8..230cf69 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getDriverName\\(\\)\\.$#" - count: 1 - path: src/PostgresEngine.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getDeletedAtColumn\\(\\)\\.$#" count: 1 diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index ae9f798..5a908ee 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -6,6 +6,7 @@ use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\PostgresConnection; use Illuminate\Support\Arr; use Illuminate\Support\LazyCollection; use InvalidArgumentException; @@ -20,7 +21,7 @@ class PostgresEngine extends Engine /** * Database connection. * - * @var \Illuminate\Database\Connection + * @var \Illuminate\Database\PostgresConnection */ protected $database; @@ -385,11 +386,11 @@ protected function connect() $connection = $this->resolver ->connection($this->config('connection')); - if ($connection->getDriverName() !== 'pgsql') { + if ($connection instanceof PostgresConnection) { + $this->database = $connection; + } else { throw new InvalidArgumentException('Connection should use pgsql driver.'); } - - $this->database = $connection; } /** From a707941ae19cab99d5f6d58a20f673037d3b9c3a Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 29 Mar 2023 16:38:05 -0400 Subject: [PATCH 12/38] Builder::macro fix Making larastan/phpstan happy by refactoring the code. Fixes Issue 2. --- phpstan-baseline.neon | 4 ---- src/PostgresEngineServiceProvider.php | 23 +++++++---------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 230cf69..0242fd2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,7 +15,3 @@ parameters: count: 1 path: src/PostgresEngine.php - - - message: "#^Access to an undefined property ScoutEngines\\\\Postgres\\\\PostgresEngineServiceProvider\\:\\:\\$callback\\.$#" - count: 1 - path: src/PostgresEngineServiceProvider.php diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index 1628c96..d390b08 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -15,7 +15,7 @@ class PostgresEngineServiceProvider extends ServiceProvider /** * @return array */ - public static function builderMacros() + public static function builderMacros(): array { return [ 'usingPhraseQuery' => PhraseToTsQuery::class, @@ -27,10 +27,8 @@ public static function builderMacros() /** * Bootstrap the application events. - * - * @return void */ - public function boot() + public function boot(): void { $this->app->make(EngineManager::class)->extend('pgsql', function () { return new PostgresEngine( @@ -44,21 +42,14 @@ public function boot() } } - /** - * @param string $name - * @param string $class - * @return void - */ - protected function registerBuilderMacro($name, $class) + protected function registerBuilderMacro(string $name, string $class): void { if (! Builder::hasMacro($name)) { - Builder::macro($name, function () use ($class) { - $this->callback = function ($builder, $config) use ($class) { + $builderMacro = (object) [ + 'callback' => function (Builder $builder, mixed $config) use ($class) { return new $class($builder->query, $config); - }; - - return $this; - }); + }]; + Builder::macro($name, $builderMacro); } } } From b812c10cb1e137325c4a76eb8fc43989cbb6a740 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 29 Mar 2023 16:38:05 -0400 Subject: [PATCH 13/38] Builder::macro fix Making larastan/phpstan happy by refactoring the code. Fixes Issue 3. --- phpstan-baseline.neon | 4 ---- src/PostgresEngineServiceProvider.php | 23 +++++++---------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 230cf69..0242fd2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,7 +15,3 @@ parameters: count: 1 path: src/PostgresEngine.php - - - message: "#^Access to an undefined property ScoutEngines\\\\Postgres\\\\PostgresEngineServiceProvider\\:\\:\\$callback\\.$#" - count: 1 - path: src/PostgresEngineServiceProvider.php diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index 1628c96..d390b08 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -15,7 +15,7 @@ class PostgresEngineServiceProvider extends ServiceProvider /** * @return array */ - public static function builderMacros() + public static function builderMacros(): array { return [ 'usingPhraseQuery' => PhraseToTsQuery::class, @@ -27,10 +27,8 @@ public static function builderMacros() /** * Bootstrap the application events. - * - * @return void */ - public function boot() + public function boot(): void { $this->app->make(EngineManager::class)->extend('pgsql', function () { return new PostgresEngine( @@ -44,21 +42,14 @@ public function boot() } } - /** - * @param string $name - * @param string $class - * @return void - */ - protected function registerBuilderMacro($name, $class) + protected function registerBuilderMacro(string $name, string $class): void { if (! Builder::hasMacro($name)) { - Builder::macro($name, function () use ($class) { - $this->callback = function ($builder, $config) use ($class) { + $builderMacro = (object) [ + 'callback' => function (Builder $builder, mixed $config) use ($class) { return new $class($builder->query, $config); - }; - - return $this; - }); + }]; + Builder::macro($name, $builderMacro); } } } From df5e7aaa5fbf075d3eaf05a26ad6265d8bf68d76 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 29 Mar 2023 16:59:01 -0400 Subject: [PATCH 14/38] Refactor PostgresEngine::map larastan/phpstan getting picky about the return type on the map function. Made a new collection variable that pushed the models in the result order. Fixes Issue 2 --- src/PostgresEngine.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 5a908ee..39634ef 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -317,23 +317,25 @@ public function mapIds($results) */ public function map(Builder $builder, $results, $model) { + $resultModels = Collection::make(); + if (empty($results)) { - return Collection::make(); + return $resultModels; } $keys = $this->mapIds($results); - $results = collect($results); - $models = $model->whereIn($model->getKeyName(), $keys->all()) ->get() ->keyBy($model->getKeyName()); - return $results->pluck($model->getKeyName()) - ->intersect($models->keys()) // Filter out no longer existing models (i.e. soft deleted) - ->map(function ($key) use ($models) { - return $models[$key]; - }); + // The models didn't come out of the database in the correct order. + // This will map the models into the resultsModel based on the results order. + foreach ($keys as $key) { + $resultModels->push($models[$key]); + } + + return $resultModels; } /** From d0fe9676fb23f4d2b65af3d20e42364050665a24 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 29 Mar 2023 21:44:54 -0400 Subject: [PATCH 15/38] Rollback Builder::macro While it made larastan/phpstan happy, the code did not work. Putting back the old code until a better solution is apparent. --- phpstan-baseline.neon | 5 ++++- src/PostgresEngineServiceProvider.php | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0242fd2..fa0f0f2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -14,4 +14,7 @@ parameters: message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:toSearchableArray\\(\\)\\.$#" count: 1 path: src/PostgresEngine.php - + - + message: "#^Access to an undefined property ScoutEngines\\\\Postgres\\\\PostgresEngineServiceProvider\\:\\:\\$callback\\.$#" + count: 1 + path: src/PostgresEngineServiceProvider.php diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index d390b08..ccb752d 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -45,11 +45,13 @@ public function boot(): void protected function registerBuilderMacro(string $name, string $class): void { if (! Builder::hasMacro($name)) { - $builderMacro = (object) [ - 'callback' => function (Builder $builder, mixed $config) use ($class) { + Builder::macro($name, function () use ($class) { + $this->callback = function ($builder, $config) use ($class) { return new $class($builder->query, $config); - }]; - Builder::macro($name, $builderMacro); + }; + + return $this; + }); } } } From 35ecdd8522272dbf347dd56e44aa01988767a13f Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 5 Apr 2023 16:07:13 -0400 Subject: [PATCH 16/38] Larastan Level 8 Working through the errors as I increase the phpstan level up to 8. Narrowing some Types to identify what is being put into collect(). searchConfig should return a string and not string|null. If the not defined then 'simple' will be the text search configuration. 'plainquery' is the defaultQueryMethod. Changed the default from 'plain' to 'plainquery'. --- phpstan.neon | 2 +- src/PostgresEngine.php | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 966655f..aae79cb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,5 +10,5 @@ parameters: checkGenericClassInNonGenericObjectType: false # Level 9 is the highest level - level: 5 + level: 8 diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 39634ef..90d1e0a 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -109,7 +109,9 @@ protected function performUpdate(Model $model) */ protected function toVector(Model $model) { - $fields = collect($model->toSearchableArray()) + /** @var array $searchableArray */ + $searchableArray = $model->toSearchableArray(); + $fields = collect($searchableArray) ->map(function ($value) { return $value === null ? '' : $value; }); @@ -282,7 +284,7 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) */ public function defaultQueryMethod($query, $config) { - switch (strtolower($this->config('search_using', 'plain'))) { + switch (strtolower($this->config('search_using', 'plainquery'))) { case 'tsquery': return new ToTsQuery($query, $config); case 'phrasequery': @@ -303,6 +305,7 @@ public function mapIds($results) { $keyName = $this->model !== null ? $this->model->getKeyName() : 'id'; + /** @var array $results */ return collect($results) ->pluck($keyName) ->values(); @@ -553,11 +556,11 @@ protected function preserveModel(Model $model) /** * Returns a search config name for a model. * - * @return string|null + * @return string */ protected function searchConfig(Model $model) { - return $this->option($model, 'config', $this->config('config', '')) ?: null; + return $this->option($model, 'config', $this->config('config', '')) ?: 'simple'; } /** From d6fea0d914214ba13826163ecce5273df2389613 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Mon, 24 Apr 2023 23:13:28 -0400 Subject: [PATCH 17/38] Changes for Testing Testing broke with the new dev packages used to improve the code. Removing the dev packages. Updated the phpunit.xml.dist to match the new schema. --- composer.json | 120 +++++++++++++++++++++++------------------------ phpunit.xml.dist | 21 +++------ 2 files changed, 64 insertions(+), 77 deletions(-) diff --git a/composer.json b/composer.json index 9895ac5..b915f29 100755 --- a/composer.json +++ b/composer.json @@ -1,65 +1,61 @@ { - "name": "devnoiseconsulting/laravel-scout-postgres-tsvector", - "description": "PostgreSQL Full Text Search Driver for Laravel Scout", - "keywords": [ - "laravel", - "laravel scout", - "search", - "postgresql", - "full text search", - "FTS" - ], - "homepage": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector", - "license": "MIT", - "support": { - "issues": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector/issues", - "source": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector" - }, - "authors": [ - { - "name": "Michael Flynn", - "email": "flynnmj@devnoise.com.com", - "homepage": "https://github.com/devNoiseConsulting" + "name": "devnoiseconsulting/laravel-scout-postgres-tsvector", + "description": "PostgreSQL Full Text Search Driver for Laravel Scout", + "keywords": [ + "laravel", + "laravel scout", + "search", + "postgresql", + "full text search", + "FTS" + ], + "homepage": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector", + "license": "MIT", + "support": { + "issues": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector/issues", + "source": "https://github.com/devNoiseConsulting/laravel-scout-postgres-tsvector" }, - { - "name": "Peter Matseykanets", - "email": "pmatseykanets@gmail.com", - "homepage": "https://github.com/pmatseykanets" - } - ], - "require": { - "php": "^8.0|^8.1|^8.2", - "illuminate/contracts": "^9.0|^10", - "illuminate/database": "^9.0|^10", - "illuminate/support": "^9.0|^10", - "laravel/scout": "^9.0|^10" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "mockery/mockery": "^1.5", - "nunomaduro/larastan": "^2.0", - "orchestra/testbench": "^8.0", - "tightenco/duster": "^1.1", - "laravel/pint": "^1.6" - }, - "autoload": { - "psr-4": { - "ScoutEngines\\Postgres\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "ScoutEngines\\Postgres\\Test\\": "tests" - } - }, - "scripts": { - "test": "vendor/bin/phpunit" - }, - "extra": { - "laravel": { - "providers": [ - "ScoutEngines\\Postgres\\PostgresEngineServiceProvider" - ] + "authors": [ + { + "name": "Michael Flynn", + "email": "flynnmj@devnoise.com.com", + "homepage": "https://github.com/devNoiseConsulting" + }, + { + "name": "Peter Matseykanets", + "email": "pmatseykanets@gmail.com", + "homepage": "https://github.com/pmatseykanets" + } + ], + "require": { + "php": "^8.0|^8.1|^8.2", + "illuminate/contracts": "^9|^10", + "illuminate/database": "^9|^10", + "illuminate/support": "^9|^10", + "laravel/scout": "^9|^10" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "mockery/mockery": "^1.5" + }, + "autoload": { + "psr-4": { + "ScoutEngines\\Postgres\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "ScoutEngines\\Postgres\\Test\\": "tests" + } + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "extra": { + "laravel": { + "providers": [ + "ScoutEngines\\Postgres\\PostgresEngineServiceProvider" + ] + } } - } -} +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f2eebe0..530b980 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,13 @@ - + + + + src/ + + tests - - - src/ - - From e71dbbd82b7a6d3acccd7199ed48dbfbd2c47e86 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Tue, 25 Apr 2023 12:35:28 -0400 Subject: [PATCH 18/38] Fixing Testing Making changes so test matches how the code works. Changed code so that the test passes. Not sure that the map_filters_out_no_longer_existing_models is really a valid test. --- src/PostgresEngine.php | 4 +++- tests/PostgresEngineTest.php | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 90d1e0a..f35c099 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -335,7 +335,9 @@ public function map(Builder $builder, $results, $model) // The models didn't come out of the database in the correct order. // This will map the models into the resultsModel based on the results order. foreach ($keys as $key) { - $resultModels->push($models[$key]); + if ($models->has($key)) { + $resultModels->push($models[$key]); + } } return $resultModels; diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 722dd68..2202af0 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -3,11 +3,11 @@ namespace ScoutEngines\Postgres\Test; use Exception; -use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Database\PostgresConnection; use Laravel\Scout\Builder; use Mockery; use ScoutEngines\Postgres\PostgresEngine; @@ -36,7 +36,7 @@ public function update_adds_object_to_index() $query->shouldReceive('selectRaw') ->with( 'to_tsvector(COALESCE(?, get_current_ts_config()), ?) || setweight(to_tsvector(COALESCE(?, get_current_ts_config()), ?), ?) AS tsvector', - [null, 'Foo', null, '', 'B'] + ['simple', 'Foo', 'simple', '', 'B'] ) ->andReturnSelf(); $query->shouldReceive('value') @@ -361,7 +361,7 @@ protected function getEngine($config = []) { $resolver = Mockery::mock(ConnectionResolverInterface::class); $resolver->shouldReceive('connection') - ->andReturn($db = Mockery::mock(Connection::class)); + ->andReturn($db = Mockery::mock(PostgresConnection::class)); $db->shouldReceive('getDriverName')->andReturn('pgsql'); @@ -377,30 +377,30 @@ protected function setDbExpectations($db, $withDefaultOrderBy = true) ->andReturn('plainto_tsquery(COALESCE(?, get_current_ts_config()), ?) AS "tsquery"'); $table->shouldReceive('crossJoin') - ->with('plainto_tsquery(COALESCE(?, get_current_ts_config()), ?) AS "tsquery"') - ->andReturnSelf() + ->with('plainto_tsquery(COALESCE(?, get_current_ts_config()), ?) AS "tsquery"') + ->andReturnSelf() ->shouldReceive('addBinding') - ->with(Mockery::type('array'), 'join') - ->andReturnSelf() + ->with(Mockery::type('array'), 'join') + ->andReturnSelf() ->shouldReceive('select') - ->with('id') - ->andReturnSelf() + ->with('id') + ->andReturnSelf() ->shouldReceive('selectRaw') - ->with('ts_rank(searchable,"tsquery") AS rank') - ->andReturnSelf() + ->with('ts_rank(searchable,"tsquery") AS rank') + ->andReturnSelf() ->shouldReceive('selectRaw') - ->with('COUNT(*) OVER () AS total_count') - ->andReturnSelf() + ->with('COUNT(*) OVER () AS total_count') + ->andReturnSelf() ->shouldReceive('whereRaw') - ->andReturnSelf(); + ->andReturnSelf(); if ($withDefaultOrderBy) { $table->shouldReceive('orderBy') - ->with('rank', 'desc') - ->andReturnSelf() + ->with('rank', 'desc') + ->andReturnSelf() ->shouldReceive('orderBy') - ->with('id') - ->andReturnSelf(); + ->with('id') + ->andReturnSelf(); } $table->shouldReceive('toSql'); From 6349dcf35865142784f42c1724cc594f4d9b6196 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Wed, 26 Apr 2023 22:06:31 -0400 Subject: [PATCH 19/38] Larastan Level 9 Working on getting the code to pass Larastan level 9. Mostly new code to get around the fact you can cast a mixed variable into the type you need. --- phpstan-baseline.neon | 2 +- src/PostgresEngine.php | 140 +++++++++++++++++++++-------------- tests/PostgresEngineTest.php | 2 +- 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fa0f0f2..d9d146d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7,7 +7,7 @@ parameters: - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:searchableAs\\(\\)\\.$#" - count: 3 + count: 4 path: src/PostgresEngine.php - diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index f35c099..438982b 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -95,19 +95,17 @@ protected function performUpdate(Model $model) return $query->update($data->all()); } + $modelKeyInfo = collect([$model->getKeyName() => $model->getKey()]); + return $query->insert( - $data->merge([ - $model->getKeyName() => $model->getKey(), - ])->all() + $data->merge($modelKeyInfo)->all() ); } /** * Get the indexed value for a given model. - * - * @return string */ - protected function toVector(Model $model) + protected function toVector(Model $model): mixed { /** @var array $searchableArray */ $searchableArray = $model->toSearchableArray(); @@ -153,19 +151,22 @@ public function delete($models) { $model = $models->first(); - if (! $this->shouldMaintainIndex($model)) { - return; - } + if ($model) { + if (! $this->shouldMaintainIndex($model)) { + return; + } - $indexColumn = $this->getIndexColumn($model); - $key = $model->getKeyName(); + $indexColumn = $this->getIndexColumn($model); + $key = $model->getKeyName(); - $ids = $models->pluck($key)->all(); + $ids = $models->pluck($key)->all(); - $this->database - ->table($model->searchableAs()) - ->whereIn($key, $ids) - ->update([$indexColumn => null]); + $this->database + ->table($model->searchableAs()) + ->whereIn($key, $ids) + ->update([$indexColumn => null]); + + } } /** @@ -202,8 +203,11 @@ public function getTotalCount($results) return 0; } - return (int) Arr::first($results) - ->total_count; + /** @var array $results */ + /** @var object{'id': int, 'rank': string, 'total_count': int} $result */ + $result = Arr::first($results); + + return (int) $result->total_count; } /** @@ -267,6 +271,7 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) ? call_user_func($builder->callback, $builder, $this->searchConfig($builder->model), $query) : $this->defaultQueryMethod($builder->query, $this->searchConfig($builder->model)); + /** @var \ScoutEngines\Postgres\TsQuery\BaseTsQueryable $tsQuery */ $query->crossJoin($this->database->raw($tsQuery->sql() . ' AS "tsquery"')); // Add TS bindings to the query $query->addBinding($tsQuery->bindings(), 'join'); @@ -284,7 +289,7 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1) */ public function defaultQueryMethod($query, $config) { - switch (strtolower($this->config('search_using', 'plainquery'))) { + switch (strtolower($this->stringConfig('search_using', 'plainquery'))) { case 'tsquery': return new ToTsQuery($query, $config); case 'phrasequery': @@ -334,6 +339,7 @@ public function map(Builder $builder, $results, $model) // The models didn't come out of the database in the correct order. // This will map the models into the resultsModel based on the results order. + /** @var int $key */ foreach ($keys as $key) { if ($models->has($key)) { $resultModels->push($models[$key]); @@ -391,7 +397,7 @@ protected function connect() } $connection = $this->resolver - ->connection($this->config('connection')); + ->connection($this->stringConfig('connection')); if ($connection instanceof PostgresConnection) { $this->database = $connection; @@ -404,11 +410,8 @@ protected function connect() * Build ranking expression that will be used in a search. * ts_rank([ weights, ] vector, query [, normalization ]) * ts_rank_cd([ weights, ] vector, query [, normalization ]). - * - * @param string $indexColumn - * @return string */ - protected function rankingExpression(Model $model, $indexColumn) + protected function rankingExpression(Model $model, string $indexColumn): string { $args = collect([$indexColumn, '"tsquery"']); @@ -427,27 +430,22 @@ protected function rankingExpression(Model $model, $indexColumn) /** * Get rank function. - * - * @return string */ - protected function rankFunction(Model $model) + protected function rankFunction(Model $model): string { $default = 'ts_rank'; - $function = $this->option($model, 'rank.function', $default); + $function = $this->stringOption($model, 'rank.function', $default); return collect(['ts_rank', 'ts_rank_cd'])->contains($function) ? $function : $default; } /** * Get the rank weight label for a given field. - * - * @param string $field - * @return string */ - protected function rankFieldWeightLabel(Model $model, $field) + protected function rankFieldWeightLabel(Model $model, string $field): string { - $label = $this->option($model, "rank.fields.{$field}"); + $label = $this->stringOption($model, "rank.fields.{$field}"); return collect(['A', 'B', 'C', 'D']) ->contains($label) ? $label : ''; @@ -455,10 +453,8 @@ protected function rankFieldWeightLabel(Model $model, $field) /** * Get rank weights. - * - * @return string */ - protected function rankWeights(Model $model) + protected function rankWeights(Model $model): string { $weights = $this->option($model, 'rank.weights'); @@ -471,20 +467,16 @@ protected function rankWeights(Model $model) /** * Get rank normalization. - * - * @return int */ - protected function rankNormalization(Model $model) + protected function rankNormalization(Model $model): int { - return $this->option($model, 'rank.normalization', 0); + return $this->intOption($model, 'rank.normalization', 0); } /** * See if the index should be maintained for a given model. - * - * @return bool */ - protected function shouldMaintainIndex(Model $model = null) + protected function shouldMaintainIndex(Model $model = null): bool { if ((bool) $this->config('maintain_index', true) === false) { return false; @@ -499,20 +491,16 @@ protected function shouldMaintainIndex(Model $model = null) /** * Get the name of the column that holds indexed documents. - * - * @return string */ - protected function getIndexColumn(Model $model) + protected function getIndexColumn(Model $model): string { - return $this->option($model, 'column', 'searchable'); + return $this->stringOption($model, 'column', 'searchable'); } /** * See if indexed documents are stored in a external table. - * - * @return mixed */ - protected function isExternalIndex(Model $model) + protected function isExternalIndex(Model $model): mixed { return $this->option($model, 'external', false); } @@ -520,11 +508,9 @@ protected function isExternalIndex(Model $model) /** * Get the model specific option value or a default. * - * @param string $key * @param mixed $default - * @return mixed */ - protected function option(Model $model, $key, $default = null) + protected function option(Model $model, string $key, mixed $default = null): mixed { if (! method_exists($model, 'searchableOptions')) { return $default; @@ -535,18 +521,58 @@ protected function option(Model $model, $key, $default = null) return Arr::get($options, $key, $default); } + /** + * Get the model specific option value or a default as an int. + */ + protected function intOption(Model $model, string $key, int $default): int + { + $value = $this->option($model, $key, $default); + + if (is_int($value)) { + return $value; + } else { + return $default; + } + } + + /** + * Get the model specific option value or a default as a string. + */ + protected function stringOption(Model $model, string $key, string $default = ''): string + { + $value = $this->option($model, $key, $default); + + if (is_string($value)) { + return $value; + } else { + return $default; + } + } + /** * Get the config value or a default. * - * @param string $key * @param mixed $default - * @return mixed */ - protected function config($key, $default = null) + protected function config(string $key, mixed $default = null): mixed { return Arr::get($this->config, $key, $default); } + /** + * Get the config value or a default as a string. + */ + protected function stringConfig(string $key, string $default = ''): string + { + $value = $this->config($key, $default); + + if (is_string($value)) { + return $value; + } else { + return $default; + } + } + /** * @return void */ @@ -562,7 +588,7 @@ protected function preserveModel(Model $model) */ protected function searchConfig(Model $model) { - return $this->option($model, 'config', $this->config('config', '')) ?: 'simple'; + return $this->stringOption($model, 'config', $this->stringConfig('config', '')) ?: ''; } /** diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 2202af0..75cb30a 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -36,7 +36,7 @@ public function update_adds_object_to_index() $query->shouldReceive('selectRaw') ->with( 'to_tsvector(COALESCE(?, get_current_ts_config()), ?) || setweight(to_tsvector(COALESCE(?, get_current_ts_config()), ?), ?) AS tsvector', - ['simple', 'Foo', 'simple', '', 'B'] + [null, 'Foo', null, '', 'B'] ) ->andReturnSelf(); $query->shouldReceive('value') From 2111ea15f9a7a69a607f3c23b05c84ff1febcf8d Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Thu, 27 Apr 2023 10:49:57 -0400 Subject: [PATCH 20/38] Larastan Level 9 II Mostly making variables so that they can be typed to prevent phpstan errors. --- .github/workflows/tests.yml | 1 - phpstan.neon | 2 +- src/PostgresEngineServiceProvider.php | 12 ++++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e668a0e..43b4bd1 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,6 @@ on: push: branches: - master - - laravel-10-compatibility pull_request: jobs: diff --git a/phpstan.neon b/phpstan.neon index aae79cb..762c86b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,5 +10,5 @@ parameters: checkGenericClassInNonGenericObjectType: false # Level 9 is the highest level - level: 8 + level: 9 diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index ccb752d..16a7e3b 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -31,10 +31,14 @@ public static function builderMacros(): array public function boot(): void { $this->app->make(EngineManager::class)->extend('pgsql', function () { - return new PostgresEngine( - $this->app->get('db'), - $this->app->get('config')->get('scout.pgsql', []) - ); + /** @var \Illuminate\Database\ConnectionResolverInterface $db */ + $db = $this->app->get('db'); + /** @var \Illuminate\Support\Facades\Config $config */ + $config = $this->app->get('config'); + /** @var array $pgScoutConfig */ + $pgScoutConfig = $config->get('scout.pgsql', []); + + return new PostgresEngine($db, $pgScoutConfig); }); foreach (self::builderMacros() as $macro => $class) { From 67dc6f53b0edb0472bfb5e6c021270a82833d307 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Thu, 27 Apr 2023 17:02:08 -0400 Subject: [PATCH 21/38] Github Action Tweaks Adding dev package requirements. Some of the dev tools to help with code improvement do not support PHP 8.0. Change removing unneeded packages for testing in GitHub Actions. --- .github/workflows/tests.yml | 7 ++++++- composer.json | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43b4bd1..0b2181a 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,13 +4,14 @@ on: push: branches: - master + - 9.x pull_request: jobs: tests: runs-on: ubuntu-latest strategy: - fail-fast: false + fail-fast: true matrix: php: [8.0, 8.1, 8.2] laravel: [^9, ^10] @@ -35,6 +36,10 @@ jobs: - name: Install dependencies run: | + composer remove nunomaduro/larastan --dev --no-update + composer remove orchestra/testbench --dev --no-update + composer remove tightenco/duster --dev --no-update + composer remove laravel/pint --dev --no-update composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update composer require "illuminate/database=${{ matrix.laravel }}" --no-update composer require "illuminate/support=${{ matrix.laravel }}" --no-update diff --git a/composer.json b/composer.json index b915f29..2faf06b 100755 --- a/composer.json +++ b/composer.json @@ -35,8 +35,12 @@ "laravel/scout": "^9|^10" }, "require-dev": { + "laravel/pint": "^1.10", + "mockery/mockery": "^1.5", + "nunomaduro/larastan": "^2.6", + "orchestra/testbench": "^8.5", "phpunit/phpunit": "^9.6", - "mockery/mockery": "^1.5" + "tightenco/duster": "^2.0" }, "autoload": { "psr-4": { From 8e5dd7e4e23f7e8f271be8d144ed120d9b527ce3 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 28 Apr 2023 11:22:17 -0400 Subject: [PATCH 22/38] Larastan Level 9 III Finally got enough Larastan under my belt to see how to get rid of the error. Fixed #3 --- composer.json | 2 +- phpstan-baseline.neon | 5 +---- src/PostgresEngineServiceProvider.php | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 2faf06b..9f434f1 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "authors": [ { "name": "Michael Flynn", - "email": "flynnmj@devnoise.com.com", + "email": "flynnmj@devnoise.com", "homepage": "https://github.com/devNoiseConsulting" }, { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d9d146d..0ec50ee 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -14,7 +14,4 @@ parameters: message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:toSearchableArray\\(\\)\\.$#" count: 1 path: src/PostgresEngine.php - - - message: "#^Access to an undefined property ScoutEngines\\\\Postgres\\\\PostgresEngineServiceProvider\\:\\:\\$callback\\.$#" - count: 1 - path: src/PostgresEngineServiceProvider.php + diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index 16a7e3b..b62390e 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -50,6 +50,7 @@ protected function registerBuilderMacro(string $name, string $class): void { if (! Builder::hasMacro($name)) { Builder::macro($name, function () use ($class) { + /** @var Builder $this */ $this->callback = function ($builder, $config) use ($class) { return new $class($builder->query, $config); }; From 9dd69c1afb7edfe5a7bed1f2f6c7d434d79c67d1 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 29 Nov 2024 22:25:48 -0500 Subject: [PATCH 23/38] Laravel Scout v11 Catching up with the latest Laravel releases. --- .github/workflows/tests.yml | 10 ++++++---- .gitignore | 3 ++- composer.json | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0b2181a..36f32bc 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,11 +13,13 @@ jobs: strategy: fail-fast: true matrix: - php: [8.0, 8.1, 8.2] - laravel: [^9, ^10] - scout: [^9, ^10] + php: [8.1, 8.2, 8.3, 8.4] + laravel: [^10, ^11] + scout: [^10, ^11] exclude: - - php: 8.0 + - php: 8.1 + laravel: ^11 + - php: 8.4 laravel: ^10 name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }} diff --git a/.gitignore b/.gitignore index 382f5a0..60718bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor .phpunit.result.cache -composer.lock \ No newline at end of file +composer.lock +.DS_Store diff --git a/composer.json b/composer.json index 9f434f1..d0f5908 100755 --- a/composer.json +++ b/composer.json @@ -28,19 +28,19 @@ } ], "require": { - "php": "^8.0|^8.1|^8.2", - "illuminate/contracts": "^9|^10", - "illuminate/database": "^9|^10", - "illuminate/support": "^9|^10", - "laravel/scout": "^9|^10" + "php": "^8.1|^8.2|^8.3|^8.4", + "illuminate/contracts": "^10|^11", + "illuminate/database": "^10|^11", + "illuminate/support": "^10|^11", + "laravel/scout": "^10|^11" }, "require-dev": { - "laravel/pint": "^1.10", - "mockery/mockery": "^1.5", - "nunomaduro/larastan": "^2.6", - "orchestra/testbench": "^8.5", + "laravel/pint": "^1.18", + "mockery/mockery": "^1.6", + "nunomaduro/larastan": "^2.9", + "orchestra/testbench": "^8.28", "phpunit/phpunit": "^9.6", - "tightenco/duster": "^2.0" + "tightenco/duster": "^2.7" }, "autoload": { "psr-4": { @@ -62,4 +62,4 @@ ] } } -} \ No newline at end of file +} From be2f9b7b22cbaeadb7c699a75ea02b38e7f61d5d Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 29 Nov 2024 22:31:04 -0500 Subject: [PATCH 24/38] Larastan and Duster Had Duster fix the PostgresEngine.php so it would be happy. Working through the new Larastan errors. No real code changes, but new type hinting to remove the errors. Any ignored errors appear to be related to the fact that the code assumes that the Model is using the Searchable trait. --- phpstan-baseline.neon | 14 +- phpstan.neon | 2 - src/PostgresEngine.php | 355 +++++++++++++------------- src/PostgresEngineServiceProvider.php | 6 +- 4 files changed, 189 insertions(+), 188 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0ec50ee..ba86bd9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,17 +1,21 @@ parameters: ignoreErrors: - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getDeletedAtColumn\\(\\)\\.$#" - count: 1 + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:searchableAs\\(\\)\\.$#" + count: 2 path: src/PostgresEngine.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:searchableAs\\(\\)\\.$#" - count: 4 + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:toSearchableArray\\(\\)\\.$#" + count: 1 + path: src/PostgresEngine.php + - + message: "#^Call to an undefined method TModel of Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:searchableAs\\(\\)\\.$#" + count: 2 path: src/PostgresEngine.php - - message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:toSearchableArray\\(\\)\\.$#" + message: "#^Call to an undefined method TModel of Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getDeletedAtColumn\\(\\)\\.$#" count: 1 path: src/PostgresEngine.php diff --git a/phpstan.neon b/phpstan.neon index 762c86b..557ca91 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,8 +7,6 @@ parameters: paths: - src/ - checkGenericClassInNonGenericObjectType: false - # Level 9 is the highest level level: 9 diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 438982b..28d9035 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -16,6 +16,10 @@ use ScoutEngines\Postgres\TsQuery\PlainToTsQuery; use ScoutEngines\Postgres\TsQuery\ToTsQuery; +/** + * @template TModel of \Illuminate\Database\Eloquent\Model + * @template TID of int|string + */ class PostgresEngine extends Engine { /** @@ -60,7 +64,7 @@ public function __construct(ConnectionResolverInterface $resolver, $config) /** * Update the given models in the index. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ public function update($models) @@ -74,77 +78,10 @@ public function update($models) } } - /** - * Perform update of the given model. - * - * @return bool|int - */ - protected function performUpdate(Model $model) - { - $data = collect([$this->getIndexColumn($model) => $this->toVector($model)]); - - $query = $this->database - ->table($model->searchableAs()) - ->where($model->getKeyName(), '=', $model->getKey()); - - if (method_exists($model, 'searchableAdditionalArray')) { - $data = $data->merge($model->searchableAdditionalArray() ?: []); - } - - if (! $this->isExternalIndex($model) || $query->exists()) { - return $query->update($data->all()); - } - - $modelKeyInfo = collect([$model->getKeyName() => $model->getKey()]); - - return $query->insert( - $data->merge($modelKeyInfo)->all() - ); - } - - /** - * Get the indexed value for a given model. - */ - protected function toVector(Model $model): mixed - { - /** @var array $searchableArray */ - $searchableArray = $model->toSearchableArray(); - $fields = collect($searchableArray) - ->map(function ($value) { - return $value === null ? '' : $value; - }); - - $bindings = collect([]); - - // The choices of parser, dictionaries and which types of tokens to index are determined - // by the selected text search configuration which can be set globally in config/scout.php - // file or individually for each model in searchableOptions() - // See https://www.postgresql.org/docs/current/static/textsearch-controls.html - $vector = 'to_tsvector(COALESCE(?, get_current_ts_config()), ?)'; - - $select = $fields->map(function ($value, $key) use ($model, $vector, $bindings) { - $bindings->push($this->searchConfig($model) ?: null) - ->push($value); - - // Set a field weight if it was specified in Model's searchableOptions() - if ($label = $this->rankFieldWeightLabel($model, $key)) { - $vector = "setweight({$vector}, ?)"; - $bindings->push($label); - } - - return $vector; - })->implode(' || '); - - return $this->database - ->query() - ->selectRaw("{$select} AS tsvector", $bindings->all()) - ->value('tsvector'); - } - /** * Remove the given model from the index. * - * @param \Illuminate\Database\Eloquent\Collection $models + * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ public function delete($models) @@ -172,6 +109,7 @@ public function delete($models) /** * Perform the given search on the engine. * + * @param \Laravel\Scout\Builder $builder * @return mixed */ public function search(Builder $builder) @@ -182,6 +120,7 @@ public function search(Builder $builder) /** * Perform the given search on the engine. * + * @param \Laravel\Scout\Builder $builder * @param int $perPage * @param int $page * @return mixed @@ -204,82 +143,12 @@ public function getTotalCount($results) } /** @var array $results */ - /** @var object{'id': int, 'rank': string, 'total_count': int} $result */ + /** @var object{'id': mixed, 'rank': string, 'total_count': int} $result */ $result = Arr::first($results); return (int) $result->total_count; } - /** - * Perform the given search on the engine. - * - * @param int|null $perPage - * @param int $page - * @return array - */ - protected function performSearch(Builder $builder, $perPage = 0, $page = 1) - { - // We have to preserve the model in order to allow for - // correct behavior of mapIds() method which currently - // does not receive a model instance - $this->preserveModel($builder->model); - - $indexColumn = $this->getIndexColumn($builder->model); - - // Build the SQL query - $query = $this->database - ->table($builder->index ?: $builder->model->searchableAs()) - ->select($builder->model->getKeyName()) - ->selectRaw("{$this->rankingExpression($builder->model, $indexColumn)} AS rank") - ->selectRaw('COUNT(*) OVER () AS total_count') - ->whereRaw("{$indexColumn} @@ \"tsquery\""); - - // Apply where clauses that were set on the builder instance if any - foreach ($builder->wheres as $key => $value) { - $query->where($key, $value); - } - - // If parsed documents are being stored in the model's table - if (! $this->isExternalIndex($builder->model)) { - // and the model uses soft deletes we need to exclude trashed rows - if ($this->usesSoftDeletes($builder->model)) { - $query->whereNull($builder->model->getDeletedAtColumn()); - } - } - - // Apply order by clauses that were set on the builder instance if any - foreach ($builder->orders as $order) { - $query->orderBy($order['column'], $order['direction']); - } - - // Apply default order by clauses (rank and id) - if (empty($builder->orders)) { - $query->orderBy('rank', 'desc') - ->orderBy($builder->model->getKeyName()); - } - - if ($perPage > 0) { - $query->skip(($page - 1) * $perPage) - ->limit($perPage); - } - - // The choices of parser, dictionaries and which types of tokens to index are determined - // by the selected text search configuration which can be set globally in config/scout.php - // file or individually for each model in searchableOptions() - // See https://www.postgresql.org/docs/current/static/textsearch-controls.html - $tsQuery = $builder->callback - ? call_user_func($builder->callback, $builder, $this->searchConfig($builder->model), $query) - : $this->defaultQueryMethod($builder->query, $this->searchConfig($builder->model)); - - /** @var \ScoutEngines\Postgres\TsQuery\BaseTsQueryable $tsQuery */ - $query->crossJoin($this->database->raw($tsQuery->sql() . ' AS "tsquery"')); - // Add TS bindings to the query - $query->addBinding($tsQuery->bindings(), 'join'); - - return $this->database - ->select($query->toSql(), $query->getBindings()); - } - /** * Returns the default query method. * @@ -304,7 +173,7 @@ public function defaultQueryMethod($query, $config) * Pluck and return the primary keys of the given results. * * @param mixed $results - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function mapIds($results) { @@ -319,9 +188,10 @@ public function mapIds($results) /** * Map the given results to instances of the given model. * + * @param \Laravel\Scout\Builder $builder * @param mixed $results * @param \Illuminate\Database\Eloquent\Model $model - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Database\Eloquent\Collection */ public function map(Builder $builder, $results, $model) { @@ -352,13 +222,15 @@ public function map(Builder $builder, $results, $model) /** * Map the given results to instances of the given model via a lazy collection. * + * @param \Laravel\Scout\Builder $builder * @param mixed $results * @param \Illuminate\Database\Eloquent\Model $model - * @return \Illuminate\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazyMap(Builder $builder, $results, $model) { - return LazyCollection::make($model->newCollection()); + // return LazyCollection::make($model->newCollection()); + return LazyCollection::make($this->map($builder, $results, $model)->all()); } /** @@ -385,11 +257,164 @@ public function deleteIndex($name) } /** - * Connect to the database. + * Flush all of the model's records from the engine. * + * @param \Illuminate\Database\Eloquent\Model $model * @return void */ - protected function connect() + public function flush($model) + { + if (! $this->shouldMaintainIndex($model)) { + return; + } + + $indexColumn = $this->getIndexColumn($model); + + $this->database + ->table($model->searchableAs()) + ->update([$indexColumn => null]); + } + + /** + * Perform update of the given model. + * + * @return bool|int + */ + protected function performUpdate(Model $model) + { + $data = collect([$this->getIndexColumn($model) => $this->toVector($model)]); + + $query = $this->database + ->table($model->searchableAs()) + ->where($model->getKeyName(), '=', $model->getKey()); + + if (method_exists($model, 'searchableAdditionalArray')) { + $data = $data->merge($model->searchableAdditionalArray() ?: []); + } + + if (! $this->isExternalIndex($model) || $query->exists()) { + return $query->update($data->all()); + } + + $modelKeyInfo = collect([$model->getKeyName() => $model->getKey()]); + + return $query->insert( + $data->merge($modelKeyInfo)->all() + ); + } + + /** + * Get the indexed value for a given model. + */ + protected function toVector(Model $model): mixed + { + /** @var array $searchableArray */ + $searchableArray = $model->toSearchableArray(); + $fields = collect($searchableArray) + ->map(function ($value) { + return $value === null ? '' : $value; + }); + + $bindings = collect([]); + + // The choices of parser, dictionaries and which types of tokens to index are determined + // by the selected text search configuration which can be set globally in config/scout.php + // file or individually for each model in searchableOptions() + // See https://www.postgresql.org/docs/current/static/textsearch-controls.html + $vector = 'to_tsvector(COALESCE(?, get_current_ts_config()), ?)'; + + $select = $fields->map(function ($value, $key) use ($model, $vector, $bindings) { + $bindings->push($this->searchConfig($model) ?: null) + ->push($value); + + // Set a field weight if it was specified in Model's searchableOptions() + if ($label = $this->rankFieldWeightLabel($model, $key)) { + $vector = "setweight({$vector}, ?)"; + $bindings->push($label); + } + + return $vector; + })->implode(' || '); + + return $this->database + ->query() + ->selectRaw("{$select} AS tsvector", $bindings->all()) + ->value('tsvector'); + } + + /** + * Perform the given search on the engine. + * + * @param \Laravel\Scout\Builder $builder + * @return array + */ + protected function performSearch(Builder $builder, ?int $perPage = 0, int $page = 1): array + { + // We have to preserve the model in order to allow for + // correct behavior of mapIds() method which currently + // does not receive a model instance + $this->preserveModel($builder->model); + + $indexColumn = $this->getIndexColumn($builder->model); + + // Build the SQL query + $query = $this->database + ->table($builder->index ?: $builder->model->searchableAs()) + ->select($builder->model->getKeyName()) + ->selectRaw("{$this->rankingExpression($builder->model, $indexColumn)} AS rank") + ->selectRaw('COUNT(*) OVER () AS total_count') + ->whereRaw("{$indexColumn} @@ \"tsquery\""); + + // Apply where clauses that were set on the builder instance if any + foreach ($builder->wheres as $key => $value) { + $query->where($key, $value); + } + + // If parsed documents are being stored in the model's table + if (! $this->isExternalIndex($builder->model)) { + // and the model uses soft deletes we need to exclude trashed rows + if ($this->usesSoftDeletes($builder->model)) { + $query->whereNull($builder->model->getDeletedAtColumn()); + } + } + + // Apply order by clauses that were set on the builder instance if any + foreach ($builder->orders as $order) { + $query->orderBy($order['column'], $order['direction']); + } + + // Apply default order by clauses (rank and id) + if (empty($builder->orders)) { + $query->orderBy('rank', 'desc') + ->orderBy($builder->model->getKeyName()); + } + + if ($perPage > 0) { + $query->skip(($page - 1) * $perPage) + ->limit($perPage); + } + + // The choices of parser, dictionaries and which types of tokens to index are determined + // by the selected text search configuration which can be set globally in config/scout.php + // file or individually for each model in searchableOptions() + // See https://www.postgresql.org/docs/current/static/textsearch-controls.html + $tsQuery = $builder->callback + ? call_user_func($builder->callback, $builder, $this->searchConfig($builder->model), $query) + : $this->defaultQueryMethod($builder->query, $this->searchConfig($builder->model)); + + /** @var \ScoutEngines\Postgres\TsQuery\BaseTsQueryable $tsQuery */ + $query->crossJoin($this->database->raw($tsQuery->sql() . ' AS "tsquery"')); + // Add TS bindings to the query + $query->addBinding($tsQuery->bindings(), 'join'); + + return $this->database + ->select($query->toSql(), $query->getBindings()); + } + + /** + * Connect to the database. + */ + protected function connect(): void { // Already connected if ($this->database !== null) { @@ -476,7 +501,7 @@ protected function rankNormalization(Model $model): int /** * See if the index should be maintained for a given model. */ - protected function shouldMaintainIndex(Model $model = null): bool + protected function shouldMaintainIndex(?Model $model = null): bool { if ((bool) $this->config('maintain_index', true) === false) { return false; @@ -507,8 +532,6 @@ protected function isExternalIndex(Model $model): mixed /** * Get the model specific option value or a default. - * - * @param mixed $default */ protected function option(Model $model, string $key, mixed $default = null): mixed { @@ -551,8 +574,6 @@ protected function stringOption(Model $model, string $key, string $default = '') /** * Get the config value or a default. - * - * @param mixed $default */ protected function config(string $key, mixed $default = null): mixed { @@ -573,50 +594,24 @@ protected function stringConfig(string $key, string $default = ''): string } } - /** - * @return void - */ - protected function preserveModel(Model $model) + protected function preserveModel(Model $model): void { $this->model = $model; } /** * Returns a search config name for a model. - * - * @return string */ - protected function searchConfig(Model $model) + protected function searchConfig(Model $model): string { return $this->stringOption($model, 'config', $this->stringConfig('config', '')) ?: ''; } /** * Checks if the model uses the SoftDeletes trait. - * - * @return bool */ - protected function usesSoftDeletes(Model $model) + protected function usesSoftDeletes(Model $model): bool { return method_exists($model, 'getDeletedAtColumn'); } - - /** - * Flush all of the model's records from the engine. - * - * @param \Illuminate\Database\Eloquent\Model $model - * @return void - */ - public function flush($model) - { - if (! $this->shouldMaintainIndex($model)) { - return; - } - - $indexColumn = $this->getIndexColumn($model); - - $this->database - ->table($model->searchableAs()) - ->update([$indexColumn => null]); - } } diff --git a/src/PostgresEngineServiceProvider.php b/src/PostgresEngineServiceProvider.php index b62390e..81d3cd1 100644 --- a/src/PostgresEngineServiceProvider.php +++ b/src/PostgresEngineServiceProvider.php @@ -10,6 +10,10 @@ use ScoutEngines\Postgres\TsQuery\ToTsQuery; use ScoutEngines\Postgres\TsQuery\WebSearchToTsQuery; +/** + * @template TModel of \Illuminate\Database\Eloquent\Model + * @template TID of int|string + */ class PostgresEngineServiceProvider extends ServiceProvider { /** @@ -50,7 +54,7 @@ protected function registerBuilderMacro(string $name, string $class): void { if (! Builder::hasMacro($name)) { Builder::macro($name, function () use ($class) { - /** @var Builder $this */ + /** @var Builder $this */ $this->callback = function ($builder, $config) use ($class) { return new $class($builder->query, $config); }; From 7a8b68eace87eedbb3621bc3a742ad86b2f4174e Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 29 Nov 2024 22:37:37 -0500 Subject: [PATCH 25/38] No Scout 11 Scout only goes up to 10. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36f32bc..b8603cb 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: matrix: php: [8.1, 8.2, 8.3, 8.4] laravel: [^10, ^11] - scout: [^10, ^11] + scout: [^10] exclude: - php: 8.1 laravel: ^11 From 3470141f8e6c55d1bb1468bede5ba2e8a8291e64 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Fri, 29 Nov 2024 22:43:55 -0500 Subject: [PATCH 26/38] Making tests happy Change type returned on performSearch to array|null. This is more to make the test happy than improve the code. Need to revise and make better tests. --- src/PostgresEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 28d9035..48cbf2d 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -348,7 +348,7 @@ protected function toVector(Model $model): mixed * @param \Laravel\Scout\Builder $builder * @return array */ - protected function performSearch(Builder $builder, ?int $perPage = 0, int $page = 1): array + protected function performSearch(Builder $builder, ?int $perPage = 0, int $page = 1): array|null { // We have to preserve the model in order to allow for // correct behavior of mapIds() method which currently From 239828358524a2cca7535ae51f9c1c07632662b7 Mon Sep 17 00:00:00 2001 From: Will Vincent Date: Mon, 3 Mar 2025 18:34:24 -0600 Subject: [PATCH 27/38] Fixed PHPUnit complaints of risky tests with no assertions. --- tests/PostgresEngineTest.php | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 75cb30a..88fa78a 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -32,25 +32,25 @@ public function update_adds_object_to_index() [$engine, $db] = $this->getEngine(); $db->shouldReceive('query') - ->andReturn($query = Mockery::mock('stdClass')); + ->andReturn($query = Mockery::mock('stdClass'))->once(); $query->shouldReceive('selectRaw') ->with( 'to_tsvector(COALESCE(?, get_current_ts_config()), ?) || setweight(to_tsvector(COALESCE(?, get_current_ts_config()), ?), ?) AS tsvector', [null, 'Foo', null, '', 'B'] ) - ->andReturnSelf(); + ->andReturnSelf()->once(); $query->shouldReceive('value') ->with('tsvector') - ->andReturn('foo'); + ->andReturn('foo')->once(); $db->shouldReceive('table') ->andReturn($table = Mockery::mock('stdClass')); $table->shouldReceive('where') ->with('id', '=', 1) - ->andReturnSelf(); + ->andReturnSelf()->once(); $table->shouldReceive('update') - ->with(['searchable' => 'foo']); + ->with(['searchable' => 'foo'])->once(); $engine->update(Collection::make([new TestModel])); } @@ -62,7 +62,7 @@ public function update_do_nothing_if_index_maintenance_turned_off_globally() { [$engine] = $this->getEngine(['maintain_index' => false]); - $engine->update(Collection::make([new TestModel])); + $this->assertNull($engine->update(Collection::make([new TestModel]))); } /** @@ -73,12 +73,15 @@ public function delete_removes_object_from_index() [$engine, $db] = $this->getEngine(); $db->shouldReceive('table') - ->andReturn($table = Mockery::mock('stdClass')); + ->andReturn($table = Mockery::mock('stdClass')) + ->once(); $table->shouldReceive('whereIn') ->with('id', [1]) - ->andReturnSelf(); + ->andReturnSelf() + ->once(); $table->shouldReceive('update') - ->with(['searchable' => null]); + ->with(['searchable' => null]) + ->once(); $engine->delete(Collection::make([new TestModel])); } @@ -142,7 +145,8 @@ public function search() ->shouldReceive('getBindings')->andReturn([null, 'foo', 1, 'qux']); $db->shouldReceive('select') - ->with(null, $table->getBindings()); + ->with(null, $table->getBindings()) + ->once(); $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1) @@ -166,7 +170,8 @@ public function search_with_order_by() ->shouldReceive('getBindings')->andReturn([null, 'foo']); $db->shouldReceive('select') - ->with(null, $table->getBindings()); + ->with(null, $table->getBindings()) + ->once(); $builder = new Builder(new TestModel, 'foo'); $builder->orderBy('bar', 'desc') @@ -191,7 +196,7 @@ public function search_with_global_config() ->shouldReceive('where')->with('bar', 1) ->shouldReceive('getBindings')->andReturn(['simple', 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1)->take(5); @@ -215,7 +220,7 @@ public function search_with_model_config() ->shouldReceive('where')->with('bar', 1) ->shouldReceive('getBindings')->andReturn(['english', 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $model = new TestModel; $model->searchableOptions['config'] = 'english'; @@ -241,7 +246,7 @@ public function search_with_soft_deletes() ->shouldReceive('whereNull')->with('deleted_at') ->shouldReceive('getBindings')->andReturn([null, 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $builder = new Builder(new SoftDeletableTestModel, 'foo'); $builder->where('bar', 1)->take(5); From 6f0a634cf8ded220587a0904d37c6c78098e988f Mon Sep 17 00:00:00 2001 From: Will Vincent Date: Mon, 3 Mar 2025 18:35:40 -0600 Subject: [PATCH 28/38] Update .gitignore to ignore phpstorm .idea directory. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 60718bd..1335ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .phpunit.result.cache composer.lock .DS_Store +.idea From 4a3cf53ab30e2acdf9b06d4724bbce5991f17f55 Mon Sep 17 00:00:00 2001 From: Will Vincent Date: Mon, 3 Mar 2025 20:05:14 -0600 Subject: [PATCH 29/38] Add queryCallback, whereIn and whereNotIn support. --- src/PostgresEngine.php | 15 +++++++ tests/PostgresEngineTest.php | 79 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 48cbf2d..1604eee 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -365,11 +365,26 @@ protected function performSearch(Builder $builder, ?int $perPage = 0, int $page ->selectRaw('COUNT(*) OVER () AS total_count') ->whereRaw("{$indexColumn} @@ \"tsquery\""); + // Apply query callback if set + if ($builder->queryCallback) { + call_user_func($builder->queryCallback, $builder); + } + // Apply where clauses that were set on the builder instance if any foreach ($builder->wheres as $key => $value) { $query->where($key, $value); } + // Apply whereIn clauses that were set on the builder instance if any + foreach ($builder->whereIns as $key => $value) { + $query->whereIn($key, $value); + } + + // Apply whereNoIn clauses that were set on the builder instance if any + foreach ($builder->whereNotIns as $key => $value) { + $query->whereNotIn($key, $value); + } + // If parsed documents are being stored in the model's table if (! $this->isExternalIndex($builder->model)) { // and the model uses soft deletes we need to exclude trashed rows diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 88fa78a..f475d36 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -180,6 +180,85 @@ public function search_with_order_by() $engine->search($builder); } + /** + * @test + */ + public function search_with_queryCallback() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('skip')->with($skip)->andReturnSelf() + ->shouldReceive('limit')->with($limit)->andReturnSelf() + ->shouldReceive('where')->with('bar', 1)->andReturnSelf() + ->shouldReceive('where')->with('baz', 'qux') + ->shouldReceive('getBindings')->andReturn([null, 'foo', 1, 'qux']); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->query(function ($q) { + $q->where('bar', 1) + ->where('baz', 'qux') + ->take(5); + }); + + $engine->search($builder); + } + + /** + * @test + */ + public function search_with_whereIn() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('whereIn')->with('bar', [1])->andReturnSelf() + ->shouldReceive('getBindings')->andReturn([null, 'foo', [1]]); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->whereIn('bar', [1]); + + $engine->search($builder); + } + + /** + * @test + */ + public function search_with_whereNotIn() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('whereNotIn')->with('bar', [1])->andReturnSelf() + ->shouldReceive('getBindings')->andReturn([null, 'foo', [1]]); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->whereNotIn('bar', [1]); + + $engine->search($builder); + } + /** * @test */ From 0e45a3b35b2db686e8ff08d97ba1c62349d3df76 Mon Sep 17 00:00:00 2001 From: Will Vincent Date: Mon, 3 Mar 2025 22:59:30 -0600 Subject: [PATCH 30/38] Fix softdelete handling. --- src/PostgresEngine.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 1604eee..f3d6ba0 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -372,6 +372,16 @@ protected function performSearch(Builder $builder, ?int $perPage = 0, int $page // Apply where clauses that were set on the builder instance if any foreach ($builder->wheres as $key => $value) { + if ($key == '__soft_deleted') { + if ($this->usesSoftDeletes($builder->model)) { + if ($value == 1) { + $query->whereNotNull($builder->model->getDeletedAtColumn()); + } else { + $query->whereNull($builder->model->getDeletedAtColumn()); + } + } + continue; + } $query->where($key, $value); } @@ -385,14 +395,6 @@ protected function performSearch(Builder $builder, ?int $perPage = 0, int $page $query->whereNotIn($key, $value); } - // If parsed documents are being stored in the model's table - if (! $this->isExternalIndex($builder->model)) { - // and the model uses soft deletes we need to exclude trashed rows - if ($this->usesSoftDeletes($builder->model)) { - $query->whereNull($builder->model->getDeletedAtColumn()); - } - } - // Apply order by clauses that were set on the builder instance if any foreach ($builder->orders as $order) { $query->orderBy($order['column'], $order['direction']); From 2c7fec04cb1bf202e5d71f1997d3df9041a26b7b Mon Sep 17 00:00:00 2001 From: Will Vincent Date: Fri, 14 Mar 2025 05:04:38 -0500 Subject: [PATCH 31/38] Update composer.json - Laravel 12 --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index d0f5908..fbe41b9 100755 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ ], "require": { "php": "^8.1|^8.2|^8.3|^8.4", - "illuminate/contracts": "^10|^11", - "illuminate/database": "^10|^11", - "illuminate/support": "^10|^11", - "laravel/scout": "^10|^11" + "illuminate/contracts": "^10|^11|^12", + "illuminate/database": "^10|^11|^12", + "illuminate/support": "^10|^11|^12", + "laravel/scout": "^10|^11|^12" }, "require-dev": { "laravel/pint": "^1.18", From b9a06576e9d86cc2ccee63405796d0fae852a389 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:27:22 -0400 Subject: [PATCH 32/38] Laravel 12 compatibility --- .github/workflows/tests.yml | 84 ++++++++++++++++++------------------- composer.json | 8 ++-- phpstan-baseline.neon | 2 +- phpstan.neon | 2 - src/PostgresEngine.php | 3 +- 5 files changed, 49 insertions(+), 50 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8603cb..d3f42ff 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,52 +1,52 @@ name: tests on: - push: - branches: - - master - - 9.x - pull_request: + push: + branches: + - master + - 9.x + pull_request: jobs: - tests: - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - php: [8.1, 8.2, 8.3, 8.4] - laravel: [^10, ^11] - scout: [^10] - exclude: - - php: 8.1 - laravel: ^11 - - php: 8.4 - laravel: ^10 + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.1, 8.2, 8.3, 8.4] + laravel: [^10, ^11, ^12] + scout: [^10] + exclude: + - php: 8.1 + laravel: ^11 + - php: 8.4 + laravel: ^10 - name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }} + name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }} - steps: - - name: Checkout code - uses: actions/checkout@v3 + steps: + - name: Checkout code + uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip - tools: composer:v2 - coverage: none + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip + tools: composer:v2 + coverage: none - - name: Install dependencies - run: | - composer remove nunomaduro/larastan --dev --no-update - composer remove orchestra/testbench --dev --no-update - composer remove tightenco/duster --dev --no-update - composer remove laravel/pint --dev --no-update - composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update - composer require "illuminate/database=${{ matrix.laravel }}" --no-update - composer require "illuminate/support=${{ matrix.laravel }}" --no-update - composer require "laravel/scout=${{ matrix.scout }}" --no-update - composer update --prefer-dist --no-interaction --no-progress + - name: Install dependencies + run: | + composer remove nunomaduro/larastan --dev --no-update + composer remove orchestra/testbench --dev --no-update + composer remove tightenco/duster --dev --no-update + composer remove laravel/pint --dev --no-update + composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update + composer require "illuminate/database=${{ matrix.laravel }}" --no-update + composer require "illuminate/support=${{ matrix.laravel }}" --no-update + composer require "laravel/scout=${{ matrix.scout }}" --no-update + composer update --prefer-dist --no-interaction --no-progress - - name: Run tests - run: vendor/bin/phpunit --verbose + - name: Run tests + run: vendor/bin/phpunit --verbose diff --git a/composer.json b/composer.json index fbe41b9..b109621 100755 --- a/composer.json +++ b/composer.json @@ -35,11 +35,11 @@ "laravel/scout": "^10|^11|^12" }, "require-dev": { - "laravel/pint": "^1.18", + "larastan/larastan": "^3.3", + "laravel/pint": "^1.22", "mockery/mockery": "^1.6", - "nunomaduro/larastan": "^2.9", - "orchestra/testbench": "^8.28", - "phpunit/phpunit": "^9.6", + "orchestra/testbench": "^10.2", + "phpunit/phpunit": "^12.1", "tightenco/duster": "^2.7" }, "autoload": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ba86bd9..00726a5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -16,6 +16,6 @@ parameters: - message: "#^Call to an undefined method TModel of Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getDeletedAtColumn\\(\\)\\.$#" - count: 1 + count: 2 path: src/PostgresEngine.php diff --git a/phpstan.neon b/phpstan.neon index 557ca91..2aeb4fe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,4 @@ includes: - - ./vendor/nunomaduro/larastan/extension.neon - phpstan-baseline.neon parameters: @@ -9,4 +8,3 @@ parameters: # Level 9 is the highest level level: 9 - diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index f3d6ba0..d1fd1cd 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -348,7 +348,7 @@ protected function toVector(Model $model): mixed * @param \Laravel\Scout\Builder $builder * @return array */ - protected function performSearch(Builder $builder, ?int $perPage = 0, int $page = 1): array|null + protected function performSearch(Builder $builder, ?int $perPage = 0, int $page = 1): ?array { // We have to preserve the model in order to allow for // correct behavior of mapIds() method which currently @@ -380,6 +380,7 @@ protected function performSearch(Builder $builder, ?int $perPage = 0, int $page $query->whereNull($builder->model->getDeletedAtColumn()); } } + continue; } $query->where($key, $value); From 70383ed7a0c41ea7f0213c9dd13327c76562eae3 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:40:06 -0400 Subject: [PATCH 33/38] Workflow fix --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d3f42ff..f7c6207 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: - name: Install dependencies run: | - composer remove nunomaduro/larastan --dev --no-update + composer remove larastan/larastan --dev --no-update composer remove orchestra/testbench --dev --no-update composer remove tightenco/duster --dev --no-update composer remove laravel/pint --dev --no-update From e4830f9d7ab7774d5f64d553a92f86154a312121 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:42:04 -0400 Subject: [PATCH 34/38] Workflox fix 2 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f7c6207..8657952 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: scout: [^10] exclude: - php: 8.1 - laravel: ^11 + laravel: [^11, ^12] - php: 8.4 laravel: ^10 From b4ac99ac30bc58665d445400d60dc966a26d8700 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:43:45 -0400 Subject: [PATCH 35/38] Workflow Fix 3 --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8657952..ced10c2 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,9 @@ jobs: scout: [^10] exclude: - php: 8.1 - laravel: [^11, ^12] + laravel: ^11, + - php: 8.1 + laravel: ^12 - php: 8.4 laravel: ^10 From 22b18a746a9644374be5bc5b7e52c8757e4b7fdb Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:46:07 -0400 Subject: [PATCH 36/38] Workflow fix 4 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ced10c2..fd17683 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,4 +51,4 @@ jobs: composer update --prefer-dist --no-interaction --no-progress - name: Run tests - run: vendor/bin/phpunit --verbose + run: vendor/bin/phpunit From d83da84ef4a6354a1d9ec383123fa9fd06951589 Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:47:38 -0400 Subject: [PATCH 37/38] Workflow Fix 5 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd17683..77a5a4a 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: scout: [^10] exclude: - php: 8.1 - laravel: ^11, + laravel: ^11 - php: 8.1 laravel: ^12 - php: 8.4 From 33d9c8a48806c10a3cc318d9e0b3543e9922c10c Mon Sep 17 00:00:00 2001 From: Michael Flynn Date: Sun, 20 Apr 2025 22:49:48 -0400 Subject: [PATCH 38/38] Workflow fix 6 --- .github/workflows/tests.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 77a5a4a..a063547 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,16 +13,9 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3, 8.4] - laravel: [^10, ^11, ^12] + php: [8.3, 8.4] + laravel: [^11, ^12] scout: [^10] - exclude: - - php: 8.1 - laravel: ^11 - - php: 8.1 - laravel: ^12 - - php: 8.4 - laravel: ^10 name: Test PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Scout ${{ matrix.scout }}