├── .github └── workflows │ └── testing.yml ├── .gitignore ├── README.md ├── composer.json ├── config └── advanced_filter.php ├── phpunit.xml ├── src ├── AdvancedFilterServiceProvider.php ├── Exceptions │ ├── DatatypeNotFoundException.php │ ├── OperatorNotFoundException.php │ ├── UnsupportedDriverException.php │ └── UnsupportedOperatorException.php ├── Fields │ ├── Field.php │ ├── FieldCast.php │ ├── FieldsFactory.php │ └── HasFields.php ├── Filter.php ├── FilterRequest.php ├── Filterable.php ├── HasFilter.php ├── Operators │ ├── Between.php │ ├── Contains.php │ ├── EndsWith.php │ ├── Equals.php │ ├── GreaterThan.php │ ├── GreaterThanOrEqual.php │ ├── In.php │ ├── LessThan.php │ ├── LessThanOrEqual.php │ ├── NotContains.php │ ├── NotEndsWith.php │ ├── NotEquals.php │ ├── NotIn.php │ ├── NotStartsWith.php │ ├── Operator.php │ └── StartsWith.php └── QueryFormats │ ├── ArrayQueryFormat.php │ ├── JsonQueryFormat.php │ ├── QueryFormat.php │ └── SeparatorQueryFormat.php └── tests ├── GeneralSearchTest.php ├── Models ├── BaseModel.php ├── Order.php ├── OrderLine.php ├── Product.php └── Store.php ├── Operators ├── BetweenTest.php ├── ContainsTest.php ├── EndsWithTest.php ├── EqualsTest.php ├── GreaterThanOrEqualTest.php ├── GreaterThanTest.php ├── InTest.php ├── LessThanOrEqualTest.php ├── LessThanTest.php ├── NotContainsTest.php ├── NotEndsWithTest.php ├── NotEqualsTest.php ├── NotInTest.php ├── NotStartsWithTest.php └── StartsWithTest.php ├── QueryFormatsTest.php ├── Seeds ├── DatabaseSeeder.php ├── OrderSeeder.php ├── ProductSeeder.php └── StoreSeeder.php └── TestCase.php /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: testing 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - DB ${{ matrix.env.DB_CONNECTION }} 13 | strategy: 14 | fail-fast: true 15 | max-parallel: 4 16 | matrix: 17 | php: [ 7.3 ] 18 | laravel: [ 5.8.*, 6.*, 7.*, 8.* ] 19 | dependency-version: [ prefer-stable ] 20 | env: 21 | - { DB_CONNECTION: 'sqlite', DB_DATABASE: ':memory:' } 22 | - { DB_CONNECTION: 'mysql', DB_DATABASE: 'test', DB_USERNAME: 'root', DB_PORT: 3306 } 23 | include: 24 | - laravel: 8.* 25 | testbench: 6.* 26 | - laravel: 7.* 27 | testbench: 5.* 28 | - laravel: 6.* 29 | testbench: 4.* 30 | - laravel: 5.8.* 31 | testbench: 3.8.* 32 | 33 | env: ${{ matrix.env }} 34 | 35 | services: 36 | mysql: 37 | image: mysql:5.7 38 | env: 39 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 40 | MYSQL_DATABASE: test 41 | ports: 42 | - 3306:3306 43 | 44 | steps: 45 | - name: Checkout code 46 | uses: actions/checkout@v1 47 | 48 | - name: Cache dependencies 49 | uses: actions/cache@v1 50 | with: 51 | path: ~/.composer/cache/files 52 | key: dependencies-${{ matrix.dependency-version }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 53 | 54 | - name: Setup PHP 55 | uses: shivammathur/setup-php@v2 56 | with: 57 | php-version: ${{ matrix.php }} 58 | 59 | - name: Install dependencies 60 | run: | 61 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 62 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 63 | 64 | - name: Execute tests 65 | run: vendor/bin/phpunit 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Advanced Filter 2 | This package allows you to filter on laravel models 3 | 4 | You can choose fields to filtering and customize its data-types, aliases and excepted operators, 5 | you can add/customize your request format, and you add new operators or overwrite the existed operators 6 | 7 | 8 | ## Installation 9 | You can install the package via composer: 10 | ``` 11 | composer require asemalalami/laravel-advanced-filter 12 | ``` 13 | 14 | The package will automatically register its service provider. 15 | 16 | You can optionally publish the config file with: 17 | ``` 18 | php artisan vendor:publish --provider="AsemAlalami\LaravelAdvancedFilter\AdvancedFilterServiceProvider" --tag="config" 19 | ``` 20 | 21 | These default config file that will be published: 22 | [Config File](https://github.com/AsemAlalami/Laravel-Advanced-Filter/blob/master/config/advanced_filter.php) 23 | 24 | ## Usage 25 | - use `HasFilter` trait in the model 26 | - add fields in the implementation of the abstract function `setupFilter` 27 | - call the `filter` scope in your controller 28 | ```php 29 | class Order extends Model 30 | { 31 | use HasFilter; 32 | 33 | protected $casts = [ 34 | 'void' => 'boolean', 35 | ]; 36 | 37 | public function channel() 38 | { 39 | return $this->belongsTo(Channel::class); 40 | } 41 | 42 | public function orderLines() 43 | { 44 | return $this->hasMany(OrderLine::class); 45 | } 46 | 47 | public function setupFilter() 48 | { 49 | $this->addField('void'); // will cast to 'boolean' from the model casts 50 | $this->addField('total')->setDatatype('numeric'); 51 | $this->addFields(['source', 'subsource', 'order_date']); 52 | // field from relation 53 | $this->addFields(['channel.created_at' => 'channel_create'])->setDatatype('date'); 54 | $this->addField('orderLines.product.sku', 'product_sku'); 55 | // field from relation count 56 | $this->addCountField('orderLines'); 57 | // custom field (raw sql) 58 | $this->addCustomField('my_total', '(shipping_cost + subtotal)'); 59 | // enable general search 60 | $this->addGeneralSearch(['source', 'orderLines.product.sku'], 'startsWith'); 61 | } 62 | 63 | // customize field filter by custom scope 64 | public function scopeWhereSource(Builder $builder, Field $field, string $operator, $value, $conjunction = 'and') 65 | { 66 | if ($operator == 'Equal') { 67 | return $builder->where(function (Builder $builder) use ($value) { 68 | $builder->where('source', $value) 69 | ->orWhere('subsource', $value); 70 | }); 71 | } 72 | 73 | // default behavior 74 | return $builder->applyOperator($operator, $field, $value, $conjunction); 75 | } 76 | } 77 | 78 | ... 79 | 80 | class OrderController extends Controller 81 | { 82 | public function index() 83 | { 84 | return Order::filter()->paginate(); // you can pass your custom request 85 | } 86 | } 87 | ``` 88 | 89 | ## Query Format 90 | Query format is the shape that you want to send your query(filters) in the request. 91 | the package support 3 formats, and you can create a new format. 92 | - `json` (default): the filters will send as json in the request 93 | ```json 94 | filters=[{"field":"email","operator":"equal","value":"abc"}] 95 | ``` 96 | - `array`: the filters will send as array in the request 97 | ``` 98 | filters[email][value]=abc&filters[email][operator]=equal 99 | ``` 100 | - `separator`: the filters will send as well as in the `array` format, but separated by a separator(`^` default) 101 | 102 | the format sets with a separator symbol `separator:^` 103 | ``` 104 | filters^email^value=abc&filters^email^operator=equal 105 | ``` 106 | > set the default query format in the config file `query_format` attribute 107 | 108 | #### Create a new query format: 109 | - create a new class and extends it from `QueryFormat`: `class MyFormat extends QueryFormat` 110 | - implement the abstract function `format` that returns `FilterRequest` object 111 | - add the class to the config file in `custom_query_format` attribute: `'custom_query_format' => MyFormat::class,` 112 | 113 | ## Fields 114 | Normal Field options: 115 | - field name is the column name 116 | - alias is the key that you want to send in the request 117 | - data-type: by default it set from model `casts`, if you want to set custom data-type, use `setDatatype` 118 | - operators: the field will accept all operators unless you use `setExceptedOperators` to exclude some operators 119 | - a relational field: only set the field name by `.` separator `channel.name`, `channel.type.name` 120 | > you can define field name by `.` separator, but you want to consider it as a non relational field 121 | > by pass `false` for `inRelation` parameter (used in NoSQL DB or join between tables) 122 | ```php 123 | $this->addField('channels.name', 'channel_name', false); 124 | ``` 125 | - customize a field query, you can make a scope for the field to customize the filter behavior. 126 | scope name must be combined 3 sections : 127 | - scope 128 | - the value of `prefix_scope_function` key in config file (`where` is the default) 129 | - field name(or relation name) for example `email` 130 | ```php 131 | public function scopeWhereEmail(Builder $builder, Field $field, string $operator, $value, $conjunction = 'and') 132 | ``` 133 | > you can customize a relational field by define the scope in the relation model OR define scope by relation name 134 | 135 | ```php 136 | OrderLine.php 137 | 138 | public function scopeWherePrice(Builder $builder, Field $field, string $operator, $value, $conjunction = 'and') 139 | 140 | OR 141 | 142 | Order.php 143 | 144 | public function scopeWhereOrderLines(Builder $builder, Field $field, string $operator, $value, $conjunction = 'and') 145 | ``` 146 | > you can use `applyOperator` function to use the default behavior `$builder->applyOperator($operator, $field, $value, $conjunction);` 147 | 148 | You can add fields to a model by using 4 functions: 149 | - `addField`(string $field, string $alias = null, ?bool $inRelation = null): by default alias value same as field name value 150 | ```php 151 | $this->addField('total')->setDatatype('numeric'); 152 | ``` 153 | - `addFields`($fields): accept an array of field and aliases: 154 | ```php 155 | $this->addFields(['created_at' => 'create_date', 'order_date'])->setDatatype('date'); 156 | ``` 157 | - `addCountField`(string $relation, string $alias = null, callable $callback = null): add a field from count of relation, 158 | use can customize the count query and alias(by default is concat relation name(snake case) and `_count`) 159 | ```php 160 | $this->addCountField('orderLines'); 161 | 162 | $this->addCountField('orderLines', 'lines_count', function (Builder $builder) { 163 | $builder->where('quantity', '>', 1); 164 | }); 165 | ``` 166 | > 167 | - `addCustomField`(string $alias, string $sqlRaw, $relation = null): add a field from raw sql query 168 | ```php 169 | $this->addCustomField('my_total', '(`shipping_cost` + `subtotal`)'); 170 | $this->addCustomField('line_subtotal', '(`price` + `quantity`)', 'orderLines'); // inside "orderLines" relation 171 | ``` 172 | 173 | ## General Search 174 | You can enable general search on some fields, and you can specify the operator (`startsWith` is the default operator) 175 | ```php 176 | $this->addGeneralSearch(['source', 'orderLines.product.sku'], 'startsWith'); 177 | ``` 178 | 179 | ## Conjunction 180 | Currently, the package support one conjunction between all fields 181 | `and` | `or`, default conjunction attribute in the config file `default_conjunction` 182 | 183 | ## Operators 184 | The package has many operators, you can create new operators, 185 | and you can customize the operators aliases that you want to send in the request 186 | - Equals (`=`, `equals`) 187 | - NotEquals (`!=`, `notEquals`) 188 | - GreaterThan (`>` , `greater`) 189 | - GreaterThanOrEqual (`>=`, `greaterOrEqual`) 190 | - LessThan (`<`, `less`) 191 | - LessThanOrEqual (`<=`, `lessOrEqual`) 192 | - In (`|`, `in`) 193 | - NotIn (`!|`, `notIn`) 194 | - Contains (`*`, `contains`) 195 | - NotContains (`!*`, `notContains`) 196 | - StartsWith (`^`, `startsWith`) 197 | - NotStartsWith (`!^`, `notStartsWith`) 198 | - EndsWith (`$`, `endsWith`) 199 | - NotEndsWith (`!$`, `notEndsWith`) 200 | - Between (`><`, `between`) 201 | 202 | #### Create a new Operator: 203 | - create a new class and extends it from `Operator`: `class MyOperator extends Operator` 204 | - implement the abstract function `apply` and `getSqlOperator` (used as a default sql operator for count and custom field) 205 | - add the class in the config file in `custom_operators` attribute: `'custom_operators' => [MyOperator::class => ['my-op', '*']],` 206 | 207 | ## Data Types: 208 | - boolean 209 | - date 210 | - datetime 211 | - numeric 212 | - string 213 | 214 | ## Config 215 | [Config File](https://github.com/AsemAlalami/Laravel-Advanced-Filter/blob/master/config/advanced_filter.php) 216 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asemalalami/laravel-advanced-filter", 3 | "description": "Laravel Advanced Filter", 4 | "keywords": [ 5 | "laravel", 6 | "filter", 7 | "filters" 8 | ], 9 | "type": "project", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Asem Alalami", 14 | "email": "asem.almi@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "dev", 18 | "autoload": { 19 | "psr-4": { 20 | "AsemAlalami\\LaravelAdvancedFilter\\": "src" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "AsemAlalami\\LaravelAdvancedFilter\\Test\\": "tests" 26 | } 27 | }, 28 | "require": { 29 | "ext-json": "*", 30 | "illuminate/database": "^5.8|^6.0|^7.0|^8.0|^9.0", 31 | "illuminate/support": "^5.8|^6.0|^7.0|^8.0|^9.0" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "8.5.x-dev", 35 | "orchestra/testbench": "5.x-dev" 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "providers": [ 40 | "AsemAlalami\\LaravelAdvancedFilter\\AdvancedFilterServiceProvider" 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /config/advanced_filter.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'Equals' => ['=', 'equals'], 17 | 'NotEquals' => ['!=', 'notEquals'], 18 | 'GreaterThan' => ['>', 'greater'], 19 | 'GreaterThanOrEqual' => ['>=', 'greaterOrEqual'], 20 | 'LessThan' => ['<', 'less'], 21 | 'LessThanOrEqual' => ['<=', 'lessOrEqual'], 22 | 'In' => ['|', 'in'], 23 | 'NotIn' => ['!|', 'notIn'], 24 | 'Contains' => ['*', 'contains'], 25 | 'NotContains' => ['!*', 'notContains'], 26 | 'StartsWith' => ['^', 'startsWith'], 27 | 'NotStartsWith' => ['!^', 'notStartsWith'], 28 | 'EndsWith' => ['$', 'endsWith'], 29 | 'NotEndsWith' => ['!$', 'notEndsWith'], 30 | 'Between' => ['><', 'between'], 31 | ], 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Custom Operators 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Add your operators here 39 | | 40 | | example: operator class and its aliases 41 | | MyOperator::class => ['my-op', '*'] 42 | | 43 | */ 44 | 45 | 'custom_operators' => [ 46 | 47 | ], 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Default Operators 52 | |-------------------------------------------------------------------------- 53 | | 54 | | The Default operators if the field sent in the request without operator 55 | | or for general search 56 | | 57 | */ 58 | 59 | 'default_operator' => 'equals', 60 | 'default_general_search_operator' => 'startsWith', 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Prefix Operator Function 65 | |-------------------------------------------------------------------------- 66 | | 67 | | This option used when binging operators to Builder 68 | | The operators bound to Builder by using macros 69 | | 70 | | example: 71 | | if you want to filter by GreaterThan operator 72 | | $orders->filterWhereGreaterThan('total', 100) 73 | | 74 | */ 75 | 76 | 'prefix_operator_function' => 'filterWhere', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Default Conjunction 81 | |-------------------------------------------------------------------------- 82 | | 83 | | This option used when request sent without conjunction 84 | | 85 | | values: and | or 86 | | 87 | */ 88 | 89 | 'default_conjunction' => 'and', 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Default Query Format 94 | |-------------------------------------------------------------------------- 95 | | 96 | | This option controls the default query format. 97 | | This format is used when sending request. 98 | | 99 | | Supported: 100 | | - "separator:^" : filters^email^value=abc&filters^email^operator=equal 101 | | - "array" : filters[email][value]=abc&filters[email][operator]=equal 102 | | - "json" (Default) : filters=[{"field":"email","operator":"equal","value":"abc"}] 103 | | 104 | */ 105 | 106 | 'query_format' => 'json', 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Custom Query Format 111 | |-------------------------------------------------------------------------- 112 | | 113 | | Add your custom query format here 114 | | this format will set as a default query format of the request 115 | | 116 | | example: MyQueryFormat::class 117 | | 118 | */ 119 | 120 | 'custom_query_format' => null, 121 | 122 | /* 123 | |-------------------------------------------------------------------------- 124 | | Request Parameters Names 125 | |-------------------------------------------------------------------------- 126 | | 127 | | This options to customize your request parameters names 128 | | 129 | */ 130 | 131 | // the parameter name that contains the fields 132 | 'param_filter_name' => 'filters', // or as prefix in "separate" query format 133 | // the parameter name that set the conjunction 134 | 'param_conjunction_name' => 'conjunction', 135 | // the parameters names that define the field 136 | 'field_params' => [ 137 | 'field' => 'field', // filed name, only used in "json" query format 138 | 'operator' => 'operator', // field operator 139 | 'value' => 'value', // field value 140 | ], 141 | // the parameter name that contains the general search value 142 | 'param_general_search_name' => 'query', 143 | 144 | /* 145 | |-------------------------------------------------------------------------- 146 | | Cast date in the database 147 | |-------------------------------------------------------------------------- 148 | | 149 | | This options will use "whereDate" function to compare date fields 150 | | the default will compare by start/end of day (as between) 151 | | this feature for big-data if you have index on the column 152 | | 153 | | And should set it to TRUE if your columns type (in the database) is not "datetime or date" 154 | | 155 | */ 156 | 157 | 'cast_db_date' => false, 158 | 159 | /* 160 | |-------------------------------------------------------------------------- 161 | | Prefix Scope Function 162 | |-------------------------------------------------------------------------- 163 | | 164 | | This options used when you want to customize field filter behavior by defining scope for it 165 | | 166 | | example: 167 | | you need to customize behavior for "email" field, the scope function name with be: 168 | | scopeWhereEmail(Builder $builder, Field $field, string $operator, $value, $conjunction = 'and') 169 | | 170 | */ 171 | 172 | 'prefix_scope_function' => 'where', 173 | 174 | /* 175 | |-------------------------------------------------------------------------- 176 | | Empty Value As NULL 177 | |-------------------------------------------------------------------------- 178 | | 179 | | This option used when you want to considered empty value as NULL 180 | | 181 | */ 182 | 183 | 'empty_as_null' => false, 184 | ]; 185 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/AdvancedFilterServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 19 | __DIR__ . '/../config/advanced_filter.php' => config_path('advanced_filter.php'), 20 | ], 'config'); 21 | } 22 | 23 | /** 24 | * Register any application services. 25 | * 26 | * @return void 27 | */ 28 | public function register() 29 | { 30 | $this->mergeConfigFrom( 31 | __DIR__ . '/../config/advanced_filter.php', 'advanced_filter' 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exceptions/DatatypeNotFoundException.php: -------------------------------------------------------------------------------- 1 | model = $model; 44 | $this->name = $name; 45 | $this->alias = $alias ?: $name; 46 | $this->isFromRelation = $isFromRelation; 47 | $this->nameExploded = explode('.', $this->name); 48 | } 49 | 50 | public function isFromRelation() 51 | { 52 | return $this->isFromRelation === null ? (count($this->nameExploded) > 1 && !$this->isCount()) : $this->isFromRelation; 53 | } 54 | 55 | /** 56 | * @param bool|null $isFromRelation 57 | */ 58 | public function setIsFromRelation(?bool $isFromRelation): void 59 | { 60 | $this->isFromRelation = $isFromRelation; 61 | } 62 | 63 | public function getRelation() 64 | { 65 | return implode('.', array_slice($this->nameExploded, 0, count($this->nameExploded) - 1)); 66 | } 67 | 68 | public function getColumn() 69 | { 70 | return $this->isCustom() ? $this->customSqlRaw : (array_slice($this->nameExploded, -1)[0] ?: null); 71 | } 72 | 73 | public function isCount() 74 | { 75 | return is_null($this->getColumn()); 76 | } 77 | 78 | public function isCustom() 79 | { 80 | return !empty($this->customSqlRaw); 81 | } 82 | 83 | /** 84 | * @param string|null $datatype 85 | * 86 | * @return $this 87 | * @throws DatatypeNotFoundException 88 | */ 89 | public function setDatatype(?string $datatype) 90 | { 91 | if (!empty($datatype)) { 92 | if (in_array($datatype, static::$primitiveDatatypes)) { 93 | $this->datatype = $datatype; 94 | } else { 95 | throw new DatatypeNotFoundException($datatype); 96 | } 97 | } 98 | 99 | 100 | return $this; 101 | } 102 | 103 | public function setCountCallback(?callable $callback) 104 | { 105 | $this->countCallback = $callback; 106 | 107 | return $this; 108 | } 109 | 110 | public function setCustomSqlRaw(string $sqlRaw) 111 | { 112 | $this->customSqlRaw = $sqlRaw; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @param array|string|string[] $operators 119 | * 120 | * @return $this 121 | */ 122 | public function setExceptedOperators($operators) 123 | { 124 | $this->exceptOperators = Arr::wrap($operators); 125 | 126 | return $this; 127 | } 128 | 129 | public function getDatatype() 130 | { 131 | if (empty($this->datatype)) { 132 | $this->datatype = $this->getFieldCastType($this->name, $this->getModel()->getCasts()); 133 | } 134 | 135 | return $this->datatype; 136 | } 137 | 138 | /** 139 | * @return Model 140 | */ 141 | public function getModel() 142 | { 143 | $model = $this->model; 144 | 145 | if ($this->isFromRelation()) { 146 | foreach (explode('.', $this->getRelation()) as $relation) { 147 | $model = $model->{$relation}()->getRelated(); 148 | } 149 | } 150 | 151 | return $model; 152 | } 153 | 154 | /** 155 | * Determined if the operator does not belong to excepted operators 156 | * 157 | * @param string $operator 158 | * 159 | * @return bool 160 | */ 161 | public function isAllowedOperator(string $operator) 162 | { 163 | return !in_array($operator, $this->exceptOperators); 164 | } 165 | 166 | public function getScopeFunctionName() 167 | { 168 | $prefix = config('advanced_filter.prefix_scope_function', 'where'); 169 | $column = ucfirst(Str::camel($this->getColumn())); 170 | 171 | return "{$prefix}{$column}"; 172 | } 173 | 174 | public function getScopeRelationFunctionName() 175 | { 176 | $prefix = config('advanced_filter.prefix_scope_function', 'where'); 177 | $relation = ucfirst(Str::camel(str_replace('.', '_', $this->getRelation()))); 178 | 179 | return "{$prefix}{$relation}"; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Fields/FieldCast.php: -------------------------------------------------------------------------------- 1 | isDatetime($casts[$field])) { 33 | return 'datetime'; 34 | } 35 | 36 | if ($this->isDate($casts[$field])) { 37 | return 'date'; 38 | } 39 | 40 | if ($this->isNumeric($casts[$field])) { 41 | return 'numeric'; 42 | } 43 | 44 | if ($this->isBoolean($casts[$field])) { 45 | return 'boolean'; 46 | } 47 | 48 | } 49 | 50 | return 'string'; 51 | } 52 | 53 | protected function isDatetime(string $cast) 54 | { 55 | return Str::startsWith($cast, 'datetime'); 56 | } 57 | 58 | protected function isDate(string $cast) 59 | { 60 | return Str::startsWith($cast, 'date'); 61 | } 62 | 63 | protected function isNumeric(string $cast) 64 | { 65 | return Str::startsWith($cast, [ 66 | 'int', 67 | 'integer', 68 | 'real', 69 | 'float', 70 | 'double', 71 | 'decimal', 72 | ]); 73 | } 74 | 75 | protected function isBoolean(string $cast) 76 | { 77 | return Str::startsWith($cast, ['bool', 'boolean']); 78 | } 79 | 80 | public function getCastedValue($value) 81 | { 82 | if (is_null($value) || is_array($value)) { 83 | return $value; 84 | } 85 | 86 | // return null if the value is empty and "empty_as_null" config value is true 87 | if (config('advanced_filter.empty_as_null', false) && empty($value)) { 88 | return null; 89 | } 90 | 91 | switch ($this->getDatatype()) { 92 | case 'numeric': 93 | return (float)$value; 94 | case 'string': 95 | return (string)$value; 96 | case 'boolean': 97 | return (bool)$value; 98 | case 'date': 99 | return $this->asDateTime($value)->startOfDay(); 100 | case 'datetime': 101 | return $this->asDateTime($value); 102 | case 'timestamp': 103 | return $this->asDateTime($value)->getTimestamp(); 104 | } 105 | 106 | return $value; 107 | } 108 | 109 | /** 110 | * Return a timestamp as DateTime object. 111 | * 112 | * @param mixed $value 113 | * @return \Illuminate\Support\Carbon 114 | */ 115 | protected function asDateTime($value) 116 | { 117 | // If this value is already a Carbon instance, we shall just return it as is. 118 | // This prevents us having to re-instantiate a Carbon instance when we know 119 | // it already is one, which wouldn't be fulfilled by the DateTime check. 120 | if ($value instanceof CarbonInterface) { 121 | return Date::instance($value); 122 | } 123 | 124 | // If the value is already a DateTime instance, we will just skip the rest of 125 | // these checks since they will be a waste of time, and hinder performance 126 | // when checking the field. We will just return the DateTime right away. 127 | if ($value instanceof DateTimeInterface) { 128 | return Date::parse( 129 | $value->format('Y-m-d H:i:s.u'), $value->getTimezone() 130 | ); 131 | } 132 | 133 | // If this value is an integer, we will assume it is a UNIX timestamp's value 134 | // and format a Carbon object from this timestamp. This allows flexibility 135 | // when defining your date fields as they might be UNIX timestamps here. 136 | if (is_numeric($value)) { 137 | return Date::createFromTimestamp($value); 138 | } 139 | 140 | // If the value is in simply year, month, day format, we will instantiate the 141 | // Carbon instances from that format. Again, this provides for simple date 142 | // fields on the database, while still supporting Carbonized conversion. 143 | if ($this->isStandardDateFormat($value)) { 144 | return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay()); 145 | } 146 | 147 | $format = $this->getDateFormat(); 148 | 149 | // Finally, we will just assume this date is in the format used by default on 150 | // the database connection and use that format to create the Carbon object 151 | // that is returned back out to the developers after we convert it here. 152 | if (Date::hasFormat($value, $format)) { 153 | return Date::createFromFormat($format, $value); 154 | } 155 | 156 | return Date::parse($value); 157 | } 158 | 159 | /** 160 | * Determine if the given value is a standard date format. 161 | * 162 | * @param string $value 163 | * @return bool 164 | */ 165 | protected function isStandardDateFormat($value) 166 | { 167 | return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value); 168 | } 169 | 170 | /** 171 | * Get the format for database stored dates. 172 | * 173 | * @return string 174 | */ 175 | public function getDateFormat() 176 | { 177 | return $this->getModel()->getDateFormat() ?: $this->getModel()->getConnection()->getQueryGrammar()->getDateFormat(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Fields/FieldsFactory.php: -------------------------------------------------------------------------------- 1 | model = $model; 28 | 29 | $this->setFields($fields); 30 | } 31 | 32 | /** 33 | * @param string|string[] $fields 34 | * 35 | * @return $this 36 | */ 37 | public function setFields($fields) 38 | { 39 | $fields = Arr::wrap($fields); 40 | 41 | foreach ($fields as $field => $alias) { 42 | if (is_int($field)) { 43 | $field = $alias; 44 | } 45 | 46 | $this->fields[$field] = $alias; 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * @param string|null $datatype 54 | * 55 | * @return FieldsFactory 56 | */ 57 | public function setDatatype(?string $datatype) 58 | { 59 | $this->datatype = $datatype; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @param string|string[] $exceptedOperators 66 | * 67 | * @return $this 68 | */ 69 | public function setExceptedOperators($exceptedOperators) 70 | { 71 | $this->exceptedOperators = Arr::wrap($exceptedOperators); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return Field[] 78 | * @throws DatatypeNotFoundException 79 | */ 80 | public function getFields() 81 | { 82 | $fields = []; 83 | foreach ($this->fields as $field => $alias) { 84 | $fields[] = (new Field($this->model, $field, $alias)) 85 | ->setExceptedOperators($this->exceptedOperators) 86 | ->setDatatype($this->datatype); 87 | } 88 | 89 | return $fields; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Fields/HasFields.php: -------------------------------------------------------------------------------- 1 | [], 'operator' => null]; 19 | 20 | /** 21 | * Add a normal/relational field 22 | * 23 | * @param string $field 24 | * @param string|null $alias 25 | * @param bool|null $inRelation 26 | * 27 | * @return Field 28 | */ 29 | public function addField(string $field, string $alias = null, ?bool $inRelation = null) 30 | { 31 | $field = new Field($this, $field, $alias, $inRelation); 32 | 33 | $this->fields[$field->name] = $field; 34 | 35 | return $field; 36 | } 37 | 38 | /** 39 | * Add normal/relational fields 40 | * 41 | * @param $fields 42 | * 43 | * @return FieldsFactory 44 | */ 45 | public function addFields($fields) 46 | { 47 | $fieldFactory = new FieldsFactory($this, $fields); 48 | 49 | $this->fieldsFactories[] = $fieldFactory; 50 | 51 | return $fieldFactory; 52 | } 53 | 54 | /** 55 | * Add a count field 56 | * 57 | * @param string $relation 58 | * @param string|null $alias 59 | * @param callable|null $callback 60 | * 61 | * @throws DatatypeNotFoundException 62 | */ 63 | public function addCountField(string $relation, string $alias = null, callable $callback = null) 64 | { 65 | $field = new Field($this, "{$relation}.", $alias ?: $this->getCountFieldAlias($relation)); 66 | 67 | $this->fields[$field->name] = $field->setDatatype('numeric')->setCountCallback($callback); 68 | } 69 | 70 | /** 71 | * Add a custom field 72 | * 73 | * @param string $alias 74 | * @param string $sqlRaw 75 | * @param null $relation 76 | * 77 | * @return Field 78 | */ 79 | public function addCustomField(string $alias, string $sqlRaw, $relation = null) 80 | { 81 | $field = new Field($this, $relation ? "{$relation}.{$alias}" : $alias, $alias); 82 | 83 | $this->fields[$field->name] = $field->setCustomSqlRaw($sqlRaw); 84 | 85 | return $field; 86 | } 87 | 88 | public function addGeneralSearch(array $fields, string $operator = null) 89 | { 90 | $this->generalSearch = ['fields' => $fields, 'operator' => $operator]; 91 | } 92 | 93 | /** 94 | * Resolve factories fields and set fields aliases 95 | * 96 | * @throws DatatypeNotFoundException 97 | */ 98 | private function resolveFields() 99 | { 100 | // add fields from factories to fields 101 | foreach ($this->fieldsFactories as $fieldsFactory) { 102 | foreach ($fieldsFactory->getFields() as $field) { 103 | if (!array_key_exists($field->name, $this->fields)) { 104 | $this->fields[$field->name] = $field; 105 | } 106 | } 107 | } 108 | 109 | // set aliases fields 110 | foreach ($this->fields as $field) { 111 | $this->fieldsAliases[$field->alias] = $field->name; 112 | } 113 | } 114 | 115 | /** 116 | * Determined if the alias exists in filterable fields 117 | * 118 | * @param string $alias 119 | * 120 | * @return Field|bool the alias does not exist 121 | */ 122 | protected function getFilterableField(string $alias) 123 | { 124 | if (array_key_exists($alias, $this->fieldsAliases)) { 125 | return $this->fields[$this->fieldsAliases[$alias]]; 126 | } 127 | 128 | return false; 129 | } 130 | 131 | /** 132 | * Get default alias for count field from a relation 133 | * 134 | * @param string $relation 135 | * 136 | * @return string 137 | */ 138 | private function getCountFieldAlias(string $relation) 139 | { 140 | return Str::snake($relation) . '_count'; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Filter.php: -------------------------------------------------------------------------------- 1 | filters[] = ['field' => $fieldName, 'operator' => $operator, 'value' => $value]; 19 | 20 | return $this; 21 | } 22 | 23 | public function getFilters() 24 | { 25 | return $this->filters; 26 | } 27 | 28 | public function setConjunction(string $conjunction) 29 | { 30 | $this->conjunction = $conjunction; 31 | 32 | return $this; 33 | } 34 | 35 | public function getConjunction() 36 | { 37 | return $this->conjunction; 38 | } 39 | 40 | /** 41 | * @return null 42 | */ 43 | public function getGeneralSearch() 44 | { 45 | return $this->generalSearch; 46 | } 47 | 48 | /** 49 | * @param string|null $generalSearch 50 | */ 51 | public function setGeneralSearch(?string $generalSearch): void 52 | { 53 | $this->generalSearch = $generalSearch; 54 | } 55 | 56 | public static function createFromRequest(Request $request = null) 57 | { 58 | $request = $request ?: request(); 59 | 60 | $filterRequest = QueryFormat::factory($request); 61 | $filterRequest->setConjunction($filterRequest->getConjunctionFromRequest($request)); 62 | $filterRequest->setGeneralSearch($filterRequest->getGeneralSearchFromRequest($request)); 63 | 64 | return $filterRequest; 65 | } 66 | 67 | private function getConjunctionFromRequest(Request $request = null) 68 | { 69 | $paramConjunctionName = config('advanced_filter.param_conjunction_name', 'conjunction'); 70 | $defaultConjunction = config('advanced_filter.default_conjunction', 'and'); 71 | 72 | return $request ? 73 | $request->input($paramConjunctionName, $defaultConjunction) : 74 | request($paramConjunctionName, $defaultConjunction); 75 | } 76 | 77 | private function getGeneralSearchFromRequest(Request $request = null) 78 | { 79 | $paramGeneralSearchName = config('advanced_filter.param_general_search_name', 'query'); 80 | 81 | return $request ? 82 | $request->input($paramGeneralSearchName) : 83 | request($paramGeneralSearchName); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Filterable.php: -------------------------------------------------------------------------------- 1 | $aliases) { 33 | if (is_int($operator)) { 34 | $operator = $aliases; 35 | } 36 | 37 | $operatorClass = $this->getOperatorsNamespace() . $operator; // class path of the operator 38 | 39 | try { 40 | /** @var Operator $operator */ 41 | $operator = new $operatorClass; 42 | $operator->setAliases(Arr::wrap($aliases)); 43 | 44 | $this->bindOperator($operator); 45 | } catch (Error $exception) { 46 | throw new OperatorNotFoundException($operator); 47 | } 48 | } 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * Bind Custom Operators to Builder 55 | * 56 | * @return $this 57 | */ 58 | private function bindCustomOperators() 59 | { 60 | $customOperators = config('advanced_filter.custom_operators', []); 61 | foreach ($customOperators as $customOperator => $aliases) { 62 | if (is_int($customOperator)) { 63 | $customOperator = $aliases; 64 | } 65 | 66 | /** @var Operator $operator */ 67 | $operator = new $customOperator; 68 | if ($operator instanceof Operator) { 69 | $operator->setAliases(Arr::wrap($aliases)); 70 | 71 | $this->bindOperator($operator); 72 | } else { 73 | throw new InvalidArgumentException('Custom operator must be instance of Operator'); 74 | } 75 | } 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Bind operators to Build by using macros 82 | * 83 | * @param Operator $operator 84 | */ 85 | private function bindOperator(Operator $operator) 86 | { 87 | // macro to apply operator of field 88 | // TODO: maybe from config 89 | Builder::macro('applyOperator', function (string $operator, $field, $value, string $conjunction = 'and') { 90 | return $this->{Operator::getFunction($operator)}($field, $value, $conjunction); 91 | }); 92 | 93 | Builder::macro(Operator::getFunction($operator->name), function (...$parameters) use ($operator) { 94 | // convert string field to Field class 95 | $field = $parameters[0]; 96 | if (is_string($parameters[0])) { 97 | $field = new Field($this->getModel(), $field); 98 | 99 | $parameters[0] = $field; 100 | } 101 | 102 | // push Builder on first index 103 | array_unshift($parameters, $this); 104 | 105 | return $operator->execute(...$parameters); 106 | }); 107 | 108 | foreach ($operator->aliases as $alias) { 109 | $this->operatorAliases[$alias] = $operator->name; 110 | } 111 | } 112 | 113 | /** 114 | * Get default operators namespace 115 | * 116 | * @return string 117 | */ 118 | private function getOperatorsNamespace() 119 | { 120 | return __NAMESPACE__ . '\\Operators\\'; 121 | } 122 | 123 | /** 124 | * Get the operator name from operator alias 125 | * 126 | * if the alias does not exists it will return the default operator from config file 127 | * 128 | * @param string $operatorAlias 129 | * 130 | * @return string 131 | */ 132 | public function getOperatorFromAliases(string $operatorAlias) 133 | { 134 | return array_key_exists($operatorAlias, $this->operatorAliases) ? 135 | $this->operatorAliases[$operatorAlias] : 136 | config('advanced_filter.default_operator', 'Equal'); 137 | } 138 | 139 | public function initializeFilterable() 140 | { 141 | $this->bindOperators(); 142 | 143 | $this->bindCustomOperators(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/HasFilter.php: -------------------------------------------------------------------------------- 1 | apply($builder, $filterRequest); 40 | 41 | $builder = $this->applyGeneralSearch($builder, $filterRequest); 42 | 43 | return $builder; 44 | } 45 | 46 | /** 47 | * Filter fields from request 48 | * 49 | * @param Builder $builder 50 | * @param FilterRequest $filterRequest 51 | * 52 | * @return Builder 53 | * @throws UnsupportedDriverException 54 | */ 55 | private function apply(Builder $builder, FilterRequest $filterRequest) 56 | { 57 | $conjunction = $filterRequest->getConjunction(); 58 | 59 | foreach ($filterRequest->getFilters() as $filter) { 60 | if ($field = $this->getFilterableField($filter['field'])) { 61 | 62 | $operator = $this->getOperatorFromAliases($filter['operator']); 63 | $value = $field->getCastedValue($filter['value']); 64 | 65 | // don't filter if the operator is in excepted operators 66 | if (!$field->isAllowedOperator($operator)) { 67 | continue; 68 | } 69 | 70 | $builder = $this->filterField($builder, $field, $operator, $value, $conjunction); 71 | } 72 | } 73 | 74 | return $builder; 75 | } 76 | 77 | /** 78 | * Filter general search from the request 79 | * 80 | * @param Builder $builder 81 | * @param FilterRequest $filterRequest 82 | * 83 | * @return Builder 84 | * @throws UnsupportedDriverException 85 | */ 86 | private function applyGeneralSearch(Builder $builder, FilterRequest $filterRequest) 87 | { 88 | if (!empty($filterRequest->getGeneralSearch())) { 89 | $operator = $this->generalSearch['operator'] ?: config('advanced_filter.default_general_search_operator'); 90 | 91 | $builder->where(function (Builder $builder) use ($filterRequest, $operator) { 92 | foreach ($this->generalSearch['fields'] as $fieldName) { 93 | $field = new Field($builder->getModel(), $fieldName); 94 | 95 | $this->filterField($builder, $field, $operator, $filterRequest->getGeneralSearch(), 'or'); 96 | } 97 | }); 98 | } 99 | 100 | return $builder; 101 | } 102 | 103 | /** 104 | * Filter a field 105 | * 106 | * @param Builder $builder 107 | * @param Field $field 108 | * @param $operator 109 | * @param $value 110 | * @param string $conjunction 111 | * 112 | * @return Builder 113 | * @throws UnsupportedDriverException 114 | */ 115 | private function filterField(Builder $builder, Field $field, $operator, $value, $conjunction = 'and') 116 | { 117 | // apply the filter inside the relation if the field from relation 118 | if ($field->isFromRelation()) { 119 | // apply on custom scope if the relation has a scope 120 | if ($this->modelHasScope($builder, $field->getScopeRelationFunctionName())) { 121 | return $builder->{$field->getScopeRelationFunctionName()}($field, $operator, $value, $conjunction); 122 | } else { 123 | if ($builder->getConnection()->getName() == 'mongodb') { 124 | throw new UnsupportedDriverException('MongoDB', 'relational'); 125 | } 126 | 127 | return $builder->has($field->getRelation(), '>=', 1, $conjunction, 128 | function (Builder $builder) use ($field, $value, $operator) { 129 | // consider as a non relation field inside the relation 130 | return $this->filterNonRelationalField($builder, $field, $operator, $value); 131 | } 132 | ); 133 | } 134 | } else { 135 | // a non relational field 136 | return $this->filterNonRelationalField($builder, $field, $operator, $value, $conjunction); 137 | } 138 | } 139 | 140 | /** 141 | * Filter a non relational field 142 | * 143 | * it checks if the field has a custom scope 144 | * 145 | * @param Builder $builder 146 | * @param Field $field 147 | * @param $operator 148 | * @param $value 149 | * @param string $conjunction 150 | * 151 | * @return mixed 152 | */ 153 | private function filterNonRelationalField(Builder $builder, Field $field, $operator, $value, $conjunction = 'and') 154 | { 155 | // apply on custom scope if the field has a scope 156 | if ($this->modelHasScope($builder, $field->getScopeFunctionName())) { 157 | return $builder->{$field->getScopeFunctionName()}($field, $operator, $value, $conjunction); 158 | } 159 | 160 | return $builder->{Operator::getFunction($operator)}($field, $value, $conjunction); 161 | } 162 | 163 | /** 164 | * Determine if the given model has a scope. 165 | * 166 | * @param Builder $builder 167 | * @param $scopeName 168 | * @return bool 169 | */ 170 | private function modelHasScope(Builder $builder, $scopeName) 171 | { 172 | return $builder->getModel() && method_exists($builder->getModel(), 'scope' . ucfirst($scopeName)); 173 | } 174 | 175 | public function initializeHasFilter() 176 | { 177 | $this->setupFilter(); 178 | 179 | $this->resolveFields(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Operators/Between.php: -------------------------------------------------------------------------------- 1 | getSqlValue($value); 21 | 22 | if (empty($value)) { 23 | return $builder; 24 | } 25 | 26 | $values = ['from' => $field->getCastedValue($value['from']), 'to' => $field->getCastedValue($value['to'])]; 27 | 28 | if ($field->getDatatype() == 'date') { 29 | $castInDB = config('advanced_filter.cast_db_date', false); 30 | if ($castInDB) { 31 | return $builder->where(function (Builder $builder) use ($values, $field, $value) { 32 | $builder->whereDate($field->getColumn(), '>=', $values['from']) 33 | ->whereDate($field->getColumn(), '<=', $values['to']); 34 | }, null, null, $conjunction); 35 | } 36 | 37 | $values['to'] = $values['to']->endOfDay(); // when using the between operator 38 | } 39 | 40 | return $builder->whereBetween($field->getColumn(), array_values($values), $conjunction); 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function getSqlOperator(): string 47 | { 48 | return 'BETWEEN'; 49 | } 50 | 51 | protected function getSqlValue($value) 52 | { 53 | if (!is_array($value) || !array_key_exists('from', $value) || !array_key_exists('to', $value)) { 54 | throw new InvalidArgumentException('The between value must be array that contains "from" and "to" keys'); 55 | } 56 | 57 | return $value; 58 | } 59 | 60 | public function applyOnCustom(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 61 | { 62 | if (empty($value)) { 63 | return $builder; 64 | } 65 | 66 | $sql = "{$field->getColumn()} {$this->getSqlOperator()} "; 67 | 68 | if ($field->getDatatype() == 'numeric') { 69 | $sql .= '(? + 0.0) and (? + 0.0)'; 70 | } else { 71 | $sql .= '? and ?'; 72 | } 73 | 74 | return $builder->whereRaw($sql, [array_values($this->getSqlValue($value))], $conjunction); 75 | } 76 | 77 | public function applyOnCount(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 78 | { 79 | throw new UnsupportedOperatorException($this->name, 'count'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Operators/Contains.php: -------------------------------------------------------------------------------- 1 | where($field->getColumn(), $this->getSqlOperator(), $this->getSqlValue($value), $conjunction); 19 | } 20 | 21 | /** 22 | * @inheritDoc 23 | */ 24 | public function getSqlOperator(): string 25 | { 26 | return 'LIKE'; 27 | } 28 | 29 | protected function getSqlValue($value) 30 | { 31 | return "%{$value}%"; 32 | } 33 | 34 | public function applyOnCount(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 35 | { 36 | throw new UnsupportedOperatorException($this->name, 'count'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Operators/EndsWith.php: -------------------------------------------------------------------------------- 1 | whereNull($field->getColumn(), $conjunction); 21 | } 22 | 23 | if ($field->getDatatype() == 'date') { 24 | $castInDB = config('advanced_filter.cast_db_date', false); 25 | 26 | if ($castInDB) { 27 | return $builder->whereDate($field->getColumn(), $this->getSqlOperator(), $value, $conjunction); 28 | } else { 29 | // "between" will not keep indexing on the column, just as an option :) 30 | return $builder->whereBetween($field->getColumn(), [$value, (clone $value)->endOfDay()], $conjunction); 31 | } 32 | } 33 | 34 | return $builder->where($field->getColumn(), $this->getSqlOperator(), $value, $conjunction); 35 | } 36 | 37 | public function getSqlOperator(): string 38 | { 39 | return '='; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Operators/GreaterThan.php: -------------------------------------------------------------------------------- 1 | getColumn(); 18 | 19 | if ($field->getDatatype() == 'date') { 20 | $castInDB = config('advanced_filter.cast_db_date', false); 21 | 22 | if ($castInDB) { 23 | return $builder->whereDate($column, '>', $value, $conjunction); 24 | } 25 | 26 | $value = $value->endOfDay(); 27 | } 28 | 29 | return $builder->where($column, '>', $value, $conjunction); 30 | } 31 | 32 | public function getSqlOperator(): string 33 | { 34 | return '>'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Operators/GreaterThanOrEqual.php: -------------------------------------------------------------------------------- 1 | getColumn(); 18 | 19 | if ($field->getDatatype() == 'date') { 20 | $castInDB = config('advanced_filter.cast_db_date', false); 21 | 22 | if ($castInDB) { 23 | return $builder->whereDate($column, '>=', $value, $conjunction); 24 | } 25 | } 26 | 27 | return $builder->where($column, '>=', $value, $conjunction); 28 | } 29 | 30 | public function getSqlOperator(): string 31 | { 32 | return '>='; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Operators/In.php: -------------------------------------------------------------------------------- 1 | getCastedValue($v); 28 | }, $this->getSqlValue($value)); 29 | 30 | if ($field->getDatatype() == 'date') { 31 | return $this->applyOnDate($builder, $field, $values, $conjunction); 32 | } 33 | 34 | // to use in NotIn operator 35 | $notIn = $this->getSqlOperator() != 'IN'; 36 | 37 | return $builder->whereIn($field->getColumn(), $values, $conjunction, $notIn); 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | public function getSqlOperator(): string 44 | { 45 | return 'IN'; 46 | } 47 | 48 | protected function getSqlValue($value) 49 | { 50 | return Arr::wrap($value); 51 | } 52 | 53 | public function applyOnCustom(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 54 | { 55 | if (empty($value)) { 56 | return $builder; 57 | } 58 | 59 | $value = implode(",", $this->getSqlValue($value)); 60 | 61 | $sql = "{$field->getColumn()} {$this->getSqlOperator()} ({$value})"; 62 | 63 | return $builder->whereRaw($sql, [], $conjunction); 64 | } 65 | 66 | public function applyOnCount(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 67 | { 68 | throw new UnsupportedOperatorException($this->name, 'count'); 69 | } 70 | 71 | private function applyOnDate(Builder $builder, Field $field, $values, string $conjunction = 'and') 72 | { 73 | if ($builder->getConnection()->getName() == 'mongodb') { 74 | throw new UnsupportedDriverException('MongoDB', 'In operator on Date'); 75 | } 76 | 77 | // cast to date string 78 | $values = array_map(function ($v) use ($field) { 79 | return $v->toDateString(); 80 | }, $values); 81 | 82 | if ($builder->getConnection()->getName() == 'sqlite') { 83 | return $this->applyDateOnSQLite($builder, $field->getColumn(), $values, $conjunction); 84 | } 85 | 86 | $builder->getQuery()->wheres[] = [ 87 | 'boolean' => $conjunction, 88 | 'type' => 'date', 89 | 'column' => $field->getColumn(), 90 | 'operator' => $this->getSqlOperator(), 91 | 'value' => new Expression("('" . implode("', '", $values) . "')"), 92 | ]; 93 | 94 | return $builder; 95 | } 96 | 97 | private function applyDateOnSQLite(Builder $builder, string $column, $values, string $conjunction = 'and') 98 | { 99 | $bind = implode(',', array_fill(0, count($values), '?')); 100 | 101 | $sql = "strftime('%Y-%m-%d', `{$column}`) {$this->getSqlOperator()} ({$bind})"; 102 | 103 | return $builder->whereRaw($sql, $values, $conjunction); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Operators/LessThan.php: -------------------------------------------------------------------------------- 1 | getColumn(); 18 | 19 | if ($field->getDatatype() == 'date') { 20 | $castInDB = config('advanced_filter.cast_db_date', false); 21 | 22 | if ($castInDB) { 23 | return $builder->whereDate($column, '<', $value, $conjunction); 24 | } 25 | 26 | $value = $value->startOfDay(); 27 | } 28 | 29 | return $builder->where($column, '<', $value, $conjunction); 30 | } 31 | 32 | public function getSqlOperator(): string 33 | { 34 | return '<'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Operators/LessThanOrEqual.php: -------------------------------------------------------------------------------- 1 | getColumn(); 18 | 19 | if ($field->getDatatype() == 'date') { 20 | $castInDB = config('advanced_filter.cast_db_date', false); 21 | 22 | if ($castInDB) { 23 | return $builder->whereDate($column, '<=', $value, $conjunction); 24 | } 25 | } 26 | 27 | return $builder->where($column, '<=', $value, $conjunction); 28 | } 29 | 30 | public function getSqlOperator(): string 31 | { 32 | return '<='; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Operators/NotContains.php: -------------------------------------------------------------------------------- 1 | whereNotNull($field->getColumn(), $conjunction); 16 | } 17 | 18 | if ($field->getDatatype() == 'date') { 19 | $castInDB = config('advanced_filter.cast_db_date', false); 20 | 21 | if ($castInDB) { 22 | return $builder->whereDate($field->getColumn(), $this->getSqlOperator(), $value, $conjunction); 23 | } else { 24 | return $builder->whereNotBetween($field->getColumn(), [$value, (clone $value)->endOfDay()], $conjunction); 25 | } 26 | } 27 | 28 | if ($field->getDatatype() == 'datetime') { 29 | return $builder->whereNotBetween($field->getColumn(), [$value, (clone $value)->endOfMinute()], $conjunction); 30 | } 31 | 32 | return $builder->where($field->getColumn(), $this->getSqlOperator(), $value, $conjunction); 33 | } 34 | 35 | public function getSqlOperator(): string 36 | { 37 | return '!='; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Operators/NotIn.php: -------------------------------------------------------------------------------- 1 | has($field->getRelation(), $this->getSqlOperator(), $value, $conjunction, $field->countCallback); 52 | } 53 | 54 | /** 55 | * The function calls when trying to apply the operator on a custom field 56 | * 57 | * @param Builder $builder 58 | * @param Field $field 59 | * @param $value 60 | * @param string $conjunction 61 | * 62 | * @return Builder 63 | */ 64 | public function applyOnCustom(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 65 | { 66 | $sql = "{$field->getColumn()} {$this->getSqlOperator()} "; 67 | 68 | /** @link https://github.com/laravel/framework/issues/31201#issuecomment-615682788 */ 69 | if ($field->getDatatype() == 'numeric') { 70 | $sql .= '(? + 0.0)'; 71 | } else { 72 | $sql .= '?'; 73 | } 74 | 75 | return $builder->whereRaw($sql, [$this->getSqlValue($value)], $conjunction); 76 | } 77 | 78 | /** 79 | * Apply the operator on the field depends on field type 80 | * 81 | * @param Builder $builder 82 | * @param Field $field 83 | * @param $value 84 | * @param string $conjunction 85 | * 86 | * @return Builder 87 | */ 88 | public function execute(Builder $builder, Field $field, $value, string $conjunction = 'and'): Builder 89 | { 90 | // if the field is custom, call applyOnCustom function 91 | if ($field->isCustom()) { 92 | if ($builder->getConnection()->getName() == 'mongodb') { 93 | throw new UnsupportedDriverException('MongoDB', 'custom'); 94 | } 95 | 96 | return $this->applyOnCustom($builder, $field, $value, $conjunction); 97 | } 98 | 99 | // if the field is count, call applyOnCount function 100 | if ($field->isCount()) { 101 | if ($builder->getConnection()->getName() == 'mongodb') { 102 | throw new UnsupportedDriverException('MongoDB', 'custom'); 103 | } 104 | 105 | return $this->applyOnCount($builder, $field, $value, $conjunction); 106 | } 107 | 108 | return $this->apply($builder, $field, $value, $conjunction); 109 | } 110 | 111 | protected function getSqlValue($value) 112 | { 113 | return $value; 114 | } 115 | 116 | public static function getFunction($operatorName) 117 | { 118 | $prefixOperatorFunction = config('advanced_filter.prefix_operator_function', 'filterWhere'); 119 | $operatorName = ucfirst($operatorName); 120 | 121 | return "{$prefixOperatorFunction}{$operatorName}"; 122 | } 123 | 124 | public function setAliases(array $aliases) 125 | { 126 | $this->aliases = $aliases; 127 | } 128 | 129 | public function __get($name) 130 | { 131 | if ($name == 'name' && empty($this->name)) { 132 | return class_basename($this); 133 | } 134 | 135 | if ($name == 'aliases' && empty($this->aliases)) { 136 | return [mb_strtolower(class_basename($this))]; 137 | } 138 | 139 | return $this->{$name}; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Operators/StartsWith.php: -------------------------------------------------------------------------------- 1 | $filter) { 17 | if (!is_int($fieldName)) { 18 | $operator = $filter[$this->fieldParams['operator']] ?? $this->defaultOperator; 19 | $value = $filter[$this->fieldParams['value']] ?? null; 20 | 21 | $requestFilter->addFilter($fieldName, $operator, $value); 22 | } 23 | } 24 | 25 | return $requestFilter; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/QueryFormats/JsonQueryFormat.php: -------------------------------------------------------------------------------- 1 | fieldParams['field']] ?? null; 20 | if ($fieldName) { 21 | $operator = $filter[$this->fieldParams['operator']] ?? $this->defaultOperator; 22 | $value = $filter[$this->fieldParams['value']] ?? null; 23 | 24 | $requestFilter->addFilter($fieldName, $operator, $value); 25 | } 26 | } 27 | 28 | return $requestFilter; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/QueryFormats/QueryFormat.php: -------------------------------------------------------------------------------- 1 | fieldParams = config('advanced_filter.field_params'); 24 | $this->defaultOperator = config('advanced_filter.default_operator'); 25 | } 26 | 27 | public abstract function format($filters): FilterRequest; 28 | 29 | public static function factory(Request $request) 30 | { 31 | if ($custom = static::loadCustomQueryFormat($request)) { 32 | return $custom; 33 | } 34 | 35 | return static::loadQueryFormat($request); 36 | } 37 | 38 | private static function loadCustomQueryFormat(Request $request) 39 | { 40 | $customQueryFormat = config('advanced_filter.custom_query_format'); 41 | 42 | if (!empty($customQueryFormat)) { 43 | try { 44 | $customFormat = new $customQueryFormat(); 45 | if ($customFormat instanceof QueryFormat) { 46 | return $customFormat->format($request); 47 | } 48 | } catch (\Exception $exception) { 49 | throw new \InvalidArgumentException('must be a valid query format'); 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | private static function loadQueryFormat(Request $request) 57 | { 58 | $filterName = config('advanced_filter.param_filter_name', 'filters'); 59 | 60 | switch (static::getQueryFormat()) { 61 | case self::QUERY_FORMAT_JSON: 62 | return (new JsonQueryFormat())->format($request->input($filterName, '[]')); 63 | case self::QUERY_FORMAT_SEPARATOR: 64 | return (new SeparatorQueryFormat())->format($request->all()); 65 | case self::QUERY_FORMAT_ARRAY: 66 | return (new ArrayQueryFormat())->format($request->input($filterName, [])); 67 | } 68 | 69 | throw new \InvalidArgumentException('must be a valid query format'); 70 | } 71 | 72 | public static function getQueryFormat() 73 | { 74 | return substr(config('advanced_filter.query_format', self::QUERY_FORMAT_JSON), 0, 9); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/QueryFormats/SeparatorQueryFormat.php: -------------------------------------------------------------------------------- 1 | getSeparatorFromFormat(); 17 | 18 | // convert string parameters to array 19 | $parameters = []; 20 | foreach ($filters as $paramName => $paramValue) { 21 | if (Str::startsWith($paramName, "{$prefix}{$separator}")) { 22 | $stringArray = $this->stringBySeparatorToStringArray($paramName, $separator); 23 | $stringArray .= "={$paramValue}"; // add the parameter value 24 | parse_str($stringArray, $filter); // parse to array 25 | 26 | $parameters = array_merge_recursive($parameters, $filter); 27 | } 28 | } 29 | 30 | // use ArrayQueryFormat 31 | return (new ArrayQueryFormat())->format($parameters[$prefix]); 32 | } 33 | 34 | private function getSeparatorFromFormat() 35 | { 36 | $queryFormat = config('advanced_filter.query_format', 'json'); 37 | $defaultSeparator = '^'; 38 | 39 | return (explode("separator:", $queryFormat)[1] ?? $defaultSeparator) ?: $defaultSeparator; 40 | } 41 | 42 | /** 43 | * Convert string separated to string array 44 | * 45 | * @param string $string 46 | * @param string $separator 47 | * 48 | * @return string 49 | */ 50 | private function stringBySeparatorToStringArray(string $string, string $separator) 51 | { 52 | $stringArray = ''; 53 | foreach (explode($separator, $string) as $index => $item) { 54 | if ($index == 0) { 55 | $stringArray .= "{$item}"; 56 | } else { 57 | $stringArray .= "[{$item}]"; 58 | } 59 | } 60 | 61 | return $stringArray; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /tests/GeneralSearchTest.php: -------------------------------------------------------------------------------- 1 | get(); 19 | 20 | $this->assertCount(2, $orders); 21 | 22 | $this->assertEquals(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Models/BaseModel.php: -------------------------------------------------------------------------------- 1 | 'date']; 31 | protected $dates = ['ship_date']; 32 | 33 | public function store() 34 | { 35 | return $this->belongsTo(Store::class); 36 | } 37 | 38 | public function orderLines() 39 | { 40 | return $this->hasMany(OrderLine::class); 41 | } 42 | 43 | public function setupFilter() 44 | { 45 | $this->addFields(['reference' => 'order_number', 'order_date', 'created_at']); 46 | $this->addFields(['subtotal', 'shipping_cost' => 'shipping'])->setDatatype('numeric'); 47 | $this->addField('ship_date')->setDatatype('datetime'); 48 | 49 | $this->addFields(['store_id', 'store.name' => 'store_name']); 50 | if (env('DB_CONNECTION') == 'sqlite') { 51 | $this->addCustomField('store_reference', '( `stores`.`name` || "-" || `orders`.`reference`)', 'store'); 52 | } else { 53 | $this->addCustomField('store_reference', 'CONCAT( `stores`.`name`, "-", `orders`.`reference`)', 'store'); 54 | } 55 | 56 | 57 | $this->addCountField('orderLines', 'lines_count'); 58 | 59 | $this->addCustomField('line_subtotal', '(`price` * `quantity`)', 'orderLines')->setDatatype('numeric'); 60 | $this->addField('orderLines.price', 'line_price'); 61 | 62 | $this->addField('orderLines.product_id', 'product_id'); 63 | $this->addField('orderLines.product.name', 'product_name'); 64 | $this->addField('orderLines.product.sku', 'product_sku'); 65 | 66 | $this->addGeneralSearch(['reference', 'orderLines.product.sku']); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Models/OrderLine.php: -------------------------------------------------------------------------------- 1 | belongsTo(Order::class); 26 | } 27 | 28 | public function product() 29 | { 30 | return $this->belongsTo(Product::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Models/Product.php: -------------------------------------------------------------------------------- 1 | hasMany(OrderLine::class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Models/Store.php: -------------------------------------------------------------------------------- 1 | hasMany(Order::class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Operators/BetweenTest.php: -------------------------------------------------------------------------------- 1 | <","value":' . $subtotal . '}]'; 17 | $request = Request::create("test?{$queryFilters}"); 18 | 19 | $orders = Order::filter($request)->get(); 20 | 21 | $this->assertCount(3, $orders); 22 | 23 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_date_fields() 28 | { 29 | $orderDate = '{"from":"2020-09-26", "to":"2020-10-01"}'; 30 | $queryFilters = 'filters=[{"field":"order_date","operator":"><","value":' . $orderDate . '}]'; 31 | $request = Request::create("test?{$queryFilters}"); 32 | 33 | $orders = Order::filter($request)->get(); 34 | 35 | $this->assertCount(2, $orders); 36 | 37 | $this->assertEquals(['LAF_0002', 'LAF_0005'], $orders->pluck('reference')->toArray()); 38 | } 39 | 40 | 41 | /** @test */ 42 | public function it_can_filter_datetime_fields() 43 | { 44 | $shipDate = '{"from":"2020-09-30 05:00:00", "to":"2020-10-02 05:00:00"}'; 45 | $queryFilters = 'filters=[{"field":"ship_date","operator":"><","value":' . $shipDate . '}]'; 46 | $request = Request::create("test?{$queryFilters}"); 47 | 48 | $orders = Order::filter($request)->get(); 49 | 50 | $this->assertCount(1, $orders); 51 | 52 | $this->assertEquals(['LAF_0004'], $orders->pluck('reference')->toArray()); 53 | } 54 | 55 | /** @test */ 56 | public function it_can_filter_custom_fields() 57 | { 58 | $lineSubtotal = '{"from":5.7, "to":8.6}'; 59 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"><","value":' . $lineSubtotal . '}]'; 60 | $request = Request::create("test?{$queryFilters}"); 61 | 62 | $orders = Order::filter($request)->get(); 63 | 64 | $this->assertCount(2, $orders); 65 | 66 | $this->assertEquals(['LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 67 | } 68 | 69 | /** @test */ 70 | public function it_can_filter_count_fields() 71 | { 72 | $linesCount = '{"from":1, "to":2}'; 73 | $queryFilters = 'filters=[{"field":"lines_count","operator":"><","value":' . $linesCount . '}]'; 74 | $request = Request::create("test?{$queryFilters}"); 75 | 76 | $this->expectException(UnsupportedOperatorException::class); 77 | 78 | Order::filter($request)->get(); 79 | } 80 | 81 | /** @test */ 82 | public function it_can_filter_relation_fields() 83 | { 84 | $productSku = '{"from": 5, "to":10}'; 85 | $queryFilters = 'filters=[{"field":"line_price","operator":"><","value":' . $productSku . '}]'; 86 | $request = Request::create("test?{$queryFilters}"); 87 | 88 | $orders = Order::filter($request)->get(); 89 | 90 | $this->assertCount(3, $orders); 91 | 92 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/Operators/ContainsTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(1, $orders); 22 | 23 | $this->assertEquals(['LAF_0005'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_numeric_fields() 28 | { 29 | $subtotal = 5; 30 | $queryFilters = 'filters=[{"field":"subtotal","operator":"*","value":"' . $subtotal . '"}]'; 31 | $request = Request::create("test?{$queryFilters}"); 32 | 33 | $orders = Order::filter($request)->get(); 34 | 35 | $this->assertCount(3, $orders); 36 | 37 | $this->assertEquals(['LAF_0001', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 38 | } 39 | 40 | /** @test */ 41 | public function it_can_filter_custom_fields() 42 | { 43 | $storeReference = 'Consulting'; 44 | $queryFilters = 'filters=[{"field":"store_reference","operator":"*","value":"' . $storeReference . '"}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertCount(3, $orders); 50 | 51 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0004'], $orders->pluck('reference')->toArray()); 52 | } 53 | 54 | /** @test */ 55 | public function it_can_not_filter_count_fields() 56 | { 57 | $linesCount = 2; 58 | $queryFilters = 'filters=[{"field":"lines_count","operator":"*","value":"' . $linesCount . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $this->expectException(UnsupportedOperatorException::class); 62 | 63 | Order::filter($request)->get(); 64 | } 65 | 66 | /** @test */ 67 | public function it_can_filter_relation_fields() 68 | { 69 | $productName = 'Nam'; 70 | $queryFilters = 'filters=[{"field":"product_name","operator":"*","value":"' . $productName . '"}]'; 71 | $request = Request::create("test?{$queryFilters}"); 72 | 73 | $orders = Order::filter($request)->get(); 74 | 75 | $this->assertCount(3, $orders); 76 | 77 | $this->assertEquals(['LAF_0001', 'LAF_0003', 'LAF_0005'], $orders->pluck('reference')->toArray()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Operators/EndsWithTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(1, $orders); 22 | 23 | $this->assertEquals(['LAF_0005'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_numeric_fields() 28 | { 29 | // on mysql will not work as expected, because of the decimal digits 30 | $subtotal = env('DB_CONNECTION') == 'sqlite' ? 5 : 50; 31 | $queryFilters = 'filters=[{"field":"subtotal","operator":"$","value":"' . $subtotal . '"}]'; 32 | $request = Request::create("test?{$queryFilters}"); 33 | 34 | $orders = Order::filter($request)->get(); 35 | 36 | if (env('DB_CONNECTION') == 'sqlite') { 37 | $this->assertCount(2, $orders); 38 | 39 | $this->assertEquals(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 40 | } else { 41 | $this->assertCount(1, $orders); 42 | 43 | $this->assertEquals(['LAF_0001'], $orders->pluck('reference')->toArray()); 44 | } 45 | } 46 | 47 | /** @test */ 48 | public function it_can_filter_custom_fields() 49 | { 50 | $storeReference = 'Consulting-LAF_0002'; 51 | $queryFilters = 'filters=[{"field":"store_reference","operator":"$","value":"' . $storeReference . '"}]'; 52 | $request = Request::create("test?{$queryFilters}"); 53 | 54 | $orders = Order::filter($request)->get(); 55 | 56 | $this->assertCount(1, $orders); 57 | 58 | $this->assertEquals(['LAF_0002'], $orders->pluck('reference')->toArray()); 59 | } 60 | 61 | /** @test */ 62 | public function it_can_not_filter_count_fields() 63 | { 64 | $linesCount = 2; 65 | $queryFilters = 'filters=[{"field":"lines_count","operator":"$","value":"' . $linesCount . '"}]'; 66 | $request = Request::create("test?{$queryFilters}"); 67 | 68 | $this->expectException(UnsupportedOperatorException::class); 69 | 70 | Order::filter($request)->get(); 71 | } 72 | 73 | /** @test */ 74 | public function it_can_filter_relation_fields() 75 | { 76 | $productName = 'libero'; 77 | $queryFilters = 'filters=[{"field":"product_name","operator":"$","value":"' . $productName . '"}]'; 78 | $request = Request::create("test?{$queryFilters}"); 79 | 80 | $orders = Order::filter($request)->get(); 81 | 82 | $this->assertCount(2, $orders); 83 | 84 | $this->assertEquals(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Operators/EqualsTest.php: -------------------------------------------------------------------------------- 1 | first(); 19 | 20 | $this->assertNotNull($order); 21 | 22 | $this->assertEquals($reference, $order->reference); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_numeric_fields() 27 | { 28 | $subtotal = 25; 29 | $queryFilters = 'filters=[{"field":"subtotal","operator":"equals","value":"' . $subtotal . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $order = Order::filter($request)->first(); 33 | 34 | $this->assertNotNull($order); 35 | 36 | $this->assertEquals('LAF_0003', $order->reference); 37 | } 38 | 39 | /** @test */ 40 | public function it_can_filter_date_fields() 41 | { 42 | $orderDate = '2020-10-2'; 43 | $queryFilters = 'filters=[{"field":"order_date","operator":"equals","value":"' . $orderDate . '"}]'; 44 | $request = Request::create("test?{$queryFilters}"); 45 | 46 | $orders = Order::filter($request)->get(); 47 | 48 | $this->assertCount(2, $orders); 49 | 50 | $this->assertEquals(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 51 | } 52 | 53 | 54 | /** @test */ 55 | public function it_can_filter_datetime_fields() 56 | { 57 | $shipDate = '2020-10-03 10:30:00'; 58 | $queryFilters = 'filters=[{"field":"ship_date","operator":"equals","value":"' . $shipDate . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $orders = Order::filter($request)->get(); 62 | 63 | $this->assertCount(1, $orders); 64 | 65 | $this->assertEquals(['LAF_0002'], $orders->pluck('reference')->toArray()); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_filter_custom_fields() 70 | { 71 | $lineSubtotal = 8.6; 72 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"equals","value":"' . $lineSubtotal . '"}]'; 73 | $request = Request::create("test?{$queryFilters}"); 74 | 75 | $orders = Order::filter($request)->get(); 76 | 77 | $this->assertCount(1, $orders); 78 | 79 | $this->assertEquals(['LAF_0005'], $orders->pluck('reference')->toArray()); 80 | } 81 | 82 | /** @test */ 83 | public function it_can_filter_count_fields() 84 | { 85 | $linesCount = 2; 86 | $queryFilters = 'filters=[{"field":"lines_count","operator":"equals","value":"' . $linesCount . '"}]'; 87 | $request = Request::create("test?{$queryFilters}"); 88 | 89 | $orders = Order::filter($request)->get(); 90 | 91 | $this->assertCount(2, $orders); 92 | 93 | $this->assertEquals(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 94 | } 95 | 96 | /** @test */ 97 | public function it_can_filter_relation_fields() 98 | { 99 | $storeName = 'Sociis Corporation'; 100 | $queryFilters = 'filters=[{"field":"store_name","operator":"equals","value":"' . $storeName . '"}]'; 101 | $request = Request::create("test?{$queryFilters}"); 102 | 103 | $orders = Order::filter($request)->get(); 104 | 105 | $this->assertCount(1, $orders); 106 | 107 | $this->assertEquals(['LAF_0005'], $orders->pluck('reference')->toArray()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Operators/GreaterThanOrEqualTest.php: -------------------------------------------------------------------------------- 1 | =","value":"' . $subtotal . '"}]'; 16 | $request = Request::create("test?{$queryFilters}"); 17 | 18 | $orders = Order::filter($request)->get(); 19 | 20 | $this->assertCount(3, $orders); 21 | 22 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_date_fields() 27 | { 28 | $orderDate = '2020-10-1'; 29 | $queryFilters = 'filters=[{"field":"order_date","operator":">=","value":"' . $orderDate . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $orders = Order::filter($request)->get(); 33 | 34 | $this->assertCount(3, $orders); 35 | 36 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 37 | } 38 | 39 | 40 | /** @test */ 41 | public function it_can_filter_datetime_fields() 42 | { 43 | $shipDate = '2020-09-30 5:25:04'; 44 | $queryFilters = 'filters=[{"field":"ship_date","operator":">=","value":"' . $shipDate . '"}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertCount(2, $orders); 50 | 51 | $this->assertEquals(['LAF_0002', 'LAF_0004'], $orders->pluck('reference')->toArray()); 52 | } 53 | 54 | /** @test */ 55 | public function it_can_filter_custom_fields() 56 | { 57 | $lineSubtotal = 8.6; 58 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":">=","value":"' . $lineSubtotal . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $orders = Order::filter($request)->get(); 62 | 63 | $this->assertCount(4, $orders); 64 | 65 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0005'], $orders->pluck('reference')->toArray()); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_filter_count_fields() 70 | { 71 | $linesCount = 1; 72 | $queryFilters = 'filters=[{"field":"lines_count","operator":">=","value":"' . $linesCount . '"}]'; 73 | $request = Request::create("test?{$queryFilters}"); 74 | 75 | $orders = Order::filter($request)->get(); 76 | 77 | $this->assertCount(5, $orders); 78 | 79 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], 80 | $orders->pluck('reference')->toArray()); 81 | } 82 | 83 | /** @test */ 84 | public function it_can_filter_relation_fields() 85 | { 86 | $linePrice = 10; 87 | $queryFilters = 'filters=[{"field":"line_price","operator":">=","value":"' . $linePrice . '"}]'; 88 | $request = Request::create("test?{$queryFilters}"); 89 | 90 | $orders = Order::filter($request)->get(); 91 | 92 | $this->assertCount(3, $orders); 93 | 94 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Operators/GreaterThanTest.php: -------------------------------------------------------------------------------- 1 | ","value":"' . $subtotal . '"}]'; 16 | $request = Request::create("test?{$queryFilters}"); 17 | 18 | $orders = Order::filter($request)->get(); 19 | 20 | $this->assertCount(2, $orders); 21 | 22 | $this->assertEquals(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_date_fields() 27 | { 28 | $orderDate = '2020-10-1'; 29 | $queryFilters = 'filters=[{"field":"order_date","operator":">","value":"' . $orderDate . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $orders = Order::filter($request)->get(); 33 | 34 | $this->assertCount(2, $orders); 35 | 36 | $this->assertEquals(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 37 | } 38 | 39 | 40 | /** @test */ 41 | public function it_can_filter_datetime_fields() 42 | { 43 | // sqlite does not support datetime datatype, but you can use "strftime", I will not support that 44 | if (env('DB_CONNECTION') == 'sqlite') { 45 | return $this->assertTrue(true); 46 | } 47 | 48 | $shipDate = '2020-09-30 5:25:04'; 49 | $queryFilters = 'filters=[{"field":"ship_date","operator":">","value":"' . $shipDate . '"}]'; 50 | $request = Request::create("test?{$queryFilters}"); 51 | 52 | $orders = Order::filter($request)->get(); 53 | 54 | $this->assertCount(1, $orders); 55 | 56 | $this->assertEquals(['LAF_0002'], $orders->pluck('reference')->toArray()); 57 | } 58 | 59 | /** @test */ 60 | public function it_can_filter_custom_fields() 61 | { 62 | $lineSubtotal = 8.6; 63 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":">","value":"' . $lineSubtotal . '"}]'; 64 | $request = Request::create("test?{$queryFilters}"); 65 | 66 | $orders = Order::filter($request)->get(); 67 | 68 | $this->assertCount(3, $orders); 69 | 70 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 71 | } 72 | 73 | /** @test */ 74 | public function it_can_filter_count_fields() 75 | { 76 | $linesCount = 1; 77 | $queryFilters = 'filters=[{"field":"lines_count","operator":">","value":"' . $linesCount . '"}]'; 78 | $request = Request::create("test?{$queryFilters}"); 79 | 80 | $orders = Order::filter($request)->get(); 81 | 82 | $this->assertCount(2, $orders); 83 | 84 | $this->assertEquals(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 85 | } 86 | 87 | /** @test */ 88 | public function it_can_filter_relation_fields() 89 | { 90 | $linePrice = 10; 91 | $queryFilters = 'filters=[{"field":"line_price","operator":">","value":"' . $linePrice . '"}]'; 92 | $request = Request::create("test?{$queryFilters}"); 93 | 94 | $orders = Order::filter($request)->get(); 95 | 96 | $this->assertCount(2, $orders); 97 | 98 | $this->assertEquals(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Operators/InTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(2, $orders); 22 | 23 | $this->assertEquals(['LAF_0001', 'LAF_0005'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_numeric_fields() 28 | { 29 | $subtotal = "[25,5.7]"; 30 | $queryFilters = 'filters=[{"field":"subtotal","operator":"|","value":' . $subtotal . '}]'; 31 | $request = Request::create("test?{$queryFilters}"); 32 | 33 | $orders = Order::filter($request)->get(); 34 | 35 | $this->assertCount(2, $orders); 36 | 37 | $this->assertEquals(['LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 38 | } 39 | 40 | /** @test */ 41 | public function it_can_filter_date_fields() 42 | { 43 | $orderDate = '["2020-10-02","2020-09-25"]'; 44 | $queryFilters = 'filters=[{"field":"order_date","operator":"|","value":' . $orderDate . '}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertCount(3, $orders); 50 | 51 | $this->assertEquals(['LAF_0001', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 52 | } 53 | 54 | 55 | /** @test */ 56 | public function it_can_filter_datetime_fields() 57 | { 58 | $shipDate = '["2020-10-03 10:30:00", "2020-09-30 05:25:04"]'; 59 | $queryFilters = 'filters=[{"field":"ship_date","operator":"|","value":' . $shipDate . '}]'; 60 | $request = Request::create("test?{$queryFilters}"); 61 | 62 | $orders = Order::filter($request)->get(); 63 | 64 | $this->assertCount(2, $orders); 65 | 66 | $this->assertEquals(['LAF_0002', 'LAF_0004'], $orders->pluck('reference')->toArray()); 67 | } 68 | 69 | /** @test */ 70 | public function it_can_filter_custom_fields() 71 | { 72 | $lineSubtotal = "[8.6,5.7]"; 73 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"|","value":' . $lineSubtotal . '}]'; 74 | $request = Request::create("test?{$queryFilters}"); 75 | 76 | $orders = Order::filter($request)->get(); 77 | 78 | $this->assertCount(2, $orders); 79 | 80 | $this->assertEquals(['LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 81 | } 82 | 83 | /** @test */ 84 | public function it_can_filter_count_fields() 85 | { 86 | $linesCount = "[1,2]"; 87 | $queryFilters = 'filters=[{"field":"lines_count","operator":"|","value":' . $linesCount . '}]'; 88 | $request = Request::create("test?{$queryFilters}"); 89 | 90 | $this->expectException(UnsupportedOperatorException::class); 91 | 92 | Order::filter($request)->get(); 93 | } 94 | 95 | /** @test */ 96 | public function it_can_filter_relation_fields() 97 | { 98 | $productSku = '["2971-KW","KU5-8ZD"]'; 99 | $queryFilters = 'filters=[{"field":"product_sku","operator":"|","value":' . $productSku . '}]'; 100 | $request = Request::create("test?{$queryFilters}"); 101 | 102 | $orders = Order::filter($request)->get(); 103 | 104 | $this->assertCount(3, $orders); 105 | 106 | $this->assertEquals(['LAF_0001', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Operators/LessThanOrEqualTest.php: -------------------------------------------------------------------------------- 1 | get(); 19 | 20 | $this->assertCount(3, $orders); 21 | 22 | $this->assertEquals(['LAF_0001', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_date_fields() 27 | { 28 | $orderDate = '2020-10-1'; 29 | $queryFilters = 'filters=[{"field":"order_date","operator":"<=","value":"' . $orderDate . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $orders = Order::filter($request)->get(); 33 | 34 | $this->assertCount(3, $orders); 35 | 36 | $this->assertEquals(['LAF_0002', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 37 | } 38 | 39 | 40 | /** @test */ 41 | public function it_can_filter_datetime_fields() 42 | { 43 | $shipDate = '2020-09-30 5:25:04'; 44 | $queryFilters = 'filters=[{"field":"ship_date","operator":"<=","value":"' . $shipDate . '"}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertCount(1, $orders); 50 | 51 | $this->assertEquals(['LAF_0004'], $orders->pluck('reference')->toArray()); 52 | } 53 | 54 | /** @test */ 55 | public function it_can_filter_custom_fields() 56 | { 57 | $lineSubtotal = 8.6; 58 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"<=","value":"' . $lineSubtotal . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $orders = Order::filter($request)->get(); 62 | 63 | $this->assertCount(2, $orders); 64 | 65 | $this->assertEquals(['LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_filter_count_fields() 70 | { 71 | $linesCount = 2; 72 | $queryFilters = 'filters=[{"field":"lines_count","operator":"<=","value":"' . $linesCount . '"}]'; 73 | $request = Request::create("test?{$queryFilters}"); 74 | 75 | $orders = Order::filter($request)->get(); 76 | 77 | $this->assertCount(5, $orders); 78 | 79 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], 80 | $orders->pluck('reference')->toArray()); 81 | } 82 | 83 | /** @test */ 84 | public function it_can_filter_relation_fields() 85 | { 86 | $linePrice = 10; 87 | $queryFilters = 'filters=[{"field":"line_price","operator":"<=","value":"' . $linePrice . '"}]'; 88 | $request = Request::create("test?{$queryFilters}"); 89 | 90 | $orders = Order::filter($request)->get(); 91 | 92 | $this->assertCount(4, $orders); 93 | 94 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Operators/LessThanTest.php: -------------------------------------------------------------------------------- 1 | get(); 19 | 20 | $this->assertCount(2, $orders); 21 | 22 | $this->assertEquals(['LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_date_fields() 27 | { 28 | $orderDate = '2020-10-1'; 29 | $queryFilters = 'filters=[{"field":"order_date","operator":"<","value":"' . $orderDate . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $orders = Order::filter($request)->get(); 33 | 34 | $this->assertCount(2, $orders); 35 | 36 | $this->assertEquals(['LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 37 | } 38 | 39 | 40 | /** @test */ 41 | public function it_can_filter_datetime_fields() 42 | { 43 | $shipDate = '2020-09-30 5:25:04'; 44 | $queryFilters = 'filters=[{"field":"ship_date","operator":"<","value":"' . $shipDate . '"}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertEmpty($orders); 50 | } 51 | 52 | /** @test */ 53 | public function it_can_filter_custom_fields() 54 | { 55 | $lineSubtotal = 8.6; 56 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"<","value":"' . $lineSubtotal . '"}]'; 57 | $request = Request::create("test?{$queryFilters}"); 58 | 59 | $orders = Order::filter($request)->get(); 60 | 61 | $this->assertCount(1, $orders); 62 | 63 | $this->assertEquals(['LAF_0004'], $orders->pluck('reference')->toArray()); 64 | } 65 | 66 | /** @test */ 67 | public function it_can_filter_count_fields() 68 | { 69 | $linesCount = 2; 70 | $queryFilters = 'filters=[{"field":"lines_count","operator":"<","value":"' . $linesCount . '"}]'; 71 | $request = Request::create("test?{$queryFilters}"); 72 | 73 | $orders = Order::filter($request)->get(); 74 | 75 | $this->assertCount(3, $orders); 76 | 77 | $this->assertEquals(['LAF_0001', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 78 | } 79 | 80 | /** @test */ 81 | public function it_can_filter_relation_fields() 82 | { 83 | $linePrice = 10; 84 | $queryFilters = 'filters=[{"field":"line_price","operator":"<","value":"' . $linePrice . '"}]'; 85 | $request = Request::create("test?{$queryFilters}"); 86 | 87 | $orders = Order::filter($request)->get(); 88 | 89 | $this->assertCount(3, $orders); 90 | 91 | $this->assertEquals(['LAF_0002', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Operators/NotContainsTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(4, $orders); 22 | 23 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_numeric_fields() 28 | { 29 | $subtotal = 5; 30 | $queryFilters = 'filters=[{"field":"subtotal","operator":"!*","value":"' . $subtotal . '"}]'; 31 | $request = Request::create("test?{$queryFilters}"); 32 | 33 | $orders = Order::filter($request)->get(); 34 | 35 | $this->assertCount(2, $orders); 36 | 37 | $this->assertEquals(['LAF_0002', 'LAF_0005'], $orders->pluck('reference')->toArray()); 38 | } 39 | 40 | /** @test */ 41 | public function it_can_filter_custom_fields() 42 | { 43 | $storeReference = 'Consulting'; 44 | $queryFilters = 'filters=[{"field":"store_reference","operator":"!*","value":"' . $storeReference . '"}]'; 45 | $request = Request::create("test?{$queryFilters}"); 46 | 47 | $orders = Order::filter($request)->get(); 48 | 49 | $this->assertCount(2, $orders); 50 | 51 | $this->assertEquals(['LAF_0003', 'LAF_0005'], $orders->pluck('reference')->toArray()); 52 | } 53 | 54 | /** @test */ 55 | public function it_can_not_filter_count_fields() 56 | { 57 | $linesCount = 2; 58 | $queryFilters = 'filters=[{"field":"lines_count","operator":"!*","value":"' . $linesCount . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $this->expectException(UnsupportedOperatorException::class); 62 | 63 | Order::filter($request)->get(); 64 | } 65 | 66 | /** @test */ 67 | public function it_can_filter_relation_fields() 68 | { 69 | $productName = 'Nam'; 70 | $queryFilters = 'filters=[{"field":"product_name","operator":"!*","value":"' . $productName . '"}]'; 71 | $request = Request::create("test?{$queryFilters}"); 72 | 73 | $orders = Order::filter($request)->get(); 74 | 75 | $this->assertCount(3, $orders); 76 | 77 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Operators/NotEndsWithTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(4, $orders); 22 | 23 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 24 | } 25 | 26 | /** @test */ 27 | public function it_can_filter_numeric_fields() 28 | { 29 | $subtotal = env('DB_CONNECTION') == 'sqlite' ? 5 : 50; 30 | $queryFilters = 'filters=[{"field":"subtotal","operator":"!$","value":"' . $subtotal . '"}]'; 31 | $request = Request::create("test?{$queryFilters}"); 32 | 33 | $orders = Order::filter($request)->get(); 34 | 35 | if (env('DB_CONNECTION') == 'sqlite') { 36 | $this->assertCount(3, $orders); 37 | 38 | $this->assertEquals(['LAF_0002', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 39 | } else { 40 | $this->assertCount(4, $orders); 41 | 42 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 43 | } 44 | } 45 | 46 | /** @test */ 47 | public function it_can_filter_custom_fields() 48 | { 49 | $storeReference = 'Consulting-LAF_0002'; 50 | $queryFilters = 'filters=[{"field":"store_reference","operator":"!$","value":"' . $storeReference . '"}]'; 51 | $request = Request::create("test?{$queryFilters}"); 52 | 53 | $orders = Order::filter($request)->get(); 54 | 55 | $this->assertCount(4, $orders); 56 | 57 | $this->assertEquals(['LAF_0001', 'LAF_0003', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 58 | } 59 | 60 | /** @test */ 61 | public function it_can_not_filter_count_fields() 62 | { 63 | $linesCount = 2; 64 | $queryFilters = 'filters=[{"field":"lines_count","operator":"!$","value":"' . $linesCount . '"}]'; 65 | $request = Request::create("test?{$queryFilters}"); 66 | 67 | $this->expectException(UnsupportedOperatorException::class); 68 | 69 | Order::filter($request)->get(); 70 | } 71 | 72 | /** @test */ 73 | public function it_can_filter_relation_fields() 74 | { 75 | $productName = 'libero'; 76 | $queryFilters = 'filters=[{"field":"product_name","operator":"!$","value":"' . $productName . '"}]'; 77 | $request = Request::create("test?{$queryFilters}"); 78 | 79 | $orders = Order::filter($request)->get(); 80 | 81 | $this->assertCount(5, $orders); 82 | 83 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], 84 | $orders->pluck('reference')->toArray()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Operators/NotEqualsTest.php: -------------------------------------------------------------------------------- 1 | get(); 19 | 20 | $this->assertCount(4, $orders); 21 | 22 | $this->assertNotContainsEquals('LAF_0005', $orders->pluck('reference')->toArray()); 23 | } 24 | 25 | /** @test */ 26 | public function it_can_filter_numeric_fields() 27 | { 28 | $subtotal = 25; 29 | $queryFilters = 'filters=[{"field":"subtotal","operator":"!=","value":"' . $subtotal . '"}]'; 30 | $request = Request::create("test?{$queryFilters}"); 31 | 32 | $orders = Order::filter($request)->get(); 33 | 34 | $this->assertCount(4, $orders); 35 | 36 | $this->assertNotContains('LAF_0003', $orders->pluck('reference')->toArray()); 37 | } 38 | 39 | /** @test */ 40 | public function it_can_filter_date_fields() 41 | { 42 | $orderDate = '2020-10-2'; 43 | $queryFilters = 'filters=[{"field":"order_date","operator":"!=","value":"' . $orderDate . '"}]'; 44 | $request = Request::create("test?{$queryFilters}"); 45 | 46 | $orders = Order::filter($request)->get(); 47 | 48 | $this->assertCount(3, $orders); 49 | 50 | $this->assertNotContains(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 51 | } 52 | 53 | 54 | /** @test */ 55 | public function it_can_filter_datetime_fields() 56 | { 57 | $shipDate = '2020-10-3 10:30:00'; 58 | $queryFilters = 'filters=[{"field":"ship_date","operator":"!=","value":"' . $shipDate . '"}]'; 59 | $request = Request::create("test?{$queryFilters}"); 60 | 61 | $orders = Order::filter($request)->get(); 62 | 63 | $this->assertCount(1, $orders); 64 | 65 | $this->assertNotContains('LAF_0002', $orders->pluck('reference')->toArray()); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_filter_custom_fields() 70 | { 71 | $lineSubtotal = 8.6; 72 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"!=","value":"' . $lineSubtotal . '"}]'; 73 | $request = Request::create("test?{$queryFilters}"); 74 | 75 | $orders = Order::filter($request)->get(); 76 | 77 | $this->assertCount(4, $orders); 78 | 79 | $this->assertNotContains('LAF_0005', $orders->pluck('reference')->toArray()); 80 | } 81 | 82 | /** @test */ 83 | public function it_can_filter_count_fields() 84 | { 85 | $linesCount = 2; 86 | $queryFilters = 'filters=[{"field":"lines_count","operator":"!=","value":"' . $linesCount . '"}]'; 87 | $request = Request::create("test?{$queryFilters}"); 88 | 89 | $orders = Order::filter($request)->get(); 90 | 91 | $this->assertCount(3, $orders); 92 | 93 | $this->assertNotContains(['LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 94 | } 95 | 96 | /** @test */ 97 | public function it_can_filter_relation_fields() 98 | { 99 | $storeName = 'Sociis Corporation'; 100 | $queryFilters = 'filters=[{"field":"store_name","operator":"!=","value":"' . $storeName . '"}]'; 101 | $request = Request::create("test?{$queryFilters}"); 102 | 103 | $orders = Order::filter($request)->get(); 104 | 105 | $this->assertCount(4, $orders); 106 | 107 | $this->assertNotContains('LAF_0005', $orders->pluck('reference')->toArray()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Operators/NotInTest.php: -------------------------------------------------------------------------------- 1 | get(); 21 | 22 | $this->assertCount(3, $orders); 23 | 24 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 25 | } 26 | 27 | /** @test */ 28 | public function it_can_filter_numeric_fields() 29 | { 30 | $subtotal = "[25,5.7]"; 31 | $queryFilters = 'filters=[{"field":"subtotal","operator":"!|","value":' . $subtotal . '}]'; 32 | $request = Request::create("test?{$queryFilters}"); 33 | 34 | $orders = Order::filter($request)->get(); 35 | 36 | $this->assertCount(3, $orders); 37 | 38 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0005'], $orders->pluck('reference')->toArray()); 39 | } 40 | 41 | /** @test */ 42 | public function it_can_filter_date_fields() 43 | { 44 | $orderDate = '["2020-10-02","2020-09-25"]'; 45 | $queryFilters = 'filters=[{"field":"order_date","operator":"!|","value":' . $orderDate . '}]'; 46 | $request = Request::create("test?{$queryFilters}"); 47 | 48 | $orders = Order::filter($request)->get(); 49 | 50 | $this->assertCount(2, $orders); 51 | 52 | $this->assertEquals(['LAF_0002', 'LAF_0005'], $orders->pluck('reference')->toArray()); 53 | } 54 | 55 | 56 | /** @test */ 57 | public function it_can_filter_datetime_fields() 58 | { 59 | $shipDate = '["2020-10-03 10:30:00", "2020-09-30 05:25:04"]'; 60 | $queryFilters = 'filters=[{"field":"ship_date","operator":"!|","value":' . $shipDate . '}]'; 61 | $request = Request::create("test?{$queryFilters}"); 62 | 63 | $orders = Order::filter($request)->get(); 64 | // 0 because of NULL-safe 65 | /** @link https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to */ 66 | $this->assertCount(0, $orders); 67 | } 68 | 69 | /** @test */ 70 | public function it_can_filter_custom_fields() 71 | { 72 | $lineSubtotal = "[8.6,5.7]"; 73 | $queryFilters = 'filters=[{"field":"line_subtotal","operator":"!|","value":' . $lineSubtotal . '}]'; 74 | $request = Request::create("test?{$queryFilters}"); 75 | 76 | $orders = Order::filter($request)->get(); 77 | 78 | $this->assertCount(3, $orders); 79 | 80 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003'], $orders->pluck('reference')->toArray()); 81 | } 82 | 83 | /** @test */ 84 | public function it_can_filter_count_fields() 85 | { 86 | $linesCount = "[1,2]"; 87 | $queryFilters = 'filters=[{"field":"lines_count","operator":"!|","value":' . $linesCount . '}]'; 88 | $request = Request::create("test?{$queryFilters}"); 89 | 90 | $this->expectException(UnsupportedOperatorException::class); 91 | 92 | Order::filter($request)->get(); 93 | } 94 | 95 | /** @test */ 96 | public function it_can_filter_relation_fields() 97 | { 98 | $productSku = '["2971-KW","KU5-8ZD"]'; 99 | $queryFilters = 'filters=[{"field":"product_sku","operator":"!|","value":' . $productSku . '}]'; 100 | $request = Request::create("test?{$queryFilters}"); 101 | 102 | $orders = Order::filter($request)->get(); 103 | 104 | $this->assertCount(3, $orders); 105 | 106 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0005'], $orders->pluck('reference')->toArray()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Operators/NotStartsWithTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertEmpty($orders); 22 | } 23 | 24 | /** @test */ 25 | public function it_can_filter_numeric_fields() 26 | { 27 | $subtotal = 5; 28 | $queryFilters = 'filters=[{"field":"subtotal","operator":"!^","value":"' . $subtotal . '"}]'; 29 | $request = Request::create("test?{$queryFilters}"); 30 | 31 | $orders = Order::filter($request)->get(); 32 | 33 | $this->assertCount(4, $orders); 34 | 35 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0005'], $orders->pluck('reference')->toArray()); 36 | } 37 | 38 | /** @test */ 39 | public function it_can_filter_custom_fields() 40 | { 41 | $storeReference = 'Sociis Corporation-LAF'; 42 | $queryFilters = 'filters=[{"field":"store_reference","operator":"!^","value":"' . $storeReference . '"}]'; 43 | $request = Request::create("test?{$queryFilters}"); 44 | 45 | $orders = Order::filter($request)->get(); 46 | 47 | $this->assertCount(4, $orders); 48 | 49 | $this->assertEquals(['LAF_0001', 'LAF_0002', 'LAF_0003', 'LAF_0004'], $orders->pluck('reference')->toArray()); 50 | } 51 | 52 | /** @test */ 53 | public function it_can_not_filter_count_fields() 54 | { 55 | $linesCount = 2; 56 | $queryFilters = 'filters=[{"field":"lines_count","operator":"!^","value":"' . $linesCount . '"}]'; 57 | $request = Request::create("test?{$queryFilters}"); 58 | 59 | $this->expectException(UnsupportedOperatorException::class); 60 | 61 | Order::filter($request)->get(); 62 | } 63 | 64 | /** @test */ 65 | public function it_can_filter_relation_fields() 66 | { 67 | $productName = 'Nam'; 68 | $queryFilters = 'filters=[{"field":"product_name","operator":"!^","value":"' . $productName . '"}]'; 69 | $request = Request::create("test?{$queryFilters}"); 70 | 71 | $orders = Order::filter($request)->get(); 72 | 73 | $this->assertCount(4, $orders); 74 | 75 | $this->assertEquals(['LAF_0002', 'LAF_0003', 'LAF_0004', 'LAF_0005'], $orders->pluck('reference')->toArray()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Operators/StartsWithTest.php: -------------------------------------------------------------------------------- 1 | get(); 20 | 21 | $this->assertCount(Order::count(), $orders); 22 | } 23 | 24 | /** @test */ 25 | public function it_can_filter_numeric_fields() 26 | { 27 | $subtotal = 5; 28 | $queryFilters = 'filters=[{"field":"subtotal","operator":"^","value":"' . $subtotal . '"}]'; 29 | $request = Request::create("test?{$queryFilters}"); 30 | 31 | $orders = Order::filter($request)->get(); 32 | 33 | $this->assertCount(1, $orders); 34 | 35 | $this->assertEquals(['LAF_0004'], $orders->pluck('reference')->toArray()); 36 | } 37 | 38 | /** @test */ 39 | public function it_can_filter_custom_fields() 40 | { 41 | $storeReference = 'Sociis Corporation-LAF'; 42 | $queryFilters = 'filters=[{"field":"store_reference","operator":"^","value":"' . $storeReference . '"}]'; 43 | $request = Request::create("test?{$queryFilters}"); 44 | 45 | $orders = Order::filter($request)->get(); 46 | 47 | $this->assertCount(1, $orders); 48 | 49 | $this->assertEquals(['LAF_0005'], $orders->pluck('reference')->toArray()); 50 | } 51 | 52 | /** @test */ 53 | public function it_can_not_filter_count_fields() 54 | { 55 | $linesCount = 2; 56 | $queryFilters = 'filters=[{"field":"lines_count","operator":"^","value":"' . $linesCount . '"}]'; 57 | $request = Request::create("test?{$queryFilters}"); 58 | 59 | $this->expectException(UnsupportedOperatorException::class); 60 | 61 | Order::filter($request)->get(); 62 | } 63 | 64 | /** @test */ 65 | public function it_can_filter_relation_fields() 66 | { 67 | $productName = 'Nam'; 68 | $queryFilters = 'filters=[{"field":"product_name","operator":"^","value":"' . $productName . '"}]'; 69 | $request = Request::create("test?{$queryFilters}"); 70 | 71 | $orders = Order::filter($request)->get(); 72 | 73 | $this->assertCount(2, $orders); 74 | 75 | $this->assertEquals(['LAF_0001', 'LAF_0003'], $orders->pluck('reference')->toArray()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/QueryFormatsTest.php: -------------------------------------------------------------------------------- 1 | 'email', 12 | 'operator' => 'equal', 13 | 'value' => 'abc', 14 | ]; 15 | 16 | private $arrayValueFilter = [ 17 | 'field' => 'email', 18 | 'operator' => 'in', 19 | 'value' => ['abc1', 'abc2'], 20 | ]; 21 | 22 | 23 | private $betweenFilter = [ 24 | 'field' => 'email', 25 | 'operator' => 'between', 26 | 'value' => ['from' => 10, 'to' => 20], 27 | ]; 28 | 29 | /** @test */ 30 | public function array_query_format() 31 | { 32 | $this->app['config']->set('advanced_filter.query_format', 'array'); 33 | 34 | $queryFilters = 'filters[email][value]=abc&filters[email][operator]=equal'; 35 | $request = Request::create("test?{$queryFilters}"); 36 | 37 | $filters = QueryFormat::factory($request)->getFilters(); 38 | 39 | $this->assertEquals([$this->normalFilter], $filters); 40 | } 41 | 42 | /** @test */ 43 | public function json_query_format() 44 | { 45 | $this->app['config']->set('advanced_filter.query_format', 'json'); 46 | 47 | $queryFilters = 'filters=[{"field":"email","operator":"equal","value":"abc"}]'; 48 | $request = Request::create("test?{$queryFilters}"); 49 | 50 | $filters = QueryFormat::factory($request)->getFilters(); 51 | 52 | $this->assertEquals([$this->normalFilter], $filters); 53 | } 54 | 55 | /** @test */ 56 | public function separator_query_format() 57 | { 58 | $this->app['config']->set('advanced_filter.query_format', 'separator:^'); 59 | 60 | $queryFilters = 'filters^email^value=abc&filters^email^operator=equal'; 61 | $request = Request::create("test?{$queryFilters}"); 62 | 63 | $filters = QueryFormat::factory($request)->getFilters(); 64 | 65 | $this->assertEquals([$this->normalFilter], $filters); 66 | } 67 | 68 | /** @test */ 69 | public function array_query_format_for_array_value() 70 | { 71 | $this->app['config']->set('advanced_filter.query_format', 'array'); 72 | 73 | $queryFilters = 'filters[email][value][0]=abc1&filters[email][value][1]=abc2&filters[email][operator]=in'; 74 | $request = Request::create("test?{$queryFilters}"); 75 | 76 | $filters = QueryFormat::factory($request)->getFilters(); 77 | 78 | $this->assertEquals([$this->arrayValueFilter], $filters); 79 | } 80 | 81 | /** @test */ 82 | public function json_query_format_for_array_value() 83 | { 84 | $this->app['config']->set('advanced_filter.query_format', 'json'); 85 | 86 | $queryFilters = 'filters=[{"field":"email","operator":"in","value":["abc1","abc2"]}]'; 87 | $request = Request::create("test?{$queryFilters}"); 88 | 89 | $filters = QueryFormat::factory($request)->getFilters(); 90 | 91 | $this->assertEquals([$this->arrayValueFilter], $filters); 92 | } 93 | 94 | /** @test */ 95 | public function separator_query_format_for_array_value() 96 | { 97 | $this->app['config']->set('advanced_filter.query_format', 'separator:^'); 98 | 99 | $queryFilters = 'filters^email^value^0=abc1&filters^email^value^1=abc2&filters^email^operator=in'; 100 | $request = Request::create("test?{$queryFilters}"); 101 | 102 | $filters = QueryFormat::factory($request)->getFilters(); 103 | 104 | $this->assertEquals([$this->arrayValueFilter], $filters); 105 | } 106 | 107 | /** @test */ 108 | public function array_query_format_for_between() 109 | { 110 | $this->app['config']->set('advanced_filter.query_format', 'array'); 111 | 112 | $queryFilters = 'filters[email][value][from]=10&filters[email][value][to]=20&filters[email][operator]=between'; 113 | $request = Request::create("test?{$queryFilters}"); 114 | 115 | $filters = QueryFormat::factory($request)->getFilters(); 116 | 117 | $this->assertEquals([$this->betweenFilter], $filters); 118 | } 119 | 120 | /** @test */ 121 | public function json_query_format_for_between() 122 | { 123 | $this->app['config']->set('advanced_filter.query_format', 'json'); 124 | 125 | $queryFilters = 'filters=[{"field":"email","operator":"between","value":{"from":10,"to":20}}]'; 126 | $request = Request::create("test?{$queryFilters}"); 127 | 128 | $filters = QueryFormat::factory($request)->getFilters(); 129 | 130 | $this->assertEquals([$this->betweenFilter], $filters); 131 | } 132 | 133 | /** @test */ 134 | public function separator_query_format_for_between() 135 | { 136 | $this->app['config']->set('advanced_filter.query_format', 'separator:^'); 137 | 138 | $queryFilters = 'filters^email^value^from=10&filters^email^value^to=20&filters^email^operator=between'; 139 | $request = Request::create("test?{$queryFilters}"); 140 | 141 | $filters = QueryFormat::factory($request)->getFilters(); 142 | 143 | $this->assertEquals([$this->betweenFilter], $filters); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/Seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(ProductSeeder::class); 17 | $this->call(StoreSeeder::class); 18 | $this->call(OrderSeeder::class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Seeds/OrderSeeder.php: -------------------------------------------------------------------------------- 1 | 1, 16 | 'reference' => 'LAF_0001', 17 | 'order_date' => '2020-10-2', 18 | 'subtotal' => 15.50, 19 | 'shipping_cost' => 1 20 | ])->orderLines()->create(['product_id' => 1, 'price' => 15, 'quantity' => 1]); 21 | 22 | 23 | Order::create([ 24 | 'store_id' => 1, 25 | 'reference' => 'LAF_0002', 26 | 'order_date' => '2020-10-1', 27 | 'ship_date' => '2020-10-03 10:30:00', 28 | 'subtotal' => 20.00, 29 | 'shipping_cost' => 0 30 | ])->orderLines()->createMany([ 31 | ['product_id' => 2, 'price' => 5, 'quantity' => 2], 32 | ['product_id' => 3, 'price' => 10, 'quantity' => 1] 33 | ]); 34 | 35 | 36 | Order::create([ 37 | 'store_id' => 2, 38 | 'reference' => 'LAF_0003', 39 | 'order_date' => '2020-10-2', 40 | 'subtotal' => 25.00, 41 | 'shipping_cost' => 1 42 | ])->orderLines()->createMany([ 43 | ['product_id' => 1, 'price' => 15, 'quantity' => 1], 44 | ['product_id' => 3, 'price' => 10, 'quantity' => 1] 45 | ]); 46 | 47 | 48 | Order::create([ 49 | 'store_id' => 3, 50 | 'reference' => 'LAF_0004', 51 | 'order_date' => '2020-09-25', 52 | 'ship_date' => '2020-09-30 05:25:04', 53 | 'subtotal' => 5.70, 54 | 'shipping_cost' => 1.5 55 | ])->orderLines()->create(['product_id' => 5, 'price' => 5.7, 'quantity' => 1]); 56 | 57 | 58 | Order::create([ 59 | 'store_id' => 5, 60 | 'reference' => 'LAF_0005', 61 | 'order_date' => '2020-09-26', 62 | 'subtotal' => 8.60, 63 | 'shipping_cost' => 3 64 | ])->orderLines()->create(['product_id' => 6, 'price' => 4.3, 'quantity' => 2]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Seeds/ProductSeeder.php: -------------------------------------------------------------------------------- 1 | 'Nam porttitor scelerisque neque. Nullam', 'sku' => '2971-KW']); 15 | Product::create(['name' => 'tellus id nunc interdum feugiat.', 'sku' => 'M0E-8H3']); 16 | Product::create(['name' => 'magna tellus faucibus leo, libero', 'sku' => '64759-40420']); 17 | Product::create(['name' => 'eu neque pellentesque massa lobortis', 'sku' => 'L5X-7J3']); 18 | Product::create(['name' => 'arcu vel quam dignissim pharetra.', 'sku' => 'KU5-8ZD']); 19 | Product::create(['name' => 'magna. Nam ligula elit, pretium', 'sku' => 'LT5W-9CV']); 20 | Product::create(['name' => 'aliquam eros turpis non enim.', 'sku' => '943262']); 21 | Product::create(['name' => 'pharetra nibh. Aliquam ornare, libero', 'sku' => '50336']); 22 | Product::create(['name' => 'purus, accumsan interdum libero dui', 'sku' => '7889-LY']); 23 | Product::create(['name' => 'sit amet ultricies sem magna', 'sku' => 'K6V-4G6']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Seeds/StoreSeeder.php: -------------------------------------------------------------------------------- 1 | 'Placerat Consulting']); 16 | Store::create(['name' => 'Hendrerit A Arcu Ltd']); 17 | Store::create(['name' => 'Proin Eget Odio Consulting']); 18 | Store::create(['name' => 'Orci In Consequat Associates']); 19 | Store::create(['name' => 'Sociis Corporation']); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | artisan('migrate:fresh'); 20 | 21 | $this->setupDatabase($this->app['db']->connection()->getSchemaBuilder()); 22 | 23 | $this->seed(DatabaseSeeder::class); 24 | 25 | RefreshDatabaseState::$migrated = true; 26 | } 27 | } 28 | 29 | protected function getPackageProviders($app) 30 | { 31 | return [ 32 | AdvancedFilterServiceProvider::class, 33 | // MongodbServiceProvider::class 34 | ]; 35 | } 36 | 37 | protected function getEnvironmentSetUp($app) 38 | { 39 | // setup mongodb config 40 | if (env('DB_CONNECTION') == 'mongodb') { 41 | Config::set('database.connections.mongodb', [ 42 | 'driver' => 'mongodb', 43 | 'host' => env('DB_HOST', '127.0.0.1'), 44 | 'port' => env('DB_PORT', 27017), 45 | 'database' => env('DB_DATABASE', 'test'), 46 | 'username' => env('DB_USERNAME'), 47 | 'password' => env('DB_PASSWORD'), 48 | 'options' => [ 49 | 'database' => 'admin' // sets the authentication database required by mongo 3 50 | ] 51 | ]); 52 | } 53 | } 54 | 55 | private function setupDatabase(Builder $schema) 56 | { 57 | $schema->create('stores', function (Blueprint $table) { 58 | $table->increments('id'); 59 | $table->string('name'); 60 | $table->timestamps(); 61 | }); 62 | 63 | $schema->create('products', function (Blueprint $table) { 64 | $table->increments('id'); 65 | $table->string('name'); 66 | $table->string('sku'); 67 | $table->timestamps(); 68 | }); 69 | 70 | $schema->create('orders', function (Blueprint $table) { 71 | $table->increments('id'); 72 | $table->unsignedInteger('store_id'); 73 | $table->string('reference'); 74 | $table->dateTime('order_date'); 75 | $table->dateTime('ship_date')->nullable(); 76 | $table->decimal('subtotal'); 77 | $table->decimal('shipping_cost'); 78 | $table->timestamps(); 79 | }); 80 | 81 | $schema->create('order_lines', function (Blueprint $table) { 82 | $table->increments('id'); 83 | $table->unsignedInteger('order_id'); 84 | $table->unsignedInteger('product_id'); 85 | $table->float('price'); 86 | $table->integer('quantity'); 87 | $table->timestamps(); 88 | }); 89 | } 90 | } 91 | --------------------------------------------------------------------------------