├── .coveralls.yml
├── .features-plan
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── laravel.yml
├── .gitignore
├── .gitkeep
├── .scrutinizer.yml
├── .stickler.yml
├── Aban-21-1402 14-27-04.gif
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── _ide_helper.php
├── composer.json
├── eloquent-filter.png
├── makefile
├── phpunit.xml
├── phpunit.xml.dist
├── src
├── Command
│ ├── MakeEloquentFilter.php
│ └── modelfilter.stub
├── Facade
│ └── EloquentFilter.php
├── QueryFilter
│ ├── Core
│ │ ├── DbBuilder
│ │ │ ├── DbBuilderWrapper.php
│ │ │ └── DbBuilderWrapperInterface.php
│ │ ├── EloquentBuilder
│ │ │ ├── EloquentModelBuilderWrapper.php
│ │ │ └── QueryBuilderWrapperInterface.php
│ │ ├── FilterBuilder
│ │ │ ├── Core
│ │ │ │ ├── QueryFilterCore.php
│ │ │ │ └── QueryFilterCoreBuilder.php
│ │ │ ├── IO
│ │ │ │ ├── Encoder.php
│ │ │ │ ├── RequestFilter.php
│ │ │ │ └── ResponseFilter.php
│ │ │ ├── MainQueryFilterBuilder.php
│ │ │ └── QueryBuilder
│ │ │ │ ├── DBQueryFilterBuilder.php
│ │ │ │ ├── EloquentQueryFilterBuilder.php
│ │ │ │ └── QueryFilterBuilder.php
│ │ ├── HelperEloquentFilter.php
│ │ ├── HelperFilter.php
│ │ ├── RateLimiting.php
│ │ └── ResolverDetection
│ │ │ ├── ResolverDetectionDb.php
│ │ │ ├── ResolverDetectionEloquent.php
│ │ │ └── ResolverDetections.php
│ ├── Detection
│ │ ├── ConditionsDetect
│ │ │ ├── DB
│ │ │ │ └── DBBuilderQueryByCondition.php
│ │ │ ├── Eloquent
│ │ │ │ └── MainBuilderQueryByCondition.php
│ │ │ └── TypeQueryConditions
│ │ │ │ ├── SpecialCondition.php
│ │ │ │ ├── WhereBetweenCondition.php
│ │ │ │ ├── WhereByOptCondition.php
│ │ │ │ ├── WhereCondition.php
│ │ │ │ ├── WhereDateCondition.php
│ │ │ │ ├── WhereDayCondition.php
│ │ │ │ ├── WhereDoesntHaveCondition.php
│ │ │ │ ├── WhereHasCondition.php
│ │ │ │ ├── WhereInCondition.php
│ │ │ │ ├── WhereLikeCondition.php
│ │ │ │ ├── WhereMonthCondition.php
│ │ │ │ ├── WhereNullCondition.php
│ │ │ │ ├── WhereOrCondition.php
│ │ │ │ └── WhereYearCondition.php
│ │ ├── Contract
│ │ │ ├── ConditionsContract.php
│ │ │ ├── DefaultConditionsContract.php
│ │ │ ├── DetectorConditionContract.php
│ │ │ ├── DetectorDbFactoryContract.php
│ │ │ ├── DetectorFactoryContract.php
│ │ │ └── MainBuilderConditionsContract.php
│ │ ├── DetectionFactory
│ │ │ ├── DetectionDbFactory.php
│ │ │ └── DetectionEloquentFactory.php
│ │ └── Detector
│ │ │ ├── DetectorConditionCondition.php
│ │ │ └── DetectorConditionDbCondition.php
│ ├── Exceptions
│ │ └── EloquentFilterException.php
│ ├── Factory
│ │ ├── QueryBuilderWrapperFactory.php
│ │ └── QueryFilterCoreFactory.php
│ ├── ModelFilters
│ │ └── Filterable.php
│ └── Queries
│ │ ├── BaseClause.php
│ │ ├── DB
│ │ ├── Special.php
│ │ ├── Where.php
│ │ ├── WhereBetween.php
│ │ ├── WhereByOpt.php
│ │ ├── WhereDate.php
│ │ ├── WhereDayQuery.php
│ │ ├── WhereDoesntHave.php
│ │ ├── WhereHas.php
│ │ ├── WhereIn.php
│ │ ├── WhereLike.php
│ │ ├── WhereMonthQuery.php
│ │ ├── WhereNotNull.php
│ │ ├── WhereNull.php
│ │ ├── WhereOr.php
│ │ └── WhereYearQuery.php
│ │ └── Eloquent
│ │ ├── Special.php
│ │ ├── Where.php
│ │ ├── WhereBetween.php
│ │ ├── WhereByOpt.php
│ │ ├── WhereCustom.php
│ │ ├── WhereDate.php
│ │ ├── WhereDayQuery.php
│ │ ├── WhereDoesntHave.php
│ │ ├── WhereHas.php
│ │ ├── WhereIn.php
│ │ ├── WhereLike.php
│ │ ├── WhereMonthQuery.php
│ │ ├── WhereNotNull.php
│ │ ├── WhereNull.php
│ │ ├── WhereOr.php
│ │ └── WhereYearQuery.php
├── ServiceProvider.php
└── config
│ ├── .gitkeep
│ └── config.php
└── tests
├── Models
├── Car.php
├── Category.php
├── CategoryPosts.php
├── CustomDetect
│ ├── WhereLikeRelation.php
│ └── WhereRelationLikeCondition.php
├── Filters
│ └── usersFilter.php
├── Order.php
├── Post.php
├── Stat.php
├── Tag.php
└── User.php
├── TestCase.php
└── Tests
├── Db
└── DbFilterMockTest.php
├── Eloquent
├── MakeEloquentFilterCommandTest.php
└── ModelFilterMockTest.php
└── RateLimiting
└── RateLimitTest.php
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: Nh0K6VukSDiNmaVyO93Z7AWK4Hs1WOzj3
2 |
--------------------------------------------------------------------------------
/.features-plan:
--------------------------------------------------------------------------------
1 | 1- make blacklist array for disable some method for custom query
2 | 2- adding support callback
3 | 3- support algolia search and 3rd party api tools
4 | 4- add macro for get some information
5 | 5- add limit number for some column to prevent bad performance queries by users
6 | 6- set max_limit on the method
7 | 7- Limit some default operators on the method or Model for prevent manipulation
8 | 8- Support change driver ORM and query builder
9 | 9- Consider a meaningfully method name for custom methods
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/laravel.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | push:
5 | schedule:
6 | - cron: '0 0 * * *'
7 |
8 | jobs:
9 | php-tests:
10 | runs-on: ${{ matrix.os }}
11 |
12 | strategy:
13 | matrix:
14 | php: [ 8.0, 8.1, 8.2, 8.3 , 8.4 ]
15 | laravel: [ 8.*, 9.*, 10.*, 11.*, 12.* ]
16 | dependency-version: [ prefer-stable ]
17 | os: [ ubuntu-latest ]
18 | include:
19 | - laravel: 12.*
20 | testbench: v10.0.0
21 | database: 10.0.x-dev
22 | - laravel: 11.*
23 | testbench: v9.0.0
24 | database: 9.0.x-dev
25 | - laravel: 10.*
26 | testbench: 8.*
27 | database: 8.0.x-dev
28 | - laravel: 9.*
29 | testbench: 7.*
30 | database: 7.0.x-dev
31 | - laravel: 8.*
32 | testbench: 6.*
33 | database: 6.x-dev
34 | exclude:
35 | - laravel: 10.*
36 | php: 8.0
37 | - laravel: 11.*
38 | php: 8.1
39 | - laravel: 11.*
40 | php: 8.0
41 | - laravel: 12.*
42 | php: 8.1
43 | - laravel: 12.*
44 | php: 8.0
45 | - laravel: 8.*
46 | dependency-version: prefer-lowest
47 |
48 |
49 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
50 |
51 | steps:
52 | - name: Checkout code
53 | uses: actions/checkout@v1
54 |
55 | - name: Setup PHP
56 | uses: shivammathur/setup-php@v2
57 | with:
58 | php-version: ${{ matrix.php }}
59 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
60 | coverage: xdebug
61 |
62 | - name: Install dependencies
63 | run: |
64 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
65 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
66 |
67 | - name: Execute tests
68 | run: make test
69 | - name: Upload coverage reports to Codecov
70 | uses: codecov/codecov-action@v3
71 | with:
72 | token: ${{ secrets.CODECOV_TOKEN }}
73 | file: ./tests/build/logs/clover.xml
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 | composer.lock
4 | tests/build
5 | .phpunit.result.cache
6 | .DS_Store
7 | tests/.DS_Store
8 | php-cs-fixer
9 |
--------------------------------------------------------------------------------
/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehdi-fathi/eloquent-filter/65a9af3ad7913977c89e57c2fbbf5276092e2e66/.gitkeep
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | build:
2 | tests:
3 | override:
4 | -
5 | coverage:
6 | file: 'tests/coverage.xml'
7 | format: 'clover'
8 |
--------------------------------------------------------------------------------
/.stickler.yml:
--------------------------------------------------------------------------------
1 | #linters:
2 | # phpcs:
3 | # standard: PSR2
4 | # fixer: true
5 | #files:
6 | # ignore:
7 | # - 'vendor/*'
8 | #fixers:
9 | # enable: true
10 |
--------------------------------------------------------------------------------
/Aban-21-1402 14-27-04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehdi-fathi/eloquent-filter/65a9af3ad7913977c89e57c2fbbf5276092e2e66/Aban-21-1402 14-27-04.gif
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Eloquent-Filter
2 |
3 | First off, thank you for considering contributing to Eloquent-Filter! It's people like you that make the open source community such a great place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
4 |
5 | ## Code of Conduct
6 |
7 | This project and everyone participating in it is governed by the [Eloquent-Filter Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
8 |
9 | ## How Can I Contribute?
10 |
11 | There are many ways you can contribute to Eloquent-Filter, and not all of them involve writing code. Here's a few ideas to get started:
12 |
13 | - Reporting Bugs
14 | - Suggesting Enhancements
15 | - Writing Code
16 | - Reviewing Pull Requests
17 | - Improving Documentation
18 |
19 | ### Reporting Bugs
20 |
21 | This section guides you through submitting a bug report for Eloquent-Filter. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
22 |
23 | **Before Submitting A Bug Report:**
24 |
25 | - Check the documentation for ways to configure or use the tool that might solve your issue.
26 | - Perform a [cursory search](https://github.com/mehdi-fathi/eloquent-filter/issues) to see if the issue has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
27 |
28 | **How Do I Submit A (Good) Bug Report?**
29 |
30 | Bugs are tracked as [GitHub issues](https://github.com/mehdi-fathi/eloquent-filter/issues). Create an issue and provide the following information:
31 |
32 | - Use a clear and descriptive title for the issue.
33 | - Describe the exact steps to reproduce the problem with as much detail as possible.
34 | - Provide specific examples to demonstrate the steps.
35 |
36 | ### Suggesting Enhancements
37 |
38 | This section guides you through submitting an enhancement suggestion for Eloquent-Filter, including completely new features and minor improvements to existing functionality.
39 |
40 | **How Do I Submit A (Good) Enhancement Suggestion?**
41 |
42 | Enhancement suggestions are tracked as GitHub issues:
43 |
44 | - Use a clear and descriptive title for the issue.
45 | - Provide a step-by-step description of the suggested enhancement with as many details as possible.
46 | - Provide specific examples to demonstrate the steps.
47 |
48 | ### Your First Code Contribution
49 |
50 | Unsure where to begin contributing to Eloquent-Filter? You can start by looking through 'beginner' and 'help-wanted' issues:
51 |
52 | - [Beginner issues](https://github.com/mehdi-fathi/eloquent-filter/labels/beginner)
53 | - [Help wanted issues](https://github.com/mehdi-fathi/eloquent-filter/labels/help%20wanted)
54 |
55 | ### Pull Requests
56 |
57 | The process described here has several goals:
58 |
59 | - Maintain Eloquent-Filter's quality
60 | - Fix problems that are important to users
61 | - Engage the community in working toward the best possible Eloquent-Filter
62 |
63 | Please follow these steps to have your contribution considered by the maintainers:
64 |
65 | 1. Follow all instructions in [the template](PULL_REQUEST_TEMPLATE.md)
66 | 2. Follow the [styleguides](#styleguides)
67 | 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
68 |
69 | ## Styleguides
70 |
71 | ### Git Commit Messages
72 |
73 | - Use the present tense ("Add feature" not "Added feature")
74 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
75 | - Limit the first line to 72 characters or less
76 | - Reference issues and pull requests liberally after the first line
77 |
78 | ### PHP Styleguide
79 |
80 | Use the [PSR-12: Extended Coding Style](https://www.php-fig.org/psr/psr-12/).
81 |
82 | ## Additional Notes
83 |
84 | ### Issue and Pull Request Labels
85 |
86 | This section lists the labels we use to help us track and manage issues and pull requests.
87 |
88 | #### Type of Issue and Issue State
89 |
90 | - `bug`: Indicates an issue with the project
91 | - `enhancement`: Indicates a new feature request
92 | - `help wanted`: Indicates that a maintainer wants help on an issue or pull request
93 | - `question`: Indicates that an issue or pull request needs more information
94 | - `good first issue`: Indicates a good issue for first-time contributors
95 |
96 | ## License
97 |
98 | By contributing to Eloquent-Filter, you agree that your contributions will be licensed under its [MIT License](LICENSE).
99 |
100 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mehdi Fathi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_ide_helper.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ./tests/
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ./src
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Command/MakeEloquentFilter.php:
--------------------------------------------------------------------------------
1 | files = $files;
60 | }
61 |
62 | /**
63 | * Execute the console command.
64 | *
65 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
66 | *
67 | * @return mixed
68 | */
69 | public function handle()
70 | {
71 | $this->makeClassName()->compileStub();
72 | $this->info(class_basename($this->getClassName()).' Created Successfully!');
73 | }
74 |
75 | /**
76 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
77 | */
78 | public function compileStub()
79 | {
80 | if ($this->files->exists($path = $this->getPath())) {
81 | $this->error("\n\n\t".$path.' Already Exists!'."\n");
82 | exit;
83 | }
84 | $this->makeDirectory($path);
85 |
86 | $stubPath = __DIR__.'/modelfilter.stub';
87 |
88 | if (!$this->files->exists($stubPath) || !is_readable($stubPath)) {
89 | $this->error(sprintf('File "%s" does not exist or is unreadable.', $stubPath));
90 | exit;
91 | }
92 |
93 | $tmp = $this->applyValuesToStub($this->files->get($stubPath));
94 | $this->files->put($path, $tmp);
95 | }
96 |
97 | /**
98 | * @param $stub
99 | *
100 | * @return string|string[]
101 | */
102 | public function applyValuesToStub($stub)
103 | {
104 | $className = $this->getClassBasename($this->getClassName());
105 | $search = ['{{class}}', '{{namespace}}'];
106 | $replace = [$className, str_replace('\\'.$className, '', $this->getClassName())];
107 |
108 | return str_replace($search, $replace, $stub);
109 | }
110 |
111 | /**
112 | * @param $class
113 | *
114 | * @return string
115 | */
116 | private function getClassBasename($class)
117 | {
118 | $class = is_object($class) ? get_class($class) : $class;
119 |
120 | return basename(str_replace('\\', '/', $class));
121 | }
122 |
123 | /**
124 | * @return string
125 | */
126 | public function getPath()
127 | {
128 | return $this->laravel->path.DIRECTORY_SEPARATOR.$this->getFileName();
129 | }
130 |
131 | /**
132 | * @return string|string[]
133 | */
134 | public function getFileName()
135 | {
136 | return str_replace([$this->getAppNamespace(), '\\'], ['', DIRECTORY_SEPARATOR], $this->getClassName().'.php');
137 | }
138 |
139 | /**
140 | * @return string
141 | */
142 | public function getAppNamespace()
143 | {
144 | return $this->laravel->getNamespace();
145 | }
146 |
147 | /**
148 | * Build the directory for the class if necessary.
149 | *
150 | * @param string $path
151 | *
152 | */
153 | protected function makeDirectory(string $path)
154 | {
155 | if (!$this->files->isDirectory(dirname($path))) {
156 | $this->files->makeDirectory(dirname($path), 0777, true, true);
157 | }
158 | }
159 |
160 | /**
161 | * Create Filter Class Name.
162 | *
163 | * @return $this
164 | */
165 | public function makeClassName()
166 | {
167 | $parts = array_map([Str::class, 'studly'], explode('\\', $this->argument('name')));
168 | $className = array_pop($parts);
169 | $ns = count($parts) > 0 ? implode('\\', $parts).'\\' : '';
170 |
171 | $fqClass = config('eloquentFilter.namespace', 'App\\ModelFilters\\').$ns.$className;
172 |
173 | if (substr($fqClass, -6, 6) !== 'Filter') {
174 | $fqClass .= 'Filter';
175 | }
176 |
177 | if (class_exists($fqClass)) {
178 | $this->error("\n\n\t$fqClass Already Exists!\n");
179 | exit;
180 | }
181 |
182 | $this->setClassName($fqClass);
183 |
184 | return $this;
185 | }
186 |
187 | /**
188 | * @param $name
189 | *
190 | * @return $this
191 | */
192 | public function setClassName($name)
193 | {
194 | $this->class = $name;
195 |
196 | return $this;
197 | }
198 |
199 | /**
200 | * @return array|string
201 | */
202 | public function getClassName()
203 | {
204 | return $this->class;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/Command/modelfilter.stub:
--------------------------------------------------------------------------------
1 | where('your_field', 'like', '%'.$value.'%');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Facade/EloquentFilter.php:
--------------------------------------------------------------------------------
1 | builder;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/DbBuilder/DbBuilderWrapperInterface.php:
--------------------------------------------------------------------------------
1 | builder;
23 | }
24 |
25 | /**
26 | * @return mixed
27 | */
28 | public function getModel(): mixed
29 | {
30 | return $this->getBuilder()->getModel();
31 | }
32 |
33 | /**
34 | * @return mixed
35 | */
36 | public function getAliasListFilter(): mixed
37 | {
38 | return $this->getModel()->getAliasListFilter();
39 | }
40 |
41 | /**
42 | * @param $request
43 | * @return mixed
44 | */
45 | public function serializeRequestFilter($request): mixed
46 | {
47 | return $this->getBuilder()->getModel()->serializeRequestFilter($request);
48 |
49 | }
50 |
51 | /**
52 | * @param $response
53 | * @return mixed
54 | */
55 | public function getResponseFilter($response): mixed
56 | {
57 | return $this->getBuilder()->getModel()->getResponseFilter($response);
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/EloquentBuilder/QueryBuilderWrapperInterface.php:
--------------------------------------------------------------------------------
1 | setInjectedDetections($injectedDetections);
56 | }
57 |
58 | $this->setDefaultDetect($defaultDetections);
59 |
60 |
61 | if ($mainBuilderConditions->getName() == DBBuilderQueryByCondition::NAME) {
62 |
63 | $factories = $this->getDbDetectorFactory($this->getDefaultDetect(), $this->getInjectedDetections());
64 |
65 | $this->setDetectDbFactory($factories);
66 |
67 | } else {
68 |
69 | $factories = $this->getDetectorFactory($this->getDefaultDetect(), $this->getInjectedDetections());
70 |
71 | $this->setDetectFactory($factories);
72 |
73 | }
74 |
75 | $this->mainBuilderConditions = $mainBuilderConditions;
76 | }
77 |
78 | /**
79 | * @return \eloquentFilter\QueryFilter\Detection\Contract\MainBuilderConditionsContract
80 | */
81 | public function getMainBuilderConditions(): MainBuilderConditionsContract
82 | {
83 | return $this->mainBuilderConditions;
84 | }
85 |
86 | /**
87 | * @param mixed $default_detect
88 | */
89 | public function setDefaultDetect($default_detect): void
90 | {
91 | $this->default_detect = $default_detect;
92 | }
93 |
94 | /**
95 | * @return array
96 | */
97 | public function getDefaultDetect(): array
98 | {
99 | return $this->default_detect;
100 | }
101 |
102 | /**
103 | * @param array $detections
104 | */
105 | public function setDetections(array $detections): void
106 | {
107 | $this->detections = $detections;
108 | }
109 |
110 | /**
111 | * @param array|null $detections
112 | * @throws \ReflectionException
113 | */
114 | public function unsetDetection(?array $detections): void
115 | {
116 | if (is_array($detections)) {
117 | $detections = array_map(function ($item) {
118 | $class = new \ReflectionClass(WhereCondition::class);
119 | return $class->getNamespaceName() . '\\' . $item;
120 | }, $detections);
121 |
122 | $array = array_diff($this->getDefaultDetect(), $detections);
123 |
124 | $this->setDefaultDetect($array);
125 | }
126 | }
127 |
128 | /**
129 | * @param DetectionEloquentFactory $detect_factory
130 | */
131 | public function setDetectFactory(DetectorFactoryContract $detect_factory): void
132 | {
133 | $this->detect_factory = $detect_factory;
134 | }
135 |
136 | /**
137 | * @return DetectionEloquentFactory
138 | */
139 | public function getDetectFactory(): DetectorFactoryContract
140 | {
141 | return $this->detect_factory;
142 | }
143 |
144 | /**
145 | * @param \eloquentFilter\QueryFilter\Detection\Contract\DetectorDbFactoryContract $detect_factory
146 | */
147 | public function setDetectDbFactory(DetectorDBFactoryContract $detect_factory): void
148 | {
149 | $this->detect_db_factory = $detect_factory;
150 | }
151 |
152 | /**
153 | * @return \eloquentFilter\QueryFilter\Detection\Contract\DetectorDbFactoryContract
154 | */
155 | public function getDetectDbFactory(): DetectorDBFactoryContract
156 | {
157 | return $this->detect_db_factory;
158 | }
159 |
160 | /**
161 | * @return array
162 | */
163 | public function getDetections(): array
164 | {
165 | return $this->detections;
166 | }
167 |
168 | /**
169 | * @param mixed $detectInjected
170 | */
171 | public function setInjectedDetections($detectInjected): void
172 | {
173 | if (!config('eloquentFilter.enabled_custom_detection')) {
174 | return;
175 | }
176 | $this->injected_detections = $detectInjected;
177 | }
178 |
179 | /**
180 | * @return mixed
181 | */
182 | public function getInjectedDetections(): mixed
183 | {
184 | return $this->injected_detections;
185 | }
186 |
187 | /**
188 | * @param array|null $default_detect
189 | * @param array|null $detectInjected
190 | *
191 | * @return DetectionEloquentFactory
192 | */
193 | public function getDetectorFactory(array $default_detect = null, array $detectInjected = null): DetectorFactoryContract
194 | {
195 | $this->mergeTypesDetections($default_detect, $detectInjected);
196 |
197 | return app(DetectionEloquentFactory::class, ['detections' => $this->getDetections()]);
198 | }
199 |
200 | /**
201 | * @param array|null $default_detect
202 | * @param array|null $detectInjected
203 | *
204 | * @return \eloquentFilter\QueryFilter\Detection\DetectionFactory\DetectionDbFactory
205 | */
206 | public function getDbDetectorFactory(array $default_detect = null, array $detectInjected = null): DetectorDbFactoryContract
207 | {
208 | $this->mergeTypesDetections($default_detect, $detectInjected);
209 |
210 | return app(DetectionDbFactory::class, ['detections' => $this->getDetections()]);
211 | }
212 |
213 | /**
214 | * @param array|null $injected_detections
215 | * @return void
216 | */
217 | public function setDetectionsInjected(?array $injected_detections): void
218 | {
219 | if (!empty($injected_detections)) {
220 | $this->setInjectedDetections($injected_detections);
221 | $this->setDetectFactory($this->getDetectorFactory($this->getDefaultDetect(), $this->getInjectedDetections()));
222 | }
223 | }
224 |
225 | /**
226 | * @return void
227 | */
228 | public function reloadDetectionInjected(): void
229 | {
230 | $this->setDetectFactory($this->getDetectorFactory($this->getDefaultDetect(), $this->getInjectedDetections()));
231 | }
232 |
233 | /**
234 | * @param array|null $injected_detections
235 | * @return void
236 | */
237 | public function setDetectionsDbInjected(?array $injected_detections): void
238 | {
239 | if (!empty($injected_detections)) {
240 | $this->setInjectedDetections($injected_detections);
241 | $this->setDetectFactory($this->getDbDetectorFactory($this->getDefaultDetect(), $this->getInjectedDetections()));
242 | }
243 | }
244 |
245 | /**
246 | * @param array|null $default_detect
247 | * @param array|null $detectInjected
248 | * @return void
249 | */
250 | private function mergeTypesDetections(?array $default_detect, ?array $detectInjected): void
251 | {
252 | $detections = $default_detect;
253 |
254 | if (!empty($detectInjected)) {
255 | $detections = array_merge($detectInjected, $default_detect);
256 | }
257 |
258 | $this->setDetections($detections);
259 | }
260 |
261 | }
262 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/IO/Encoder.php:
--------------------------------------------------------------------------------
1 | request = $request;
42 | }
43 |
44 | /**
45 | * @param $request
46 | * @return void
47 | */
48 | public function setPureRequest($request)
49 | {
50 | $this->request = $request;
51 | $this->handleRequestEncoded($request);
52 | }
53 |
54 | /**
55 | * @param array|null $request
56 | * @param $salt
57 | */
58 | public function setRequestEncoded(?array $request, $salt): void
59 | {
60 | $this->requestEncoded = $this->encodeWithSalt(json_encode($request), $salt);
61 | }
62 |
63 | /**
64 | * @return void
65 | */
66 | public function handelSerializeRequestFilter($request)
67 | {
68 | $this->setRequest($request);
69 | }
70 |
71 | /**
72 | * @param array|null $request
73 | */
74 | public function setRequest(?array $request): void
75 | {
76 | if (!empty($request['page'])) {
77 | unset($request['page']);
78 | }
79 |
80 | $request_key_filter = config('eloquentFilter.request_filter_key');
81 |
82 | if (!empty($request_key_filter)) {
83 | $request = (!empty($request[$request_key_filter])) ? $request[$request_key_filter] : [];
84 | }
85 |
86 | $request = array_filter($request, function ($value) {
87 | return !is_null($value) && $value !== '';
88 | });
89 |
90 | foreach ($request as $key => $item) {
91 | if (is_array($item)) {
92 | if (array_key_exists('start', $item) && array_key_exists('end', $item)) {
93 | if (!isset($item['start']) && !isset($item['end'])) {
94 | unset($request[$key]);
95 | }
96 | }
97 | }
98 | }
99 |
100 | $this->request = $request;
101 | }
102 |
103 | /**
104 | * @return array|null
105 | */
106 | public function getRequest(): ?array
107 | {
108 | return $this->request;
109 | }
110 |
111 | /**
112 | * @param array|null $ignore_request
113 | * @param array|null $accept_request
114 | * @param $builder_model
115 | * @return array|null
116 | */
117 | public function setFilterRequests(array $ignore_request = null, array $accept_request = null, $builder_model): ?array
118 | {
119 | if (!empty($this->getRequest())) {
120 | if (!empty(config('eloquentFilter.ignore_request'))) {
121 | $ignore_request = array_merge(config('eloquentFilter.ignore_request'), (array)$ignore_request);
122 | }
123 | if (!empty($ignore_request)) {
124 | $this->updateRequestByIgnoreRequest($ignore_request);
125 | }
126 | if (!empty($accept_request)) {
127 | $this->setAcceptRequest($accept_request);
128 | $this->updateRequestByAcceptRequest($this->getAcceptRequest());
129 | }
130 |
131 | foreach ($this->getRequest() as $name => $value) {
132 |
133 | $value = $this->getCastedMethodValue($name, $builder_model, $value);
134 |
135 | if (is_array($value) && !empty($builder_model) && method_exists($builder_model, $name)) {
136 | if (HelperFilter::isAssoc($value)) {
137 | unset($this->request[$name]);
138 | $out = HelperFilter::convertRelationArrayRequestToStr($name, $value);
139 | $this->setRequest(array_merge($out, $this->request));
140 | }
141 | }
142 | }
143 | }
144 |
145 | return $this->getRequest();
146 | }
147 |
148 | /**
149 | * @param $alias_list_filter
150 | * @return void
151 | */
152 | public function makeAliasRequestFilter($alias_list_filter)
153 | {
154 | if (empty($this->getRequest())) {
155 | return;
156 | }
157 | $req = $this->getRequest();
158 |
159 | $req = collect($req)->mapWithKeys(function ($item, $key) use ($alias_list_filter) {
160 | $key1 = array_search($key, $alias_list_filter);
161 |
162 | if (!empty($alias_list_filter[$key1])) {
163 | $req[$key1] = $this->getRequest()[$key];
164 | } else {
165 | $req[$key] = $item;
166 | }
167 |
168 | return $req;
169 | })->toArray();
170 |
171 | if (!empty($req)) {
172 | $this->setRequest($req);
173 | }
174 | }
175 |
176 | /**
177 | * @param $ignore_request
178 | */
179 | private function updateRequestByIgnoreRequest($ignore_request)
180 | {
181 | $this->setIgnoreRequest($ignore_request);
182 | $data = Arr::except($this->getRequest(), $ignore_request);
183 | $this->setRequest($data);
184 | }
185 |
186 | /**
187 | * @param $accept_request
188 | */
189 | private function updateRequestByAcceptRequest($accept_request)
190 | {
191 | $accept_request_new = HelperFilter::array_slice_keys($this->getRequest(), $accept_request);
192 | if (!empty($accept_request_new)) {
193 | $this->setAcceptRequest(HelperFilter::array_slice_keys($this->getRequest(), $accept_request));
194 | $this->setRequest($this->getAcceptRequest());
195 | } else {
196 | $this->setRequest([]);
197 | }
198 | }
199 |
200 | /**
201 | * @param array|null $ignore_request
202 | * @param array|null $accept_request
203 | * @param array|null $serialize_request_filter
204 | * @param $alias_list_filter
205 | * @param $model
206 | * @return void
207 | */
208 | public function requestAlter(?array $ignore_request, ?array $accept_request, ?array $serialize_request_filter, $alias_list_filter, $model): void
209 | {
210 | $this->handelSerializeRequestFilter($serialize_request_filter);
211 |
212 | if ($alias_list_filter) {
213 | $this->makeAliasRequestFilter($alias_list_filter);
214 | }
215 |
216 | $this->setFilterRequests($ignore_request, $accept_request, $model);
217 | }
218 |
219 | /**
220 | * @param array $ignore_request
221 | */
222 | private function setIgnoreRequest(array $ignore_request): void
223 | {
224 | $this->ignore_request = $ignore_request;
225 | }
226 |
227 | /**
228 | * @param array $accept_request
229 | */
230 | private function setAcceptRequest(array $accept_request): void
231 | {
232 | if (!empty($accept_request)) {
233 | $this->accept_request = $accept_request;
234 | }
235 | }
236 |
237 | /**
238 | * @return mixed
239 | */
240 | public function getAcceptRequest()
241 | {
242 | return $this->accept_request;
243 | }
244 |
245 | /**
246 | * @return mixed
247 | */
248 | public function getIgnoreRequest()
249 | {
250 | return $this->ignore_request;
251 | }
252 |
253 | /**
254 | * @param int|string $name
255 | * @param $builder_model
256 | * @param mixed $value
257 | * @return mixed
258 | */
259 | private function getCastedMethodValue(int|string $name, $builder_model, mixed $value): mixed
260 | {
261 | $castMethod = config('eloquentFilter.cast_method_sign') . $name;
262 |
263 | if (!empty($builder_model) && method_exists($builder_model, $castMethod)) {
264 | $value = $builder_model->{$castMethod}($value);
265 | $this->request[$name] = $value;
266 | }
267 | return $value;
268 | }
269 |
270 | /**
271 | * @param array|null $ignore_request
272 | * @param array|null $accept_request
273 | * @return void
274 | */
275 | public function handleRequestDb(?array $ignore_request, ?array $accept_request): void
276 | {
277 |
278 | $serialize_request_filter = $this->getRequest();
279 |
280 | $this->requestAlter(
281 | ignore_request: $ignore_request,
282 | accept_request: $accept_request,
283 | serialize_request_filter: $serialize_request_filter,
284 | alias_list_filter: $alias_list_filter ?? [],
285 | model: null,
286 | );
287 | }
288 |
289 | /**
290 | * @param $builder
291 | * @param array|null $ignore_request
292 | * @param array|null $accept_request
293 | * @return void
294 | */
295 | public function handleRequest($builder, ?array $ignore_request, ?array $accept_request): void
296 | {
297 |
298 | $serialize_request_filter = $builder->getModel()->serializeRequestFilter($this->getRequest());
299 |
300 | $alias_list_filter = $builder->getModel()->getAliasListFilter();
301 |
302 | $this->requestAlter(
303 | ignore_request: $ignore_request,
304 | accept_request: $accept_request,
305 | serialize_request_filter: $serialize_request_filter,
306 | alias_list_filter: $alias_list_filter ?? [],
307 | model: $builder->getModel(),
308 | );
309 | }
310 |
311 | /**
312 | * @param $request
313 | * @return void
314 | */
315 | private function handleRequestEncoded($request): void
316 | {
317 | if (isset($request['hashed_filters'])) {
318 | $this->request = json_decode($this->decodeWithSalt($request['hashed_filters'], config('eloquentFilter.request_salt')), true);
319 | } else {
320 | $this->setRequestEncoded($request, config('eloquentFilter.request_salt'));
321 | }
322 | }
323 |
324 | }
325 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/IO/ResponseFilter.php:
--------------------------------------------------------------------------------
1 | response;
21 | }
22 |
23 | /**
24 | * @param mixed $response
25 | */
26 | public function setResponse(mixed $response): void
27 | {
28 | $this->response = $response;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/MainQueryFilterBuilder.php:
--------------------------------------------------------------------------------
1 | checkRateLimit();
46 |
47 | if (!empty($request)) {
48 | $this->requestFilter->setPureRequest($request);
49 | }
50 |
51 | if ($this->checkEnableEloquentFilter()) {
52 | return $builder;
53 | }
54 |
55 | return $this->buildQuery(
56 | builder: $builder,
57 | ignore_request: $ignore_request,
58 | accept_request: $accept_request,
59 | detections_injected: $detections_injected,
60 | black_list_detections: $black_list_detections
61 | );
62 |
63 | }
64 |
65 |
66 | /**
67 | * @return string
68 | */
69 | public function getNameBuilder(): string
70 | {
71 | $MainBuilderConditions = $this->queryFilterCore->getMainBuilderConditions();
72 |
73 | return $MainBuilderConditions->getName();
74 | }
75 |
76 | /**
77 | * @return bool
78 | */
79 | private function checkEnableEloquentFilter(): bool
80 | {
81 | return !config('eloquentFilter.enabled') || empty($this->requestFilter->getRequest());
82 | }
83 |
84 | /**
85 | * @param $builder
86 | * @param array|null $ignore_request
87 | * @param array|null $accept_request
88 | * @param array|null $detections_injected
89 | * @param array|null $black_list_detections
90 | * @return mixed|null
91 | * @throws \ReflectionException
92 | */
93 | private function buildQuery($builder, ?array $ignore_request, ?array $accept_request, ?array $detections_injected, ?array $black_list_detections): mixed
94 | {
95 | if ($this->isDbBuilder()) {
96 |
97 | return $this->buildDbQuery($builder, $ignore_request, $accept_request, $detections_injected, $black_list_detections);
98 | }
99 |
100 | return $this->buildEloquentQuery($builder, $ignore_request, $accept_request, $detections_injected, $black_list_detections);
101 | }
102 |
103 | /**
104 | * @param $builder
105 | * @param array|null $ignore_request
106 | * @param array|null $accept_request
107 | * @param array|null $detections_injected
108 | * @param array|null $black_list_detections
109 | * @return null
110 | * @throws \ReflectionException
111 | */
112 | private function buildDbQuery($builder, ?array $ignore_request, ?array $accept_request, ?array $detections_injected, ?array $black_list_detections): mixed
113 | {
114 |
115 | $this->requestFilter->handleRequestDb(
116 | ignore_request: $ignore_request,
117 | accept_request: $accept_request
118 | );
119 |
120 | $DBQueryFilterBuilder = new DBQueryFilterBuilder($this->queryFilterCore, $this->requestFilter, $this->responseFilter);
121 |
122 | return $DBQueryFilterBuilder->apply(
123 | builder: $builder,
124 | detections_injected: $detections_injected,
125 | black_list_detections: $black_list_detections
126 | );
127 | }
128 |
129 | /**
130 | * @param $builder
131 | * @param array|null $ignore_request
132 | * @param array|null $accept_request
133 | * @param array|null $detections_injected
134 | * @param array|null $black_list_detections
135 | * @return mixed
136 | * @throws \ReflectionException
137 | */
138 | private function buildEloquentQuery($builder, ?array $ignore_request, ?array $accept_request, ?array $detections_injected, ?array $black_list_detections): mixed
139 | {
140 | $this->requestFilter->handleRequest(
141 | builder: $builder,
142 | ignore_request: $ignore_request,
143 | accept_request: $accept_request
144 | );
145 |
146 | $eloquentQueryFilterBuilder = new EloquentQueryFilterBuilder($this->queryFilterCore, $this->requestFilter, $this->responseFilter);
147 |
148 | return $eloquentQueryFilterBuilder->apply(
149 | builder: $builder,
150 | detections_injected: $detections_injected,
151 | black_list_detections: $black_list_detections
152 | );
153 | }
154 |
155 | /**
156 | * @return bool
157 | */
158 | private function isDbBuilder(): bool
159 | {
160 | return $this->getNameBuilder() == DBBuilderQueryByCondition::NAME;
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/QueryBuilder/DBQueryFilterBuilder.php:
--------------------------------------------------------------------------------
1 | queryBuilderWrapper = $queryBuilderWrapper;
23 | }
24 |
25 | /**
26 | * @return \eloquentFilter\QueryFilter\Core\DbBuilder\DbBuilderWrapperInterface
27 | */
28 | public function getQueryBuilderWrapper(): DbBuilderWrapperInterface
29 | {
30 | return $this->queryBuilderWrapper;
31 | }
32 |
33 | /**
34 | * @param $builder
35 | * @param array|null $detections_injected
36 | * @param array|null $black_list_detections
37 | *
38 | * @return mixed
39 | * @throws \ReflectionException
40 | */
41 | public function apply($builder, array $detections_injected = null, array $black_list_detections = null): mixed
42 | {
43 | $this->setMacroIsUsedPackage();
44 |
45 | $this->setQueryBuilderWrapper(QueryBuilderWrapperFactory::createDbQueryBuilder($builder));
46 |
47 | $this->resolveDetections($detections_injected, $black_list_detections);
48 |
49 | return $this->responseFilter->getResponse();
50 | }
51 |
52 | /**
53 | * @return void
54 | * @throws \ReflectionException
55 | */
56 | private function resolveDetections($detections_injected, $black_list_detections)
57 | {
58 | $this->queryFilterCore->unsetDetection($black_list_detections);
59 | $this->queryFilterCore->setDetectionsDbInjected($detections_injected);
60 |
61 | /** @see \eloquentFilter\QueryFilter\Core\ResolverDetection\ResolverDetectionDb */
62 | app()->bind('ResolverDetectionsDb', function () {
63 | return new ResolverDetectionDb(
64 | builder: $this->getQueryBuilderWrapper()->getBuilder(),
65 | request: $this->requestFilter->getRequest(),
66 | detector_db_factory: $this->queryFilterCore->getDetectDbFactory(),
67 | main_builder_conditions_contract: $this->queryFilterCore->getMainBuilderConditions()
68 | );
69 | });
70 |
71 | /** @see ResolverDetectionDb::getResolverOut() */
72 | $responseResolver = app('ResolverDetectionsDb')->getResolverOut();
73 |
74 | $this->responseFilter->setResponse($responseResolver);
75 | }
76 |
77 |
78 | /**
79 | * @return void
80 | */
81 | private function setMacroIsUsedPackage(): void
82 | {
83 | \Illuminate\Database\Query\Builder::macro('isUsedEloquentFilter', function () {
84 | return config('eloquentFilter.enabled');
85 | });
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/QueryBuilder/EloquentQueryFilterBuilder.php:
--------------------------------------------------------------------------------
1 | queryBuilderWrapper = $queryBuilderWrapper;
23 | }
24 |
25 | /**
26 | * @return \eloquentFilter\QueryFilter\Core\EloquentBuilder\EloquentModelBuilderWrapper
27 | */
28 | public function getQueryBuilderWrapper(): EloquentModelBuilderWrapper
29 | {
30 | return $this->queryBuilderWrapper;
31 | }
32 |
33 | /**
34 | * @param $builder
35 | * @param array|null $detections_injected
36 | * @param array|null $black_list_detections
37 | *
38 | * @return mixed
39 | * @throws \ReflectionException
40 | */
41 | public function apply($builder, array $detections_injected = null, array $black_list_detections = null): mixed
42 | {
43 |
44 | $this->buildExclusiveMacros($detections_injected);
45 |
46 | $this->setQueryBuilderWrapper(QueryBuilderWrapperFactory::createEloquentQueryBuilder($builder));
47 |
48 | $this->resolveDetections($detections_injected, $black_list_detections);
49 |
50 | return $this->getQueryBuilderWrapper()->getResponseFilter($this->responseFilter->getResponse());
51 | }
52 |
53 | /**
54 | * @return void
55 | * @throws \ReflectionException
56 | */
57 | private function resolveDetections($detections_injected, $black_list_detections)
58 | {
59 | $this->queryFilterCore->unsetDetection($black_list_detections);
60 | $this->queryFilterCore->reloadDetectionInjected();
61 |
62 | $this->queryFilterCore->setDetectionsInjected($detections_injected);
63 |
64 | /** @see ResolverDetectionEloquent */
65 | app()->bind('ResolverDetectionEloquent', function () {
66 | return new ResolverDetectionEloquent(
67 | builder: $this->getQueryBuilderWrapper()->getBuilder(),
68 | request: $this->requestFilter->getRequest(),
69 | detector_factory: $this->queryFilterCore->getDetectFactory(),
70 | main_builder_conditions_contract: $this->queryFilterCore->getMainBuilderConditions()
71 | );
72 | });
73 |
74 | /** @see ResolverDetectionEloquent::getResolverOut() */
75 | $responseResolver = app('ResolverDetectionEloquent')->getResolverOut();
76 |
77 |
78 | $this->responseFilter->setResponse($responseResolver);
79 | }
80 |
81 | /**
82 | * @return string
83 | */
84 | public function getNameDriver()
85 | {
86 | $MainBuilderConditions = $this->queryFilterCore->getMainBuilderConditions();
87 |
88 | return $MainBuilderConditions->getName();
89 | }
90 |
91 | /**
92 | * @param array|null $detections_injected
93 | * @return void
94 | */
95 | private function buildExclusiveMacros(?array $detections_injected): void
96 | {
97 | $this->setMacroIsUsedPackage();
98 |
99 | $this->setMacroDetectionInjectedList($detections_injected);
100 |
101 | }
102 |
103 | /**
104 | * @return void
105 | */
106 | private function setMacroIsUsedPackage(): void
107 | {
108 | \Illuminate\Database\Eloquent\Builder::macro('isUsedEloquentFilter', function () {
109 | return config('eloquentFilter.enabled');
110 | });
111 | }
112 |
113 | /**
114 | * @param array|null $detections_injected
115 | * @return void
116 | */
117 | private function setMacroDetectionInjectedList(?array $detections_injected): void
118 | {
119 | \Illuminate\Database\Eloquent\Builder::macro('getDetectionsInjected', function () use ($detections_injected) {
120 | return $detections_injected;
121 | });
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/FilterBuilder/QueryBuilder/QueryFilterBuilder.php:
--------------------------------------------------------------------------------
1 | requestFilter->getRequest()[$index];
19 | }
20 |
21 | return $this->requestFilter->getRequest();
22 | }
23 |
24 | /**
25 | * @return mixed
26 | */
27 | public function getAcceptedRequest()
28 | {
29 | return $this->requestFilter->getAcceptRequest();
30 | }
31 |
32 | /**
33 | * @return mixed
34 | */
35 | public function getIgnoredRequest()
36 | {
37 | return $this->requestFilter->getIgnoreRequest();
38 | }
39 |
40 |
41 | /**
42 | * @return mixed
43 | */
44 | public function getRequestEncoded()
45 | {
46 | return $this->requestFilter->requestEncoded;
47 | }
48 |
49 | /**
50 | * @return mixed
51 | */
52 | public function setRequestEncoded($request, $salt)
53 | {
54 | return $this->requestFilter->setRequestEncoded($request, $salt);
55 | }
56 |
57 | /**
58 | * @return mixed
59 | */
60 | public function getInjectedDetections()
61 | {
62 | return $this->queryFilterCore->getInjectedDetections();
63 | }
64 |
65 | /**
66 | * @return mixed
67 | */
68 | public function getResponse()
69 | {
70 | return $this->responseFilter->getResponse();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/HelperFilter.php:
--------------------------------------------------------------------------------
1 | $item) {
27 | $index = $key;
28 | for ($i = 0; $i <= 9; $i++) {
29 | $index = rtrim($index, '.'.$i);
30 | }
31 | $new[$index][] = $out[$key];
32 | }
33 | $out = $new;
34 | } else {
35 | $key_search_start = $field.'.'.key($args).'.start';
36 | $key_search_end = $field.'.'.key($args).'.end';
37 |
38 | if (Arr::exists($out, $key_search_start) && Arr::exists($out, $key_search_end)) {
39 | foreach ($args as $key => $item) {
40 | $new[$field.'.'.$key] = $args[$key];
41 | }
42 | $out = $new;
43 | }
44 | }
45 | } else {
46 | $out = Arr::dot($args, $field.'.');
47 | }
48 |
49 | return $out;
50 | }
51 |
52 | /**
53 | * @param array $arr
54 | *
55 | * @return bool
56 | */
57 | public static function isAssoc(array $arr): bool
58 | {
59 | if ([] === $arr) {
60 | return false;
61 | }
62 |
63 | return array_keys($arr) !== range(0, count($arr) - 1);
64 | }
65 |
66 | /**
67 | * @param $request
68 | * @param null $keys
69 | *
70 | * @return array
71 | */
72 | public static function array_slice_keys($request, $keys = null): array
73 | {
74 | $request = (array) $request;
75 |
76 | return array_intersect_key($request, array_fill_keys($keys, '1'));
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/RateLimiting.php:
--------------------------------------------------------------------------------
1 | resolveRateLimitKey();
28 | $maxAttempts = config('eloquentFilter.rate_limit.max_attempts', 60);
29 | $decayMinutes = config('eloquentFilter.rate_limit.decay_minutes', 1);
30 |
31 | if ($limiter->tooManyAttempts($key, $maxAttempts)) {
32 | $seconds = $limiter->availableIn($key);
33 |
34 | $headers = [
35 | 'X-RateLimit-Limit' => $maxAttempts,
36 | 'X-RateLimit-Remaining' => 0,
37 | 'X-RateLimit-Reset' => $seconds,
38 | 'Retry-After' => $seconds,
39 | ];
40 |
41 | throw new ThrottleRequestsException(
42 | config('eloquentFilter.rate_limit.error_message', 'Too many filter requests. Please try again later.'),
43 | null,
44 | $headers
45 | );
46 | }
47 |
48 | $limiter->hit($key, $decayMinutes * 60);
49 |
50 | // Store rate limit info in request for later use
51 | if (config('eloquentFilter.rate_limit.include_headers', true)) {
52 | $remaining = $limiter->remaining($key, $maxAttempts);
53 | $request = request();
54 |
55 | // Ensure attributes is initialized
56 | if (!isset($request->attributes)) {
57 | $request->attributes = new ParameterBag();
58 | }
59 |
60 |
61 | $request->attributes->set('rate_limit', [
62 | 'limit' => $maxAttempts,
63 | 'remaining' => $remaining
64 | ]);
65 | }
66 | }
67 |
68 | /**
69 | * Get the rate limiting key
70 | */
71 | protected function resolveRateLimitKey(): string
72 | {
73 | $request = request();
74 |
75 | return sha1(sprintf(
76 | '%s|%s|%s',
77 | $request->user() ? $request->user()->getAuthIdentifier() : $request->ip(),
78 | $request->path(),
79 | get_class($this)
80 | ));
81 | }
82 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Core/ResolverDetection/ResolverDetectionDb.php:
--------------------------------------------------------------------------------
1 | builder = $builder;
24 | $this->request = $request;
25 | $this->detector_db_factory = $detector_db_factory;
26 | $this->main_builder_conditions = $main_builder_conditions_contract;
27 | }
28 |
29 | /**
30 | * @return array
31 | */
32 | public function getFiltersDetection(): array
33 | {
34 | $filter_detections = collect($this->request)->map(function ($values, $filter){
35 | return $this->resolve($filter, $values);
36 | })->reverse()->filter(function ($item) {
37 | return $item instanceof BaseClause;
38 | })->toArray();
39 |
40 | $out = Arr::isAssoc($filter_detections) ? $filter_detections : [];
41 |
42 | return $out;
43 | }
44 |
45 | /**
46 | * @param $filterName
47 | * @param $values
48 | *
49 | * @return Application|mixed
50 | * @throws ReflectionException
51 | *
52 | */
53 | protected function resolve($filterName, $values)
54 | {
55 | $detectedConditions = $this->detector_db_factory->buildDetections($filterName, $values);
56 |
57 | $builderDriver = $this->main_builder_conditions->build($detectedConditions);
58 |
59 | return app($builderDriver, ['filter' => $filterName, 'values' => $values]);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/ResolverDetection/ResolverDetectionEloquent.php:
--------------------------------------------------------------------------------
1 | builder = $builder;
24 | $this->request = $request;
25 | $this->detector_factory = $detector_factory;
26 |
27 | $this->main_builder_conditions = $main_builder_conditions_contract;
28 | }
29 | /**
30 | * @return array
31 | */
32 | public function getFiltersDetection(): array
33 | {
34 | $model = $this->builder->getModel();
35 |
36 | $filter_detections = collect($this->request)->map(function ($values, $filter) use ($model) {
37 | return $this->resolve($filter, $values, $model);
38 | })->reverse()->filter(function ($item) {
39 | return $item instanceof BaseClause;
40 | })->toArray();
41 |
42 | $out = Arr::isAssoc($filter_detections) ? $filter_detections : [];
43 |
44 | return $out;
45 | }
46 |
47 | /**
48 | * @param $filterName
49 | * @param $values
50 | * @param $model
51 | *
52 | * @return Application|mixed
53 | * @throws ReflectionException
54 | *
55 | */
56 | protected function resolve($filterName, $values, $model)
57 | {
58 | $detectedConditions = $this->detector_factory->buildDetections($filterName, $values, $model);
59 |
60 | $builderDriver = $this->main_builder_conditions->build($detectedConditions);
61 |
62 | return app($builderDriver, ['filter' => $filterName, 'values' => $values]);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/QueryFilter/Core/ResolverDetection/ResolverDetections.php:
--------------------------------------------------------------------------------
1 | getFiltersDetection();
42 |
43 | $out = app(Pipeline::class)
44 | ->send($this->builder)
45 | ->through($filter_detections)
46 | ->thenReturn();
47 |
48 | return $out;
49 | }
50 |
51 | /**
52 | * @return array
53 | */
54 | abstract public function getFiltersDetection(): array;
55 | }
56 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/ConditionsDetect/DB/DBBuilderQueryByCondition.php:
--------------------------------------------------------------------------------
1 | Where::class,
43 | 'WhereBetween' => WhereBetween::class,
44 | 'WhereByOpt' => WhereByOpt::class,
45 | 'WhereDate' => WhereDate::class,
46 | 'WhereDoesntHave' => WhereDoesntHave::class,
47 | 'WhereHas' => WhereHas::class,
48 | 'WhereIn' => WhereIn::class,
49 | 'WhereLike' => WhereLike::class,
50 | 'WhereOr' => WhereOr::class,
51 | 'WhereNull' => WhereNull::class,
52 | 'WhereNotNull' => WhereNotNull::class,
53 | 'Special' => Special::class,
54 | // 'WhereCustom' => WhereCustom::class,
55 | default => null,
56 | };
57 |
58 | if (empty($builder) && !empty($condition)) {
59 | return $condition;
60 | }
61 |
62 | return $builder;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/ConditionsDetect/Eloquent/MainBuilderQueryByCondition.php:
--------------------------------------------------------------------------------
1 | Where::class,
43 | 'WhereBetween' => WhereBetween::class,
44 | 'WhereByOpt' => WhereByOpt::class,
45 | 'WhereDate' => WhereDate::class,
46 | 'WhereHas' => WhereHas::class,
47 | 'WhereIn' => WhereIn::class,
48 | 'WhereLike' => WhereLike::class,
49 | 'WhereOr' => WhereOr::class,
50 | 'WhereDoesntHave' => WhereDoesntHave::class,
51 | 'WhereNull' => WhereNull::class,
52 | 'WhereNotNull' => WhereNotNull::class,
53 | 'Special' => Special::class,
54 | 'WhereCustom' => WhereCustom::class,
55 | default => null,
56 | };
57 |
58 | if (empty($builder) && !empty($condition)) {
59 | return $condition;
60 | }
61 |
62 | return $builder;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/ConditionsDetect/TypeQueryConditions/SpecialCondition.php:
--------------------------------------------------------------------------------
1 | $this->detections]);
32 |
33 | $method = $detect->detect($field, $params);
34 |
35 | return $method;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/DetectionFactory/DetectionEloquentFactory.php:
--------------------------------------------------------------------------------
1 | $this->detections]);
34 |
35 | $class_name = null;
36 | if (!empty($model)) {
37 | $class_name = class_basename($model);
38 | }
39 |
40 | $method = $detect->detect($field, $params, $model->getWhiteListFilter(), $model->checkModelHasOverrideMethod($field), $class_name);
41 |
42 | return $method;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/Detector/DetectorConditionCondition.php:
--------------------------------------------------------------------------------
1 | map(function ($detector_obj) {
36 | if (!empty($detector_obj)) {
37 | $reflect = new \ReflectionClass($detector_obj);
38 | if ($reflect->implementsInterface(DefaultConditionsContract::class)) {
39 | return $detector_obj;
40 | }
41 | }
42 | })->toArray();
43 |
44 | $this->setDetector($detector_collect);
45 | }
46 |
47 | /**
48 | * @param \Illuminate\Support\Collection $detector
49 | */
50 | public function setDetector(\Illuminate\Support\Collection $detector): void
51 | {
52 | $this->detector = $detector;
53 | }
54 |
55 | /**
56 | * @return \Illuminate\Support\Collection
57 | */
58 | public function getDetector(): \Illuminate\Support\Collection
59 | {
60 | return $this->detector;
61 | }
62 |
63 | /**
64 | * @param string $field
65 | * @param $params
66 | * @param null $getWhiteListFilter
67 | * @param bool $hasOverrideMethod
68 | * @param $className
69 | * @return string|null
70 | */
71 | public function detect(string $field, $params, $getWhiteListFilter = null, bool $hasOverrideMethod = false, $className = null): ?string
72 | {
73 | $out = $this->getDetector()->map(function ($item) use ($field, $params, $getWhiteListFilter, $hasOverrideMethod, $className) {
74 | if ($this->handelListFields($field, $getWhiteListFilter, $hasOverrideMethod, $className)) {
75 | if ($hasOverrideMethod) {
76 | $query = WhereCustom::class;
77 | } else {
78 | /** @see DefaultConditionsContract::detect() */
79 | $query = $item::detect($field, $params);
80 | }
81 |
82 | if (!empty($query)) {
83 | return $query;
84 | }
85 | }
86 | })->filter();
87 |
88 | return $out->first();
89 | }
90 |
91 | /**
92 | * @param string $field
93 | * @param array|null $list_white_filter_model
94 | * @param bool $has_override_method
95 | * @param $model_class
96 | *
97 | * @return bool
98 | * @throws Exception
99 | *
100 | */
101 | private function handelListFields(string $field, ?array $list_white_filter_model, bool $has_override_method, $model_class): bool
102 | {
103 | if ($this->checkSetWhiteListFields($field, $list_white_filter_model) || $this->checkReservedParam($field) || $has_override_method) {
104 | return true;
105 | }
106 |
107 | throw new EloquentFilterException(sprintf($this->errorExceptionWhileList, $field, $model_class, $field, $field), 1);
108 | }
109 |
110 | /**
111 | * @param string $field
112 | * @param array|null $query
113 | *
114 | * @return bool
115 | */
116 | private function checkSetWhiteListFields(string $field, ?array $query): bool
117 | {
118 | if (in_array($field, $query) || (!empty($query[0]) && $query[0] == '*')) {
119 | return true;
120 | }
121 |
122 | return false;
123 | }
124 |
125 | /**
126 | * @param string $field
127 | * @return bool
128 | */
129 | private function checkReservedParam(string $field): bool
130 | {
131 | return ($field == SpecialCondition::PARAM_NAME || $field == WhereOrCondition::PARAM_NAME || $field == WhereDoesntHaveCondition::PARAM_NAME);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/QueryFilter/Detection/Detector/DetectorConditionDbCondition.php:
--------------------------------------------------------------------------------
1 | map(function ($detector_obj) {
29 | if (!empty($detector_obj)) {
30 | $reflect = new \ReflectionClass($detector_obj);
31 | if ($reflect->implementsInterface(DefaultConditionsContract::class)) {
32 | return $detector_obj;
33 | }
34 | }
35 | })->toArray();
36 |
37 | $this->setDetector($detector_collect);
38 | }
39 |
40 | /**
41 | * @param \Illuminate\Support\Collection $detector
42 | */
43 | public function setDetector(\Illuminate\Support\Collection $detector): void
44 | {
45 | $this->detector = $detector;
46 | }
47 |
48 | /**
49 | * @return \Illuminate\Support\Collection
50 | */
51 | public function getDetector(): \Illuminate\Support\Collection
52 | {
53 | return $this->detector;
54 | }
55 |
56 | /**
57 | * @param string $field
58 | * @param $params
59 | * @param null $getWhiteListFilter
60 | * @param bool $hasOverrideMethod
61 | * @param $className
62 | * @return string|null
63 | */
64 | public function detect(string $field, $params, $getWhiteListFilter = null, bool $hasOverrideMethod = false, $className = null): ?string
65 | {
66 | $out = $this->getDetector()->map(function ($item) use ($field, $params) {
67 | /** @see DefaultConditionsContract::detect() */
68 | $query = $item::detect($field, $params);
69 |
70 | if (!empty($query)) {
71 | return $query;
72 | }
73 | })->filter();
74 |
75 | return $out->first();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/QueryFilter/Exceptions/EloquentFilterException.php:
--------------------------------------------------------------------------------
1 | $this->getDefaultDetectorsEloquent(),
38 | 'injectedDetections' => null,
39 | 'mainBuilderConditions' => $mainBuilderConditions
40 | ]
41 | );
42 | }
43 |
44 | /**
45 | * @return \eloquentFilter\QueryFilter\Core\FilterBuilder\Core\QueryFilterCore
46 | */
47 | public function createQueryFilterCoreDBQueryBuilder(): QueryFilterCore
48 | {
49 | $mainBuilderConditions = new DBBuilderQueryByCondition();
50 | return app(QueryFilterCoreBuilder::class,
51 | [
52 | 'defaultDetections' => $this->getDefaultDetectorsEloquent(),
53 | 'injectedDetections' => null,
54 | 'mainBuilderConditions' => $mainBuilderConditions
55 | ]
56 | );
57 | }
58 |
59 | /**
60 | * @return array
61 | * @note DON'T CHANGE ORDER THESE BASED ON FLIMSY REASON.
62 | */
63 | private function getDefaultDetectorsEloquent(): array
64 | {
65 | return [
66 | SpecialCondition::class,
67 | WhereBetweenCondition::class,
68 | WhereByOptCondition::class,
69 | WhereLikeCondition::class,
70 | WhereInCondition::class,
71 | WhereOrCondition::class,
72 | WhereHasCondition::class,
73 | WhereDoesntHaveCondition::class,
74 | WhereDateCondition::class,
75 | WhereNullCondition::class,
76 | WhereMonthCondition::class,
77 | WhereYearCondition::class,
78 | WhereDayCondition::class,
79 | WhereCondition::class,
80 | ];
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/QueryFilter/ModelFilters/Filterable.php:
--------------------------------------------------------------------------------
1 | ignore_request,
56 | accept_request: $this->accept_request,
57 | detections_injected: $this->getObjectCustomDetect(),
58 | black_list_detections: $this->black_list_detections
59 | );
60 | }
61 |
62 | /**
63 | * @param $builder
64 | * @param array|null $request
65 | */
66 | public function scopeIgnoreRequest($builder, ?array $request = null)
67 | {
68 | $this->ignoreRequestFilter($request);
69 | }
70 |
71 | public function ignoreRequestFilter(?array $request = null)
72 | {
73 | $this->ignore_request = $request;
74 | return $this;
75 | }
76 |
77 | /**
78 | * @param $builder
79 | * @param array|null $request
80 | */
81 | public function scopeAcceptRequest($builder, ?array $request = null)
82 | {
83 | $this->acceptRequestFilter($request);
84 | }
85 |
86 | /**
87 | * @param array|null $request
88 | * @return \eloquentFilter\QueryFilter\ModelFilters\Filterable
89 | */
90 | public function acceptRequestFilter(?array $request = null)
91 | {
92 | $this->accept_request = $request;
93 | return $this;
94 |
95 | }
96 |
97 | /**
98 | * @param $builder
99 | * @param array|null $black_list_detections
100 | */
101 | public function scopeSetBlackListDetection($builder, ?array $black_list_detections = null)
102 | {
103 | $this->black_list_detections = $black_list_detections;
104 | }
105 |
106 | /**
107 | * @param $builder
108 | * @param array|null $object_custom_detect
109 | */
110 | public function scopeSetCustomDetection($builder, ?array $object_custom_detect = null)
111 | {
112 | $this->setObjectCustomDetect($object_custom_detect);
113 | }
114 |
115 | /**
116 | * @return mixed
117 | */
118 | private function getObjectCustomDetect()
119 | {
120 | if (method_exists($this, 'EloquentFilterCustomDetection') && empty($this->object_custom_detect) && $this->getLoadInjectedDetections()) {
121 | $this->setObjectCustomDetect($this->EloquentFilterCustomDetection());
122 | }
123 |
124 | return $this->object_custom_detect;
125 | }
126 |
127 | /**
128 | * @param mixed $object_custom_detect
129 | */
130 | private function setObjectCustomDetect($object_custom_detect): void
131 | {
132 | $this->object_custom_detect = $object_custom_detect;
133 | }
134 |
135 | /**
136 | * @return mixed
137 | */
138 | public static function getWhiteListFilter(): array
139 | {
140 | return (self::$whiteListFilter ?? []);
141 | }
142 |
143 | /**
144 | * @return array|null
145 | */
146 | public function getAliasListFilter(): ?array
147 | {
148 | return ($this->aliasListFilter ?? null);
149 | }
150 |
151 | /**
152 | * @param $value
153 | *
154 | * @return mixed
155 | */
156 | public static function addWhiteListFilter($value)
157 | {
158 | if (isset(self::$whiteListFilter)) {
159 | self::$whiteListFilter[] = $value;
160 | }
161 | }
162 |
163 | /**
164 | * @param array $array
165 | */
166 | public static function setWhiteListFilter(array $array)
167 | {
168 | if (isset(self::$whiteListFilter)) {
169 | self::$whiteListFilter = $array;
170 | }
171 | }
172 |
173 | /**
174 | * @param string $method
175 | *
176 | * @return bool
177 | */
178 | public function checkModelHasOverrideMethod(string $method): bool
179 | {
180 | $method = WhereCustom::getMethod($method);
181 |
182 | return method_exists($this, $method);
183 | }
184 |
185 | /**
186 | * @param $builder
187 | * @param $load_default_detection
188 | */
189 | public function scopeSetLoadInjectedDetection($builder, $load_default_detection)
190 | {
191 | $this->load_injected_detections = $load_default_detection;
192 | }
193 |
194 | /**
195 | * @return bool
196 | */
197 | public function getLoadInjectedDetections(): bool
198 | {
199 | return $this->load_injected_detections;
200 | }
201 |
202 | /**
203 | * @return null
204 | */
205 | public function getResponseFilter($response)
206 | {
207 | return $response;
208 | }
209 |
210 | /**
211 | * @return null
212 | */
213 | public function serializeRequestFilter($request)
214 | {
215 | return $request;
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/BaseClause.php:
--------------------------------------------------------------------------------
1 | apply($query);
35 |
36 | $this->recordLog($out, $startTime);
37 |
38 | return $out;
39 | }
40 |
41 | /**
42 | * @param $query
43 | *
44 | * @return Builder
45 | */
46 | abstract protected function apply($query);
47 |
48 | /**
49 | * @param $query
50 | * @param $startTime
51 | * @return void
52 | */
53 | public function recordLog($query, $startTime): void
54 | {
55 | if (config('eloquentFilter.log.has_keeping_query')) {
56 |
57 | $endTime = microtime(true);
58 | $executionTime = $endTime - $startTime;
59 |
60 | if (!empty(config('eloquentFilter.log.max_time_query'))) {
61 |
62 | if ($executionTime >= config('eloquentFilter.log.max_time_query')) {
63 |
64 | $this->createLog($query, $executionTime);
65 | }
66 |
67 | } else {
68 |
69 | $this->createLog($query, $executionTime);
70 | }
71 |
72 | }
73 |
74 | }
75 |
76 | /**
77 | * @param $query
78 | * @param $executionTime
79 | * @return void
80 | */
81 | private function createLog($query, $executionTime): void
82 | {
83 | Log::info('eloquentFilter query', [
84 | 'query' => $query->toSql(),
85 | 'binding' => $query->getBindings(),
86 | 'time' => $executionTime,
87 | 'type' => config('eloquentFilter.log.type'),
88 | ]);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/Special.php:
--------------------------------------------------------------------------------
1 | [
21 | 'limit',
22 | 'orderBy',
23 | ],
24 | ];
25 |
26 | /**
27 | * @param $query
28 | *
29 | * @return Builder
30 | * @throws \Exception
31 | *
32 | */
33 | public function apply($query)
34 | {
35 | foreach ($this->values as $key => $param_value) {
36 | if (!in_array($key, self::$reserve_param[SpecialCondition::PARAM_NAME])) {
37 | throw new EloquentFilterException("$key is not in f_params array.", 2);
38 | }
39 | if (is_array($param_value)) {
40 | $this->values['orderBy']['field'] = explode(',', $this->values['orderBy']['field']);
41 | foreach ($this->values['orderBy']['field'] as $order_by) {
42 | $query->orderBy($order_by, $this->values['orderBy']['type']);
43 | }
44 | } else {
45 | if (config('eloquentFilter.max_limit') > 0) {
46 | $param_value = min(config('eloquentFilter.max_limit'), $param_value);
47 | }
48 | $query->limit($param_value);
49 | }
50 | }
51 |
52 | return $query;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/Where.php:
--------------------------------------------------------------------------------
1 | where($this->filter, $this->values);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereBetween.php:
--------------------------------------------------------------------------------
1 | values['start'];
21 | $end = $this->values['end'];
22 |
23 | return $query->whereBetween($this->filter, [$start, $end]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereByOpt.php:
--------------------------------------------------------------------------------
1 | values['operator'];
20 | $value = $this->values['value'];
21 |
22 | return $query->where("$this->filter", "$opt", $value);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereDate.php:
--------------------------------------------------------------------------------
1 | whereDate($this->filter, $this->values);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereDayQuery.php:
--------------------------------------------------------------------------------
1 | whereDay($this->filter, $this->values['day']);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereDoesntHave.php:
--------------------------------------------------------------------------------
1 | from;
22 | $tableJoin = Str::singular($this->values);
23 | $foreignKey = sprintf('"%s"."%s_id"', $from, $tableJoin);
24 |
25 | $key = $this->values . '.id';
26 |
27 | return $query->from($from)->whereNotExists(function ($q) use ($foreignKey, $from, $key) {
28 | $q->select(DB::raw(1))->from($this->values)->where($key, '=', new Raw($foreignKey));
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereHas.php:
--------------------------------------------------------------------------------
1 | filter);
25 | $field_row = end($field_row);
26 |
27 | $relation = str_replace('.'.$field_row, '', $this->filter);
28 | $relationTable = Str::plural($relation);
29 |
30 | $value = $this->values;
31 | $from = $query->from;
32 |
33 | return $query->whereExists(function ($q) use ($relationTable, $field_row, $value, $from) {
34 | $foreignKey = sprintf('%s.%s_id', $from, Str::singular($relationTable));
35 |
36 | $q->select(DB::raw(1))
37 | ->from($relationTable)
38 | ->whereRaw("$relationTable.id = $foreignKey");
39 |
40 | if (is_array($value)) {
41 | $q->whereIn($field_row, $value);
42 | } else {
43 | $q->where($field_row, $value);
44 | }
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereIn.php:
--------------------------------------------------------------------------------
1 | whereIn($this->filter, $this->values);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereLike.php:
--------------------------------------------------------------------------------
1 | where("$this->filter", 'like', $this->values['like']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereMonthQuery.php:
--------------------------------------------------------------------------------
1 | whereMonth($this->filter, $this->values['month']);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereNotNull.php:
--------------------------------------------------------------------------------
1 | whereNotNull($this->filter);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereNull.php:
--------------------------------------------------------------------------------
1 | whereNull($this->filter);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereOr.php:
--------------------------------------------------------------------------------
1 | values);
21 | $value = reset($this->values);
22 |
23 | return $query->orWhere($field, $value);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/DB/WhereYearQuery.php:
--------------------------------------------------------------------------------
1 | whereYear($this->filter, $this->values['year']);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/Special.php:
--------------------------------------------------------------------------------
1 | [
20 | 'limit',
21 | 'orderBy',
22 | ],
23 | ];
24 |
25 | /**
26 | * @param $query
27 | *
28 | * @return Builder
29 | * @throws \Exception
30 | *
31 | */
32 | public function apply($query)
33 | {
34 | foreach ($this->values as $key => $param_value) {
35 | if (!in_array($key, self::$reserve_param[SpecialCondition::PARAM_NAME])) {
36 | throw new EloquentFilterException("$key is not in f_params array.", 2);
37 | }
38 | if (is_array($param_value)) {
39 | $this->values['orderBy']['field'] = explode(',', $this->values['orderBy']['field']);
40 | foreach ($this->values['orderBy']['field'] as $order_by) {
41 | $query->orderBy($order_by, $this->values['orderBy']['type']);
42 | }
43 | } else {
44 | if (config('eloquentFilter.max_limit') > 0) {
45 | $param_value = min(config('eloquentFilter.max_limit'), $param_value);
46 | }
47 | $query->limit($param_value);
48 | }
49 | }
50 |
51 | return $query;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/Where.php:
--------------------------------------------------------------------------------
1 | where($this->filter, $this->values);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereBetween.php:
--------------------------------------------------------------------------------
1 | values['start'];
21 | $end = $this->values['end'];
22 |
23 | return $query->whereBetween($this->filter, [$start, $end]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereByOpt.php:
--------------------------------------------------------------------------------
1 | values['operator'];
21 | $value = $this->values['value'];
22 |
23 | return $query->where("$this->filter", "$opt", $value);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereCustom.php:
--------------------------------------------------------------------------------
1 | getMethod($this->filter);
21 | return $query->getModel()->$method($query, $this->values);
22 | }
23 |
24 | /**
25 | * @param $filter
26 | * @return string
27 | */
28 | public static function getMethod($filter): string
29 | {
30 | $custom_method_sign = config('eloquentFilter.custom_method_sign');
31 |
32 | $filter = ucfirst($filter);
33 | $method = $custom_method_sign . $filter;
34 | return $method;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereDate.php:
--------------------------------------------------------------------------------
1 | whereDate($this->filter, $this->values);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereDayQuery.php:
--------------------------------------------------------------------------------
1 | whereDay($this->filter, $this->values['day']);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereDoesntHave.php:
--------------------------------------------------------------------------------
1 | doesntHave($this->values);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereHas.php:
--------------------------------------------------------------------------------
1 | filter);
21 | $field_row = end($field_row);
22 |
23 | $conditions = str_replace('.'.$field_row, '', $this->filter);
24 |
25 | $value = $this->values;
26 |
27 | return $query->whereHas(
28 | $conditions,
29 | function ($q) use ($value, $field_row) {
30 | $condition = 'where';
31 | if (is_array($value)) {
32 | $condition = 'whereIn';
33 | }
34 | $q->$condition($field_row, $value);
35 | }
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereIn.php:
--------------------------------------------------------------------------------
1 | whereIn($this->filter, $this->values);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereLike.php:
--------------------------------------------------------------------------------
1 | where("$this->filter", 'like', $this->values['like']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereMonthQuery.php:
--------------------------------------------------------------------------------
1 | whereMonth($this->filter, $this->values['month']);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereNotNull.php:
--------------------------------------------------------------------------------
1 | whereNotNull($this->filter);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereNull.php:
--------------------------------------------------------------------------------
1 | whereNull($this->filter);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereOr.php:
--------------------------------------------------------------------------------
1 | values);
21 | $value = reset($this->values);
22 |
23 | return $query->orWhere($field, $value);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QueryFilter/Queries/Eloquent/WhereYearQuery.php:
--------------------------------------------------------------------------------
1 | whereYear($this->filter, $this->values['year']);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | configurePaths();
26 |
27 | $this->mergeConfig();
28 |
29 | $this->registerBindings();
30 |
31 | // Register RateLimiter singleton
32 | $this->app->singleton('eloquent.filter.limiter', function ($app) {
33 | return new RateLimiter($app['cache.store']);
34 | });
35 | }
36 |
37 | /**
38 | * Bootstrap any application services.
39 | */
40 | public function boot(): void
41 | {
42 | if ($this->app['config']->get('eloquentFilter.rate_limit.enabled', true)) {
43 | $this->app['router']->aliasMiddleware('filter.throttle', FilterRateLimiter::class);
44 | }
45 | }
46 |
47 | /**
48 | * Configure package paths.
49 | */
50 | private function configurePaths(): void
51 | {
52 | $this->publishes([
53 | __DIR__ . '/config/config.php' => config_path('eloquentFilter.php'),
54 | ]);
55 | }
56 |
57 | /**
58 | * Merge configuration.
59 | */
60 | private function mergeConfig()
61 | {
62 | $this->mergeConfigFrom(
63 | __DIR__ . '/config/config.php',
64 | 'eloquentFilter'
65 | );
66 | }
67 |
68 | /**
69 | *
70 | */
71 | private function registerBindings(): void
72 | {
73 | $this->registerAllDependencies();
74 |
75 | $this->commands([MakeEloquentFilter::class]);
76 | }
77 |
78 | /**
79 | * @return void
80 | */
81 | private function registerAllDependencies(): void
82 | {
83 | /* @var $queryFilterCoreFactory QueryFilterCoreFactory */
84 | $queryFilterCoreFactory = app(QueryFilterCoreFactory::class);
85 |
86 | $mainQueryFilterBuilder = $this->setMainQueryFilterBuilder();
87 |
88 | $this->attachFilterToQueryBuilder($mainQueryFilterBuilder, $queryFilterCoreFactory);
89 |
90 | $this->attachResponseFilterToQueryBuilder($mainQueryFilterBuilder, $queryFilterCoreFactory);
91 |
92 | $this->setEloquentFilter($mainQueryFilterBuilder, $queryFilterCoreFactory);
93 | }
94 |
95 | /**
96 | * @param \Closure $mainQueryFilterBuilder
97 | * @param \eloquentFilter\QueryFilter\Factory\QueryFilterCoreFactory $queryFilterCoreFactory
98 | * @return void
99 | */
100 | private function attachFilterToQueryBuilder(\Closure $mainQueryFilterBuilder, QueryFilterCoreFactory $queryFilterCoreFactory): void
101 | {
102 | \Illuminate\Database\Query\Builder::macro('filter', function ($request = null) use ($mainQueryFilterBuilder, $queryFilterCoreFactory) {
103 |
104 | if (empty($request)) {
105 | $request = request()->query();
106 | }
107 |
108 | app()->singleton(
109 | 'eloquentFilter',
110 | function () use ($mainQueryFilterBuilder, $queryFilterCoreFactory, $request) {
111 | return $mainQueryFilterBuilder($request, $queryFilterCoreFactory->createQueryFilterCoreDBQueryBuilder());
112 | }
113 | );
114 |
115 | /** @see MainQueryFilterBuilder::apply() */
116 | return app('eloquentFilter')->apply(builder: $this, request: $request);
117 | });
118 | }
119 |
120 | /**
121 | * @param \Closure $mainQueryFilterBuilder
122 | * @param \eloquentFilter\QueryFilter\Factory\QueryFilterCoreFactory $queryFilterCoreFactory
123 | * @return void
124 | */
125 | private function attachResponseFilterToQueryBuilder(\Closure $mainQueryFilterBuilder, QueryFilterCoreFactory $queryFilterCoreFactory): void
126 | {
127 | \Illuminate\Database\Query\Builder::macro('getResponseFilter', function ($callback = null) use ($mainQueryFilterBuilder, $queryFilterCoreFactory) {
128 |
129 | if (!empty($callback)) {
130 | return call_user_func($callback, EloquentFilter::getResponse());
131 | }
132 |
133 | });
134 | }
135 |
136 | /**
137 | * @param \Closure $mainQueryFilterBuilder
138 | * @param \eloquentFilter\QueryFilter\Factory\QueryFilterCoreFactory $queryFilterCoreFactory
139 | * @return void
140 | */
141 | private function setEloquentFilter(\Closure $mainQueryFilterBuilder, QueryFilterCoreFactory $queryFilterCoreFactory): void
142 | {
143 | $this->app->singleton(
144 | 'eloquentFilter',
145 | function () use ($mainQueryFilterBuilder, $queryFilterCoreFactory) {
146 |
147 | /* @see MainQueryFilterBuilder */
148 | return $mainQueryFilterBuilder($this->app->get('request')->query(), $queryFilterCoreFactory->createQueryFilterCoreEloquentBuilder());
149 | }
150 | );
151 | }
152 |
153 | /**
154 | * @return \Closure
155 | */
156 | private function setMainQueryFilterBuilder(): \Closure
157 | {
158 | $mainQueryFilterBuilder = function ($requestData, QueryFilterCore $queryFilterCore) {
159 | $requestFilter = app(RequestFilter::class, ['request' => $requestData]);
160 | $responseFilter = app(ResponseFilter::class);
161 |
162 | return app(MainQueryFilterBuilder::class, [
163 | 'queryFilterCore' => $queryFilterCore,
164 | 'requestFilter' => $requestFilter,
165 | 'responseFilter' => $responseFilter,
166 | ]);
167 | };
168 | return $mainQueryFilterBuilder;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/config/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehdi-fathi/eloquent-filter/65a9af3ad7913977c89e57c2fbbf5276092e2e66/src/config/.gitkeep
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | env('EloquentFilter_ENABLED', true),
9 |
10 | /*
11 | * Enable / disable Custom Detection EloquentFilter.
12 | */
13 | 'enabled_custom_detection' => env('EloquentFilter_Custom_Detection_ENABLED', true),
14 |
15 | /*
16 | * Set key for declare request for just eloquent filter
17 | */
18 | 'request_filter_key' => '', // filter
19 |
20 | /*
21 | * Set index array for ignore request by default example : [ 'show_query','new_trend' ]
22 | */
23 | 'ignore_request' => [],
24 |
25 | 'max_limit' => 20, /* It's a limitation for preventing making awful queries mistakenly by the developer or intentionally by a villain user. you can disable it just with comment it. */
26 |
27 | /*
28 | |--------------------------------------------------------------------------
29 | | Eloquent Filter Settings
30 | |--------------------------------------------------------------------------
31 | |
32 | | This is the namespace custom eloquent filter
33 | |
34 | */
35 |
36 | 'namespace' => 'App\\ModelFilters\\',
37 |
38 | 'log' => [
39 | 'has_keeping_query' => false,
40 | 'max_time_query' => null,
41 | 'type' => 'eloquentFilter.query'
42 | ],
43 | 'filtering_keys' => [
44 | ],
45 |
46 | /*
47 | * Set salt for encode request.
48 | */
49 | 'request_salt' => 1234,
50 |
51 | /*
52 | * Cast sign method is prefix name method for change data before filtering.
53 | */
54 | 'cast_method_sign' => 'filterSet',
55 |
56 | /*
57 | * custom sign method is prefix name method for custom methods in models.
58 | */
59 | 'custom_method_sign' => 'filterCustom',
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Rate Limiting
64 | |--------------------------------------------------------------------------
65 | |
66 | | Configure the rate limiting for filter requests. This helps prevent
67 | | abuse and ensures optimal performance of your application.
68 | |
69 | */
70 | 'rate_limit' => [
71 | // Whether to enable rate limiting
72 | 'enabled' => env('EloquentFilter_RATE_LIMIT_ENABLED', false),
73 |
74 | // Maximum number of attempts within the decay minutes
75 | 'max_attempts' => env('EloquentFilter_RATE_LIMIT', 60),
76 |
77 | // Number of minutes until the rate limit resets
78 | 'decay_minutes' => env('EloquentFilter_RATE_DECAY', 1),
79 |
80 | // Whether to include rate limit headers in the response
81 | 'include_headers' => env('EloquentFilter_RATE_LIMIT_HEADERS', true),
82 |
83 | // Custom response message when rate limit is exceeded
84 | 'error_message' => env('EloquentFilter_RATE_LIMIT_MESSAGE', 'Too many filter requests. Please try again later.'),
85 | ],
86 |
87 | /*
88 | |--------------------------------------------------------------------------
89 | | Cache Configuration
90 | |--------------------------------------------------------------------------
91 | |
92 | | Configure caching settings for rate limiting.
93 | |
94 | */
95 | 'cache' => [
96 | // Cache prefix for rate limiting keys
97 | 'prefix' => 'eloquent_filter_rate_limit:',
98 |
99 | // Cache store to use for rate limiting
100 | 'store' => env('EloquentFilter_CACHE_DRIVER', null),
101 | ],
102 | ];
103 |
--------------------------------------------------------------------------------
/tests/Models/Car.php:
--------------------------------------------------------------------------------
1 | belongsTo(Category::class);
29 | }
30 |
31 | public function address()
32 | {
33 | return $this->belongsTo(Category::class, 'foo_id');
34 | }
35 |
36 | public function activeFoo()
37 | {
38 | return $this->belongsTo(Category::class, 'foo_id')->where('active', true);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Models/Category.php:
--------------------------------------------------------------------------------
1 | hasMany(Stat::class);
35 | }
36 |
37 | /**
38 | * @param array|null $request
39 | * @return mixed
40 | */
41 | public function serializeRequestFilter(?array $request)
42 | {
43 | if (!empty($request['new_title'])) {
44 | foreach ($request['new_title'] as &$item) {
45 | $item = trim($item, '__');
46 | }
47 | $request['title'] = $request['new_title'];
48 | unset($request['new_title']);
49 | }
50 |
51 | return $request;
52 | }
53 |
54 | /**
55 | * This is a sample custom query.
56 | *
57 | * @param \Illuminate\Database\Eloquent\Builder $builder
58 | * @param $value
59 | *
60 | * @return \Illuminate\Database\Eloquent\Builder
61 | */
62 | public function filterCustomSample_like(Builder $builder, $value)
63 | {
64 | return $builder->where('title', 'like', '%'.$value.'%');
65 | }
66 |
67 | public function filterSetDesc($value)
68 | {
69 | return trim($value);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Models/CategoryPosts.php:
--------------------------------------------------------------------------------
1 | whereHas('foo', function ($q) {
22 | $q->where('bam', 'like', '%'.$this->values['like_relation_value'].'%');
23 | })
24 | ->where("$this->filter", '<>', $this->values['value'])
25 | ->where('email', 'like', '%'.$this->values['email'].'%')
26 | ->limit($this->values['limit']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Models/CustomDetect/WhereRelationLikeCondition.php:
--------------------------------------------------------------------------------
1 | where('username', 'like', '%'.$value.'%');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Models/Order.php:
--------------------------------------------------------------------------------
1 | 'code',
25 | ];
26 |
27 | public function getResponseFilter($out)
28 | {
29 | $data['data'] = $out;
30 |
31 | return $data;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Models/Tag.php:
--------------------------------------------------------------------------------
1 | belongsTo(Category::class);
28 | }
29 |
30 | public function address()
31 | {
32 | return $this->belongsTo(Category::class, 'foo_id');
33 | }
34 |
35 | public function activeFoo()
36 | {
37 | return $this->belongsTo(Category::class, 'foo_id')->where('active', true);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Models/User.php:
--------------------------------------------------------------------------------
1 | belongsTo(Category::class);
36 | }
37 |
38 | public function address()
39 | {
40 | return $this->belongsTo(Category::class, 'foo_id');
41 | }
42 |
43 | public function activeFoo()
44 | {
45 | return $this->belongsTo(Category::class, 'foo_id')->where('active', true);
46 | }
47 |
48 | public function EloquentFilterCustomDetection(): array
49 | {
50 | return [
51 | WhereRelationLikeCondition::class,
52 | ];
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | request = m::mock(Request::class);
29 |
30 | // Initialize ParameterBag for attributes
31 | $this->requestAttributes = new ParameterBag();
32 |
33 | // Mock User implementing Authenticatable
34 | $this->user = new class implements Authenticatable {
35 | public function getAuthIdentifier() {
36 | return 1;
37 | }
38 |
39 | public function getAuthIdentifierName() {
40 | return 'id';
41 | }
42 |
43 | public function getAuthPassword() {
44 | return 'hashed-password';
45 | }
46 |
47 | public function getRememberToken() {
48 | return 'remember-token';
49 | }
50 |
51 | public function setRememberToken($value) {
52 | // Not needed for our tests
53 | }
54 |
55 | public function getRememberTokenName() {
56 | return 'remember_token';
57 | }
58 |
59 | public function getAuthPasswordName()
60 | {
61 | return 'password';
62 | }
63 | };
64 |
65 | // Setup request methods
66 | $this->request->shouldReceive('user')->byDefault()->andReturn(null);
67 | $this->request->shouldReceive('path')->andReturn('/test');
68 | $this->request->shouldReceive('ip')->andReturn('127.0.0.1');
69 |
70 | // Setup attributes methods
71 | $this->request->shouldReceive('getAttribute')
72 | ->andReturnUsing(function ($key, $default = null) {
73 | return $this->requestAttributes->get($key, $default);
74 | });
75 |
76 | $this->request->shouldReceive('attributes')
77 | ->andReturn($this->requestAttributes);
78 |
79 | app()->bind(
80 | 'request',
81 | function () {
82 | return $this->request;
83 | }
84 | );
85 | }
86 |
87 | /**
88 | * @param Application $app
89 | *
90 | * @return array
91 | */
92 | protected function getPackageProviders($app)
93 | {
94 | return [eloquentFilter\ServiceProvider::class];
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Tests/Db/DbFilterMockTest.php:
--------------------------------------------------------------------------------
1 | builder = m::mock(Builder::class);
28 | }
29 |
30 | public function testWhere()
31 | {
32 | $this->request->shouldReceive('query')->andReturn(
33 | [
34 | 'title' => 'sport',
35 | ]
36 | );
37 |
38 | $categories = DB::table('categories')->filter();
39 |
40 | $builder = DB::table('categories')->where('title', 'sport');
41 |
42 | $this->assertSame($categories->toSql(), $builder->toSql());
43 |
44 | $this->assertEquals(['sport'], $categories->getBindings());
45 | }
46 |
47 | public function testWhereWithEloquentFilter()
48 | {
49 | $this->request->shouldReceive('query')->andReturn(
50 | [
51 | 'title' => 'sport',
52 | ]
53 | );
54 |
55 | $categories = DB::table('categories')->filter();
56 |
57 | $builder = DB::table('categories')->where('title', 'sport');
58 |
59 | $this->assertSame($categories->toSql(), $builder->toSql());
60 |
61 | $this->assertEquals(['sport'], $categories->getBindings());
62 |
63 | $builder = new Category();
64 |
65 | $builder = $builder->query()->where('title', 'sport');
66 |
67 | $this->request->shouldReceive('query')->andReturn(
68 | [
69 | 'title' => 'sport',
70 | ]
71 | );
72 |
73 | $categories = Category::filter($this->request->query());
74 |
75 | $this->assertSame($categories->toSql(), $builder->toSql());
76 |
77 | $this->assertEquals(['sport'], $categories->getBindings());
78 | }
79 |
80 | public function testWhereEloquentWithDB()
81 | {
82 |
83 | $builder = new Category();
84 |
85 | $builder = $builder->query()->where('title', 'sport');
86 |
87 | $this->request->shouldReceive('query')->andReturn(
88 | [
89 | 'title' => 'sport',
90 | ]
91 | );
92 |
93 | $categories = Category::filter();
94 |
95 | $this->assertSame($categories->toSql(), $builder->toSql());
96 |
97 | $this->assertEquals(['sport'], $categories->getBindings());
98 |
99 | $this->request->shouldReceive('query')->andReturn(
100 | [
101 | 'title' => 'sport',
102 | ]
103 | );
104 |
105 | $categories = DB::table('categories')->filter();
106 |
107 | $builder = DB::table('categories')->where('title', 'sport');
108 |
109 | $this->assertSame($categories->toSql(), $builder->toSql());
110 |
111 | $this->assertEquals(['sport'], $categories->getBindings());
112 | }
113 |
114 |
115 | public function testWhereIn()
116 | {
117 | $this->request->shouldReceive('query')->andReturn(
118 | [
119 | 'username' => ['mehdi', 'ali'],
120 | 'family' => null,
121 | ]
122 | );
123 |
124 | $users = DB::table('users')->filter();
125 |
126 | $builder = DB::table('users')
127 | ->wherein('username', ['mehdi', 'ali']);
128 |
129 | $this->assertSame($users->toSql(), $builder->toSql());
130 |
131 | $this->assertEquals(['mehdi', 'ali'], $users->getBindings());
132 | }
133 |
134 | public function testWhereInWithArray()
135 | {
136 | $this->request->shouldReceive('query')->andReturn(
137 | [
138 | ]
139 | );
140 |
141 | $users = DB::table('users')->filter([
142 | 'username' => ['mehdi', 'ali'],
143 | 'family' => null,
144 | ]);
145 |
146 | $builder = DB::table('users')
147 | ->wherein('username', ['mehdi', 'ali']);
148 |
149 | $this->assertSame($users->toSql(), $builder->toSql());
150 |
151 | $this->assertEquals(['mehdi', 'ali'], $users->getBindings());
152 | }
153 |
154 | public function testWhereLike()
155 | {
156 | $builder = DB::table('users')->where('email', 'like', '%meh%');
157 | $this->request->shouldReceive('query')->andReturn(
158 | [
159 | 'email' => [
160 | 'like' => '%meh%',
161 | ],
162 | ]
163 | );
164 |
165 | $users = DB::table('users')->filter();
166 |
167 | $this->assertSame($users->toSql(), $builder->toSql());
168 | $this->assertSame(['%meh%'], $builder->getBindings());
169 | }
170 |
171 | public function testWhereOr1()
172 | {
173 | $builder = DB::table('users')
174 | ->where('baz', 'boo')
175 | ->where('count_posts', 22)
176 | ->orWhere('baz', 'joo');
177 |
178 | $this->request->shouldReceive('query')->andReturn(
179 | [
180 | 'baz' => 'boo',
181 | 'count_posts' => 22,
182 | 'or' => [
183 | 'baz' => 'joo',
184 | ],
185 | ]
186 | );
187 |
188 | $users = DB::table('users')->filter();
189 |
190 | $users_to_sql = str_replace('(', '', $users->toSql());
191 | $users_to_sql = str_replace(')', '', $users_to_sql);
192 | $this->assertSame($users_to_sql, $builder->toSql());
193 | $this->assertEquals(['boo', 22, 'joo'], $users->getBindings());
194 | }
195 |
196 | public function testWhereByOpt()
197 | {
198 | $builder = DB::table('categories')->where('count_posts', '>', 35);
199 |
200 | $this->request->shouldReceive('query')->andReturn(
201 | [
202 | 'count_posts' => [
203 | 'operator' => '>',
204 | 'value' => 35,
205 | ],
206 | ]
207 | );
208 |
209 | $categories = DB::table('categories')->filter();
210 |
211 | $this->assertSame($categories->toSql(), $builder->toSql());
212 |
213 | $this->assertEquals([35], $categories->getBindings());
214 | }
215 |
216 | public function testMaxLimit()
217 | {
218 |
219 | $builder = DB::table('users')->limit(20);
220 |
221 | $this->request->shouldReceive('query')->andReturn(
222 | [
223 | 'f_params' => [
224 | 'limit' => 25,
225 | ],
226 | ]
227 | );
228 |
229 | $users = DB::table('users')->filter();
230 |
231 | $this->assertSame($users->toSql(), $builder->toSql());
232 | }
233 |
234 |
235 | public function testFParamException()
236 | {
237 | try {
238 | $this->request->shouldReceive('query')->andReturn(
239 | [
240 | 'f_params' => [
241 | 'orderBys' => [
242 | 'field' => 'id',
243 | 'type' => 'ASC',
244 | ],
245 | ],
246 | ]
247 | );
248 |
249 | DB::table('users')->filter();
250 | } catch (EloquentFilterException $e) {
251 | $this->assertEquals(2, $e->getCode());
252 | }
253 | }
254 |
255 | public function testExclusiveException()
256 | {
257 | $this->expectException(EloquentFilterException::class);
258 |
259 | $this->request->shouldReceive('query')->andReturn(
260 | [
261 | 'f_params' => [
262 | 'orderBys11' => [
263 | 'field' => 'id',
264 | 'type' => 'ASC',
265 | ],
266 | ],
267 | ]
268 | );
269 |
270 | DB::table('users')->filter();
271 | }
272 |
273 |
274 | public function testFParamOrder()
275 | {
276 | $builder = DB::table('users')
277 | ->orderBy('id')
278 | ->orderBy('count_posts');
279 |
280 | $this->request->shouldReceive('query')->andReturn(
281 | [
282 | 'f_params' => [
283 | 'orderBy' => [
284 | 'field' => 'id,count_posts',
285 | 'type' => 'ASC',
286 | ],
287 | ],
288 | ]
289 | );
290 |
291 | $users = DB::table('users')->filter();
292 |
293 | $this->assertSame($users->toSql(), $builder->toSql());
294 | }
295 |
296 |
297 | //
298 | // public function testWhereByOptWithTrashed()
299 | // {
300 | // $builder = new Category();
301 | //
302 | // $builder = $builder->newQuery()->withTrashed()
303 | // ->where('count_posts', '>', 35);
304 | //
305 | // $this->request->shouldReceive('query')->andReturn(
306 | // [
307 | // 'count_posts' => [
308 | // 'operator' => '>',
309 | // 'value' => 35,
310 | // ],
311 | // ]
312 | // );
313 | //
314 | // $users = Category::withTrashed()->filter();
315 | //
316 | // $this->assertSame($users->toSql(), $builder->toSql());
317 | //
318 | // $this->assertEquals([35], $users->getBindings());
319 | // }
320 | //
321 | // public function testMessRequest()
322 | // {
323 | // $builder = new User();
324 | //
325 | // $builder = $builder->newQuery()
326 | // ->where('username', 'mehdi');
327 | //
328 | // $this->request->shouldReceive('query')->andReturn(
329 | // [
330 | // ]
331 | // );
332 | //
333 | // $data = ['username' => 'mehdi'];
334 | //
335 | // $users = User::filter($data);
336 | //
337 | // $this->assertSame($users->toSql(), $builder->toSql());
338 | // }
339 |
340 | public function testWhereByOptZero()
341 | {
342 | $builder = new Category();
343 |
344 | $builder = DB::table('categories')
345 | ->where('count_posts', '>', 0);
346 |
347 | $this->request->shouldReceive('query')->andReturn(
348 | [
349 | 'count_posts' => [
350 | 'operator' => '>',
351 | 'value' => 0,
352 | ],
353 | ]
354 | );
355 |
356 | $categories = DB::table('categories')->filter();
357 |
358 | $this->assertSame($categories->toSql(), $builder->toSql());
359 |
360 | $this->assertEquals([0], $categories->getBindings());
361 | }
362 |
363 | public function testWhereBetween()
364 | {
365 |
366 | $builder = DB::table('tags')->whereBetween(
367 | 'created_at',
368 | [
369 | '2019-01-01 17:11:46',
370 | '2019-02-06 10:11:46',
371 | ]
372 | );
373 |
374 | $this->request->shouldReceive('query')->andReturn(
375 | [
376 | 'created_at' => [
377 | 'start' => '2019-01-01 17:11:46',
378 | 'end' => '2019-02-06 10:11:46',
379 | ],
380 | ]
381 | );
382 |
383 | $users = DB::table('tags')->filter();
384 |
385 | $this->assertSame($users->toSql(), $builder->toSql());
386 | $this->assertEquals(['2019-01-01 17:11:46', '2019-02-06 10:11:46'], $builder->getBindings());
387 | $this->assertEquals(['2019-01-01 17:11:46', '2019-02-06 10:11:46'], $users->getBindings());
388 | }
389 |
390 | //
391 | public function testWhereDate()
392 | {
393 | $builder = new Tag();
394 |
395 | $builder = DB::table('tags')->whereDate(
396 | 'created_at',
397 | '2022-07-14',
398 | );
399 |
400 | $this->request->shouldReceive('query')->andReturn(
401 | [
402 | 'created_at' =>
403 | '2022-07-14'
404 |
405 | ]
406 | );
407 |
408 | $users = DB::table('tags')->filter();
409 |
410 | $this->assertSame($users->toSql(), $builder->toSql());
411 | $this->assertEquals(['2022-07-14'], $builder->getBindings());
412 | $this->assertEquals(['2022-07-14'], $users->getBindings());
413 | }
414 |
415 | public function testResponseCallback()
416 | {
417 | $this->request->shouldReceive('query')->andReturn(
418 | [
419 | 'title' => 'sport',
420 | ]
421 | );
422 |
423 | $categories = DB::table('categories')->filter()->getResponseFilter(function ($out) {
424 |
425 | $data['data'] = $out;
426 |
427 | return $data;
428 | });
429 |
430 | $builder = DB::table('categories')->where('title', 'sport');
431 |
432 | $this->assertSame($categories['data']->toSql(), $builder->toSql());
433 |
434 | $this->assertEquals(['sport'], $categories['data']->getBindings());
435 | }
436 |
437 |
438 | public function testMacros()
439 | {
440 |
441 | $this->request->shouldReceive('query')->andReturn(
442 | [
443 | 'title' => 'sport',
444 | ]
445 | );
446 |
447 | $categories = DB::table('categories')->filter();
448 |
449 | $builder = DB::table('categories')->where('title', 'sport');
450 |
451 | $this->assertSame($categories->toSql(), $builder->toSql());
452 |
453 | $this->assertEquals(['sport'], $categories->getBindings());
454 |
455 | $this->assertTrue($categories->isUsedEloquentFilter());
456 |
457 | }
458 |
459 | public function testWhereDoesntHave()
460 | {
461 | $tags_db = DB::table('tags')
462 | ->whereNotExists(function ($query) {
463 | $query->select(DB::raw(1))
464 | ->from('categories')
465 | ->whereColumn('categories.id', 'tags.category_id');
466 | })->where('baz', 'joo');
467 |
468 | $this->request->shouldReceive('query')->andReturn(
469 | [
470 | 'doesnt_have' => 'categories',
471 | 'baz' => 'joo',
472 | ]
473 | );
474 |
475 | $tags_filters = DB::table('tags')->filter();
476 |
477 | $this->assertSame($tags_filters->toSql(), $tags_db->toSql());
478 | $this->assertEquals(['joo'], $tags_db->getBindings());
479 | $this->assertEquals(['joo'], $tags_filters->getBindings());
480 | }
481 |
482 | public function testSimpleWhereHas()
483 | {
484 | // Create the expected query
485 | $expected = DB::table('posts')
486 | ->whereExists(function ($query) {
487 | $query->select(DB::raw(1))
488 | ->from('categories')
489 | ->whereRaw('categories.id = posts.category_id')
490 | ->where('name', 'Technology');
491 | });
492 |
493 | // Create the filter query
494 | $this->request->shouldReceive('query')->andReturn([
495 | 'category.name' => 'Technology'
496 | ]);
497 |
498 | $filtered = DB::table('posts')->filter();
499 |
500 | // Assert the queries match
501 | $this->assertSame($filtered->toSql(), $expected->toSql());
502 | $this->assertEquals(['Technology'], $filtered->getBindings());
503 | $this->assertEquals(['Technology'], $expected->getBindings());
504 | }
505 |
506 | public function testWhereHasWithArray()
507 | {
508 | // Create the expected query
509 | $expected = DB::table('posts')
510 | ->whereExists(function ($query) {
511 | $query->select(DB::raw(1))
512 | ->from('categories')
513 | ->whereRaw('categories.id = posts.category_id')
514 | ->whereIn('name', ['Technology', 'Science']);
515 | });
516 |
517 | // Create the filter query
518 | $this->request->shouldReceive('query')->andReturn([
519 | 'category.name' => ['Technology', 'Science']
520 | ]);
521 |
522 | $filtered = DB::table('posts')->filter();
523 |
524 | // Assert the queries match
525 | $this->assertSame($filtered->toSql(), $expected->toSql());
526 | $this->assertEquals(['Technology', 'Science'], $filtered->getBindings());
527 | $this->assertEquals(['Technology', 'Science'], $expected->getBindings());
528 | }
529 |
530 | public function testWhereHasWithMultipleConditions()
531 | {
532 | // Create the expected query
533 | $expected = DB::table('posts')
534 | ->whereExists(function ($query) {
535 | $query->select(DB::raw(1))
536 | ->from('categories')
537 | ->whereRaw('categories.id = posts.category_id')
538 | ->where('name', 'Technology');
539 | })
540 | ->where('status', 'published');
541 |
542 | // Create the filter query
543 | $this->request->shouldReceive('query')->andReturn([
544 | 'category.name' => 'Technology',
545 | 'status' => 'published'
546 | ]);
547 |
548 | $filtered = DB::table('posts')->filter();
549 |
550 | // Assert the queries match
551 | $this->assertSame($filtered->toSql(), $expected->toSql());
552 | $this->assertEquals(['Technology', 'published'], $filtered->getBindings());
553 | $this->assertEquals(['Technology', 'published'], $expected->getBindings());
554 | }
555 |
556 | public function testWhereHasWithNestedRelation()
557 | {
558 | // Create the expected query
559 | $expected = DB::table('posts')
560 | ->whereExists(function ($query) {
561 | $query->select(DB::raw(1))
562 | ->from('categories')
563 | ->whereRaw('categories.id = posts.category_id')
564 | ->where('type', 'featured');
565 | });
566 |
567 | // Create the filter query
568 | $this->request->shouldReceive('query')->andReturn([
569 | 'category.type' => 'featured'
570 | ]);
571 |
572 | $filtered = DB::table('posts')->filter();
573 |
574 | // Assert the queries match
575 | $this->assertSame($filtered->toSql(), $expected->toSql());
576 | $this->assertEquals(['featured'], $filtered->getBindings());
577 | $this->assertEquals(['featured'], $expected->getBindings());
578 | }
579 |
580 | public function testWhereYear()
581 | {
582 | $builder = DB::table('users');
583 |
584 | $builder = $builder->whereYear('created_at', 2024);
585 |
586 | $this->request->shouldReceive('query')->andReturn([
587 | 'created_at' => [
588 | 'year' => 2024
589 | ]
590 | ]);
591 | $filtered = DB::table('users')->filter();
592 |
593 | $this->assertEquals($builder->toSql(), $filtered->toSql());
594 | $this->assertEquals([2024], $builder->getBindings());
595 | }
596 |
597 | public function testWhereMonth()
598 | {
599 | $builder = DB::table('users');
600 | $builder = $builder->whereMonth('created_at', 3);
601 |
602 | $this->request->shouldReceive('query')->andReturn([
603 | 'created_at' => [
604 | 'month' => 3
605 | ]
606 | ]);
607 |
608 | $filtered = DB::table('users')->filter();
609 |
610 | $this->assertEquals($builder->toSql(), $filtered->toSql());
611 | $this->assertEquals([03], $builder->getBindings());
612 | }
613 |
614 | public function testWhereDay()
615 | {
616 | $builder = DB::table('users');
617 | $builder = $builder->whereDay('created_at', 15);
618 |
619 | $this->request->shouldReceive('query')->andReturn([
620 | 'created_at' => [
621 | 'day' => 15
622 | ]
623 | ]);
624 |
625 | $filtered = DB::table('users')->filter();
626 |
627 | $this->assertEquals($builder->toSql(), $filtered->toSql());
628 | $this->assertEquals([15], $builder->getBindings());
629 | }
630 |
631 | public function tearDown(): void
632 | {
633 | m::close();
634 | }
635 | }
636 |
--------------------------------------------------------------------------------
/tests/Tests/Eloquent/MakeEloquentFilterCommandTest.php:
--------------------------------------------------------------------------------
1 | filesystem = m::mock(Filesystem::class);
27 | $this->command = m::mock('eloquentFilter\Command\MakeEloquentFilter[argument]', [$this->filesystem]);
28 | }
29 |
30 | public function tearDown(): void
31 | {
32 | m::close();
33 | }
34 |
35 | /**
36 | *
37 | * @param $argument
38 | * @param $class
39 | */
40 | public function testMakeClassName($argument = 'User', $class = 'UserFilter')
41 | {
42 | $this->command->shouldReceive('argument')->andReturn($argument);
43 | $this->command->makeClassName();
44 | $this->assertEquals("App\\ModelFilters\\$class", $this->command->getClassName());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Tests/RateLimiting/RateLimitTest.php:
--------------------------------------------------------------------------------
1 | rateLimiter = m::mock(RateLimiter::class);
25 | app()->instance('eloquent.filter.limiter', $this->rateLimiter);
26 | config(['eloquentFilter.rate_limit.enabled' => true]);
27 | }
28 |
29 | protected function mockRateLimiterNotExceeded()
30 | {
31 | $this->rateLimiter->shouldReceive('tooManyAttempts')
32 | ->once()
33 | ->andReturn(false);
34 |
35 | $this->rateLimiter->shouldReceive('hit')
36 | ->once();
37 |
38 | $this->rateLimiter->shouldReceive('remaining')
39 | ->once()
40 | ->andReturn(59);
41 | }
42 |
43 | protected function mockRateLimiterExceeded()
44 | {
45 | $this->rateLimiter->shouldReceive('tooManyAttempts')
46 | ->once()
47 | ->andReturn(true);
48 |
49 | $this->rateLimiter->shouldReceive('availableIn')
50 | ->once()
51 | ->andReturn(60);
52 | }
53 |
54 | public function testRateLimitNotExceeded()
55 | {
56 | $this->mockRateLimiterNotExceeded();
57 |
58 | // This should not throw an exception
59 | $this->checkRateLimit();
60 |
61 | // Verify rate limit info was stored
62 | $rateLimitInfo = request()->attributes->all()['rate_limit'];
63 |
64 | $this->assertEquals(60, $rateLimitInfo['limit']);
65 | $this->assertEquals(59, $rateLimitInfo['remaining']);
66 | }
67 |
68 | public function testRateLimitExceeded()
69 | {
70 | $this->mockRateLimiterExceeded();
71 |
72 | $this->expectException(ThrottleRequestsException::class);
73 | $this->checkRateLimit();
74 | }
75 |
76 | public function testRateLimitDisabled()
77 | {
78 | config(['eloquentFilter.rate_limit.enabled' => false]);
79 |
80 | // No methods should be called on the rate limiter
81 | $this->rateLimiter->shouldNotReceive('tooManyAttempts');
82 | $this->rateLimiter->shouldNotReceive('hit');
83 | $this->rateLimiter->shouldNotReceive('remaining');
84 |
85 | // This should not throw an exception
86 | $this->checkRateLimit();
87 | }
88 |
89 | public function testRateLimitWithAuthenticatedUser()
90 | {
91 | $this->actingAs($this->user);
92 | $this->mockRateLimiterNotExceeded();
93 |
94 | $this->checkRateLimit();
95 |
96 | // Verify rate limit info was stored
97 | $rateLimitInfo = request()->attributes->all()['rate_limit'];
98 |
99 |
100 | $this->assertEquals(60, $rateLimitInfo['limit']);
101 | $this->assertEquals(59, $rateLimitInfo['remaining']);
102 | }
103 |
104 | public function testRateLimitWithCustomUser()
105 | {
106 | $customUser = new class implements Authenticatable {
107 | public function getAuthIdentifier() {
108 | return 999;
109 | }
110 |
111 | public function getAuthIdentifierName() {
112 | return 'id';
113 | }
114 |
115 | public function getAuthPassword() {
116 | return 'hashed-password';
117 | }
118 |
119 | public function getRememberToken() {
120 | return 'remember-token';
121 | }
122 |
123 | public function setRememberToken($value) {
124 | // Not needed for our tests
125 | }
126 |
127 | public function getRememberTokenName() {
128 | return 'remember_token';
129 | }
130 |
131 | public function getAuthPasswordName()
132 | {
133 | return 'password';
134 | }
135 | };
136 |
137 | $this->actingAs($customUser);
138 | $this->mockRateLimiterNotExceeded();
139 |
140 | $this->checkRateLimit();
141 |
142 | // Verify rate limit info was stored
143 | $rateLimitInfo = request()->attributes->all()['rate_limit'];
144 | $this->assertEquals(60, $rateLimitInfo['limit']);
145 | $this->assertEquals(59, $rateLimitInfo['remaining']);
146 | }
147 | }
--------------------------------------------------------------------------------