├── .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 | [](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 | [](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 | [](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 |
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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ##
48 |
49 |
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
.
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
.
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 | }
--------------------------------------------------------------------------------