├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Filter.php ├── FilterInterface.php ├── Filterable.php └── QueryBuilder.php └── tests ├── .gitkeep ├── EloquentFilterTest.php ├── filters ├── PublishedFilter.php └── StatusActiveFilter.php ├── migrations └── 2015_03_08_005622_create_posts_table.php └── models └── Post.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5.9 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - hhvm 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --prefer-source --no-interaction --dev 13 | 14 | script: phpunit 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sercan Çakır 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eloquent Filterable 2 | 3 | With this package, you can optimize query clauses calling before or after the filter. You can manage your queries from a single interface. 4 | 5 | ## Installation 6 | 7 | [PHP](https://php.net/) 5.5.9+ or [HHVM](http://hhvm.com/), and [Composer](https://getcomposer.org/) are required. 8 | 9 | To get the latest version of Eloquent Filterable, simply add the following line to the require block of your `composer.json` file: 10 | 11 | ```php 12 | "require": { 13 | "mayoz/eloquent-filterable": "~2.0" 14 | } 15 | ``` 16 | 17 | You'll then need to run `composer install` or `composer update` to download it and have the autoloader updated. Or use to shortcut installed through terminal: 18 | 19 | ```bash 20 | composer require mayoz/eloquent-filterable ~2.0 21 | ``` 22 | 23 | ## Usage 24 | 25 | 1. Create your query filters. 26 | 2. Create your Eloquent models. 27 | 3. Define `Filterable` trait and use the query filters in the Eloquent model. 28 | 29 | ### Create Filters 30 | 31 | All filters should be extend `Mayoz\Filter\Filter` abstract class. Thus, can be used `before` and `after` methods in your filters. 32 | 33 | #### The Before Method 34 | 35 | The `before` method responsible to position the **head** of the WHERE clause of the query. For example; we need `published = 1` of query WHERE clause. However, this clause would like to work on before the other clauses. 36 | 37 | ```php 38 | where('published', '=', 1); 53 | } 54 | } 55 | ``` 56 | 57 | #### The After Method 58 | 59 | The `after` method responsible to position the **end** of the WHERE clause of the query. For example, if need `status = 'active'` of query WHERE clause. However, this clause would like to work on after the other clauses. 60 | 61 | ```php 62 | where('status', '=', 'active'); 77 | } 78 | } 79 | ``` 80 | 81 | ### Create Model 82 | 83 | Create your model file. If you want to manage queries add the `Filterable` trait your model file. And than, assign the all associative filters to `$filters` variable. Consider the following example: 84 | 85 | ```php 86 | **Important:** The order of the filter is important when adding (if need multiple before or after filters) query clause. The before filters are added the head of the clause according to the reference sequence. Likewise, the after filters. 110 | 111 | ### Debug 112 | 113 | Now the `Post` model is ready to use. You ready? Okay, we're testing the query. Don't forget to enable the [query logging](http://laravel.com/docs/5.1/database#listening-for-query-events) for examine the model queries. 114 | 115 | ```php 116 | $model = \App\Post::where('views', '>', 100)->get(); 117 | ``` 118 | 119 | Query logging output: 120 | 121 | ```sql 122 | SELECT * 123 | FROM `posts` 124 | WHERE `published` = 1 # added by automatic filter. 125 | AND `views` > 100 # user defined where clause 126 | AND `status` = 'active' # added by automatic filter. 127 | ``` 128 | 129 | ## Why Use? 130 | 131 | For example, you might want to show only the approved text of the visitors on the site. However, administrators can see them all. Create a new extended filter: 132 | 133 | ```php 134 | where('status', '=', 'active'); 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | Hocus, pocus! Visitors will display only the active posts. But administrator display all. Ok? 159 | 160 | # Contributing 161 | 162 | Love innovation and simplicity. Please use issues all errors or suggestions reporting. Follow the steps for contributing: 163 | 164 | 1. Fork the repo. 165 | 2. Follow the [Laravel Coding Style](http://laravel.com/docs/master/contributions#coding-style). 166 | 3. If necessary, check your changes with unittest. 167 | 4. Commit all changes. 168 | 5. Push your commit and create a new PR. 169 | 6. Wait for merge the PR. 170 | 171 | # Unit Test 172 | 173 | Please create your tests and check before PR. Use the command: 174 | 175 | ```bash 176 | $ phpunit 177 | ``` 178 | 179 | # License 180 | 181 | Eloquent Filterable is licensed under [The MIT License (MIT)](https://github.com/mayoz/eloquent-filterable/blob/master/LICENSE). 182 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mayoz/eloquent-filterable", 3 | "description": "Query filter for your Eloquent models.", 4 | "keywords": ["laravel", "eloquent", "filter"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Sercan Çakır", 9 | "email": "srcnckr@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9", 14 | "illuminate/database": "5.1.*" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "~4.0", 18 | "orchestra/testbench": "~3.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Mayoz\\Filter\\": "src/" 23 | } 24 | }, 25 | "minimum-stability": "stable" 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Filter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Mayoz\Filter; 13 | 14 | /** 15 | * Filter. 16 | * 17 | * @author Sercan Çakır 18 | */ 19 | abstract class Filter implements FilterInterface 20 | { 21 | /** 22 | * The first executable filter clause. 23 | * 24 | * @param mixed $query 25 | * @return void 26 | */ 27 | public function before($query) 28 | { 29 | // nothing 30 | } 31 | 32 | /** 33 | * The last executable filter clause. 34 | * 35 | * @param mixed $query 36 | * @return void 37 | */ 38 | public function after($query) 39 | { 40 | // nothing 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/FilterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Mayoz\Filter; 13 | 14 | /** 15 | * Filter interface. 16 | * 17 | * @author Sercan Çakır 18 | */ 19 | interface FilterInterface 20 | { 21 | /** 22 | * The first executable filter clause. 23 | * 24 | * @param mixed $query 25 | * @return void 26 | */ 27 | public function before($query); 28 | 29 | /** 30 | * The last executable filter clause. 31 | * 32 | * @param mixed $query 33 | * @return void 34 | */ 35 | public function after($query); 36 | } 37 | -------------------------------------------------------------------------------- /src/Filterable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Mayoz\Filter; 13 | 14 | use Exception; 15 | 16 | /** 17 | * Filter trait. 18 | * 19 | * @author Sercan Çakır 20 | */ 21 | trait Filterable 22 | { 23 | /** 24 | * Get a new query builder for the model's table. 25 | * 26 | * @return \Illuminate\Database\Eloquent\Builder 27 | */ 28 | public function newQuery() 29 | { 30 | $query = parent::newQuery(); 31 | 32 | $this->filterQuery($query, 'before'); 33 | 34 | return $query; 35 | } 36 | 37 | /** 38 | * Get a new query builder instance for the connection. 39 | * 40 | * @return \Mayoz\Filter\QueryBuilder 41 | */ 42 | protected function newBaseQueryBuilder() 43 | { 44 | $conn = $this->getConnection(); 45 | 46 | $grammar = $conn->getQueryGrammar(); 47 | 48 | $builder = new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); 49 | 50 | $builder->setModel($this); 51 | 52 | return $builder; 53 | } 54 | 55 | /** 56 | * Filter handler. 57 | * 58 | * @param mixed $query 59 | * @param string $schedule 60 | * @return void 61 | * @throws \Exception 62 | */ 63 | public function filterQuery($query, $schedule = 'before') 64 | { 65 | if (property_exists($this, 'filters')) 66 | { 67 | array_map(function($filter) use ($query, $schedule) 68 | { 69 | $instance = new $filter; 70 | 71 | // All filters must be implements the FilterableInterface. 72 | // Trust me, this is necessary for the future version capabilities. 73 | if ( ! $instance instanceof FilterInterface) 74 | { 75 | throw new Exception("The filter must be implement `Mayoz\Filter\FilterInterface`!"); 76 | } 77 | 78 | // The filter should have of before or after (or both) methods. 79 | // Let's check, do meet the requested method of control? 80 | if ( ! method_exists($instance, $schedule)) 81 | { 82 | throw new Exception("The `{$schedule}` method not found in `{$filter}`!"); 83 | } 84 | 85 | call_user_func([$instance, $schedule], $query); 86 | }, $this->filters); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Mayoz\Filter; 13 | 14 | use Illuminate\Database\Query\Builder; 15 | use Illuminate\Database\Eloquent\Model; 16 | 17 | /** 18 | * Query builder. 19 | * 20 | * @author Sercan Çakır 21 | */ 22 | class QueryBuilder extends Builder 23 | { 24 | /** 25 | * The model being queried. 26 | * 27 | * @var \Illuminate\Database\Eloquent\Model 28 | */ 29 | protected $model; 30 | 31 | /** 32 | * Set a model instance for the model being queried. 33 | * 34 | * @param \Illuminate\Database\Eloquent\Model $model 35 | * @return static 36 | */ 37 | public function setModel(Model $model) 38 | { 39 | $this->model = $model; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Execute the query as a "select" statement. 46 | * 47 | * @param array $columns 48 | * @return array|static[] 49 | */ 50 | public function get($columns = array('*')) 51 | { 52 | if ($this->model instanceof Model) 53 | { 54 | $this->model->filterQuery($this, 'after'); 55 | } 56 | 57 | return parent::get($columns); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayoz/eloquent-filterable/aab09899403ac11a19087008608db39e40476256/tests/.gitkeep -------------------------------------------------------------------------------- /tests/EloquentFilterTest.php: -------------------------------------------------------------------------------- 1 | app->make('Illuminate\Contracts\Console\Kernel'); 16 | 17 | // Call migrations specific to our tests, e.g. to seed the db 18 | $artisan->call('migrate', array( 19 | '--database' => 'testbench', 20 | '--path' => '../tests/migrations', 21 | )); 22 | } 23 | 24 | /** 25 | * Define environment setup. 26 | * 27 | * @param Illuminate\Foundation\Application $app 28 | * @return void 29 | */ 30 | protected function getEnvironmentSetUp($app) 31 | { 32 | $app['path.base'] = __DIR__ . '/../src'; 33 | 34 | $app['config']->set('database.default', 'testbench'); 35 | $app['config']->set('database.connections.testbench', [ 36 | 'driver' => 'sqlite', 37 | 'database' => ':memory:', 38 | 'prefix' => '', 39 | ]); 40 | 41 | DB::connection()->enableQueryLog(); 42 | } 43 | 44 | public function testAll() 45 | { 46 | $post = Post::all(); 47 | $debug = last(DB::getQueryLog()); 48 | 49 | $this->assertCount(3, $post); 50 | $this->assertEquals('select * from "posts" where "published" = ? and "status" = ?', $debug['query']); 51 | } 52 | 53 | public function testGet() 54 | { 55 | $post = Post::where('views', '>=', 10)->get(); 56 | $debug = last(DB::getQueryLog()); 57 | 58 | $this->assertCount(2, $post); 59 | $this->assertEquals('select * from "posts" where "published" = ? and "views" >= ? and "status" = ?', $debug['query']); 60 | } 61 | 62 | public function testFirst() 63 | { 64 | $post = Post::first(); 65 | $debug = last(DB::getQueryLog()); 66 | 67 | $this->assertEquals('Bar', $post->title); 68 | $this->assertEquals('select * from "posts" where "published" = ? and "status" = ? limit 1', $debug['query']); 69 | } 70 | 71 | public function testFirstWithClause() 72 | { 73 | $post = Post::where('views', '>', 10)->first(); 74 | $debug = last(DB::getQueryLog()); 75 | 76 | $this->assertEquals('FooBar', $post->title); 77 | $this->assertEquals('select * from "posts" where "published" = ? and "views" > ? and "status" = ? limit 1', $debug['query']); 78 | } 79 | 80 | public function testAggregate() 81 | { 82 | $count = Post::count(); 83 | $debug = last(DB::getQueryLog()); 84 | 85 | $this->assertEquals(3, $count); 86 | $this->assertEquals('select count(*) as aggregate from "posts" where "published" = ? and "status" = ?', $debug['query']); 87 | } 88 | 89 | public function testAggregateWithClause() 90 | { 91 | $count = Post::where('views', '>=', 10)->count(); 92 | $debug = last(DB::getQueryLog()); 93 | 94 | $this->assertEquals(2, $count); 95 | $this->assertEquals('select count(*) as aggregate from "posts" where "published" = ? and "views" >= ? and "status" = ?', $debug['query']); 96 | } 97 | 98 | public function testExists() 99 | { 100 | $exists = Post::where('views', '>=', 10)->exists(); 101 | $debug = last(DB::getQueryLog()); 102 | 103 | $this->assertTrue($exists); 104 | $this->assertEquals('select exists(select * from "posts" where "published" = ? and "views" >= ?) as "exists"', $debug['query']); 105 | } 106 | 107 | public function testLists() 108 | { 109 | $lists = Post::where('views', '>=', 10)->lists('id', 'title'); 110 | $debug = last(DB::getQueryLog()); 111 | 112 | $this->assertEquals(['Bar' => 2, 'FooBar' => 3], $lists->toArray()); 113 | $this->assertEquals('select "id", "title" from "posts" where "published" = ? and "views" >= ? and "status" = ?', $debug['query']); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /tests/filters/PublishedFilter.php: -------------------------------------------------------------------------------- 1 | where('published', '=', 1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/filters/StatusActiveFilter.php: -------------------------------------------------------------------------------- 1 | where('status', '=', 'active'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/migrations/2015_03_08_005622_create_posts_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('title'); 18 | $table->integer('views')->default(0); 19 | $table->integer('published'); 20 | $table->string('status'); 21 | }); 22 | 23 | DB::table('posts')->insert([ 24 | 'title' => 'Foo', 25 | 'views' => 10, 26 | 'published' => 0, 27 | 'status' => 'active' 28 | ]); 29 | 30 | DB::table('posts')->insert([ 31 | 'title' => 'Bar', 32 | 'views' => 10, 33 | 'published' => 1, 34 | 'status' => 'active' 35 | ]); 36 | 37 | DB::table('posts')->insert([ 38 | 'title' => 'FooBar', 39 | 'views' => 20, 40 | 'published' => 1, 41 | 'status' => 'active' 42 | ]); 43 | 44 | DB::table('posts')->insert([ 45 | 'title' => 'Baz', 46 | 'views' => 10, 47 | 'published' => 1, 48 | 'status' => 'passive' 49 | ]); 50 | 51 | DB::table('posts')->insert([ 52 | 'title' => 'FooBaz', 53 | 'views' => 5, 54 | 'published' => 1, 55 | 'status' => 'active' 56 | ]); 57 | 58 | DB::table('posts')->insert([ 59 | 'title' => 'Qux', 60 | 'views' => 5, 61 | 'published' => 1, 62 | 'status' => 'passive' 63 | ]); 64 | } 65 | 66 | /** 67 | * Reverse the migrations. 68 | * 69 | * @return void 70 | */ 71 | public function down() 72 | { 73 | Schema::drop('posts'); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/models/Post.php: -------------------------------------------------------------------------------- 1 |