Repository: biiiiiigmonster/hasin Branch: master Commit: 931dc68018e2 Files: 67 Total size: 88.0 KB Directory structure: gitextract_znsrf2cn/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── dependabot-auto-merge.yml │ ├── fix-php-code-style-issues.yml │ └── run-tests.yml ├── .gitignore ├── .scrutinizer.yml ├── CHANGELOG.md ├── LICENSE ├── README-CN.md ├── README.md ├── _laravel_ide_helper.php ├── composer.json ├── database/ │ ├── factories/ │ │ ├── CommentFactory.php │ │ ├── CountryFactory.php │ │ ├── HistoryFactory.php │ │ ├── ImageFactory.php │ │ ├── PhoneFactory.php │ │ ├── PostFactory.php │ │ ├── RoleFactory.php │ │ ├── SupplierFactory.php │ │ ├── TagFactory.php │ │ ├── UserFactory.php │ │ └── VideoFactory.php │ └── migrations/ │ └── create_hasin_test_table.php ├── phpunit.xml.dist ├── pint.json ├── src/ │ ├── Database/ │ │ └── Eloquent/ │ │ ├── BuilderMixin.php │ │ └── RelationMixin.php │ └── HasinServiceProvider.php └── tests/ ├── Features/ │ ├── DoesntHaveInTest.php │ ├── DoesntHaveMorphInTest.php │ ├── HasInTest.php │ ├── HasMorphInTest.php │ ├── OrDoesntHaveInTest.php │ ├── OrDoesntHaveMorphInTest.php │ ├── OrHasInTest.php │ ├── OrHasMorphInTest.php │ ├── OrWhereDoesntHaveInTest.php │ ├── OrWhereDoesntHaveMorphInTest.php │ ├── OrWhereHasInTest.php │ ├── OrWhereHasMorphInTest.php │ ├── OrWhereMorphRelationInTest.php │ ├── OrWhereRelationInTest.php │ ├── WhereDoesntHaveInTest.php │ ├── WhereDoesntHaveMorphInTest.php │ ├── WhereHasInTest.php │ ├── WhereHasMorphInTest.php │ ├── WhereMorphRelationInTest.php │ ├── WhereRelationInTest.php │ └── WithWhereHasInTest.php ├── Models/ │ ├── Comment.php │ ├── Country.php │ ├── History.php │ ├── Image.php │ ├── Phone.php │ ├── Post.php │ ├── Role.php │ ├── RoleUser.php │ ├── Supplier.php │ ├── Tag.php │ ├── Taggable.php │ ├── User.php │ └── Video.php ├── Pest.php └── TestCase.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Path-based git attributes # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". /.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore /phpunit.xml export-ignore /phpunit.xml.dist export-ignore /art export-ignore /database export-ignore /docs export-ignore /tests export-ignore /.editorconfig export-ignore /.php_cs.dist.php export-ignore /psalm.xml export-ignore /psalm.xml.dist export-ignore /pint.json export-ignore /testbench.yaml export-ignore /UPGRADING.md export-ignore /phpstan.neon.dist export-ignore /phpstan-baseline.neon export-ignore /.scrutinizer.yml export-ignore ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Ask a question url: https://github.com/biiiiiigmonster/hasin/discussions/new?category=q-a about: Ask the community for help - name: Request a feature url: https://github.com/biiiiiigmonster/hasin/discussions/new?category=ideas about: Share ideas for new features - name: Report a security issue url: https://github.com/biiiiiigmonster/hasin/security/policy about: Learn how to notify us for sensitive bugs - name: Report a bug url: https://github.com/biiiiiigmonster/hasin/issues/new about: Report a reproducable bug ================================================ FILE: .github/dependabot.yml ================================================ # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "dependencies" ================================================ FILE: .github/workflows/dependabot-auto-merge.yml ================================================ name: dependabot-auto-merge on: pull_request_target permissions: pull-requests: write contents: write jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Auto-merge Dependabot PRs for semver-minor updates if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Auto-merge Dependabot PRs for semver-patch updates if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} ================================================ FILE: .github/workflows/fix-php-code-style-issues.yml ================================================ name: Fix PHP code style issues on: [push] jobs: php-code-styling: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 with: ref: ${{ github.head_ref }} - name: Fix PHP code style issues uses: aglipanci/laravel-pint-action@2.6 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: Fix styling ================================================ FILE: .github/workflows/run-tests.yml ================================================ name: run-tests on: push: branches: - master pull_request: branches: - master jobs: test: runs-on: ${{ matrix.os }} services: mysql: image: mysql:5.7 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: laravel ports: - 33306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 strategy: fail-fast: true matrix: os: [ubuntu-latest] php: [8.2, 8.3, 8.4] laravel: ['12.*', '13.*'] stability: [prefer-lowest, prefer-stable] include: - laravel: 12.* testbench: 10.* - laravel: 13.* testbench: 11.* exclude: - laravel: 13.* php: 8.2 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo coverage: xdebug - name: Setup problem matchers run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Execute tests run: vendor/bin/pest env: DB_PORT: 33306 DB_PASSWORD: root ================================================ FILE: .gitignore ================================================ .idea .phpunit.result.cache build composer.lock coverage docs phpunit.xml testbench.yaml vendor node_modules .phpunit.cache ================================================ FILE: .scrutinizer.yml ================================================ checks: php: code_rating: true remove_extra_empty_lines: true remove_php_closing_tag: true remove_trailing_whitespace: true fix_use_statements: remove_unused: true preserve_multiple: false preserve_blanklines: true order_alphabetically: true fix_linefeed: true fix_line_ending: true fix_identation_4spaces: true build: image: default-bionic environment: php: version: 8.2 tests: override: - command: './vendor/bin/pest' ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to `hasin` will be documented in this file. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) Yunfeng Lu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-CN.md ================================================ [English](./README.md) | 中文
# LARAVEL HASIN [![Latest Version on Packagist](https://img.shields.io/packagist/v/biiiiiigmonster/hasin.svg?style=flat-square)](https://packagist.org/packages/biiiiiigmonster/hasin) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/biiiiiigmonster/hasin/run-tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/biiiiiigmonster/hasin/actions?query=workflow%3Arun-tests+branch%3Amaster) [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/biiiiiigmonster/hasin/fix-php-code-style-issues.yml?branch=master&label=code%20style&style=flat-square)](https://github.com/biiiiiigmonster/hasin/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/biiiiiigmonster/hasin/badge.svg?branch=master)](https://coveralls.io/github/biiiiiigmonster/hasin?branch=master) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/biiiiiigmonster/hasin.svg?label=Scrutinizer&style=flat-square)](https://scrutinizer-ci.com/g/biiiiiigmonster/hasin/) [![Total Downloads](https://img.shields.io/packagist/dt/biiiiiigmonster/hasin.svg?style=flat-square)](https://packagist.org/packages/biiiiiigmonster/hasin)
`hasin`是一个基于`where in`语法实现的`Laravel ORM`关联关系查询的扩展包,部分业务场景下可以替代`Laravel ORM`中基于`where exists`语法实现的`has`,以获取更高的性能。 ## 安装 | Laravel 版本 | 安装命令 | |-----------------|-----------------------------------------------------| | Laravel 12 | ``` composer require biiiiiigmonster/hasin:^5.0 ``` | | Laravel 11 | ``` composer require biiiiiigmonster/hasin:^4.0 ``` | | Laravel 10 | ``` composer require biiiiiigmonster/hasin:^3.0 ``` | | Laravel 9 | ``` composer require biiiiiigmonster/hasin:^2.0 ``` | | Laravel 5.5 ~ 8 | ``` composer require biiiiiigmonster/hasin:^1.0 ``` | ## 简介 `Laravel ORM`的关联关系非常强大,基于关联关系的查询`has`也给我们提供了诸多灵活的调用方式,然而某些情形下,`has`使用了**where exists**语法实现 例如: ```php // User hasMany Post User::has('posts')->get(); ``` #### `select * from users where exists (select * from posts where users.id = posts.user_id)` > exists是对外表做loop循环,每次loop循环再对内表(子查询)进行查询,那么因为对内表的查询使用的索引(内表效率高,故可用大表),而外表有多大都需要遍历,不可避免(尽量用小表),故内表大的使用exists,可加快效率。 当**User表**数据量较大的时候,就会出现性能问题,那么这时候用**where in**语法将会极大的提高性能 #### `select * from users where users.id in (select posts.user_id from posts)` > in是把外表和内表做hash连接,先查询内表,再把内表结果与外表匹配,对外表使用索引(外表效率高,可用大表),而内表多大都需要查询,不可避免,故外表大的使用in,可加快效率。 因此在代码中使用`has(hasMorph)`或者`hasIn(hasMorphIn)`应由**数据体量**来决定…… ```php /** * SQL: * * select * from `users` * where exists * ( * select * from `posts` * where `users`.`id` = `posts`.`user_id` * ) * limit 10 offset 0 */ $users = User::has('posts')->paginate(10); /** * SQL: * * select * from `users` * where `users`.`id` in * ( * select `posts`.`user_id` from `posts` * ) * limit 10 offset 0 */ $users = User::hasIn('posts')->paginate(10); ``` ## 使用 此扩展方法`hasIn(hasMorphIn)`支持`Laravel ORM`中的所有关联关系,入参调用及内部实现流程与框架的`has(hasMorph)`完全一致。 > hasIn ```php // hasIn User::hasIn('posts')->get(); // orHasIn User::where('age', '>', 18)->orHasIn('posts')->get(); // doesntHaveIn User::doesntHaveIn('posts')->get(); // orDoesntHaveIn User::where('age', '>', 18)->orDoesntHaveIn('posts')->get(); ``` > whereHasIn ```php // whereHasIn User::whereHasIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // orWhereHasIn User::where('age', '>', 18)->orWhereHasIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // whereDoesntHaveIn User::whereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // orWhereDoesntHaveIn User::where('age', '>', 18)->orWhereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); ``` > hasMorphIn ```php Image::hasMorphIn('imageable', [Post::class, Comment::class])->get(); ``` ### 嵌套关联 ```php User::hasIn('posts.comments')->get(); ``` ## 测试 ```bash composer test ``` ## 联系交流 wx:biiiiiigmonster(备注:hasin) ## 协议 [MIT 协议](LICENSE) ================================================ FILE: README.md ================================================ English | [中文](./README-CN.md)
# LARAVEL HASIN [![Latest Version on Packagist](https://img.shields.io/packagist/v/biiiiiigmonster/hasin.svg?style=flat-square)](https://packagist.org/packages/biiiiiigmonster/hasin) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/biiiiiigmonster/hasin/run-tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/biiiiiigmonster/hasin/actions?query=workflow%3Arun-tests+branch%3Amaster) [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/biiiiiigmonster/hasin/fix-php-code-style-issues.yml?branch=master&label=code%20style&style=flat-square)](https://github.com/biiiiiigmonster/hasin/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/biiiiiigmonster/hasin/badge.svg?branch=master)](https://coveralls.io/github/biiiiiigmonster/hasin?branch=master) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/biiiiiigmonster/hasin.svg?label=Scrutinizer&style=flat-square)](https://scrutinizer-ci.com/g/biiiiiigmonster/hasin/) [![Total Downloads](https://img.shields.io/packagist/dt/biiiiiigmonster/hasin.svg?style=flat-square)](https://packagist.org/packages/biiiiiigmonster/hasin)
The `hasin` is composer package based on `where in` syntax to query the relationship of `laravel ORM`, which can replace `has` based on `where exists` syntax in some business scenarios to obtain higher performance. # Installation | Laravel Version | Install command | |-----------------|-----------------------------------------------------| | Laravel 12 | ``` composer require biiiiiigmonster/hasin:^5.0 ``` | | Laravel 11 | ``` composer require biiiiiigmonster/hasin:^4.0 ``` | | Laravel 10 | ``` composer require biiiiiigmonster/hasin:^3.0 ``` | | Laravel 9 | ``` composer require biiiiiigmonster/hasin:^2.0 ``` | | Laravel 5.5 ~ 8 | ``` composer require biiiiiigmonster/hasin:^1.0 ``` | # Introductions The relationship of `laravel ORM` is very powerful, and the query `has` based on the relationship also provides us with many flexible calling methods. However, in some cases, `has` is implemented with **where exists** syntax. For example: ```php // User hasMany Post User::has('posts')->get(); ``` #### `select * from users where exists (select * from posts where users.id = posts.user_id)` > 'exists' is a loop to the external table, and then queries the internal table (subQuery) every time. Because the index used for the query of the internal table (the internal table is efficient, so it can be used as a large table), and how much of the external table needs to be traversed, it is inevitable (try to use a small table), so the use of exists for the large internal table can speed up the efficiency. When the **User** table has a large amount of data, there will be performance problems, so the **where in** syntax will greatly improve the performance. #### `select * from users where users.id in (select posts.user_id from posts)` > 'in' is to hash connect the appearance and inner table, first query the inner table, then match the result of the inner table with the appearance, and use the index for the outer table (the appearance is efficient, and large tables can be used). Most of the inner tables need to be queried, which is inevitable. Therefore, using 'in' with large appearance can speed up the efficiency. Therefore, the use of `has(hasMorph)` or `hasIn(hasMorphIn)` in code should be determined by **data size** ```php /** * SQL: * * select * from `users` * where exists * ( * select * from `posts` * where `users`.`id` = `posts`.`user_id` * ) * limit 10 offset 0 */ $users = User::has('posts')->paginate(10); /** * SQL: * * select * from `users` * where `users`.`id` in * ( * select `posts`.`user_id` from `posts` * ) * limit 10 offset 0 */ $users = User::hasIn('posts')->paginate(10); ``` # Usage example `hasIn(hasMorphIn)` supports all `Relations` in `laravel ORM`. The call mode and internal implementation are completely consistent with `has(hasMorph)` of the framework. > hasIn ```php // hasIn User::hasIn('posts')->get(); // orHasIn User::where('age', '>', 18)->orHasIn('posts')->get(); // doesntHaveIn User::doesntHaveIn('posts')->get(); // orDoesntHaveIn User::where('age', '>', 18)->orDoesntHaveIn('posts')->get(); ``` > whereHasIn ```php // whereHasIn User::whereHasIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // orWhereHasIn User::where('age', '>', 18)->orWhereHasIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // whereDoesntHaveIn User::whereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); // orWhereDoesntHaveIn User::where('age', '>', 18)->orWhereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 10); })->get(); ``` > hasMorphIn ```php Image::hasMorphIn('imageable', [Post::class, Comment::class])->get(); ``` ### Nested Relation ```php User::hasIn('posts.comments')->get(); ``` # Testing ```bash composer test ``` >**Tips**: before testing, you need to configure your database connection in the `phpunit.xml.dist`. # License [MIT](./LICENSE) ================================================ FILE: _laravel_ide_helper.php ================================================ =', $count = 1, $boolean = 'and', Closure $callback = null) { return $this; } /** * Add a relationship count / whereIn condition to the query with an "or". * * @param string $relation * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orHasIn($relation, $operator = '>=', $count = 1) { return $this; } /** * Add a relationship count / whereIn condition to the query. * * @param string $relation * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function doesntHaveIn($relation, $boolean = 'and', Closure $callback = null) { return $this; } /** * Add a relationship count / whereIn condition to the query with an "or". * * @return Closure * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orDoesntHaveIn() { return $this; } /** * Add a relationship count / whereIn condition to the query with where clauses. * * @param string $relation * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function whereHasIn($relation, Closure $callback = null, $operator = '>=', $count = 1) { return $this; } /** * Add a relationship count / whereIn condition to the query with where clauses and an "or". * * @param string $relation * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orWhereHasIn($relation, Closure $callback = null, $operator = '>=', $count = 1) { return $this; } /** * Add a relationship count / whereIn condition to the query with where clauses. * * @param string $relation * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function whereDoesntHaveIn($relation, Closure $callback = null) { return $this; } /** * Add a relationship count / whereIn condition to the query with where clauses and an "or". * * @param string $relation * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orWhereDoesntHaveIn($relation, Closure $callback = null) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $operator * @param int $count * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function hasMorphIn($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orHasMorphIn($relation, $types, $operator = '>=', $count = 1) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function doesntHaveMorphIn($relation, $types, $boolean = 'and', Closure $callback = null) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orDoesntHaveMorphIn($relation, $types) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses. * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin * * @return Closure */ public function whereHasMorphIn() { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orWhereHasMorphIn($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function whereDoesntHaveMorphIn($relation, $types, Closure $callback = null) { return $this; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses and an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static * * @see \BiiiiiigMonster\Hasin\Database\Eloquent\BuilderMixin */ public function orWhereDoesntHaveMorphIn($relation, $types, Closure $callback = null) { return $this; } /** * Add a basic where clause to a relationship query. * * @param string $relation * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereRelationIn($relation, $column, $operator = null, $value = null) { return $this; } /** * Add an "or where" clause to a relationship query. * * @param string $relation * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereRelationIn($relation, $column, $operator = null, $value = null) { return $this; } /** * Add a polymorphic relationship condition to the query with a where clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereMorphRelationIn($relation, $types, $column, $operator = null, $value = null) { return $this; } /** * Add a polymorphic relationship condition to the query with an "or where" clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereMorphRelationIn($relation, $types, $column, $operator = null, $value = null) { return $this; } } } } ================================================ FILE: composer.json ================================================ { "name": "biiiiiigmonster/hasin", "description": "Laravel framework relation has in implement", "type": "library", "keywords": [ "laravel", "orm", "relation", "whereHas" ], "license": "MIT", "authors": [ { "name": "YunFeng Lu", "email": "603707288@qq.com" } ], "require": { "laravel/framework": "^12|^13.0" }, "require-dev": { "laravel/pint": "^1.1", "orchestra/testbench": "^10.0|^11.0", "pestphp/pest": "^3.0|^4.4", "pestphp/pest-plugin-laravel": "^3.0|^4.1" }, "autoload": { "psr-4": { "BiiiiiigMonster\\Hasin\\": "src", "BiiiiiigMonster\\Hasin\\Database\\Factories\\": "database/factories" } }, "autoload-dev": { "psr-4": { "BiiiiiigMonster\\Hasin\\Tests\\": "tests" } }, "extra": { "laravel": { "providers": [ "BiiiiiigMonster\\Hasin\\HasinServiceProvider" ] } }, "scripts": { "test": "vendor/bin/pest", "format": "vendor/bin/pint" }, "config": { "sort-packages": true, "allow-plugins": true }, "minimum-stability": "dev", "prefer-stable": true } ================================================ FILE: database/factories/CommentFactory.php ================================================ $this->faker->sentence, 'status' => $this->faker->numberBetween(0, 9), ]; } } ================================================ FILE: database/factories/CountryFactory.php ================================================ $this->faker->country ]; } } ================================================ FILE: database/factories/HistoryFactory.php ================================================ $this->faker->address ]; } } ================================================ FILE: database/factories/ImageFactory.php ================================================ $this->faker->url ]; } } ================================================ FILE: database/factories/PhoneFactory.php ================================================ $this->faker->phoneNumber ]; } } ================================================ FILE: database/factories/PostFactory.php ================================================ $this->faker->title, 'votes' => $this->faker->numberBetween(0, 100), ]; } } ================================================ FILE: database/factories/RoleFactory.php ================================================ $this->faker->name ]; } } ================================================ FILE: database/factories/SupplierFactory.php ================================================ $this->faker->name ]; } } ================================================ FILE: database/factories/TagFactory.php ================================================ $this->faker->name ]; } } ================================================ FILE: database/factories/UserFactory.php ================================================ $this->faker->userName, 'age' => $this->faker->numberBetween(10, 30), ]; } } ================================================ FILE: database/factories/VideoFactory.php ================================================ $this->faker->name ]; } } ================================================ FILE: database/migrations/create_hasin_test_table.php ================================================ id(); $table->bigInteger('user_id')->default(0); $table->string('content'); $table->timestamps(); }); Schema::create('comments', function (Blueprint $table) { $table->id(); $table->morphs('commentable'); $table->string('content'); $table->tinyInteger('status')->default(0); $table->timestamps(); }); Schema::create('countries', function (Blueprint $table) { $table->id(); $table->string('name')->default(''); $table->timestamps(); }); Schema::create('images', function (Blueprint $table) { $table->id(); $table->string('url')->default(''); $table->morphs('imageable'); $table->timestamps(); }); Schema::create('phones', function (Blueprint $table) { $table->id(); $table->bigInteger('user_id')->default(0); $table->string('phone_number')->default(''); $table->timestamps(); }); Schema::create('roles', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); Schema::create('role_user', function (Blueprint $table) { $table->id(); $table->bigInteger('user_id')->default(0); $table->bigInteger('role_id')->default(0); $table->timestamps(); }); Schema::create('suppliers', function (Blueprint $table) { $table->id(); $table->string('name')->default(''); $table->timestamps(); }); Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name')->default(''); $table->timestamps(); }); Schema::create('taggables', function (Blueprint $table) { $table->id(); $table->bigInteger('tag_id')->default(0); $table->morphs('taggable'); $table->timestamps(); }); Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('username')->default(''); $table->tinyInteger('age')->default(0); $table->bigInteger('country_id')->default(0); $table->bigInteger('supplier_id')->default(0); $table->timestamps(); }); Schema::create('videos', function (Blueprint $table) { $table->id(); $table->string('name')->default(''); $table->timestamps(); }); Schema::create('posts', function (Blueprint $table) { $table->id(); $table->bigInteger('user_id')->default(0); $table->string('title')->default(''); $table->integer('votes')->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('histories'); Schema::dropIfExists('comments'); Schema::dropIfExists('countries'); Schema::dropIfExists('images'); Schema::dropIfExists('phones'); Schema::dropIfExists('roles'); Schema::dropIfExists('role_user'); Schema::dropIfExists('suppliers'); Schema::dropIfExists('tags'); Schema::dropIfExists('taggables'); Schema::dropIfExists('users'); Schema::dropIfExists('videos'); Schema::dropIfExists('posts'); } }; ================================================ FILE: phpunit.xml.dist ================================================ tests ./src ================================================ FILE: pint.json ================================================ { "preset": "psr12", "exclude": [ "build" ] } ================================================ FILE: src/Database/Eloquent/BuilderMixin.php ================================================ =', $count = 1, $boolean = 'and', ?Closure $callback = null): Builder { /** @var Builder $this */ if (is_string($relation)) { if (str_contains($relation, '.')) { return $this->hasInNested($relation, $operator, $count, $boolean, $callback); } $relation = $this->getRelationWithoutConstraints($relation); } if ($relation instanceof MorphTo) { return $this->hasMorphIn($relation, ['*'], $operator, $count, $boolean, $callback); } // If we only need to check for the existence of the relation, then we can optimize // the subquery to only run a "where in" clause instead of this full "count" // clause. This will make these queries run much faster compared with a count. $method = $this->canUseExistsForExistenceCheck($operator, $count) ? 'getRelationExistenceInQuery' : 'getRelationExistenceCountQuery'; $hasInQuery = $relation->{$method}( $relation->getRelated()->newQueryWithoutRelationships(), $this ); // Next we will call any given callback as an "anonymous" scope so they can get the // proper logical grouping of the where clauses if needed by this Eloquent query // builder. Then, we will be ready to finalize and return this query instance. if ($callback) { $hasInQuery->callScope($callback); } return $this->addHasInWhere( $hasInQuery, $relation, $operator, $count, $boolean ); }; } /** * Add nested relationship count / whereIn conditions to the query. * * Sets up recursive call to whereHas until we finish the nested relation. * * @return Closure */ protected function hasInNested(): Closure { return function ($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null): Builder { /** @var Builder $this */ $relations = explode('.', $relations); $doesntHave = $operator === '<' && $count === 1; if ($doesntHave) { $operator = '>='; $count = 1; } $closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) { // In order to nest "hasIn", we need to add count relation constraints on the // callback Closure. We'll do this by simply passing the Closure its own // reference to itself so it calls itself recursively on each segment. count($relations) > 1 ? $q->whereHasIn(array_shift($relations), $closure) : $q->hasIn(array_shift($relations), $operator, $count, 'and', $callback); }; return $this->hasIn(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure); }; } /** * Add a relationship count / whereIn condition to the query with an "or". * * @return Closure */ public function orHasIn(): Closure { return function ($relation, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasIn($relation, $operator, $count, 'or'); }; } /** * Add a relationship count / whereIn condition to the query. * * @return Closure */ public function doesntHaveIn(): Closure { return function ($relation, $boolean = 'and', ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->hasIn($relation, '<', 1, $boolean, $callback); }; } /** * Add a relationship count / whereIn condition to the query with an "or". * * @return Closure */ public function orDoesntHaveIn(): Closure { return function ($relation): Builder { /** @var Builder $this */ return $this->doesntHaveIn($relation, 'or'); }; } /** * Add a relationship count / whereIn condition to the query with where clauses. * * @return Closure */ public function whereHasIn(): Closure { return function ($relation, ?Closure $callback = null, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasIn($relation, $operator, $count, 'and', $callback); }; } /** * Add a relationship count / exists condition to the query with whereIn clauses. * * Also load the relationship with same condition. * * @return Closure */ public function withWhereHasIn(): Closure { return function ($relation, ?Closure $callback = null, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->whereHasIn(Str::before($relation, ':'), $callback, $operator, $count) ->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation); }; } /** * Add a relationship count / whereIn condition to the query with where clauses and an "or". * * @return Closure */ public function orWhereHasIn(): Closure { return function ($relation, ?Closure $callback = null, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasIn($relation, $operator, $count, 'or', $callback); }; } /** * Add a relationship count / whereIn condition to the query with where clauses. * * @return Closure */ public function whereDoesntHaveIn(): Closure { return function ($relation, ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->doesntHaveIn($relation, 'and', $callback); }; } /** * Add a relationship count / whereIn condition to the query with where clauses and an "or". * * @return Closure */ public function orWhereDoesntHaveIn(): Closure { return function ($relation, ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->doesntHaveIn($relation, 'or', $callback); }; } /** * Add a polymorphic relationship count / whereIn condition to the query. * * @return Closure */ public function hasMorphIn(): Closure { return function ($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null): Builder { /** @var Builder $this */ if (is_string($relation)) { $relation = $this->getRelationWithoutConstraints($relation); } $types = (array) $types; if ($types === ['*']) { $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all(); } foreach ($types as &$type) { $type = Relation::getMorphedModel($type) ?? $type; } return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) { foreach ($types as $type) { $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) { $belongsTo = $this->getBelongsToRelation($relation, $type); if ($callback) { $callback = function ($query) use ($callback, $type) { return $callback($query, $type); }; } $query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type())->getMorphClass()) ->whereHasIn($belongsTo, $callback, $operator, $count); }); } }, null, null, $boolean); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with an "or". * * @return Closure */ public function orHasMorphIn(): Closure { return function ($relation, $types, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasMorphIn($relation, $types, $operator, $count, 'or'); }; } /** * Add a polymorphic relationship count / whereIn condition to the query. * * @return Closure */ public function doesntHaveMorphIn(): Closure { return function ($relation, $types, $boolean = 'and', ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->hasMorphIn($relation, $types, '<', 1, $boolean, $callback); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with an "or". * * @return Closure */ public function orDoesntHaveMorphIn(): Closure { return function ($relation, $types): Builder { /** @var Builder $this */ return $this->doesntHaveMorphIn($relation, $types, 'or'); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses. * * @return Closure */ public function whereHasMorphIn(): Closure { return function ($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasMorphIn($relation, $types, $operator, $count, 'and', $callback); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses and an "or". * * @return Closure */ public function orWhereHasMorphIn(): Closure { return function ($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1): Builder { /** @var Builder $this */ return $this->hasMorphIn($relation, $types, $operator, $count, 'or', $callback); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses. * * @return Closure */ public function whereDoesntHaveMorphIn(): Closure { return function ($relation, $types, ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->doesntHaveMorphIn($relation, $types, 'and', $callback); }; } /** * Add a polymorphic relationship count / whereIn condition to the query with where clauses and an "or". * * @return Closure */ public function orWhereDoesntHaveMorphIn(): Closure { return function ($relation, $types, ?Closure $callback = null): Builder { /** @var Builder $this */ return $this->doesntHaveMorphIn($relation, $types, 'or', $callback); }; } /** * Add a basic where clause to a relationship query. * * @return Closure */ public function whereRelationIn(): Closure { return function ($relation, $column, $operator = null, $value = null): Builder { return $this->whereHasIn($relation, function ($query) use ($column, $operator, $value) { if ($column instanceof Closure) { $column($query); } else { $query->where($column, $operator, $value); } }); }; } /** * Add an "or where" clause to a relationship query. * * @return Closure */ public function orWhereRelationIn(): Closure { return function ($relation, $column, $operator = null, $value = null): Builder { return $this->orWhereHasIn($relation, function ($query) use ($column, $operator, $value) { if ($column instanceof Closure) { $column($query); } else { $query->where($column, $operator, $value); } }); }; } /** * Add a polymorphic relationship condition to the query with a where clause. * * @return Closure */ public function whereMorphRelationIn(): Closure { return function ($relation, $types, $column, $operator = null, $value = null): Builder { return $this->whereHasMorphIn($relation, $types, function ($query) use ($column, $operator, $value) { $query->where($column, $operator, $value); }); }; } /** * Add a polymorphic relationship condition to the query with an "or where" clause. * * @return Closure */ public function orWhereMorphRelationIn(): Closure { return function ($relation, $types, $column, $operator = null, $value = null): Builder { return $this->orWhereHasMorphIn($relation, $types, function ($query) use ($column, $operator, $value) { $query->where($column, $operator, $value); }); }; } /** * Add the "hasin" condition whereIn clause to the query. * * @return Closure */ protected function addHasInWhere(): Closure { return function (Builder $hasInQuery, Relation $relation, $operator, $count, $boolean): Builder { /** @var Builder $this */ $hasInQuery->mergeConstraintsFrom($relation->getQuery()); return $this->canUseExistsForExistenceCheck($operator, $count) ? $this->whereIn($relation->getRelationWhereInKey(), $hasInQuery->toBase(), $boolean, $operator === '<' && $count === 1) : $this->addWhereCountQuery($hasInQuery->toBase(), $operator, $count, $boolean); }; } } ================================================ FILE: src/Database/Eloquent/RelationMixin.php ================================================ select($columns); }; // basic builder $belongsTo = function (Builder $query, Builder $parentQuery) use ($relation): Builder { $columns = $query->qualifyColumn($this->ownerKey); $relationQuery = $relation($query, $parentQuery, $columns); if ($parentQuery->getQuery()->from == $query->getQuery()->from) { $relationQuery->from( $query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash() ); $relationQuery->getModel()->setTable($hash); } return $relationQuery; }; $belongsToMany = function (Builder $query, Builder $parentQuery) use ($relation): Builder { $columns = $this->getExistenceCompareKey(); if ($parentQuery->getQuery()->from == $query->getQuery()->from) { $query->select($columns); $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash()); $this->related->setTable($hash); } $this->performJoin($query); return $relation($query, $parentQuery, $columns); }; $hasOneOrMany = function (Builder $query, Builder $parentQuery) use ($relation): Builder { $columns = $this->getExistenceCompareKey(); if ($query->getQuery()->from == $parentQuery->getQuery()->from) { $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); $query->getModel()->setTable($hash); } return $relation($query, $parentQuery, $columns); }; $hasOneOrManyThrough = function (Builder $query, Builder $parentQuery) use ($relation): Builder { $columns = $this->getQualifiedFirstKeyName(); if ($parentQuery->getQuery()->from === $query->getQuery()->from) { $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey); if ($this->throughParentSoftDeletes()) { $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); } $query->getModel()->setTable($hash); return $relation($query, $parentQuery, $columns); } if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); if ($this->throughParentSoftDeletes()) { $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); } return $relation($query, $parentQuery, $columns); } $this->performJoin($query); return $relation($query, $parentQuery, $columns); }; // extended builder $hasOne = function (Builder $query, Builder $parentQuery) use ($hasOneOrMany): Builder { if ($this->isOneOfMany()) { $this->mergeOneOfManyJoinsTo($query); } return $hasOneOrMany($query, $parentQuery); }; $morphOneOrMany = function (Builder $query, Builder $parentQuery) use ($hasOneOrMany): Builder { return $hasOneOrMany($query, $parentQuery)->where( $query->qualifyColumn($this->getMorphType()), $this->morphClass ); }; $morphOne = function (Builder $query, Builder $parentQuery) use ($morphOneOrMany): Builder { if ($this->isOneOfMany()) { $this->mergeOneOfManyJoinsTo($query); } return $morphOneOrMany($query, $parentQuery); }; $morphToMany = function (Builder $query, Builder $parentQuery) use ($belongsToMany): Builder { return $belongsToMany($query, $parentQuery)->where( $this->qualifyPivotColumn($this->morphType), $this->morphClass ); }; return match (true) { // extended relation $this instanceof HasOne => $hasOne($query, $parentQuery), $this instanceof MorphOne => $morphOne($query, $parentQuery), $this instanceof MorphOneOrMany => $morphOneOrMany($query, $parentQuery), $this instanceof MorphToMany => $morphToMany($query, $parentQuery), // basic relation $this instanceof BelongsTo => $belongsTo($query, $parentQuery), $this instanceof BelongsToMany => $belongsToMany($query, $parentQuery), $this instanceof HasOneOrMany => $hasOneOrMany($query, $parentQuery), $this instanceof HasOneOrManyThrough => $hasOneOrManyThrough($query, $parentQuery), default => throw new LogicException( sprintf('%s must be a relationship instance.', $this::class) ) }; }; } public function getRelationWhereInKey(): Closure { return fn (): string => match (true) { $this instanceof BelongsTo => $this->getQualifiedForeignKeyName(), $this instanceof HasOneOrMany, $this instanceof BelongsToMany => $this->getQualifiedParentKeyName(), $this instanceof HasOneOrManyThrough => $this->getQualifiedLocalKeyName(), default => throw new LogicException( sprintf('%s must be a relationship instance.', $this::class) ) }; } } ================================================ FILE: src/HasinServiceProvider.php ================================================ orderBy('id')->pluck('id'); $doesntHaveIn = User::doesntHaveIn('posts')->orderBy('id')->pluck('id'); expect($doesntHave)->toEqual($doesntHaveIn); }); test('nested doesntHaveIn same as nested doesntHave', function () { $doesntHave = User::doesntHave('posts.comments')->orderBy('id')->pluck('id'); $doesntHaveIn = User::doesntHaveIn('posts.comments')->orderBy('id')->pluck('id'); expect($doesntHave)->toEqual($doesntHaveIn); }); ================================================ FILE: tests/Features/DoesntHaveMorphInTest.php ================================================ orderBy('id')->pluck('id'); $doesntHaveMorphIn = Comment::doesntHaveMorphIn('commentable', [Post::class])->orderBy('id')->pluck('id'); expect($doesntHaveMorph)->toEqual($doesntHaveMorphIn); }); ================================================ FILE: tests/Features/HasInTest.php ================================================ orderBy('id')->pluck('id'); $hasIn = User::hasIn('posts')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('HasOne: hasIn same as has', function () { $has = User::has('phone')->orderBy('id')->pluck('id'); $hasIn = User::hasIn('phone')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('BelongsTo: hasIn same as has', function () { $has = User::has('country')->orderBy('id')->pluck('id'); $hasIn = User::hasIn('country')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('BelongsToMany: hasIn same as has', function () { $has = User::has('roles')->orderBy('id')->pluck('id'); $hasIn = User::hasIn('roles')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('HasOneThrough: hasIn same as has', function () { $has = Supplier::has('userHistory')->orderBy('id')->pluck('id'); $hasIn = Supplier::hasIn('userHistory')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('HasManyThrough: hasIn same as has', function () { $has = Country::has('posts')->orderBy('id')->pluck('id'); $hasIn = Country::hasIn('posts')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('MorphOne: hasIn same as has', function () { $has = User::has('image')->orderBy('id')->pluck('id'); $hasIn = User::hasIn('image')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('MorphTo: hasIn same as has', function () { $has = Image::has('imageable')->orderBy('id')->pluck('id'); $hasIn = Image::hasIn('imageable')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('MorphMany: hasIn same as has', function () { $has = Video::has('comments')->orderBy('id')->pluck('id'); $hasIn = Video::hasIn('comments')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('MorphToMany: hasIn same as has', function () { $has = Video::has('tags')->orderBy('id')->pluck('id'); $hasIn = Video::hasIn('tags')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('hasIn(gte 2) same as has(gte 2)', function () { $has = User::has('posts', '>=', 2)->orderBy('id')->pluck('id'); $hasIn = User::hasIn('posts', '>=', 2)->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('nested hasIn same as nested has', function () { $has = User::has('posts.comments')->orderBy('id')->pluck('id'); $hasIn = User::hasIn('posts.comments')->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); test('nested hasIn(gte 2) same as nested has(gte 2)', function () { $has = User::has('posts.comments', '>=', 2)->orderBy('id')->pluck('id'); $hasIn = User::hasIn('posts.comments', '>=', 2)->orderBy('id')->pluck('id'); expect($has)->toEqual($hasIn); }); ================================================ FILE: tests/Features/HasMorphInTest.php ================================================ orderBy('id')->pluck('id'); $hasMorphIn = Comment::hasMorphIn('commentable', [Post::class])->orderBy('id')->pluck('id'); expect($hasMorph)->toEqual($hasMorphIn); }); test('hasMorphIn(gte 2) same as hasMorph(gte 2)', function () { $hasMorph = Comment::hasMorph('commentable', [Post::class], '>=', 2)->orderBy('id')->pluck('id'); $hasMorphIn = Comment::hasMorphIn('commentable', [Post::class], '>=', 2)->orderBy('id')->pluck('id'); expect($hasMorph)->toEqual($hasMorphIn); }); ================================================ FILE: tests/Features/OrDoesntHaveInTest.php ================================================ ', 18)->orDoesntHave('posts')->orderBy('id')->pluck('id'); $orDoesntHaveIn = User::where('age', '>', 18)->orDoesntHaveIn('posts')->orderBy('id')->pluck('id'); expect($orDoesntHave)->toEqual($orDoesntHaveIn); }); test('nested orDoesntHaveIn same as nested orDoesntHave', function () { $orDoesntHave = User::where('age', '>', 18)->orDoesntHave('posts.comments')->orderBy('id')->pluck('id'); $orDoesntHaveIn = User::where('age', '>', 18)->orDoesntHaveIn('posts.comments')->orderBy('id')->pluck('id'); expect($orDoesntHave)->toEqual($orDoesntHaveIn); }); ================================================ FILE: tests/Features/OrDoesntHaveMorphInTest.php ================================================ ', 2)->orDoesntHaveMorph('commentable', [Post::class])->orderBy('id')->pluck('id'); $orDoesntHaveMorphIn = Comment::where('status', '>', 2)->orDoesntHaveMorphIn('commentable', [Post::class])->orderBy('id')->pluck('id'); expect($orDoesntHaveMorph)->toEqual($orDoesntHaveMorphIn); }); ================================================ FILE: tests/Features/OrHasInTest.php ================================================ ', 18)->orHas('posts')->orderBy('id')->pluck('id'); $orHasIn = User::where('age', '>', 18)->orHasIn('posts')->orderBy('id')->pluck('id'); expect($orHas)->toEqual($orHasIn); }); test('orHasIn(gte 2) same as orHas(gte 2)', function () { $orHas = User::where('age', '>', 18)->orHas('posts', '>=', 2)->orderBy('id')->pluck('id'); $orHasIn = User::where('age', '>', 18)->orHasIn('posts', '>=', 2)->orderBy('id')->pluck('id'); expect($orHas)->toEqual($orHasIn); }); test('nested orHasIn same as nested orHas', function () { $orHas = User::where('age', '>', 18)->orHas('posts.comments')->orderBy('id')->pluck('id'); $orHasIn = User::where('age', '>', 18)->orHasIn('posts.comments')->orderBy('id')->pluck('id'); expect($orHas)->toEqual($orHasIn); }); test('nested orHasIn(gte 2) same as nested orHas(gte 2)', function () { $orHas = User::where('age', '>', 18)->orHas('posts.comments', '>=', 2)->orderBy('id')->pluck('id'); $orHasIn = User::where('age', '>', 18)->orHasIn('posts.comments', '>=', 2)->orderBy('id')->pluck('id'); expect($orHas)->toEqual($orHasIn); }); ================================================ FILE: tests/Features/OrHasMorphInTest.php ================================================ ', 2)->orHasMorph('commentable', [Post::class])->orderBy('id')->pluck('id'); $orHasMorphIn = Comment::where('status', '>', 2)->orHasMorphIn('commentable', [Post::class])->orderBy('id')->pluck('id'); expect($orHasMorph)->toEqual($orHasMorphIn); }); test('orHasMorphIn(gte 2) same as orHasMorph(gte 2)', function () { $orHasMorph = Comment::where('status', '>', 2)->orHasMorph('commentable', [Post::class], '>=', 2)->orderBy('id')->pluck('id'); $orHasMorphIn = Comment::where('status', '>', 2)->orHasMorphIn('commentable', [Post::class], '>=', 2)->orderBy('id')->pluck('id'); expect($orHasMorph)->toEqual($orHasMorphIn); }); ================================================ FILE: tests/Features/OrWhereDoesntHaveInTest.php ================================================ ', 18)->orWhereDoesntHave('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); $orWhereDoesntHaveIn = User::where('age', '>', 18)->orWhereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); expect($orWhereDoesntHave)->toEqual($orWhereDoesntHaveIn); }); test('nested orWhereDoesntHaveIn same as nested orWhereDoesntHave', function () { $orWhereDoesntHave = User::where('age', '>', 18)->orWhereDoesntHave('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); $orWhereDoesntHaveIn = User::where('age', '>', 18)->orWhereDoesntHaveIn('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); expect($orWhereDoesntHave)->toEqual($orWhereDoesntHaveIn); }); ================================================ FILE: tests/Features/OrWhereDoesntHaveMorphInTest.php ================================================ ', 2)->orWhereDoesntHaveMorph('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); $orWhereDoesntHaveMorphIn = Comment::where('status', '>', 2)->orWhereDoesntHaveMorphIn('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); expect($orWhereDoesntHaveMorph)->toEqual($orWhereDoesntHaveMorphIn); }); ================================================ FILE: tests/Features/OrWhereHasInTest.php ================================================ ', 18)->orWhereHas('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); $orWhereHasIn = User::where('age', '>', 18)->orWhereHasIn('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); expect($orWhereHas)->toEqual($orWhereHasIn); }); test('nested orWhereHasIn same as nested orWhereHas', function () { $orWhereHas = User::where('age', '>', 18)->orWhereHas('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); $orWhereHasIn = User::where('age', '>', 18)->orWhereHasIn('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); expect($orWhereHas)->toEqual($orWhereHasIn); }); ================================================ FILE: tests/Features/OrWhereHasMorphInTest.php ================================================ ', 2)->orWhereHasMorph('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); $orWhereHasMorphIn = Comment::where('status', '>', 2)->orWhereHasMorphIn('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); expect($orWhereHasMorph)->toEqual($orWhereHasMorphIn); }); ================================================ FILE: tests/Features/OrWhereMorphRelationInTest.php ================================================ =', 2) ->orWhereMorphRelation('commentable', [Post::class], 'title', 'like', '%code%') ->orderBy('id')->pluck('id'); $orWhereMorphRelationIn = Comment::where('status', '>=', 2) ->orWhereMorphRelationIn('commentable', [Post::class], 'title', 'like', '%code%') ->orderBy('id')->pluck('id'); expect($orWhereMorphRelation)->toEqual($orWhereMorphRelationIn); }); ================================================ FILE: tests/Features/OrWhereRelationInTest.php ================================================ ', 18) ->orWhereRelation('posts', 'title', 'like', '%code%') ->orderBy('id')->pluck('id'); $orWhereRelationIn = User::where('age', '>', 18) ->orWhereRelationIn('posts', 'title', 'like', '%code%') ->orderBy('id')->pluck('id'); expect($orWhereRelation)->toEqual($orWhereRelationIn); }); test('nested whereRelationIn same as nested whereRelation', function () { $orWhereRelation = User::where('age', '>', 18) ->orWhereRelation('posts.comments', 'status', '>=', '2') ->orderBy('id')->pluck('id'); $orWhereRelationIn = User::where('age', '>', 18) ->orWhereRelationIn('posts.comments', 'status', '>=', '2') ->orderBy('id')->pluck('id'); expect($orWhereRelation)->toEqual($orWhereRelationIn); }); ================================================ FILE: tests/Features/WhereDoesntHaveInTest.php ================================================ where('votes', '>', 20); })->orderBy('id')->pluck('id'); $whereDoesntHaveIn = User::whereDoesntHaveIn('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); expect($whereDoesntHave)->toEqual($whereDoesntHaveIn); }); test('nested whereDoesntHaveIn same as nested whereDoesntHave', function () { $whereDoesntHave = User::whereDoesntHave('posts.comments', function ($query) { $query->where('status', '>', 2); ; })->orderBy('id')->pluck('id'); $whereDoesntHaveIn = User::whereDoesntHaveIn('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); expect($whereDoesntHave)->toEqual($whereDoesntHaveIn); }); ================================================ FILE: tests/Features/WhereDoesntHaveMorphInTest.php ================================================ where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); $whereDoesntHaveMorphIn = Comment::whereDoesntHaveMorphIn('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); expect($whereDoesntHaveMorph)->toEqual($whereDoesntHaveMorphIn); }); ================================================ FILE: tests/Features/WhereHasInTest.php ================================================ where('votes', '>', 20); })->orderBy('id')->pluck('id'); $whereHasIn = User::whereHasIn('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->pluck('id'); expect($whereHas)->toEqual($whereHasIn); }); test('nested whereHasIn same as nested whereHas', function () { $whereHas = User::whereHas('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); $whereHasIn = User::whereHasIn('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->pluck('id'); expect($whereHas)->toEqual($whereHasIn); }); ================================================ FILE: tests/Features/WhereHasMorphInTest.php ================================================ where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); $whereHasMorphIn = Comment::whereHasMorphIn('commentable', [Post::class], function ($query) { $query->where('title', 'like', '%code%'); })->orderBy('id')->pluck('id'); expect($whereHasMorph)->toEqual($whereHasMorphIn); }); ================================================ FILE: tests/Features/WhereMorphRelationInTest.php ================================================ orderBy('id')->pluck('id'); $whereMorphRelationIn = Comment::whereMorphRelationIn('commentable', [Post::class], 'title', 'like', '%code%') ->orderBy('id')->pluck('id'); expect($whereMorphRelation)->toEqual($whereMorphRelationIn); }); ================================================ FILE: tests/Features/WhereRelationInTest.php ================================================ orderBy('id')->pluck('id'); $whereRelationIn = User::whereRelationIn('posts', 'title', 'like', '%code%')->orderBy('id')->pluck('id'); expect($whereRelation)->toEqual($whereRelationIn); }); test('nested whereRelationIn same as nested whereRelation', function () { $whereRelation = User::whereRelation('posts.comments', 'status', '>=', '2')->orderBy('id')->pluck('id'); $whereRelationIn = User::whereRelationIn('posts.comments', 'status', '>=', '2')->orderBy('id')->pluck('id'); expect($whereRelation)->toEqual($whereRelationIn); }); ================================================ FILE: tests/Features/WithWhereHasInTest.php ================================================ where('votes', '>', 20); })->orderBy('id')->get(); $whereHasIn = User::withWhereHasIn('posts', function ($query) { $query->where('votes', '>', 20); })->orderBy('id')->get(); expect($whereHas->pluck('id'))->toEqual($whereHasIn->pluck('id')) ->and($whereHas->pluck('posts.id'))->toEqual($whereHasIn->pluck('posts.id')); }); test('nested withWhereHasIn same as nested withWhereHas', function () { $whereHas = User::withWhereHas('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->get(); $whereHasIn = User::withWhereHasIn('posts.comments', function ($query) { $query->where('status', '>', 2); })->orderBy('id')->get(); expect($whereHas->pluck('id'))->toEqual($whereHasIn->pluck('id')) ->and($whereHas->pluck('posts.id'))->toEqual($whereHasIn->pluck('posts.id')) ->and($whereHas->pluck('posts.comments.id'))->toEqual($whereHasIn->pluck('posts.comments.id')); }); ================================================ FILE: tests/Models/Comment.php ================================================ morphTo(); } } ================================================ FILE: tests/Models/Country.php ================================================ hasManyThrough(Post::class, User::class); } } ================================================ FILE: tests/Models/History.php ================================================ morphTo(); } } ================================================ FILE: tests/Models/Phone.php ================================================ belongsTo(User::class); } public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); } public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); } public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable')->using(Taggable::class); } } ================================================ FILE: tests/Models/Role.php ================================================ belongsToMany(User::class)->using(RoleUser::class)->withTimestamps(); } } ================================================ FILE: tests/Models/RoleUser.php ================================================ hasOneThrough(History::class, User::class); } } ================================================ FILE: tests/Models/Tag.php ================================================ morphToMany(Post::class, 'taggable')->using(Taggable::class); } public function videos(): MorphToMany { return $this->morphToMany(Video::class, 'taggable')->using(Taggable::class); } } ================================================ FILE: tests/Models/Taggable.php ================================================ belongsTo(Country::class); } public function supplier(): BelongsTo { return $this->belongsTo(Supplier::class); } public function phone(): HasOne { return $this->hasOne(Phone::class); } public function history(): HasOne { return $this->hasOne(History::class); } public function posts(): HasMany { return $this->hasMany(Post::class); } public function roles(): BelongsToMany { return $this->belongsToMany(Role::class)->using(RoleUser::class)->withTimestamps(); } public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); } } ================================================ FILE: tests/Models/Video.php ================================================ morphMany(Comment::class, 'commentable'); } public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable')->using(Taggable::class); } } ================================================ FILE: tests/Pest.php ================================================ in(__DIR__); ================================================ FILE: tests/TestCase.php ================================================ migration->up(); } protected function destroyDatabaseMigrations() { $this->migration->down(); } protected function defineDatabaseSeeders() { $tags = Tag::factory(20)->create(); $countries = Country::factory(15)->create(); $suppliers = Supplier::factory(15)->create(); $roles = Role::factory(10)->create(); $users = User::factory(15) ->has(History::factory()) ->has(Phone::factory()) ->has(Image::factory(3)) ->hasAttached($roles->random(5)) ->sequence(fn () => ['country_id' => $countries->pluck('id')->random()]) ->sequence(fn () => ['supplier_id' => $suppliers->pluck('id')->random()]) ->create(); $posts = Post::factory(15) ->sequence(fn () => ['user_id' => $users->pluck('id')->random()]) ->hasAttached($tags->random(15)) ->create(); $videos = Video::factory(15)->hasAttached($tags->random(15))->create(); $posts->random(5)->map(function ($post) { Comment::factory(3)->for($post, 'commentable')->create(); Image::factory(2)->for($post, 'imageable')->create(); }); $videos->random(5)->map(function ($video) { Comment::factory(3)->for($video, 'commentable')->create(); }); } public function getEnvironmentSetUp($app) { config()->set('database.connections.mysql.prefix', 'hasin_test_'); Schema::defaultStringLength(191); Factory::guessFactoryNamesUsing( fn (string $modelName) => 'BiiiiiigMonster\\Hasin\\Database\\Factories\\'.class_basename($modelName).'Factory' ); $this->migration = include __DIR__.'/../database/migrations/create_hasin_test_table.php'; } }