├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitlab-ci.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── changelog.md ├── composer.json ├── config └── awesio-repository.php ├── contributing.md ├── database └── factories │ ├── ModelFactory.php │ └── SubmodelFactory.php ├── docs └── index.md ├── phpunit.xml.dist ├── src ├── Commands │ ├── RepositoryMakeCommand.php │ ├── RepositoryMakeMainCommand.php │ ├── RepositoryScopeMakeCommand.php │ ├── RepositoryScopesMakeCommand.php │ └── stubs │ │ ├── repository.stub │ │ ├── scope.stub │ │ └── scopes.stub ├── Contracts │ ├── CriteriaInterface.php │ ├── CriterionInterface.php │ ├── RepositoryInterface.php │ └── ScopesInterface.php ├── Criteria │ └── FindWhere.php ├── Eloquent │ ├── BaseRepository.php │ └── RepositoryAbstract.php ├── Exceptions │ └── RepositoryException.php ├── RepositoryServiceProvider.php ├── Scopes │ ├── Clauses │ │ ├── OrWhereLikeScope.php │ │ ├── OrWhereScope.php │ │ ├── OrderByScope.php │ │ ├── WhereDateGreaterScope.php │ │ ├── WhereDateLessScope.php │ │ ├── WhereLikeScope.php │ │ └── WhereScope.php │ ├── ScopeAbstract.php │ ├── Scopes.php │ └── ScopesAbstract.php └── Services │ └── Replacer.php └── tests ├── Stubs ├── InvalidRepository.php ├── Model.php ├── Repository.php ├── Scope.php └── Submodel.php ├── TestCase.php └── Unit ├── Criteria └── FindWhereTest.php ├── RepositoryTest.php └── Scopes ├── OrWhereLikeTest.php ├── OrWhereTest.php ├── OrderByTest.php ├── ScopeTest.php ├── ScopesTest.php ├── WhereLikeTest.php └── WhereTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: awesdotio 4 | open_collective: awesdotio -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Tell us what happens instead of the expected behavior 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Your issue may already be reported! 11 | Please search on the [issue tracker](../) before creating one. 12 | 13 | ## Expected Behavior 14 | 15 | 16 | 17 | ## Current Behavior 18 | 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | ## Steps to Reproduce (for bugs) 26 | 27 | 28 | 1. 29 | 2. 30 | 3. 31 | 4. 32 | 33 | ## Context 34 | 35 | 36 | 37 | ## Your Environment 38 | 39 | * Version used: 40 | * Browser Name and version: 41 | * Operating System and version (desktop or mobile): 42 | * Link to your project: 43 | 44 | ## System GA 45 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/issues)](https://github.com/awes-io/issues) 46 | -------------------------------------------------------------------------------- /.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 | Your issue may already be reported! 11 | Please search on the [issue tracker](../) before creating one. 12 | 13 | ## Expected Behavior 14 | 15 | 16 | 17 | ## Current Behavior 18 | 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | 26 | ## System GA 27 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/issues)](https://github.com/awes-io/issues) 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | A similar PR may already be submitted! 2 | Please search among the [Pull request](../) before creating one. 3 | 4 | Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: 5 | 6 | 7 | ## Summary 8 | 9 | 10 | 11 | This PR fixes/implements the following **bugs/features** 12 | 13 | * [ ] Bug 1 14 | * [ ] Bug 2 15 | * [ ] Feature 1 16 | * [ ] Feature 2 17 | * [ ] Breaking changes 18 | 19 | 20 | 21 | Explain the **motivation** for making this change. What existing problem does the pull request solve? 22 | 23 | 24 | 25 | ## Test plan (required) 26 | 27 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 28 | 29 | 30 | 31 | ## Code formatting 32 | 33 | 34 | 35 | ## Closing issues 36 | 37 | 38 | Fixes # 39 | 40 | ## System GA 41 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/awes-io)](https://github.com/awes-io/issues) 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .idea 4 | /tests/report 5 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: php:7.2 2 | 3 | cache: 4 | paths: 5 | - vendor/ 6 | 7 | before_script: 8 | # Install git, the php image doesn't have installed 9 | - apt-get update -yqq 10 | - apt-get install git -yqq 11 | 12 | # Install Xdebug 13 | - pecl install xdebug 14 | - docker-php-ext-enable xdebug 15 | 16 | # Install composer 17 | - curl -sS https://getcomposer.org/installer | php 18 | 19 | # Install all project dependencies 20 | - php composer.phar install 21 | 22 | # Run our tests 23 | test: 24 | only: 25 | - master 26 | - dev 27 | script: 28 | - vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-text --colors=never 29 | coverage: '/^\s*Methods:\s*\d+.\d+\%\s*\(\d+\/\d+\)/' 30 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at contact@awes.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present, Awescode GmbH (www.awescode.de) 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Awes.io logo 4 | 5 |

6 | 7 |

Repository

8 | 9 |

Repository Pattern in Laravel. The package allows to filter by request out-of-the-box, as well as to integrate customized criteria and any kind of filters.

10 | 11 |

12 | 13 | Coverage report 14 | 15 | 16 | Last version 17 | 18 | 19 | Build status 20 | 21 | 22 | Downloads 23 | 24 | 25 | License 26 | 27 | 28 | CDN Ready 29 | 30 | 31 | laravel 32 | 33 | 34 | Last commit 35 | 36 | 37 | Analytics 38 | 39 | 40 | Hosted by Package Kit 41 | 42 | 43 | Patreon 44 | 45 |

46 | 47 | ## 48 |

49 | Repository Laravel 50 |

51 | 52 | 53 | ## Table of Contents 54 | 55 | - Installation 56 | - Configuration 57 | - Overview 58 | - Usage 59 | - Create a Model 60 | - Create a Repository 61 | - Use built-in methods 62 | - Create a Criteria 63 | - Scope, Filter, and Order 64 | - Artisan Commands 65 | - Testing 66 | 67 | ## Installation 68 | 69 | Via Composer 70 | 71 | ``` bash 72 | $ composer require awes-io/repository 73 | ``` 74 | 75 | The package will automatically register itself. 76 | 77 | ## Configuration 78 | 79 | First publish config: 80 | 81 | ```bash 82 | php artisan vendor:publish --provider="AwesIO\Repository\RepositoryServiceProvider" --tag="config" 83 | ``` 84 | 85 | ```php 86 | // $repository->smartPaginate() related parameters 87 | 'smart_paginate' => [ 88 | // name of request parameter to take paginate by value from 89 | 'request_parameter' => 'limit', 90 | // default paginate by value 91 | 'default_limit' => 15, 92 | // max paginate by value 93 | 'max_limit' => 100, 94 | ] 95 | ``` 96 | 97 | ## Overview 98 | 99 | 100 | ##### Package allows you to filter data based on incoming request parameters: 101 | 102 | ``` 103 | https://example.com/news?title=Title&custom=value&orderBy=name_desc 104 | ``` 105 | 106 | It will automatically apply built-in constraints onto the query as well as any custom scopes and criteria you need: 107 | 108 | ```php 109 | protected $searchable = [ 110 | // where 'title' equals 'Title' 111 | 'title', 112 | ]; 113 | 114 | protected $scopes = [ 115 | // and custom parameter used in your scope 116 | 'custom' => MyScope::class, 117 | ]; 118 | ``` 119 | 120 | ```php 121 | class MyScope extends ScopeAbstract 122 | { 123 | public function scope($builder, $value, $scope) 124 | { 125 | return $builder->where($scope, $value)->orWhere(...); 126 | } 127 | } 128 | ``` 129 | 130 | Ordering by any field is available: 131 | 132 | ```php 133 | protected $scopes = [ 134 | // orderBy field 135 | 'orderBy' => OrderByScope::class, 136 | ]; 137 | ``` 138 | 139 | Package can also apply any custom criteria: 140 | 141 | ```php 142 | return $this->news->withCriteria([ 143 | new MyCriteria([ 144 | 'category_id' => '1', 'name' => 'Name' 145 | ]) 146 | ... 147 | ])->get(); 148 | ``` 149 | 150 | ## Usage 151 | 152 | ### Create a Model 153 | 154 | Create your model: 155 | 156 | ```php 157 | namespace App; 158 | 159 | use Illuminate\Database\Eloquent\Model; 160 | 161 | class News extends Model 162 | { 163 | ... 164 | } 165 | ``` 166 | 167 | ### Create a Repository 168 | 169 | Extend it from `AwesIO\Repository\Eloquent\BaseRepository` and provide `entity()` method to return full model class name: 170 | 171 | ```php 172 | namespace App; 173 | 174 | use AwesIO\Repository\Eloquent\BaseRepository; 175 | 176 | class NewsRepository extends BaseRepository 177 | { 178 | public function entity() 179 | { 180 | return News::class; 181 | } 182 | } 183 | ``` 184 | 185 | ### Use built-in methods 186 | 187 | ```php 188 | use App\NewsRepository; 189 | 190 | class NewsController extends BaseController 191 | { 192 | protected $news; 193 | 194 | public function __construct(NewsRepository $news) 195 | { 196 | $this->news = $news; 197 | } 198 | .... 199 | } 200 | ``` 201 | 202 | Execute the query as a "select" statement or get all results: 203 | 204 | ```php 205 | $news = $this->news->get(); 206 | ``` 207 | 208 | Execute the query and get the first result: 209 | 210 | ```php 211 | $news = $this->news->first(); 212 | ``` 213 | 214 | Find a model by its primary key: 215 | 216 | ```php 217 | $news = $this->news->find(1); 218 | ``` 219 | 220 | Add basic where clauses and execute the query: 221 | 222 | ```php 223 | $news = $this->news->->findWhere([ 224 | // where id equals 1 225 | 'id' => '1', 226 | // other "where" operations 227 | ['news_category_id', '<', '3'], 228 | ... 229 | ]); 230 | ``` 231 | 232 | Paginate the given query: 233 | 234 | ```php 235 | $news = $this->news->paginate(15); 236 | ``` 237 | 238 | Paginate the given query into a simple paginator: 239 | 240 | ```php 241 | $news = $this->news->simplePaginate(15); 242 | ``` 243 | 244 | Paginate the given query by 'limit' request parameter: 245 | 246 | ```php 247 | $news = $this->news->smartPaginate(); 248 | ``` 249 | 250 | Add an "order by" clause to the query: 251 | 252 | ```php 253 | $news = $this->news->orderBy('title', 'desc')->get(); 254 | ``` 255 | 256 | Save a new model and return the instance: 257 | 258 | ```php 259 | $news = $this->news->create($request->all()); 260 | ``` 261 | 262 | Update a record: 263 | 264 | ```php 265 | $this->news->update($request->all(), $id); 266 | ``` 267 | 268 | Delete a record by id: 269 | 270 | ```php 271 | $this->news->destroy($id); 272 | ``` 273 | 274 | Attach models to the parent: 275 | 276 | ```php 277 | $this->news->attach($parentId, $relationship, $idsToAttach); 278 | ``` 279 | 280 | Detach models from the relationship: 281 | 282 | ```php 283 | $this->news->detach($parentId, $relationship, $idsToDetach); 284 | ``` 285 | 286 | Find model or throw an exception if not found: 287 | 288 | ```php 289 | $this->news->findOrFail($id); 290 | ``` 291 | 292 | Execute the query and get the first result or throw an exception: 293 | 294 | ```php 295 | $this->news->firstOrFail(); 296 | ``` 297 | 298 | ### Create a Criteria 299 | 300 | Criteria are a way to build up specific query conditions. 301 | 302 | ```php 303 | use AwesIO\Repository\Contracts\CriterionInterface; 304 | 305 | class MyCriteria implements CriterionInterface { 306 | 307 | protected $conditions; 308 | 309 | public function __construct(array $conditions) 310 | { 311 | $this->conditions = $conditions; 312 | } 313 | 314 | public function apply($entity) 315 | { 316 | foreach ($this->conditions as $field => $value) { 317 | $entity = $entity->where($field, '=', $value); 318 | } 319 | return $entity; 320 | } 321 | } 322 | ``` 323 | 324 | Multiple Criteria can be applied: 325 | 326 | ```php 327 | use App\NewsRepository; 328 | 329 | class NewsController extends BaseController 330 | { 331 | protected $news; 332 | 333 | public function __construct(NewsRepository $news) 334 | { 335 | $this->news = $news; 336 | } 337 | 338 | public function index() 339 | { 340 | return $this->news->withCriteria([ 341 | new MyCriteria([ 342 | 'category_id' => '1', 'name' => 'Name' 343 | ]), 344 | new WhereAdmin(), 345 | ... 346 | ])->get(); 347 | } 348 | } 349 | ``` 350 | 351 | ### Scope, Filter and Order 352 | 353 | In your repository define which fields can be used to scope your queries by setting `$searchable` property. 354 | 355 | ```php 356 | protected $searchable = [ 357 | // where 'title' equals parameter value 358 | 'title', 359 | // orWhere equals 360 | 'body' => 'or', 361 | // where like 362 | 'author' => 'like', 363 | // orWhere like 364 | 'email' => 'orLike', 365 | ]; 366 | ``` 367 | 368 | Search by searchables: 369 | 370 | ```php 371 | public function index($request) 372 | { 373 | return $this->news->scope($request)->get(); 374 | } 375 | ``` 376 | 377 | ``` 378 | https://example.com/news?title=Title&body=Text&author=&email=gmail 379 | ``` 380 | 381 | Also several serchables enabled by default: 382 | 383 | ```php 384 | protected $scopes = [ 385 | // orderBy field 386 | 'orderBy' => OrderByScope::class, 387 | // where created_at date is after 388 | 'begin' => WhereDateGreaterScope::class, 389 | // where created_at date is before 390 | 'end' => WhereDateLessScope::class, 391 | ]; 392 | ``` 393 | 394 | ```php 395 | $this->news->scope($request)->get(); 396 | ``` 397 | 398 | Enable ordering for specific fields by adding `$orderable` property to your model class: 399 | 400 | ```php 401 | public $orderable = ['email']; 402 | ``` 403 | 404 | ``` 405 | https://example.com/news?orderBy=email_desc&begin=2019-01-24&end=2019-01-26 406 | ``` 407 | 408 | `orderBy=email_desc` will order by email in descending order, `orderBy=email` - in ascending 409 | 410 | You can also build your own custom scopes. In your repository override `scope()` method: 411 | 412 | ```php 413 | public function scope($request) 414 | { 415 | // apply build-in scopes 416 | parent::scope($request); 417 | 418 | // apply custom scopes 419 | $this->entity = (new NewsScopes($request))->scope($this->entity); 420 | 421 | return $this; 422 | } 423 | ``` 424 | 425 | Create your `scopes` class and extend `ScopesAbstract` 426 | 427 | ```php 428 | use AwesIO\Repository\Scopes\ScopesAbstract; 429 | 430 | class NewsScopes extends ScopesAbstract 431 | { 432 | protected $scopes = [ 433 | // here you can add field-scope mappings 434 | 'field' => MyScope::class, 435 | ]; 436 | } 437 | ``` 438 | 439 | Now you can build any scopes you need: 440 | 441 | ```php 442 | use AwesIO\Repository\Scopes\ScopeAbstract; 443 | 444 | class MyScope extends ScopeAbstract 445 | { 446 | public function scope($builder, $value, $scope) 447 | { 448 | return $builder->where($scope, $value); 449 | } 450 | } 451 | ``` 452 | 453 | ### Artisan Commands 454 | 455 | Package provides useful artisan command: 456 | 457 | ```bash 458 | php artisan repository:generate Models/Order --scope=Search 459 | ``` 460 | 461 | #### It'll generate several classes for ```App\Models\Order```: 462 | 463 | Main repository: ```App\Repositories\Orders\OrdersRepository``` 464 | 465 | Main scopes class: ```App\Repositories\Orders\Scopes\OrdersScopes``` 466 | 467 | Individual search scope class: ```App\Repositories\Orders\Scopes\SearchOrdersScope``` 468 | 469 | ## Testing 470 | 471 | The coverage of the package is Coverage report. 472 | 473 | You can run the tests with: 474 | 475 | ```bash 476 | composer test 477 | ``` 478 | 479 | ## Contributing 480 | 481 | Please see [contributing.md](contributing.md) for details and a todolist. 482 | 483 | ## Credits 484 | 485 | - [Galymzhan Begimov](https://github.com/begimov) 486 | - [All Contributors](contributing.md) 487 | 488 | ## License 489 | 490 | [MIT](http://opensource.org/licenses/MIT) 491 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Repository` will be documented in this file. 4 | 5 | ## Version 1.0 6 | 7 | ### Added 8 | - Everything 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awes-io/repository", 3 | "description": "Implementation of repository pattern for Laravel. The package allows out-of-the-box filtering of data based on parameters in the request, and also allows you to quickly integrate the list filters and custom criteria.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Awescode GmbH", 9 | "email": "info@awescode.de", 10 | "homepage": "https://www.awescode.de", 11 | "role": "Owner" 12 | }, 13 | { 14 | "name": "Galymzhan Begimov", 15 | "email": "begimov@gmail.com", 16 | "homepage": "https://github.com/begimov" 17 | } 18 | ], 19 | "support": { 20 | "email": "support@awescode.de" 21 | }, 22 | "homepage": "https://github.com/awes-io/repository", 23 | "keywords": ["Laravel", "Repository", "pattern", "filters", "criteria", "scope", "php"], 24 | "require": { 25 | "illuminate/support": "~5|~6|~7|~8" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "~7.0", 29 | "mockery/mockery": "^1.1", 30 | "orchestra/testbench": "~3.0", 31 | "sempro/phpunit-pretty-print": "^1.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "AwesIO\\Repository\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "AwesIO\\Repository\\Tests\\": "tests/" 41 | } 42 | }, 43 | "scripts": { 44 | "test": "vendor/bin/phpunit --colors=always --configuration phpunit.xml.dist --debug" 45 | }, 46 | "extra": { 47 | "laravel": { 48 | "providers": [ 49 | "AwesIO\\Repository\\RepositoryServiceProvider" 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/awesio-repository.php: -------------------------------------------------------------------------------- 1 | smartPaginate() related parameters 8 | |-------------------------------------------------------------------------- 9 | */ 10 | 'smart_paginate' => [ 11 | 'request_parameter' => 'limit', 12 | 'default_limit' => 15, 13 | 'max_limit' => 100, 14 | ] 15 | 16 | ]; 17 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Awes.io 2 | 3 | Want to contribute to Awes.io? We provide a Contribution Guide to help you get started. 4 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(\AwesIO\Repository\Tests\Stubs\Model::class, function (Faker $faker) { 17 | return [ 18 | 'name' => $faker->name, 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/SubmodelFactory.php: -------------------------------------------------------------------------------- 1 | define(\AwesIO\Repository\Tests\Stubs\Submodel::class, function (Faker $faker) { 17 | return [ 18 | 'name' => $faker->name, 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

Repository

2 | 3 |

Repository Pattern in Laravel. The package allows to filter by request out-of-the-box, as well as to integrate customized criteria and any kind of filters.

4 | 5 | ## Table of Contents 6 | 7 | - Installation 8 | - Configuration 9 | - Overview 10 | - Usage 11 | - Create a Model 12 | - Create a Repository 13 | - Use built-in methods 14 | - Create a Criteria 15 | - Scope, Filter, and Order 16 | - Artisan Commands 17 | - Testing 18 | 19 | ## Installation 20 | 21 | Via Composer 22 | 23 | ``` bash 24 | $ composer require awes-io/repository 25 | ``` 26 | 27 | The package will automatically register itself. 28 | 29 | ## Configuration 30 | 31 | First publish config: 32 | 33 | ```bash 34 | php artisan vendor:publish --provider="AwesIO\Repository\RepositoryServiceProvider" --tag="config" 35 | ``` 36 | 37 | ```php 38 | // $repository->smartPaginate() related parameters 39 | 'smart_paginate' => [ 40 | // name of request parameter to take paginate by value from 41 | 'request_parameter' => 'limit', 42 | // default paginate by value 43 | 'default_limit' => 15, 44 | // max paginate by value 45 | 'max_limit' => 100, 46 | ] 47 | ``` 48 | 49 | ## Overview 50 | 51 | 52 | ##### Package allows you to filter data based on incoming request parameters: 53 | 54 | ``` 55 | https://example.com/news?title=Title&custom=value&orderBy=name_desc 56 | ``` 57 | 58 | It will automatically apply built-in constraints onto the query as well as any custom scopes and criteria you need: 59 | 60 | ```php 61 | protected $searchable = [ 62 | // where 'title' equals 'Title' 63 | 'title', 64 | ]; 65 | 66 | protected $scopes = [ 67 | // and custom parameter used in your scope 68 | 'custom' => MyScope::class, 69 | ]; 70 | ``` 71 | 72 | ```php 73 | class MyScope extends ScopeAbstract 74 | { 75 | public function scope($builder, $value, $scope) 76 | { 77 | return $builder->where($scope, $value)->orWhere(...); 78 | } 79 | } 80 | ``` 81 | 82 | Ordering by any field is available: 83 | 84 | ```php 85 | protected $scopes = [ 86 | // orderBy field 87 | 'orderBy' => OrderByScope::class, 88 | ]; 89 | ``` 90 | 91 | Package can also apply any custom criteria: 92 | 93 | ```php 94 | return $this->news->withCriteria([ 95 | new MyCriteria([ 96 | 'category_id' => '1', 'name' => 'Name' 97 | ]) 98 | ... 99 | ])->get(); 100 | ``` 101 | 102 | ## Usage 103 | 104 | ### Create a Model 105 | 106 | Create your model: 107 | 108 | ```php 109 | namespace App; 110 | 111 | use Illuminate\Database\Eloquent\Model; 112 | 113 | class News extends Model 114 | { 115 | ... 116 | } 117 | ``` 118 | 119 | ### Create a Repository 120 | 121 | Extend it from `AwesIO\Repository\Eloquent\BaseRepository` and provide `entity()` method to return full model class name: 122 | 123 | ```php 124 | namespace App; 125 | 126 | use AwesIO\Repository\Eloquent\BaseRepository; 127 | 128 | class NewsRepository extends BaseRepository 129 | { 130 | public function entity() 131 | { 132 | return News::class; 133 | } 134 | } 135 | ``` 136 | 137 | ### Use built-in methods 138 | 139 | ```php 140 | use App\NewsRepository; 141 | 142 | class NewsController extends BaseController 143 | { 144 | protected $news; 145 | 146 | public function __construct(NewsRepository $news) 147 | { 148 | $this->news = $news; 149 | } 150 | .... 151 | } 152 | ``` 153 | 154 | Execute the query as a "select" statement or get all results: 155 | 156 | ```php 157 | $news = $this->news->get(); 158 | ``` 159 | 160 | Execute the query and get the first result: 161 | 162 | ```php 163 | $news = $this->news->first(); 164 | ``` 165 | 166 | Find a model by its primary key: 167 | 168 | ```php 169 | $news = $this->news->find(1); 170 | ``` 171 | 172 | Add basic where clauses and execute the query: 173 | 174 | ```php 175 | $news = $this->news->->findWhere([ 176 | // where id equals 1 177 | 'id' => '1', 178 | // other "where" operations 179 | ['news_category_id', '<', '3'], 180 | ... 181 | ]); 182 | ``` 183 | 184 | Paginate the given query: 185 | 186 | ```php 187 | $news = $this->news->paginate(15); 188 | ``` 189 | 190 | Paginate the given query into a simple paginator: 191 | 192 | ```php 193 | $news = $this->news->simplePaginate(15); 194 | ``` 195 | 196 | Paginate the given query by 'limit' request parameter: 197 | 198 | ```php 199 | $news = $this->news->smartPaginate(); 200 | ``` 201 | 202 | Add an "order by" clause to the query: 203 | 204 | ```php 205 | $news = $this->news->orderBy('title', 'desc')->get(); 206 | ``` 207 | 208 | Save a new model and return the instance: 209 | 210 | ```php 211 | $news = $this->news->create($request->all()); 212 | ``` 213 | 214 | Update a record: 215 | 216 | ```php 217 | $this->news->update($request->all(), $id); 218 | ``` 219 | 220 | Delete a record by id: 221 | 222 | ```php 223 | $this->news->destroy($id); 224 | ``` 225 | 226 | Attach models to the parent: 227 | 228 | ```php 229 | $this->news->attach($parentId, $relationship, $idsToAttach); 230 | ``` 231 | 232 | Detach models from the relationship: 233 | 234 | ```php 235 | $this->news->detach($parentId, $relationship, $idsToDetach); 236 | ``` 237 | 238 | Find model or throw an exception if not found: 239 | 240 | ```php 241 | $this->news->findOrFail($id); 242 | ``` 243 | 244 | Execute the query and get the first result or throw an exception: 245 | 246 | ```php 247 | $this->news->firstOrFail(); 248 | ``` 249 | 250 | ### Create a Criteria 251 | 252 | Criteria are a way to build up specific query conditions. 253 | 254 | ```php 255 | use AwesIO\Repository\Contracts\CriterionInterface; 256 | 257 | class MyCriteria implements CriterionInterface { 258 | 259 | protected $conditions; 260 | 261 | public function __construct(array $conditions) 262 | { 263 | $this->conditions = $conditions; 264 | } 265 | 266 | public function apply($entity) 267 | { 268 | foreach ($this->conditions as $field => $value) { 269 | $entity = $entity->where($field, '=', $value); 270 | } 271 | return $entity; 272 | } 273 | } 274 | ``` 275 | 276 | Multiple Criteria can be applied: 277 | 278 | ```php 279 | use App\NewsRepository; 280 | 281 | class NewsController extends BaseController 282 | { 283 | protected $news; 284 | 285 | public function __construct(NewsRepository $news) 286 | { 287 | $this->news = $news; 288 | } 289 | 290 | public function index() 291 | { 292 | return $this->news->withCriteria([ 293 | new MyCriteria([ 294 | 'category_id' => '1', 'name' => 'Name' 295 | ]), 296 | new WhereAdmin(), 297 | ... 298 | ])->get(); 299 | } 300 | } 301 | ``` 302 | 303 | ### Scope, Filter and Order 304 | 305 | In your repository define which fields can be used to scope your queries by setting `$searchable` property. 306 | 307 | ```php 308 | protected $searchable = [ 309 | // where 'title' equals parameter value 310 | 'title', 311 | // orWhere equals 312 | 'body' => 'or', 313 | // where like 314 | 'author' => 'like', 315 | // orWhere like 316 | 'email' => 'orLike', 317 | ]; 318 | ``` 319 | 320 | Search by searchables: 321 | 322 | ```php 323 | public function index($request) 324 | { 325 | return $this->news->scope($request)->get(); 326 | } 327 | ``` 328 | 329 | ``` 330 | https://example.com/news?title=Title&body=Text&author=&email=gmail 331 | ``` 332 | 333 | Also several serchables enabled by default: 334 | 335 | ```php 336 | protected $scopes = [ 337 | // orderBy field 338 | 'orderBy' => OrderByScope::class, 339 | // where created_at date is after 340 | 'begin' => WhereDateGreaterScope::class, 341 | // where created_at date is before 342 | 'end' => WhereDateLessScope::class, 343 | ]; 344 | ``` 345 | 346 | ```php 347 | $this->news->scope($request)->get(); 348 | ``` 349 | 350 | Enable ordering for specific fields by adding `$orderable` property to your model class: 351 | 352 | ```php 353 | public $orderable = ['email']; 354 | ``` 355 | 356 | ``` 357 | https://example.com/news?orderBy=email_desc&begin=2019-01-24&end=2019-01-26 358 | ``` 359 | 360 | `orderBy=email_desc` will order by email in descending order, `orderBy=email` - in ascending 361 | 362 | You can also build your own custom scopes. In your repository override `scope()` method: 363 | 364 | ```php 365 | public function scope($request) 366 | { 367 | // apply build-in scopes 368 | parent::scope($request); 369 | 370 | // apply custom scopes 371 | $this->entity = (new NewsScopes($request))->scope($this->entity); 372 | 373 | return $this; 374 | } 375 | ``` 376 | 377 | Create your `scopes` class and extend `ScopesAbstract` 378 | 379 | ```php 380 | use AwesIO\Repository\Scopes\ScopesAbstract; 381 | 382 | class NewsScopes extends ScopesAbstract 383 | { 384 | protected $scopes = [ 385 | // here you can add field-scope mappings 386 | 'field' => MyScope::class, 387 | ]; 388 | } 389 | ``` 390 | 391 | Now you can build any scopes you need: 392 | 393 | ```php 394 | use AwesIO\Repository\Scopes\ScopeAbstract; 395 | 396 | class MyScope extends ScopeAbstract 397 | { 398 | public function scope($builder, $value, $scope) 399 | { 400 | return $builder->where($scope, $value); 401 | } 402 | } 403 | ``` 404 | 405 | ### Artisan Commands 406 | 407 | Package provides useful artisan command: 408 | 409 | ```bash 410 | php artisan repository:generate Models/Order --scope=Search 411 | ``` 412 | 413 | #### It'll generate several classes for ```App\Models\Order```: 414 | 415 | Main repository: ```App\Repositories\Orders\OrdersRepository``` 416 | 417 | Main scopes class: ```App\Repositories\Orders\Scopes\OrdersScopes``` 418 | 419 | Individual search scope class: ```App\Repositories\Orders\Scopes\SearchOrdersScope``` 420 | 421 | ## Testing 422 | 423 | The coverage of the package is Coverage report. 424 | 425 | You can run the tests with: 426 | 427 | ```bash 428 | composer test 429 | ``` -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Commands/RepositoryMakeCommand.php: -------------------------------------------------------------------------------- 1 | type = $this->type . ' ' . $name; 52 | 53 | $model = $this->option('model'); 54 | 55 | $scopes = $this->option('scopes'); 56 | 57 | $stub = (new Replacer(parent::buildClass($name))) 58 | ->replace($scopes); 59 | 60 | $stub = str_replace('NamespacedDummyModel', $model, $stub); 61 | 62 | return str_replace('DummyModel', last(explode('\\', $model)), $stub); 63 | } 64 | 65 | /** 66 | * Get the stub file for the generator. 67 | * 68 | * @return string 69 | */ 70 | protected function getStub() 71 | { 72 | return __DIR__.'/stubs/repository.stub'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Commands/RepositoryMakeMainCommand.php: -------------------------------------------------------------------------------- 1 | baseNamespace = 'Repositories\\'.Str::plural($this->getNameInput()); 41 | 42 | $this->createRepository(); 43 | 44 | $scope = $this->option('scope'); 45 | 46 | $this->createScopes( 47 | $scope 48 | ? Str::singular($scope) . Str::plural($this->getNameInput()) . 'Scope' 49 | : $scope, 50 | Str::camel($scope) 51 | ); 52 | 53 | if ($scope) { 54 | $this->createScope($scope); 55 | } 56 | } 57 | 58 | /** 59 | * Create a repository. 60 | * 61 | * @return void 62 | */ 63 | protected function createRepository() 64 | { 65 | $this->call('make:repository', [ 66 | 'name' => $this->getNamespacedRepository(), 67 | '--model' => $this->getNamespacedModel(), 68 | '--scopes' => $this->getNamespacedScopes(), 69 | ]); 70 | } 71 | 72 | /** 73 | * Create a repository scopes. 74 | * 75 | * @return void 76 | */ 77 | protected function createScopes($scope, $scopeName) 78 | { 79 | $this->call('make:repository:scopes', [ 80 | 'name' => $this->getNamespacedScopes(), 81 | '--scope' => $scope, 82 | '--scope_name' => $scopeName, 83 | ]); 84 | } 85 | 86 | /** 87 | * Create a repository scope. 88 | * 89 | * @return void 90 | */ 91 | protected function createScope($scope) 92 | { 93 | $this->call('make:repository:scope', [ 94 | 'name' => $this->getNamespacedScope($scope) 95 | ]); 96 | } 97 | 98 | /** 99 | * Get the desired class name from the input. 100 | * 101 | * @return string 102 | */ 103 | protected function getNameInput() 104 | { 105 | return last(explode('/', trim($this->argument('modelName')))); 106 | } 107 | 108 | /** 109 | * Get the root namespace for the class. 110 | * 111 | * @return string 112 | */ 113 | protected function rootNamespace() 114 | { 115 | return $this->laravel->getNamespace(); 116 | } 117 | 118 | private function getNamespacedModel() 119 | { 120 | return implode( 121 | '\\', explode('/', trim($this->argument('modelName'))) 122 | ); 123 | } 124 | 125 | private function getNamespacedScopes() 126 | { 127 | return $this->baseNamespace .'\Scopes\\' 128 | . Str::plural($this->getNameInput()) . 'Scopes'; 129 | } 130 | 131 | private function getNamespacedScope($scope) 132 | { 133 | return $this->baseNamespace .'\Scopes\\' 134 | . Str::singular($scope) . Str::plural($this->getNameInput()) . 'Scope'; 135 | } 136 | 137 | private function getNamespacedRepository() 138 | { 139 | $repository = Str::studly(class_basename($this->getNameInput())); 140 | 141 | return $this->baseNamespace .'\\' 142 | . Str::plural($repository) . 'Repository'; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/Commands/RepositoryScopeMakeCommand.php: -------------------------------------------------------------------------------- 1 | type = $this->type . ' ' . $name; 52 | 53 | return (new Replacer(parent::buildClass($name))) 54 | ->replace($name); 55 | } 56 | 57 | /** 58 | * Get the stub file for the generator. 59 | * 60 | * @return string 61 | */ 62 | protected function getStub() 63 | { 64 | return __DIR__.'/stubs/scope.stub'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Commands/RepositoryScopesMakeCommand.php: -------------------------------------------------------------------------------- 1 | type = $this->type . ' ' . $name; 52 | 53 | $stub = (new Replacer(parent::buildClass($name))) 54 | ->replace($name); 55 | 56 | if ($scope = $this->option('scope')) { 57 | $stub = str_replace('SearchScope', $scope, $stub); 58 | $scopeName = $this->option('scope_name'); 59 | $stub = str_replace('search', $scopeName, $stub); 60 | } 61 | 62 | return $stub; 63 | } 64 | 65 | /** 66 | * Get the stub file for the generator. 67 | * 68 | * @return string 69 | */ 70 | protected function getStub() 71 | { 72 | return __DIR__.'/stubs/scopes.stub'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Commands/stubs/repository.stub: -------------------------------------------------------------------------------- 1 | entity = (new DummyScope($request))->scope($this->entity); 27 | return $this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Commands/stubs/scope.stub: -------------------------------------------------------------------------------- 1 | where($scope, $value); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Commands/stubs/scopes.stub: -------------------------------------------------------------------------------- 1 | SearchScope::class, 11 | ]; 12 | } -------------------------------------------------------------------------------- /src/Contracts/CriteriaInterface.php: -------------------------------------------------------------------------------- 1 | conditions = $conditions; 14 | } 15 | 16 | public function apply($entity) 17 | { 18 | foreach ($this->conditions as $field => $value) { 19 | 20 | if (is_array($value)) { 21 | 22 | list($field, $condition, $val) = $value; 23 | 24 | $entity = $entity->where($field, $condition, $val); 25 | 26 | } else { 27 | 28 | $entity = $entity->where($field, '=', $value); 29 | } 30 | } 31 | return $entity; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Eloquent/BaseRepository.php: -------------------------------------------------------------------------------- 1 | entity->get($columns); 19 | 20 | $this->reset(); 21 | 22 | return $results; 23 | } 24 | 25 | /** 26 | * Execute the query and get the first result. 27 | * 28 | * @param array $columns 29 | * @return \Illuminate\Database\Eloquent\Model|object|static|null 30 | */ 31 | public function first($columns = ['*']) 32 | { 33 | $results = $this->entity->first($columns); 34 | 35 | $this->reset(); 36 | 37 | return $results; 38 | } 39 | 40 | /** 41 | * Find a model by its primary key. 42 | * 43 | * @param mixed $id 44 | * @param array $columns 45 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null 46 | */ 47 | public function find($id, $columns = ['*']) 48 | { 49 | $results = $this->entity->find($id, $columns); 50 | 51 | $this->reset(); 52 | 53 | return $results; 54 | } 55 | 56 | /** 57 | * Add basic where clauses and execute the query. 58 | * 59 | * @param array $conditions 60 | * @param array $columns 61 | * 62 | * @return \Illuminate\Database\Eloquent\Collection 63 | */ 64 | public function findWhere(array $conditions, array $columns = ['*']) 65 | { 66 | return $this->withCriteria([ 67 | new FindWhere($conditions) 68 | ])->get($columns); 69 | } 70 | 71 | /** 72 | * Paginate the given query. 73 | * 74 | * @param int $perPage 75 | * @param array $columns 76 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 77 | * 78 | * @throws \InvalidArgumentException 79 | */ 80 | public function paginate($perPage = null, $columns = ['*']) 81 | { 82 | $results = $this->entity->paginate($perPage, $columns); 83 | 84 | $this->reset(); 85 | 86 | return $results; 87 | } 88 | 89 | /** 90 | * Paginate the given query into a simple paginator. 91 | * 92 | * @param int $perPage 93 | * @param array $columns 94 | * @return \Illuminate\Contracts\Pagination\Paginator 95 | */ 96 | public function simplePaginate($perPage = null, $columns = ['*']) 97 | { 98 | $results = $this->entity->simplePaginate($perPage, $columns); 99 | 100 | $this->reset(); 101 | 102 | return $results; 103 | } 104 | 105 | /** 106 | * Save a new model and return the instance. 107 | * 108 | * @param array $attributes 109 | * 110 | * @return \Illuminate\Database\Eloquent\Model 111 | */ 112 | public function create(array $attributes) 113 | { 114 | $results = $this->entity->create($attributes); 115 | 116 | $this->reset(); 117 | 118 | return $results; 119 | } 120 | 121 | /** 122 | * Update a record. 123 | * 124 | * @param array $values 125 | * @param int $id 126 | * 127 | * @return int 128 | */ 129 | public function update(array $values, $id, $attribute = "id") 130 | { 131 | $model = $this->entity->where($attribute, $id)->firstOrFail(); 132 | 133 | $results = $model->update($values); 134 | 135 | $this->reset(); 136 | 137 | return $results; 138 | } 139 | 140 | /** 141 | * Delete a record by id. 142 | * 143 | * @param int $id 144 | * 145 | * @return mixed 146 | */ 147 | public function destroy($id) 148 | { 149 | $results = $this->entity->destroy($id); 150 | 151 | $this->reset(); 152 | 153 | return $results; 154 | } 155 | 156 | /** 157 | * Attach models to the parent. 158 | * 159 | * @param int $id 160 | * @param string $relation 161 | * @param mixed $ids 162 | * @return void 163 | */ 164 | public function attach($id, $relation, $ids) 165 | { 166 | return $this->find($id)->{$relation}()->attach($ids); 167 | } 168 | 169 | /** 170 | * Detach models from the relationship. 171 | * 172 | * @param int $id 173 | * @param string $relation 174 | * @param mixed $ids 175 | * @return int 176 | */ 177 | public function detach($id, $relation, $ids) 178 | { 179 | return $this->find($id)->{$relation}()->detach($ids); 180 | } 181 | 182 | /** 183 | * Find a model by its primary key or throw an exception. 184 | * 185 | * @param mixed $id 186 | * @param array $columns 187 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[] 188 | * 189 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 190 | */ 191 | public function findOrFail($id, $columns = ['*']) 192 | { 193 | $results = $this->entity->findOrFail($id, $columns); 194 | 195 | $this->reset(); 196 | 197 | return $results; 198 | } 199 | 200 | /** 201 | * Find a model by its primary key or throw an exception and return repository instance. 202 | * 203 | * @param mixed $id 204 | * @param array $columns 205 | * @return \AwesIO\Repository\Contracts\RepositoryInterface 206 | * 207 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 208 | */ 209 | public function findOrFailRepo($id, $columns = ['*']) 210 | { 211 | $this->entity = $this->entity->findOrFail($id, $columns); 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Execute the query and get the first result or throw an exception. 218 | * 219 | * @param array $columns 220 | * @return \Illuminate\Database\Eloquent\Model|static 221 | * 222 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 223 | */ 224 | public function firstOrFail($columns = ['*']) 225 | { 226 | $results = $this->entity->firstOrFail($columns); 227 | 228 | $this->reset(); 229 | 230 | return $results; 231 | } 232 | 233 | /** 234 | * Paginate the given query by 'limit' request parameter 235 | * @return mixed 236 | */ 237 | public function smartPaginate($perPage = null) 238 | { 239 | $limit = (int) request()->input( 240 | config('awesio-repository.smart_paginate.request_parameter'), 241 | config('awesio-repository.smart_paginate.default_limit') 242 | ); 243 | 244 | if ($limit === 0) $limit = ($perPage) ?: config('awesio-repository.smart_paginate.default_limit'); 245 | 246 | $maxLimit = config('awesio-repository.smart_paginate.max_limit'); 247 | 248 | $limit = ($limit <= $maxLimit) ? $limit : $maxLimit; 249 | 250 | return $this->paginate($limit); 251 | } 252 | 253 | /** 254 | * Add an "order by" clause to the query. 255 | * 256 | * @param string $column 257 | * @param string $direction 258 | * @return $this 259 | */ 260 | public function orderBy($column, $direction = 'asc') 261 | { 262 | $this->entity = $this->entity->orderBy($column, $direction); 263 | 264 | return $this; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/Eloquent/RepositoryAbstract.php: -------------------------------------------------------------------------------- 1 | entity = $this->resolveEntity(); 21 | } 22 | 23 | abstract public function entity(); 24 | 25 | public function withCriteria(array $criteria) 26 | { 27 | foreach ($criteria as $criterion) { 28 | $this->entity = $criterion->apply($this->entity); 29 | } 30 | return $this; 31 | } 32 | 33 | public function scope($request) 34 | { 35 | $this->entity = (new Scopes($request, $this->searchable))->scope($this->entity); 36 | 37 | return $this; 38 | } 39 | 40 | public function reset() 41 | { 42 | $this->entity = $this->resolveEntity(); 43 | 44 | } 45 | 46 | public function __call($method, $parameters) 47 | { 48 | if (method_exists($this->entity, 'scope' . ucfirst($method))) { 49 | 50 | $this->entity = $this->entity->{$method}(...$parameters); 51 | 52 | return $this; 53 | } 54 | $this->entity = call_user_func_array([$this->entity, $method], $parameters); 55 | 56 | return $this; 57 | } 58 | 59 | public function __get($name) 60 | { 61 | return $this->entity->{$name}; 62 | } 63 | 64 | protected function resolveEntity() 65 | { 66 | $model = app()->make($this->entity()); 67 | 68 | if (!$model instanceof Model) { 69 | throw new RepositoryException( 70 | "Class {$this->entity()} must be an instance of Illuminate\\Database\\Eloquent\\Model" 71 | ); 72 | } 73 | return $model; 74 | } 75 | } -------------------------------------------------------------------------------- /src/Exceptions/RepositoryException.php: -------------------------------------------------------------------------------- 1 | publishes([ 21 | __DIR__.'/../config/awesio-repository.php' => config_path('awesio-repository.php'), 22 | ], 'config'); 23 | 24 | if ($this->app->runningInConsole()) { 25 | $this->commands([ 26 | RepositoryMakeMainCommand::class, 27 | RepositoryMakeCommand::class, 28 | RepositoryScopesMakeCommand::class, 29 | RepositoryScopeMakeCommand::class, 30 | ]); 31 | } 32 | } 33 | 34 | /** 35 | * Register any application services. 36 | * 37 | * @return void 38 | */ 39 | public function register() 40 | { 41 | $this->mergeConfigFrom(__DIR__.'/../config/awesio-repository.php', 'awesio-repository'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Scopes/Clauses/OrWhereLikeScope.php: -------------------------------------------------------------------------------- 1 | orWhere($scope, 'like', "%$value%"); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/OrWhereScope.php: -------------------------------------------------------------------------------- 1 | orWhere($scope, $value); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/OrderByScope.php: -------------------------------------------------------------------------------- 1 | orderable ?? []; 14 | 15 | if (array_pop($arr) == 'desc' 16 | && in_array($field = implode('_', $arr), $orderable)) { 17 | 18 | return $builder->orderBy($field, 'desc'); 19 | 20 | } elseif (in_array($value, $orderable)) { 21 | 22 | return $builder->orderBy($value, 'asc'); 23 | } 24 | return $builder; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/WhereDateGreaterScope.php: -------------------------------------------------------------------------------- 1 | whereDate('created_at', '>', $value); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/WhereDateLessScope.php: -------------------------------------------------------------------------------- 1 | whereDate('created_at', '<', $value); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/WhereLikeScope.php: -------------------------------------------------------------------------------- 1 | where($scope, 'like', "%$value%"); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/Clauses/WhereScope.php: -------------------------------------------------------------------------------- 1 | where($scope, $value); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Scopes/ScopeAbstract.php: -------------------------------------------------------------------------------- 1 | mappings(), $key); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Scopes/Scopes.php: -------------------------------------------------------------------------------- 1 | OrderByScope::class, 17 | 'begin' => WhereDateGreaterScope::class, 18 | 'end' => WhereDateLessScope::class, 19 | ]; 20 | 21 | public function __construct($request, $searchable) 22 | { 23 | parent::__construct($request); 24 | 25 | foreach ($searchable as $key => $value) { 26 | 27 | if (is_string($key)) { 28 | $this->scopes[$key] = $this->mappings($value); 29 | } else { 30 | $this->scopes[$value] = WhereScope::class; 31 | } 32 | } 33 | } 34 | 35 | protected function mappings($key) 36 | { 37 | $mappings = [ 38 | 'or' => OrWhereScope::class, 39 | 'like' => WhereLikeScope::class, 40 | 'orLike' => OrWhereLikeScope::class, 41 | ]; 42 | 43 | return $mappings[$key] ?? WhereScope::class; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Scopes/ScopesAbstract.php: -------------------------------------------------------------------------------- 1 | request = $request; 14 | } 15 | 16 | public function scope($builder) 17 | { 18 | $scopes = $this->getScopes(); 19 | 20 | foreach ($scopes as $scope => $value) { 21 | $builder = $this->resolveScope($scope)->scope($builder, $value, $scope); 22 | } 23 | return $builder; 24 | } 25 | 26 | protected function resolveScope($scope) 27 | { 28 | return new $this->scopes[$scope]; 29 | } 30 | 31 | protected function getScopes() 32 | { 33 | return $this->filterScopes( 34 | $this->request->only(array_keys($this->scopes)) 35 | ); 36 | } 37 | 38 | protected function filterScopes($scopes) 39 | { 40 | return array_filter($scopes, function ($scope) { 41 | return isset($scope); 42 | } 43 | ); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Services/Replacer.php: -------------------------------------------------------------------------------- 1 | stub = $stub; 14 | } 15 | 16 | public function replace(...$replaceables) 17 | { 18 | foreach ($replaceables as $replaceable) { 19 | 20 | $exploded = explode('\\', $replaceable); 21 | 22 | end($exploded); 23 | 24 | $resource = prev($exploded); 25 | 26 | $this->replaceDummies($replaceable, Str::singular($resource)); 27 | } 28 | return $this->stub; 29 | } 30 | 31 | private function replaceDummies($model, $name) 32 | { 33 | $model = str_replace('/', '\\', $model); 34 | 35 | $this->replaceNamespacedDummies($model, $name); 36 | 37 | $model = class_basename(trim($model, '\\')); 38 | 39 | $this->replaceNonNamespacedDummies($model, $name); 40 | } 41 | 42 | private function replaceNamespacedDummies($model, $name) 43 | { 44 | $namespaceModel = app()->getNamespace() . $model; 45 | 46 | $stub = (Str::startsWith($model, '\\')) 47 | ? str_replace('NamespacedDummy' . $name, trim($model, '\\'), $this->stub) 48 | : str_replace('NamespacedDummy' . $name, $namespaceModel, $this->stub); 49 | 50 | $this->stub = str_replace( 51 | "use {$namespaceModel};\nuse {$namespaceModel};", "use {$namespaceModel};", $stub 52 | ); 53 | } 54 | 55 | private function replaceNonNamespacedDummies($model, $name) 56 | { 57 | $stub = str_replace('DocDummy' . $name, Str::snake($model, ' '), $this->stub); 58 | 59 | $stub = str_replace('Dummy' . $name, $model, $stub); 60 | 61 | $plural = Str::plural(Str::before($model, $name)); 62 | 63 | $stub = str_replace('dummy' . $name, Str::camel($plural), $stub); 64 | 65 | $this->stub = str_replace('dummySnake' . $name, Str::snake($plural), $stub); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Stubs/InvalidRepository.php: -------------------------------------------------------------------------------- 1 | belongsToMany(Submodel::class); 17 | 18 | } 19 | public function scopeName($query, $name) 20 | { 21 | return $query->where('name', $name); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Stubs/Repository.php: -------------------------------------------------------------------------------- 1 | 'name']; 12 | } 13 | 14 | public function scope($builder, $value, $scope) 15 | { 16 | $scope = $this->resolveScopeValue($scope); 17 | 18 | return $builder->where($scope, $value); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/Stubs/Submodel.php: -------------------------------------------------------------------------------- 1 | artisan('db:seed', ['--class' => 'AwesIO\News\Seeds\NewsCategoriesTableSeeder']); 18 | 19 | $this->withFactories(__DIR__ . '/../database/factories'); 20 | } 21 | 22 | /** 23 | * Define environment setup. 24 | * 25 | * @param \Illuminate\Foundation\Application $app 26 | * @return void 27 | */ 28 | protected function getEnvironmentSetUp($app) 29 | { 30 | $app['config']->set('app.debug', env('APP_DEBUG', true)); 31 | 32 | $this->setUpDatabase($app); 33 | } 34 | 35 | /** 36 | * Load package service provider 37 | * @param \Illuminate\Foundation\Application $app 38 | * @return array 39 | */ 40 | protected function getPackageProviders($app) 41 | { 42 | return [ 43 | RepositoryServiceProvider::class 44 | ]; 45 | } 46 | 47 | protected function setUpDatabase($app) 48 | { 49 | $builder = $app['db']->connection()->getSchemaBuilder(); 50 | 51 | $builder->create('models', function (Blueprint $table) { 52 | $table->increments('id'); 53 | $table->string('name'); 54 | $table->timestamps(); 55 | }); 56 | 57 | $builder->create('submodels', function (Blueprint $table) { 58 | $table->increments('id'); 59 | $table->string('name'); 60 | $table->timestamps(); 61 | }); 62 | 63 | $builder->create('model_submodel', function (Blueprint $table) { 64 | $table->increments('id'); 65 | $table->integer('model_id'); 66 | $table->integer('submodel_id'); 67 | }); 68 | } 69 | } -------------------------------------------------------------------------------- /tests/Unit/Criteria/FindWhereTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | $model = factory(Model::class)->create(); 17 | 18 | $criterion = new FindWhere([ 19 | 'id' => $model->id, 20 | ['name', 'like', $model->name] 21 | ]); 22 | 23 | $results = $criterion->apply(new Model)->get(); 24 | 25 | $this->assertEquals($model->id, $results->first()->id); 26 | 27 | $this->assertCount(1, $results); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Unit/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(Model::class, $repository->entity()); 29 | } 30 | 31 | /** @test */ 32 | public function it_gets_collection() 33 | { 34 | $repository = new Repository; 35 | 36 | $this->assertInstanceOf(Collection::class, $repository->get()); 37 | } 38 | 39 | /** @test */ 40 | public function it_gets_first_model() 41 | { 42 | $model = factory(Model::class)->create(); 43 | 44 | $repository = new Repository; 45 | 46 | $this->assertInstanceOf(BaseModel::class, $result = $repository->first()); 47 | 48 | $this->assertEquals($model->name, $result->name); 49 | } 50 | 51 | /** @test */ 52 | public function it_finds_model() 53 | { 54 | $model = factory(Model::class)->create(); 55 | 56 | $repository = new Repository; 57 | 58 | $this->assertInstanceOf(BaseModel::class, $result = $repository->find($model->id)); 59 | 60 | $this->assertEquals($model->name, $result->name); 61 | } 62 | 63 | /** @test */ 64 | public function it_finds_model_by_where_clauses() 65 | { 66 | $model = factory(Model::class)->create(); 67 | 68 | $repository = new Repository; 69 | 70 | $this->assertInstanceOf(Collection::class, $results = $repository->findWhere([ 71 | 'name' => $model->name 72 | ])); 73 | 74 | $this->assertEquals($model->id, $results->first()->id); 75 | } 76 | 77 | /** @test */ 78 | public function it_resets_model() 79 | { 80 | $model1 = factory(Model::class)->create(); 81 | 82 | $model2 = factory(Model::class)->create(); 83 | 84 | $repository = new Repository; 85 | 86 | $results1 = $repository->findWhere([ 87 | 'name' => $model1->name 88 | ]); 89 | 90 | $results2 = $repository->findWhere([ 91 | 'name' => $model2->name 92 | ]); 93 | 94 | $this->assertEquals($model1->id, $results1->first()->id); 95 | 96 | $this->assertEquals($model2->id, $results2->first()->id); 97 | } 98 | 99 | /** @test */ 100 | public function it_uses_criteria() 101 | { 102 | $model = factory(Model::class)->create(); 103 | 104 | $repository = new Repository; 105 | 106 | $results = $repository->withCriteria([ 107 | new FindWhere([ 108 | 'name' => $model->name 109 | ]) 110 | ])->get(); 111 | 112 | $this->assertEquals($model->id, $results->first()->id); 113 | } 114 | 115 | /** @test */ 116 | public function it_scopes_request() 117 | { 118 | $model1 = factory(Model::class)->create(); 119 | 120 | $model2 = factory(Model::class)->create(); 121 | 122 | $repository = new Repository; 123 | 124 | $repository->searchable = [ 125 | 'name', 126 | ]; 127 | 128 | $request = Request::create( 129 | '/', 130 | 'GET', 131 | ['name' => $model2->name] 132 | ); 133 | 134 | $results = $repository->scope($request)->get(); 135 | 136 | $this->assertEquals($model2->id, $results->first()->id); 137 | } 138 | 139 | /** @test */ 140 | public function it_throws_exception_if_entity_doesnt_belong_to_valid_class() 141 | { 142 | $this->expectException(RepositoryException::class); 143 | 144 | $repository = new InvalidRepository; 145 | } 146 | 147 | /** @test */ 148 | public function it_paginates() 149 | { 150 | $model = factory(Model::class, 10)->create(); 151 | 152 | $repository = new Repository; 153 | 154 | $this->assertInstanceOf(LengthAwarePaginator::class, $results = $repository->paginate()); 155 | } 156 | 157 | /** @test */ 158 | public function it_simple_paginates() 159 | { 160 | $model = factory(Model::class, 10)->create(); 161 | 162 | $repository = new Repository; 163 | 164 | $this->assertInstanceOf(Paginator::class, $results = $repository->simplePaginate()); 165 | } 166 | 167 | /** @test */ 168 | public function it_creates_new_record() 169 | { 170 | $model = factory(Model::class)->make(); 171 | 172 | $repository = new Repository; 173 | 174 | $results = $repository->create($model->getAttributes()); 175 | 176 | $this->assertDatabaseHas('models', [ 177 | 'name' => $model->name 178 | ]); 179 | } 180 | 181 | /** @test */ 182 | public function it_updates_existing_record() 183 | { 184 | $model = factory(Model::class)->create(); 185 | 186 | $repository = new Repository; 187 | 188 | $results = $repository->update([ 189 | 'name' => $name = uniqid() 190 | ], $model->id); 191 | 192 | $this->assertDatabaseHas('models', [ 193 | 'name' => $name 194 | ]); 195 | } 196 | 197 | /** @test */ 198 | public function it_destroys_existing_record_by_id() 199 | { 200 | $model = factory(Model::class)->create(); 201 | 202 | $this->assertDatabaseHas('models', [ 203 | 'name' => $model->name 204 | ]); 205 | 206 | $repository = new Repository; 207 | 208 | $results = $repository->destroy($model->id); 209 | 210 | $this->assertDatabaseMissing('models', [ 211 | 'name' => $model->name 212 | ]); 213 | } 214 | 215 | /** @test */ 216 | public function it_attaches_model_to_parent() 217 | { 218 | $model = factory(Model::class)->create(); 219 | 220 | $submodel = factory(Submodel::class)->create(); 221 | 222 | $repository = new Repository; 223 | 224 | $repository->attach($model->id, 'submodels', $submodel->id); 225 | 226 | $this->assertDatabaseHas('model_submodel', [ 227 | 'model_id' => $model->id, 228 | 'submodel_id' => $submodel->id 229 | ]); 230 | } 231 | 232 | /** @test */ 233 | public function it_detaches_model_from_parent() 234 | { 235 | $model = factory(Model::class)->create(); 236 | 237 | $submodel = factory(Submodel::class)->create(); 238 | 239 | $repository = new Repository; 240 | 241 | $repository->attach($model->id, 'submodels', $submodel->id); 242 | 243 | $this->assertDatabaseHas('model_submodel', [ 244 | 'model_id' => $model->id, 245 | 'submodel_id' => $submodel->id 246 | ]); 247 | 248 | $repository->detach($model->id, 'submodels', $submodel->id); 249 | 250 | $this->assertDatabaseMissing('model_submodel', [ 251 | 'model_id' => $model->id, 252 | 'submodel_id' => $submodel->id 253 | ]); 254 | } 255 | 256 | /** @test */ 257 | public function it_finds_or_fails() 258 | { 259 | $model = factory(Model::class)->create(); 260 | 261 | $repository = new Repository; 262 | 263 | $result = $repository->findOrFail($model->id); 264 | 265 | $this->assertEquals($model->name, $result->name); 266 | 267 | $this->expectException(ModelNotFoundException::class); 268 | 269 | $repository->findOrFail($model->id + 1); 270 | } 271 | 272 | /** @test */ 273 | public function it_finds_and_returns_respository_instance_or_fails() 274 | { 275 | $model = factory(Model::class, 20)->create(); 276 | 277 | $repository = new Repository; 278 | 279 | $result = $repository->findOrFailRepo($model->first()->id); 280 | 281 | $this->assertInstanceOf(RepositoryInterface::class, $result); 282 | 283 | $this->expectException(ModelNotFoundException::class); 284 | 285 | $repository->findOrFailRepo(22); 286 | } 287 | 288 | /** @test */ 289 | public function it_finds_first_or_fails() 290 | { 291 | $model = factory(Model::class)->create(); 292 | 293 | $repository = new Repository; 294 | 295 | $result = $repository->where('id', $model->id)->firstOrFail(); 296 | 297 | $this->assertInstanceOf(Model::class, $result); 298 | 299 | $this->assertEquals($model->name, $result->name); 300 | 301 | $this->expectException(ModelNotFoundException::class); 302 | 303 | $repository->where('id', $model->id + 1)->firstOrFail(); 304 | } 305 | 306 | /** @test */ 307 | public function it_can_smart_paginate() 308 | { 309 | $model = factory(Model::class, 20)->create(); 310 | 311 | $repository = new Repository; 312 | 313 | $result = $repository->smartPaginate(); 314 | 315 | $this->assertInstanceOf(LengthAwarePaginator::class, $result); 316 | } 317 | 318 | /** @test */ 319 | public function it_can_smart_paginate_by_default_limit() 320 | { 321 | $model = factory(Model::class, 20)->create(); 322 | 323 | $repository = new Repository; 324 | 325 | $result = $repository->smartPaginate(); 326 | 327 | $this->assertEquals( 328 | config('awesio-repository.smart_paginate.default_limit'), 329 | $result->perPage() 330 | ); 331 | } 332 | 333 | /** @test */ 334 | public function it_can_smart_paginate_by_limit_parameter_if_its_less_or_equals_max_limit() 335 | { 336 | $model = factory(Model::class, 20)->create(); 337 | 338 | $repository = new Repository; 339 | 340 | request()->merge([ 341 | config('awesio-repository.smart_paginate.request_parameter') => $limit = random_int( 342 | 1, config('awesio-repository.smart_paginate.max_limit') 343 | ) 344 | ]); 345 | 346 | $result = $repository->smartPaginate(); 347 | 348 | $this->assertEquals($limit, $result->perPage()); 349 | } 350 | 351 | /** @test */ 352 | public function it_can_smart_paginate_by_max_limit() 353 | { 354 | $model = factory(Model::class, 20)->create(); 355 | 356 | $repository = new Repository; 357 | 358 | request()->merge([ 359 | config('awesio-repository.smart_paginate.request_parameter') => $limit = random_int( 360 | $max = config('awesio-repository.smart_paginate.max_limit'), $max + 1000 361 | ) 362 | ]); 363 | 364 | $result = $repository->smartPaginate(); 365 | 366 | $this->assertEquals($max, $result->perPage()); 367 | } 368 | 369 | /** @test */ 370 | public function it_can_smart_paginate_if_request_parametert_is_string() 371 | { 372 | $model = factory(Model::class, 20)->create(); 373 | 374 | $repository = new Repository; 375 | 376 | request()->merge([ 377 | config('awesio-repository.smart_paginate.request_parameter') => 'string' 378 | ]); 379 | 380 | $result = $repository->smartPaginate(); 381 | 382 | $this->assertEquals(15, $result->perPage()); 383 | } 384 | 385 | /** @test */ 386 | public function it_executes_model_scopes() 387 | { 388 | $model = factory(Model::class)->create([ 389 | 'name' => $name = uniqid() 390 | ]); 391 | 392 | $repository = new Repository; 393 | 394 | $result = $repository->name($name)->get(); 395 | 396 | $this->assertEquals($name, $result->first()->name); 397 | } 398 | 399 | /** @test */ 400 | public function it_executes_model_methods() 401 | { 402 | $model = factory(Model::class)->create(); 403 | 404 | $repository = new Repository; 405 | 406 | $result = $repository->submodels(); 407 | 408 | $this->assertInstanceOf(Repository::class, $result); 409 | } 410 | 411 | /** @test */ 412 | public function it_returns_model_props() 413 | { 414 | $model = factory(Model::class)->create(); 415 | 416 | $model->submodels()->save( 417 | $submodel = factory(Submodel::class)->create() 418 | ); 419 | 420 | $repository = new Repository; 421 | 422 | $result = $repository->findOrFailRepo($model->id)->submodels; 423 | 424 | $this->assertInstanceOf(Submodel::class, $result->first()); 425 | 426 | $this->assertEquals($submodel->name, $result->first()->name); 427 | } 428 | 429 | /** @test */ 430 | public function it_orders_by() 431 | { 432 | factory(Model::class, 5)->create(); 433 | 434 | $repository = new Repository; 435 | 436 | $results = $repository->get(); 437 | 438 | $this->assertEquals(1, $results->first()->id); 439 | 440 | $results = $repository->orderBy('id', 'desc')->get(); 441 | 442 | $this->assertEquals(5, $results->first()->id); 443 | } 444 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/OrWhereLikeTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $model = factory(Model::class)->create(); 18 | 19 | $scope = new OrWhereLikeScope; 20 | 21 | $results = $scope->scope( 22 | new Model, 23 | Str::after($model->name, $model->name[0]), 24 | 'name' 25 | )->get(); 26 | 27 | $this->assertEquals($model->id, $results->first()->id); 28 | 29 | $this->assertCount(1, $results); 30 | } 31 | 32 | /** @test */ 33 | public function it_scopes_by_orwhere_clause() 34 | { 35 | factory(Model::class, 5)->create(); 36 | 37 | $model1 = factory(Model::class)->create(); 38 | $model2 = factory(Model::class)->create(); 39 | 40 | $scope = new OrWhereLikeScope; 41 | 42 | $builder = $scope->scope( 43 | new Model, 44 | Str::after($model1->name, $model1->name[0]), 45 | 'name' 46 | ); 47 | 48 | $results = $scope->scope( 49 | $builder, 50 | Str::after($model2->name, $model2->name[0]), 51 | 'name' 52 | )->get(); 53 | 54 | $this->assertCount(2, $results); 55 | } 56 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/OrWhereTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | $model = factory(Model::class)->create(); 17 | 18 | $scope = new OrWhereScope; 19 | 20 | $results = $scope->scope(new Model, $model->name, 'name')->get(); 21 | 22 | $this->assertEquals($model->id, $results->first()->id); 23 | 24 | $this->assertCount(1, $results); 25 | } 26 | 27 | /** @test */ 28 | public function it_scopes_by_orwhere_clause() 29 | { 30 | factory(Model::class, 5)->create(); 31 | 32 | $model1 = factory(Model::class)->create(); 33 | $model2 = factory(Model::class)->create(); 34 | 35 | $scope = new OrWhereScope; 36 | 37 | $builder = $scope->scope(new Model, $model1->name, 'name'); 38 | 39 | $results = $scope->scope($builder, $model2->name, 'name')->get(); 40 | 41 | $this->assertCount(2, $results); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/OrderByTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], $scope->mappings()); 17 | } 18 | 19 | /** @test */ 20 | public function it_returns_ignores_wrong_order_postfix() 21 | { 22 | factory(Model::class, 5)->create(); 23 | 24 | $scope = new OrderByScope; 25 | 26 | $results = Model::get(); 27 | 28 | $this->assertEquals(1, $results->first()->id); 29 | 30 | $results = $scope->scope(new Model, 'id_wrong-order-postfix', '')->get(); 31 | 32 | $this->assertEquals(1, $results->first()->id); 33 | } 34 | 35 | /** @test */ 36 | public function it_orders_by_asc() 37 | { 38 | factory(Model::class, 5)->create(); 39 | 40 | $scope = new OrderByScope; 41 | 42 | $results = Model::get(); 43 | 44 | $this->assertEquals(1, $results->first()->id); 45 | 46 | $results = $scope->scope(new Model, 'id', '')->get(); 47 | 48 | $this->assertEquals(1, $results->first()->id); 49 | } 50 | 51 | /** @test */ 52 | public function it_orders_by_desc() 53 | { 54 | factory(Model::class, 5)->create(); 55 | 56 | $scope = new OrderByScope; 57 | 58 | $results = Model::get(); 59 | 60 | $this->assertEquals(1, $results->first()->id); 61 | 62 | $results = $scope->scope(new Model, 'id_desc', '')->get(); 63 | 64 | $this->assertEquals(5, $results->first()->id); 65 | } 66 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/ScopeTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $model = factory(Model::class)->create(); 18 | 19 | $scope = new Scope; 20 | 21 | $results = $scope->scope(new Model, $model->name, 'full_name')->get(); 22 | 23 | $this->assertEquals($model->id, $results->first()->id); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/ScopesTest.php: -------------------------------------------------------------------------------- 1 | 'name'] 19 | ); 20 | 21 | $scopes = new Scopes($request, ['name' => 'like']); 22 | 23 | factory(Model::class, 5)->create(); 24 | 25 | $model = factory(Model::class)->create([ 26 | 'name' => 'name' 27 | ]); 28 | 29 | $results = Model::get(); 30 | 31 | $this->assertEquals(1, $results->first()->id); 32 | 33 | $results = $scopes->scope(new Model)->get(); 34 | 35 | $this->assertEquals($model->id, $results->first()->id); 36 | } 37 | 38 | /** @test */ 39 | public function it_scopes_request_by_orderBy() 40 | { 41 | $request = Request::create( 42 | '/', 43 | 'GET', 44 | ['orderBy' => 'id_desc'] 45 | ); 46 | 47 | $scopes = new Scopes($request, []); 48 | 49 | factory(Model::class, 5)->create(); 50 | 51 | $results = Model::get(); 52 | 53 | $this->assertEquals(1, $results->first()->id); 54 | 55 | $results = $scopes->scope(new Model)->get(); 56 | 57 | $this->assertEquals(5, $results->first()->id); 58 | } 59 | 60 | /** @test */ 61 | public function it_scopes_request_by_begin_date() 62 | { 63 | $model = factory(Model::class)->create(); 64 | 65 | $date = $model->created_at; 66 | 67 | $request = Request::create( 68 | '/', 69 | 'GET', 70 | ['begin' => $date->subYear()->toDateString()] 71 | ); 72 | 73 | $scopes = new Scopes($request, []); 74 | 75 | $results = $scopes->scope(new Model)->get(); 76 | 77 | $this->assertEquals(1, $results->count()); 78 | 79 | $date = $model->created_at; 80 | 81 | $request = Request::create( 82 | '/', 83 | 'GET', 84 | ['begin' => $date->addYear()->toDateString()] 85 | ); 86 | 87 | $scopes = new Scopes($request, []); 88 | 89 | $results = $scopes->scope(new Model)->get(); 90 | 91 | $this->assertEquals(0, $results->count()); 92 | } 93 | 94 | /** @test */ 95 | public function it_scopes_request_by_end_date() 96 | { 97 | $model = factory(Model::class)->create(); 98 | 99 | $date = $model->created_at; 100 | 101 | $request = Request::create( 102 | '/', 103 | 'GET', 104 | ['end' => $date->subYear()->toDateString()] 105 | ); 106 | 107 | $scopes = new Scopes($request, []); 108 | 109 | $results = $scopes->scope(new Model)->get(); 110 | 111 | $this->assertEquals(0, $results->count()); 112 | 113 | $date = $model->created_at; 114 | 115 | $request = Request::create( 116 | '/', 117 | 'GET', 118 | ['end' => $date->addYear()->toDateString()] 119 | ); 120 | 121 | $scopes = new Scopes($request, []); 122 | 123 | $results = $scopes->scope(new Model)->get(); 124 | 125 | $this->assertEquals(1, $results->count()); 126 | } 127 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/WhereLikeTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $model = factory(Model::class)->create(); 18 | 19 | $scope = new WhereLikeScope; 20 | 21 | $results = $scope->scope( 22 | new Model, 23 | Str::after($model->name, $model->name[0]), 24 | 'name' 25 | )->get(); 26 | 27 | $this->assertEquals($model->id, $results->first()->id); 28 | 29 | $this->assertCount(1, $results); 30 | } 31 | 32 | /** @test */ 33 | public function it_scopes_by_where_clause() 34 | { 35 | factory(Model::class, 5)->create(); 36 | 37 | $model1 = factory(Model::class)->create(); 38 | $model2 = factory(Model::class)->create(); 39 | 40 | $scope = new WhereLikeScope; 41 | 42 | $builder = $scope->scope( 43 | new Model, 44 | Str::after($model1->name, $model1->name[0]), 45 | 'name' 46 | ); 47 | 48 | $results = $scope->scope($builder, $model2->id, 'id')->get(); 49 | 50 | $this->assertCount(0, $results); 51 | } 52 | } -------------------------------------------------------------------------------- /tests/Unit/Scopes/WhereTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | $model = factory(Model::class)->create(); 17 | 18 | $scope = new WhereScope; 19 | 20 | $results = $scope->scope(new Model, $model->name, 'name')->get(); 21 | 22 | $this->assertEquals($model->id, $results->first()->id); 23 | 24 | $this->assertCount(1, $results); 25 | } 26 | 27 | /** @test */ 28 | public function it_scopes_by_where_clause() 29 | { 30 | factory(Model::class, 5)->create(); 31 | 32 | $model1 = factory(Model::class)->create(); 33 | $model2 = factory(Model::class)->create(); 34 | 35 | $scope = new WhereScope; 36 | 37 | $builder = $scope->scope(new Model, $model1->name, 'name'); 38 | 39 | $results = $scope->scope($builder, $model2->id, 'id')->get(); 40 | 41 | $this->assertCount(0, $results); 42 | } 43 | } --------------------------------------------------------------------------------