├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
└── src
├── Heroicpixels
└── Filterable
│ ├── Filterable.php
│ ├── FilterableServiceProvider.php
│ ├── FilterableTrait.php
│ └── FilterableWrapper.php
└── config
├── .gitkeep
└── config.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 | - 5.5
7 |
8 | before_script:
9 | - curl -s http://getcomposer.org/installer | php
10 | - php composer.phar install --dev
11 |
12 | script: phpunit
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Dave Hodgins, Heroicpixels
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Filterable
2 | =======
3 | This package gives you a convenient way to automatically filter Eloquent results based on query string parameters in the URL. Filterable parses the query string, compares it with columns that you'd like to automatically filter, then creates a dynamic scope that is used by Eloquent to construct the SQL.
4 |
5 | * [Installation](#installation)
6 | * [Copyright & License](#license)
7 | * [Usage](#usage)
8 | * [Single Value](#single-value)
9 | * [Multiple Values](#multiple-values)
10 | * [Multiple Parameters](#multiple-parameters)
11 | * [Boolean Operators](#boolean-operators)
12 | * [Comparison Operators](#comparison-operators)
13 |
14 |
15 | Installation
16 | ============
17 | Add the package to 'require' in your composer.json file:
18 |
19 | "require": {
20 | "heroicpixels/filterable": "dev-master"
21 | },
22 |
23 | Run 'composer dump-autoload' from the command line:
24 |
25 | #composer dump-autoload
26 |
27 | Register the service provider in 'app/config/app.php'. Service provider:
28 |
29 | 'providers' => array(
30 | \\...
31 | 'Heroicpixels\Filterable\FilterableServiceProvider',
32 | \\...
33 | );
34 |
35 | License
36 | =======
37 | Copyright 2014 Dave Hodgins
38 | Released under MIT license (http://opensource.org/licenses/MIT). See LICENSE file for details.
39 |
40 | Usage
41 | =====
42 |
43 | NOTE: this package also includes a version (**FilterableWrapper.php**) that can be used to wrap a DB or Eloquent object, and a version (**FilterableTrait.php**) that can be used as a trait with an Eloquent model.
44 |
45 | **Filterable.php**
46 |
47 | Edit your Eloquent model to extend 'Heroicpixels\Filterable\Filterable'.
48 |
49 | class Object extends Heroicpixels\Filterable\Filterable {
50 | // ...
51 | }
52 |
53 | **FilterableWrapper.php**
54 |
55 | Give FilterableWrapper a DB or Eloquent object.
56 |
57 | $object = DB::table('objects');
58 | $objects = FilterableWrapper($object);
59 |
60 | **FilterableTrait.php**
61 |
62 | class Object extends Eloquent {
63 |
64 | use Heroicpixels\Filterable\FilterableTrait;
65 |
66 | }
67 |
68 | The examples below use the Filterable class!
69 |
70 | In the above example, class Object corresponds to table 'objects':
71 |
72 | | id | color | shape | total |
73 | |:-----|:--------|:-----------|:--------|
74 | | 1 | red | square | 150 |
75 | | 2 | blue | square | 2000 |
76 | | 3 | green | circle | 575 |
77 | | 4 | yellow | triangle | 15 |
78 | | 5 | red | triangle | 900 |
79 | | 6 | red | triangle | 600 |
80 |
81 | Filterable Columns
82 | ------------------
83 | Specify the column you want to automatically filter.
84 |
85 | $columns = [ 'color', 'shape', 'total' ];
86 |
87 | For example:
88 |
89 | http://www.your-site/?color=blue&shape=round&total=500
90 |
91 | You can also alias the columns if you prefer not to reveal them:
92 |
93 | $columns = [ 'col' => 'color', 'sha' => 'shape', 'tot' => 'total' ];
94 |
95 | For example:
96 |
97 | http://www.your-site/?col=blue&sha=round&tot=500
98 |
99 | To filter results, simply pass the columns to Eloquent using filterColumns():
100 |
101 | $objects = Object::filterColumns($columns)->get()->toArray();
102 |
103 | You can also filter joins:
104 |
105 | $columns = array('color' => 'objects.color',
106 | 'name' => 'objects.name',
107 | 'shape' => 'objects.shape',
108 | 'category' => 'cat_object.cat_id');
109 | $objects = Object::join('cat_object', 'objects.id', '=', 'cat_object.object_id')
110 | ->filterColumns($columns)
111 | ->get()->toArray();
112 |
113 | And you can filter eager loads:
114 |
115 | /**
116 | * Columns available in main query
117 | */
118 | $columns = array('color' => 'objects.color',
119 | 'name' => 'objects.name',
120 | 'shape' => 'objects.shape');
121 | $objects = Object::with(array('categories' => function($q) {
122 | /**
123 | * Columns available to sub-query
124 | */
125 | $columns = array('category' => 'cat_object.cat_id');
126 | $q->filterColumns($columns);
127 | }))->filterColumns($columns)
128 | ->get()
129 | ->toArray();
130 |
131 | The following examples demonstrate how query string parameters can be used.
132 |
133 | Single Value
134 | ------------
135 |
136 | ?color=red
137 |
138 | SELECT ... WHERE ... color = 'red'
139 |
140 | | id | color | shape | total |
141 | |:-----|:--------|:-----------|:--------|
142 | | 1 | red | square | 150 |
143 | | 5 | red | triangle | 900 |
144 | | 6 | red | triangle | 600 |
145 |
146 | Multiple Values
147 | ---------------
148 |
149 | ?color[]=red&color[]=blue
150 |
151 | SELECT ... WHERE ... color = 'red' OR color = 'blue'
152 |
153 | | id | color | shape | total |
154 | |:-----|:--------|:-----------|:--------|
155 | | 1 | red | square | 150 |
156 | | 2 | blue | square | 2000 |
157 | | 5 | red | triangle | 900 |
158 | | 6 | red | triangle | 600 |
159 |
160 | Multiple Parameters
161 | -------------------
162 |
163 | ?color[]=red&shape[]=triangle
164 |
165 | SELECT ... WHERE ... color = 'red' AND shape = 'triangle'
166 |
167 | | id | color | shape | total |
168 | |:-----|:--------|:-----------|:--------|
169 | | 5 | red | triangle | 900 |
170 | | 6 | red | triangle | 600 |
171 |
172 | Boolean Operators
173 | -----------------
174 |
175 | ?color[]=red&shape[]=triangle&bool[shape]=or
176 |
177 | SELECT ... WHERE ... color = 'red' OR shape = 'triangle'
178 |
179 | | id | color | shape | total |
180 | |:-----|:--------|:-----------|:--------|
181 | | 4 | yellow | triangle | 15 |
182 | | 5 | red | triangle | 900 |
183 | | 6 | red | triangle | 600 |
184 |
185 | Comparison Operators
186 | --------------------
187 | **Greater Than**
188 |
189 | ?total=599&operator[total]=>
190 |
191 | SELECT ... WHERE ... total > '599'
192 |
193 | | id | color | shape | total |
194 | |:-----|:--------|:-----------|:--------|
195 | | 2 | blue | square | 2000 |
196 | | 5 | red | triangle | 900 |
197 | | 6 | red | triangle | 600 |
198 |
199 | **Less Than**
200 |
201 | ?total=600&operator[total]=<
202 |
203 | SELECT ... WHERE ... total < '600'
204 |
205 | | id | color | shape | total |
206 | |:-----|:--------|:-----------|:--------|
207 | | 1 | red | square | 150 |
208 | | 3 | green | circle | 575 |
209 | | 4 | yellow | triangle | 15 |
210 |
211 | **Not Equal**
212 |
213 | ?shape=triangle&operator[shape]=!=
214 |
215 | SELECT ... WHERE ... shape != 'triangle'
216 |
217 | | id | color | shape | total |
218 | |:-----|:--------|:-----------|:--------|
219 | | 4 | yellow | triangle | 15 |
220 | | 5 | red | triangle | 900 |
221 | | 6 | red | triangle | 600 |
222 |
223 | **Between**
224 |
225 | ?total[start]=900&total[end]=5000
226 |
227 | SELECT ... WHERE ... total BETWEEN '900' AND '5000'
228 |
229 | | id | color | shape | total |
230 | |:-----|:--------|:-----------|:--------|
231 | | 2 | blue | square | 2000 |
232 | | 5 | red | triangle | 900 |
233 |
234 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "heroicpixels/filterable",
3 | "type": "library",
4 | "description": "Laravel package for dynamically filtering Eloquent results using URL query string.",
5 | "license": "MIT",
6 | "keywords": ["laravel", "laravel 4", "eloquent", "database"],
7 | "authors": [
8 | {
9 | "name": "Dave Hodgins",
10 | "email": "dave@heroicpixels.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.3.0",
15 | "illuminate/support": ">=4.1"
16 | },
17 | "autoload": {
18 | "psr-0": {
19 | "Heroicpixels\\Filterable\\": "src/"
20 | }
21 | },
22 | "minimum-stability": "stable",
23 | "extra": {
24 | "branch-alias": {
25 | "dev-master": "1.0.x-dev"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Heroicpixels/Filterable/Filterable.php:
--------------------------------------------------------------------------------
1 | filterable = array(
29 | 'bools' => array('and' => 'where', 'or' => 'orWhere'),
30 | 'callbacks' => array(),
31 | 'columns' => array(),
32 | 'defaultOperator' => '=',
33 | 'defaultWhere' => 'where',
34 | 'filters' => array(),
35 | 'orderby' => 'orderby',
36 | 'order' => 'order',
37 | 'operators' => array('=', '<', '<=', '>', '>=', '!=', 'like'),
38 | 'qstring' => array()
39 | );
40 | return $this;
41 | }
42 | /**
43 | * Specify the columns that can be dynamically filtered from the query string.
44 | * Columns can be referenced directly - array('column1', 'column2') - or they
45 | * can be aliased - array('co1' => 'column1', 'co2' => 'column2').
46 | *
47 | * @param array $columns The columns to use
48 | * @param bool $append Append or overwrite exisiting values
49 | *
50 | * @return $this
51 | */
52 | public function setColumns($columns, $append = true)
53 | {
54 | if ( is_null($this->filterable) ) {
55 | $this->resetFilterableOptions();
56 | }
57 | if ( count(array_filter(array_keys($columns), 'is_string')) == 0 ) {
58 | // Numeric indexes, so build new associative index array
59 | $columns = array_combine($columns, $columns);
60 | }
61 | if ( !$append ) {
62 | // Overwrite data
63 | $this->filterable['columns'] = array();
64 | }
65 |
66 | foreach ( $columns as $k => $v ) {
67 | // Strip off callbacks
68 | if ( is_callable($v) ) {
69 | $this->filterable['callbacks'][] = $v;
70 | unset($columns[$k]);
71 | }
72 | }
73 | $this->filterable['columns'] = array_merge($this->filterable['columns'], $columns);
74 | return $this;
75 | }
76 | /**
77 | * Parse the query string
78 | *
79 | * @param $str array The query string
80 | * @param $append Append or overwrite existing query string data
81 | * @param $default Default to $_SERVER['QUERY_STRING'] if $str isn't given
82 | *
83 | * @return $this
84 | */
85 | public function setQuerystring(array $str = array(), $append = true, $default = true)
86 | {
87 | if ( is_null($this->filterable) ) {
88 | $this->resetFilterableOptions();
89 | }
90 | if ( sizeof($str) == 0 && $default ) {
91 | // Default to PHP query string
92 | parse_str($_SERVER['QUERY_STRING'], $this->filterable['qstring']);
93 | } else {
94 | $this->filterable['qstring'] = $str;
95 | }
96 | if ( sizeof($this->filterable['qstring']) > 0 ) {
97 | if ( !$append ) {
98 | // Overwrite data
99 | $this->filterable['filters'] = array();
100 | }
101 | foreach ( $this->filterable['qstring'] as $k => $v ) {
102 | if ( $v == '' ) {
103 | continue;
104 | }
105 | $thisColumn = isset($this->filterable['columns'][$k]) ? $this->filterable['columns'][$k] : false;
106 | if ( $thisColumn ) {
107 | // Query string part matches column (or alias)
108 | $this->filterable['filters'][$thisColumn]['val'] = $v;
109 | // Evaluate boolean parameter in query string
110 | $thisBoolData = isset($this->filterable['qstring']['bool'][$k]) ? $this->filterable['qstring']['bool'][$k] : false;
111 | $thisBoolAvailable = $thisBoolData && isset($this->filterable['bools'][$thisBoolData]) ? $this->filterable['bools'][$thisBoolData] : false;
112 | if ( $thisBoolData && $thisBoolAvailable ) {
113 | $this->filterable['filters'][$thisColumn]['boolean'] = $thisBoolAvailable;
114 | } else {
115 | $this->filterable['filters'][$thisColumn]['boolean'] = $this->filterable['defaultWhere'];
116 | }
117 | // Evaluate operator parameters in the query string
118 | if ( isset($this->filterable['qstring']['operator'][$k]) && in_array($this->filterable['qstring']['operator'][$k], $this->filterable['operators']) ) {
119 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['qstring']['operator'][$k];
120 | } else {
121 | // Default operator
122 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['defaultOperator'];
123 | }
124 | }
125 | }
126 | }
127 | return $this;
128 | }
129 | /**
130 | * Laravel Eloquent query scope.
131 | *
132 | * @param $query Eloquent query object
133 | * @return Eloquent query object
134 | */
135 | public function scopeFilterColumns($query, $columns = array(), $validate = false)
136 | {
137 | if ( sizeof($columns) > 0 ) {
138 | // Set columns that can be filtered
139 | $this->setColumns($columns);
140 | }
141 | // Validate columns
142 | if ( $validate ) {
143 | $this->validateColumns();
144 | }
145 | // Ensure that query string is parsed at least once
146 | if ( sizeof($this->filterable['filters']) == 0 ) {
147 | $this->setQuerystring();
148 | }
149 | // Apply conditions to Eloquent query object
150 | if ( sizeof($this->filterable['filters']) > 0 ) {
151 | foreach ( $this->filterable['filters'] as $k => $v ) {
152 | $where = $v['boolean'];
153 | if ( is_array($v['val']) ) {
154 | if ( isset($v['val']['start']) && isset($v['val']['end']) ) {
155 | // BETWEEN a AND b
156 | $query->whereBetween($k, array($v['val']['start'], $v['val']['end']));
157 | } else {
158 | // a = b OR c = d OR...
159 | $query->{$where}(function($q) use ($k, $v, $query)
160 | {
161 | foreach ( $v['val'] as $key => $val ) {
162 | $q->orWhere($k, $v['operator'], $val);
163 | }
164 | });
165 | }
166 | } else {
167 | // a = b
168 | $query->{$where}($k, $v['operator'], $v['val']);
169 | }
170 | }
171 | }
172 | // Apply callbacks
173 | if ( sizeof($this->filterable['callbacks']) > 0 ) {
174 | foreach ( $this->filterable['callbacks'] as $v ) {
175 | $v($query);
176 | }
177 | }
178 | // Sorting
179 | if ( isset($this->filterable['qstring'][$this->filterable['orderby']]) && isset($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]]) ) {
180 | $order = isset($this->filterable['qstring'][$this->filterable['order']]) ? $this->filterable['qstring'][$this->filterable['order']] : 'asc';
181 | $query->orderBy($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]], $order);
182 | }
183 | return $query;
184 | }
185 | /**
186 | *
187 | * Validate the specified columns against the table's actual columns.
188 | *
189 | * @return $this
190 | */
191 | public function validateColumns()
192 | {
193 | if ( class_exists('\\Doctrine\\DBAL\\Driver\\PDOMySql\\Driver') ) {
194 | $columns = DB::connection()->getDoctrineSchemaManager()->listTableColumns($this->getTable());
195 | foreach ( $columns as $column ) {
196 | $name = $column->getName();
197 | $columns[$name] = $name;
198 | }
199 | $this->filterable['columns'] = array_intersect($this->filterable['columns'], $columns);
200 | return $this;
201 | }
202 | die('You must have Doctrine installed in order to validate columns');
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/Heroicpixels/Filterable/FilterableServiceProvider.php:
--------------------------------------------------------------------------------
1 | filterable = array(
24 | 'bools' => array('and' => 'where', 'or' => 'orWhere'),
25 | 'callbacks' => array(),
26 | 'columns' => array(),
27 | 'defaultOperator' => '=',
28 | 'defaultWhere' => 'where',
29 | 'filters' => array(),
30 | 'orderby' => 'orderby',
31 | 'order' => 'order',
32 | 'operators' => array('=', '<', '<=', '>', '>=', '!=', 'like'),
33 | 'qstring' => array()
34 | );
35 | return $this;
36 | }
37 | /**
38 | * Specify the columns that can be dynamically filtered from the query string.
39 | * Columns can be referenced directly - array('column1', 'column2') - or they
40 | * can be aliased - array('co1' => 'column1', 'co2' => 'column2').
41 | *
42 | * @param array $columns The columns to use
43 | * @param bool $append Append or overwrite exisiting values
44 | *
45 | * @return $this
46 | */
47 | public function setColumns($columns, $append = true)
48 | {
49 | if ( is_null($this->filterable) ) {
50 | $this->resetFilterableOptions();
51 | }
52 | if ( count(array_filter(array_keys($columns), 'is_string')) == 0 ) {
53 | // Numeric indexes, so build new associative index array
54 | $columns = array_combine($columns, $columns);
55 | }
56 | if ( !$append ) {
57 | // Overwrite data
58 | $this->filterable['columns'] = array();
59 | }
60 |
61 | foreach ( $columns as $k => $v ) {
62 | // Strip off callbacks
63 | if ( is_callable($v) ) {
64 | $this->filterable['callbacks'][] = $v;
65 | unset($columns[$k]);
66 | }
67 | }
68 | $this->filterable['columns'] = array_merge($this->filterable['columns'], $columns);
69 | return $this;
70 | }
71 | /**
72 | * Parse the query string
73 | *
74 | * @param $str array The query string
75 | * @param $append Append or overwrite existing query string data
76 | * @param $default Default to $_SERVER['QUERY_STRING'] if $str isn't given
77 | *
78 | * @return $this
79 | */
80 | public function setQuerystring(array $str = array(), $append = true, $default = true)
81 | {
82 | if ( is_null($this->filterable) ) {
83 | $this->resetFilterableOptions();
84 | }
85 | if ( sizeof($str) == 0 && $default ) {
86 | // Default to PHP query string
87 | parse_str($_SERVER['QUERY_STRING'], $this->filterable['qstring']);
88 | } else {
89 | $this->filterable['qstring'] = $str;
90 | }
91 | if ( sizeof($this->filterable['qstring']) > 0 ) {
92 | if ( !$append ) {
93 | // Overwrite data
94 | $this->filterable['filters'] = array();
95 | }
96 | foreach ( $this->filterable['qstring'] as $k => $v ) {
97 | if ( $v == '' ) {
98 | continue;
99 | }
100 | $thisColumn = isset($this->filterable['columns'][$k]) ? $this->filterable['columns'][$k] : false;
101 | if ( $thisColumn ) {
102 | // Query string part matches column (or alias)
103 | $this->filterable['filters'][$thisColumn]['val'] = $v;
104 | // Evaluate boolean parameter in query string
105 | $thisBoolData = isset($this->filterable['qstring']['bool'][$k]) ? $this->filterable['qstring']['bool'][$k] : false;
106 | $thisBoolAvailable = $thisBoolData && isset($this->filterable['bools'][$thisBoolData]) ? $this->filterable['bools'][$thisBoolData] : false;
107 | if ( $thisBoolData && $thisBoolAvailable ) {
108 | $this->filterable['filters'][$thisColumn]['boolean'] = $thisBoolAvailable;
109 | } else {
110 | $this->filterable['filters'][$thisColumn]['boolean'] = $this->filterable['defaultWhere'];
111 | }
112 | // Evaluate operator parameters in the query string
113 | if ( isset($this->filterable['qstring']['operator'][$k]) && in_array($this->filterable['qstring']['operator'][$k], $this->filterable['operators']) ) {
114 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['qstring']['operator'][$k];
115 | } else {
116 | // Default operator
117 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['defaultOperator'];
118 | }
119 | }
120 | }
121 | }
122 | return $this;
123 | }
124 | /**
125 | * Laravel Eloquent query scope.
126 | *
127 | * @param $query Eloquent query object
128 | * @return Eloquent query object
129 | */
130 | public function scopeFilterColumns($query, $columns = array(), $validate = false)
131 | {
132 | if ( sizeof($columns) > 0 ) {
133 | // Set columns that can be filtered
134 | $this->setColumns($columns);
135 | }
136 | // Validate columns
137 | if ( $validate ) {
138 | $this->validateColumns();
139 | }
140 | // Ensure that query string is parsed at least once
141 | if ( sizeof($this->filterable['filters']) == 0 ) {
142 | $this->setQuerystring();
143 | }
144 | // Apply conditions to Eloquent query object
145 | if ( sizeof($this->filterable['filters']) > 0 ) {
146 | foreach ( $this->filterable['filters'] as $k => $v ) {
147 | $where = $v['boolean'];
148 | if ( is_array($v['val']) ) {
149 | if ( isset($v['val']['start']) && isset($v['val']['end']) ) {
150 | // BETWEEN a AND b
151 | $query->whereBetween($k, array($v['val']['start'], $v['val']['end']));
152 | } else {
153 | // a = b OR c = d OR...
154 | $query->{$where}(function($q) use ($k, $v, $query)
155 | {
156 | foreach ( $v['val'] as $key => $val ) {
157 | $q->orWhere($k, $v['operator'], $val);
158 | }
159 | });
160 | }
161 | } else {
162 | // a = b
163 | $query->{$where}($k, $v['operator'], $v['val']);
164 | }
165 | }
166 | }
167 | // Apply callbacks
168 | if ( sizeof($this->filterable['callbacks']) > 0 ) {
169 | foreach ( $this->filterable['callbacks'] as $v ) {
170 | $v($query);
171 | }
172 | }
173 | // Sorting
174 | if ( isset($this->filterable['qstring'][$this->filterable['orderby']]) && isset($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]]) ) {
175 | $order = isset($this->filterable['qstring'][$this->filterable['order']]) ? $this->filterable['qstring'][$this->filterable['order']] : 'asc';
176 | $query->orderBy($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]], $order);
177 | }
178 | return $query;
179 | }
180 | /**
181 | *
182 | * Validate the specified columns against the table's actual columns.
183 | *
184 | * @return $this
185 | */
186 | public function validateColumns()
187 | {
188 | if ( class_exists('\\Doctrine\\DBAL\\Driver\\PDOMySql\\Driver') ) {
189 | $columns = DB::connection()->getDoctrineSchemaManager()->listTableColumns($this->getTable());
190 | foreach ( $columns as $column ) {
191 | $name = $column->getName();
192 | $columns[$name] = $name;
193 | }
194 | $this->filterable['columns'] = array_intersect($this->filterable['columns'], $columns);
195 | return $this;
196 | }
197 | die('You must have Doctrine installed in order to validate columns');
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/Heroicpixels/Filterable/FilterableWrapper.php:
--------------------------------------------------------------------------------
1 | model = $model;
25 | }
26 |
27 | /**
28 | * Setup data structures and default values
29 | */
30 | public function resetFilterableOptions() {
31 | $this->filterable = array(
32 | 'bools' => array('and' => 'where', 'or' => 'orWhere'),
33 | 'callbacks' => array(),
34 | 'columns' => array(),
35 | 'defaultOperator' => '=',
36 | 'defaultWhere' => 'where',
37 | 'filters' => array(),
38 | 'orderby' => 'orderby',
39 | 'order' => 'order',
40 | 'operators' => array('=', '<', '<=', '>', '>=', '!=', 'like'),
41 | 'qstring' => array()
42 | );
43 | return $this;
44 | }
45 | /**
46 | * Specify the columns that can be dynamically filtered from the query string.
47 | * Columns can be referenced directly - array('column1', 'column2') - or they
48 | * can be aliased - array('co1' => 'column1', 'co2' => 'column2').
49 | *
50 | * @param array $columns The columns to use
51 | * @param bool $append Append or overwrite exisiting values
52 | *
53 | * @return $this
54 | */
55 | public function setColumns($columns, $append = true) {
56 | if ( is_null($this->filterable) ) {
57 | $this->resetFilterableOptions();
58 | }
59 | if ( count(array_filter(array_keys($columns), 'is_string')) == 0 ) {
60 | // Numeric indexes, so build new associative index array
61 | $columns = array_combine($columns, $columns);
62 | }
63 | if ( !$append ) {
64 | // Overwrite data
65 | $this->filterable['columns'] = array();
66 | }
67 | foreach ( $columns as $k => $v ) {
68 | // Strip off callbacks
69 | if ( is_callable($v) ) {
70 | $this->filterable['callbacks'][] = $v;
71 | unset($columns[$k]);
72 | }
73 | }
74 | $this->filterable['columns'] = array_merge($this->filterable['columns'], $columns);
75 | return $this;
76 | }
77 | /**
78 | * Parse the query string
79 | *
80 | * @param $str array The query string
81 | * @param $append Append or overwrite existing query string data
82 | * @param $default Default to $_SERVER['QUERY_STRING'] if $str isn't given
83 | *
84 | * @return $this
85 | */
86 | public function setQuerystring(array $str = array(), $append = true, $default = true) {
87 | if ( is_null($this->filterable) ) {
88 | $this->resetFilterableOptions();
89 | }
90 | if ( sizeof($str) == 0 && $default ) {
91 | // Default to PHP query string
92 | parse_str($_SERVER['QUERY_STRING'], $this->filterable['qstring']);
93 | } else {
94 | $this->filterable['qstring'] = $str;
95 | }
96 | if ( sizeof($this->filterable['qstring']) > 0 ) {
97 | if ( !$append ) {
98 | // Overwrite data
99 | $this->filterable['filters'] = array();
100 | }
101 | foreach ( $this->filterable['qstring'] as $k => $v ) {
102 | if ( $v == '' ) {
103 | continue;
104 | }
105 | $thisColumn = isset($this->filterable['columns'][$k]) ? $this->filterable['columns'][$k] : false;
106 | if ( $thisColumn ) {
107 | // Query string part matches column (or alias)
108 | $this->filterable['filters'][$thisColumn]['val'] = $v;
109 | // Evaluate boolean parameter in query string
110 | $thisBoolData = isset($this->filterable['qstring']['bool'][$k]) ? $this->filterable['qstring']['bool'][$k] : false;
111 | $thisBoolAvailable = $thisBoolData && isset($this->filterable['bools'][$thisBoolData]) ? $this->filterable['bools'][$thisBoolData] : false;
112 | if ( $thisBoolData && $thisBoolAvailable ) {
113 | $this->filterable['filters'][$thisColumn]['boolean'] = $thisBoolAvailable;
114 | } else {
115 | $this->filterable['filters'][$thisColumn]['boolean'] = $this->filterable['defaultWhere'];
116 | }
117 | // Evaluate operator parameters in the query string
118 | if ( isset($this->filterable['qstring']['operator'][$k]) && in_array($this->filterable['qstring']['operator'][$k], $this->filterable['operators']) ) {
119 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['qstring']['operator'][$k];
120 | } else {
121 | // Default operator
122 | $this->filterable['filters'][$thisColumn]['operator'] = $this->filterable['defaultOperator'];
123 | }
124 | }
125 | }
126 | }
127 | return $this;
128 | }
129 | /**
130 | * Apply filters to DB/Eloquent object
131 | *
132 | * @param $query Eloquent query object
133 | * @return Eloquent query object
134 | */
135 | public function filterColumns($columns = array(), $validate = false) {
136 | if ( sizeof($columns) > 0 ) {
137 | // Set columns that can be filtered
138 | $this->setColumns($columns);
139 | }
140 | // Validate columns
141 | if ( $validate ) {
142 | $this->validateColumns();
143 | }
144 | // Ensure that query string is parsed at least once
145 | if ( sizeof($this->filterable['filters']) == 0 ) {
146 | $this->setQuerystring();
147 | }
148 | // Apply conditions to Eloquent query object
149 | if ( sizeof($this->filterable['filters']) > 0 ) {
150 | foreach ( $this->filterable['filters'] as $k => $v ) {
151 | $where = $v['boolean'];
152 | if ( is_array($v['val']) ) {
153 | if ( isset($v['val']['start']) && isset($v['val']['end']) ) {
154 | // BETWEEN a AND b
155 | $this->model->whereBetween($k, array($v['val']['start'], $v['val']['end']));
156 | } else {
157 | // a = b OR c = d OR...
158 | $this->model->{$where}(function($q) use ($k, $v)
159 | {
160 | foreach ( $v['val'] as $key => $val ) {
161 | $q->orWhere($k, $v['operator'], $val);
162 | }
163 | });
164 | }
165 | } else {
166 | // a = b
167 | $this->model->{$where}($k, $v['operator'], $v['val']);
168 | }
169 | }
170 | }
171 | // Apply callbacks
172 | if ( sizeof($this->filterable['callbacks']) > 0 ) {
173 | foreach ( $this->filterable['callbacks'] as $v ) {
174 | $v($this->model);
175 | }
176 | }
177 | // Sorting
178 | if ( isset($this->filterable['qstring'][$this->filterable['orderby']]) && isset($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]]) ) {
179 | $order = isset($this->filterable['qstring'][$this->filterable['order']]) ? $this->filterable['qstring'][$this->filterable['order']] : 'asc';
180 | $this->model->orderBy($this->filterable['columns'][$this->filterable['qstring'][$this->filterable['orderby']]], $order);
181 | }
182 | return $this->model;
183 | }
184 | /**
185 | *
186 | * Validate the specified columns against the table's actual columns.
187 | *
188 | * @return $this
189 | */
190 | public function validateColumns() {
191 | if ( class_exists('\\Doctrine\\DBAL\\Driver\\PDOMySql\\Driver') ) {
192 | $columns = DB::connection()->getDoctrineSchemaManager()->listTableColumns($this->getTable());
193 | foreach ( $columns as $column ) {
194 | $name = $column->getName();
195 | $columns[$name] = $name;
196 | }
197 | $this->filterable['columns'] = array_intersect($this->filterable['columns'], $columns);
198 | return $this;
199 | }
200 | die('You must have Doctrine installed in order to validate columns');
201 | }
202 | /**
203 | * Pass methods to the DB/Eloquent object
204 | */
205 | public function __call($name, $arguments) {
206 | $retrieval = in_array($name, array('all', 'find', 'findOrFail', 'first', 'get', 'lists', 'paginate'));
207 | $this->model = call_user_func_array(array($this->model, $name), $arguments);
208 | if ( $retrieval ) {
209 | return $this->model;
210 | } else {
211 | return $this;
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/config/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heroicpixels/filterable/04d6bdeaf5fbc77e19d666cb06739a4af85d3d4f/src/config/.gitkeep
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | '=',
3 | 'defaultWhere' => 'where');
--------------------------------------------------------------------------------