├── .gitignore ├── .scrutinizer.yml ├── LICENSE ├── README.md ├── codeception.yml ├── composer.json ├── src └── Jedrzej │ └── Sortable │ ├── Criterion.php │ └── SortableTrait.php └── tests ├── _bootstrap.php ├── unit.suite.yml └── unit ├── CriterionTest.php ├── SortableTraitTest.php ├── TestBuilder.php ├── TestModel.php ├── TestModelWithAllFieldsSortable.php ├── TestModelWithDefaultSortOrder.php ├── TestModelWithDefaultSortingCriteria.php ├── TestModelWithSortableCallbackMethod.php ├── TestModelWithSortableMethod.php ├── TestModelWithSortableProperty.php └── _bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | tests/_output/* 2 | tests/*/*Tester.php 3 | composer.lock 4 | vendor 5 | .idea 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'tests/*' 4 | tools: 5 | external_code_coverage: 6 | timeout: 60 7 | 8 | build: 9 | tests: 10 | before: 11 | - 'vendor/codeception/codeception/codecept build' 12 | checks: 13 | php: 14 | verify_property_names: true 15 | verify_argument_usable_as_reference: true 16 | verify_access_scope_valid: true 17 | variable_existence: true 18 | useless_calls: true 19 | use_statement_alias_conflict: true 20 | uppercase_constants: true 21 | unused_variables: true 22 | unused_properties: true 23 | unused_parameters: true 24 | unused_methods: true 25 | unreachable_code: true 26 | side_effects_or_types: true 27 | too_many_arguments: false 28 | symfony_request_injection: true 29 | switch_fallthrough_commented: true 30 | sql_injection_vulnerabilities: true 31 | single_namespace_per_use: true 32 | simplify_boolean_return: true 33 | security_vulnerabilities: true 34 | require_scope_for_properties: true 35 | require_scope_for_methods: true 36 | require_php_tag_first: true 37 | psr2_switch_declaration: true 38 | psr2_class_declaration: true 39 | property_assignments: true 40 | precedence_mistakes: true 41 | precedence_in_conditions: true 42 | return_doc_comments: true 43 | return_doc_comment_if_not_inferrable: true 44 | phpunit_assertions: true 45 | php5_style_constructor: true 46 | parse_doc_comments: true 47 | parameter_non_unique: true 48 | overriding_private_members: true 49 | one_class_per_file: true 50 | non_commented_empty_catch_block: true 51 | no_unnecessary_if: true 52 | no_unnecessary_final_modifier: true 53 | no_underscore_prefix_in_properties: true 54 | parameter_doc_comments: true 55 | param_doc_comment_if_not_inferrable: true 56 | optional_parameters_at_the_end: true 57 | no_underscore_prefix_in_methods: true 58 | no_trait_type_hints: true 59 | no_trailing_whitespace: true 60 | no_short_open_tag: true 61 | no_property_on_interface: true 62 | no_non_implemented_abstract_methods: true 63 | no_global_keyword: true 64 | no_goto: true 65 | no_exit: true 66 | no_eval: true 67 | no_error_suppression: true 68 | no_empty_statements: true 69 | no_duplicate_arguments: true 70 | no_debug_code: true 71 | no_commented_out_code: true 72 | missing_arguments: true 73 | method_calls_on_non_object: true 74 | instanceof_class_exists: true 75 | foreach_usable_as_reference: true 76 | more_specific_types_in_doc_comments: true 77 | function_in_camel_caps: true 78 | foreach_traversable: true 79 | fix_doc_comments: true 80 | encourage_shallow_comparison: true 81 | duplication: true 82 | deprecated_code_usage: true 83 | deadlock_detection_in_loops: true 84 | code_rating: true 85 | closure_use_not_conflicting: true 86 | closure_use_modifiable: true 87 | catch_class_exists: true 88 | encourage_single_quotes: true 89 | encourage_postdec_operator: true 90 | blank_line_after_namespace_declaration: true 91 | avoid_useless_overridden_methods: true 92 | avoid_usage_of_logical_operators: true 93 | avoid_superglobals: true 94 | avoid_length_functions_in_loops: true 95 | avoid_entity_manager_injection: true 96 | avoid_duplicate_types: true 97 | avoid_conflicting_incrementers: true 98 | avoid_corrupting_byteorder_marks: true 99 | avoid_closing_tag: true 100 | avoid_aliased_php_functions: true 101 | assignment_of_null_return: true 102 | argument_type_checks: true 103 | avoid_todo_comments: true 104 | avoid_perl_style_comments: true 105 | avoid_multiple_statements_on_same_line: true 106 | avoid_fixme_comments: true 107 | classes_in_camel_caps: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sortable trait for Laravel's Eloquent models 2 | 3 | This package adds sorting functionality to Eloquent models in Laravel 4/5/6. 4 | 5 | You could also find those packages useful: 6 | 7 | - [Searchable](https://github.com/jedrzej/searchable) - Allows filtering your models using request parameters 8 | - [Withable](https://github.com/jedrzej/withable) - Allows eager loading of relations using request parameters 9 | - [Pimpable](https://github.com/jedrzej/pimpable) - A meta package that combines Sortable, Searchable and Withable behaviours 10 | 11 | ## Composer install 12 | 13 | Add the following line to `composer.json` file in your project: 14 | 15 | "jedrzej/sortable": "0.0.12" 16 | 17 | or run the following in the commandline in your project's root folder: 18 | 19 | composer require "jedrzej/sortable" "0.0.12" 20 | 21 | ## Setting up sortable models 22 | 23 | In order to make an Eloquent model sortable, add the trait to the model and define a list of fields that the model can be sorted by. 24 | You can either define a `$sortable` property or implement a `getSortableAttributes` method if you want to execute some logic to define 25 | list of sortable fields. 26 | 27 | ```php 28 | use Jedrzej\Sortable\SortableTrait; 29 | 30 | class Post extends Eloquent 31 | { 32 | use SortableTrait; 33 | 34 | // either a property holding a list of sortable fields... 35 | public $sortable = ['title', 'forum_id', 'created_at']; 36 | 37 | // ...or a method that returns a list of sortable fields 38 | public function getSortableAttributes() 39 | { 40 | return ['title', 'forum_id', 'created_at']; 41 | } 42 | } 43 | ``` 44 | 45 | In order to make all fields sortable put an asterisk `*` in the list of sortable fields: 46 | 47 | ```php 48 | public $sortable = ['*']; 49 | ``` 50 | 51 | ## Sorting models 52 | 53 | `SortableTrait` adds a `sorted()` scope to the model - you can pass it a query being an array of sorting conditions: 54 | 55 | ```php 56 | // return all posts sorted by creation date in descending order 57 | Post::sorted('created_at,desc')->get(); 58 | 59 | // return all users sorted by level in ascending order and then by points indescending orders 60 | User::sorted(['level,asc', 'points,desc'])->get(); 61 | ``` 62 | or it will use `sort` parameter from the request as default: 63 | 64 | ```php 65 | // return all posts sorted by creation date in descending order by appending to URL 66 | ?sort=created_at,desc 67 | //and then calling 68 | Post::sorted()->get(); 69 | 70 | // return all users sorted by level in ascending order and then by points indescending orders by appending to URL 71 | ?sort[]=level,asc&sort[]=points,desc 72 | // and then calling 73 | User::sorted()->get(); 74 | ``` 75 | ## Overwriting default sorting logic 76 | 77 | It is possible to overwrite how sorting parameters are used and applied to the query by implementing a callback in your 78 | model named `sortFieldName`, e.g.: 79 | ```php 80 | // return all posts sorted by creation date in descending order 81 | Post::sorted('created_at,desc')->get(); 82 | 83 | // in model class overwrite the sorting logic so that 'created' field is used instead of 'created_at' 84 | public function sortCreatedAt($query, $direction = 'desc') 85 | { 86 | return $query->orderBy('created', $direction); 87 | } 88 | ``` 89 | 90 | ## Defining default sorting criteria 91 | 92 | It is possible to define default sorting criteria that will be used if no sorting criteria are provided in the request or 93 | passed to `sorted` method of your model. Default sorting criteria should be defined in $defaultSortCriteria property, e.g.: 94 | 95 | ```php 96 | // sort by latest first 97 | protected $defaultSortCriteria = ['created_at,desc']; 98 | ``` 99 | 100 | ## Defining default sorting order 101 | 102 | By default asc is considered as default sorting order. It is possible to define default sorting order that will be used if no sorting order is provided in the request or 103 | passed to `sorted` method of your model. Default sorting order should be defined in $defaultSortOrder property, e.g.: 104 | 105 | ```php 106 | // sort in desc order by default if no order is specified in request 107 | protected $defaultSortOrder = 'desc'; 108 | // sort in asc order by default if no order is specified in request 109 | protected $defaultSortOrder = 'asc'; 110 | ``` 111 | 112 | ## Additional configuration 113 | 114 | If you are using `sort` request parameter for other purpose, you can change the name of the parameter that will be 115 | interpreted as sorting criteria by setting a `$sortParameterName` property in your model, e.g.: 116 | ```php 117 | protected $sortParameterName = 'sortBy'; 118 | ``` 119 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | helpers: tests/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | colors: true 10 | memory_limit: 1024M 11 | coverage: 12 | enabled: true 13 | include: 14 | - src/* -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jedrzej/sortable", 3 | "description": "Sortable trait for Laravel's Eloquent models - sort your models using request parameters", 4 | "keywords": [ 5 | "laravel", 6 | "sort", 7 | "sortable", 8 | "eloquent", 9 | "model", 10 | "api", 11 | "request" 12 | ], 13 | "homepage": "https://github.com/jedrzej/sortable", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Jędrzej Kuryło", 18 | "email": "jedrzej.kurylo@gmail.com" 19 | } 20 | ], 21 | "require": { 22 | "illuminate/database": "*", 23 | "illuminate/support": "*" 24 | }, 25 | "require-dev": { 26 | "codeception/codeception": "2.*", 27 | "codeception/specify": "*", 28 | "codeception/assert-throws": "*", 29 | "mockery/mockery": "*" 30 | }, 31 | "suggest": { 32 | "jedrzej/searchable": "Allows filtering your models using request parameters", 33 | "jedrzej/withable": "Allows eager loading of relations using request parameters" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Jedrzej\\": "src/Jedrzej/", 38 | "Illuminate\\": "vendor/laravel/framework/src/Illuminate/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Jedrzej/Sortable/Criterion.php: -------------------------------------------------------------------------------- 1 | field; 22 | } 23 | 24 | /** 25 | * @return string 26 | */ 27 | public function getOrder() 28 | { 29 | return $this->order; 30 | } 31 | 32 | /** 33 | * Creates criterion object for given value. 34 | * 35 | * @param string $value query value 36 | * @param string $defaultOrder default sort order if order is not given explicitly in query 37 | * 38 | * @return Criterion 39 | */ 40 | public static function make($value, $defaultOrder = self::ORDER_ASCENDING) 41 | { 42 | $value = static::prepareValue($value); 43 | list($field, $order) = static::parseFieldAndOrder($value, $defaultOrder); 44 | 45 | static::validateFieldName($field); 46 | 47 | return new static($field, $order); 48 | } 49 | 50 | /** 51 | * Applies criterion to query. 52 | * 53 | * @param Builder $builder query builder 54 | */ 55 | public function apply(Builder $builder) 56 | { 57 | $sortMethod = 'sort' . Str::studly($this->getField()); 58 | 59 | if(method_exists($builder->getModel(), $sortMethod)) { 60 | call_user_func_array([$builder->getModel(), $sortMethod], [$builder, $this->getOrder()]); 61 | } else { 62 | $builder->orderBy($this->getField(), $this->getOrder()); 63 | } 64 | } 65 | 66 | /** 67 | * @param string $field field name 68 | * @param string $order sort order 69 | */ 70 | protected function __construct($field, $order) 71 | { 72 | if (!in_array($order, [static::ORDER_ASCENDING, static::ORDER_DESCENDING])) { 73 | throw new InvalidArgumentException('Invalid order value'); 74 | } 75 | 76 | $this->field = $field; 77 | $this->order = $order; 78 | } 79 | 80 | /** 81 | * Makes sure field names contain only allowed characters 82 | * 83 | * @param string $fieldName 84 | */ 85 | protected static function validateFieldName($fieldName) { 86 | if (!preg_match('/^[a-zA-Z0-9\-_:\.]+$/', $fieldName)) { 87 | throw new InvalidArgumentException(sprintf('Incorrect field name: %s', $fieldName)); 88 | } 89 | } 90 | 91 | /** 92 | * Cleans value and converts to array if needed. 93 | * 94 | * @param string $value value 95 | * 96 | * @return string 97 | */ 98 | protected static function prepareValue($value) 99 | { 100 | return trim($value, " \t\n\r\0\x0B"); 101 | } 102 | 103 | /** 104 | * Parse query parameter and get field name and order. 105 | * 106 | * @param string $value 107 | * @param string $defaultOrder default sort order if order is not given explicitly in query 108 | * 109 | * @return string[] 110 | * 111 | * @throws InvalidArgumentException when unable to parse field name or order 112 | */ 113 | protected static function parseFieldAndOrder($value, $defaultOrder) 114 | { 115 | if (preg_match('/^([^,]+)(,(asc|desc))?$/', $value, $match)) { 116 | return [$match[1], isset($match[3]) ? $match[3] : $defaultOrder]; 117 | 118 | } 119 | 120 | throw new InvalidArgumentException(sprintf('Unable to parse field name or order from "%s"', $value)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Jedrzej/Sortable/SortableTrait.php: -------------------------------------------------------------------------------- 1 | _getSortParameterName()] is used by default 14 | * with fallback to $this->defaultSortCriteria if $this->_getSortParameterName() parameter is missing 15 | * in the request parameters 16 | */ 17 | public function scopeSorted(Builder $builder, $query = []) 18 | { 19 | $query = (array)($query ?: Request::input($this->_getSortParameterName(), $this->_getDefaultSortCriteria())); 20 | 21 | if (empty($query)) { 22 | $query = $this->_getDefaultSortCriteria(); 23 | } 24 | 25 | //unwrap sorting criteria array (for backwards compatibility) 26 | if (is_array($query) && array_key_exists($this->_getSortParameterName(), $query)) { 27 | $query = (array)$query[$this->_getSortParameterName()]; 28 | } 29 | 30 | $criteria = $this->getCriteria($builder, $query); 31 | $this->applyCriteria($builder, $criteria); 32 | } 33 | 34 | /** 35 | * Builds sort criteria based on model's sortable fields and query parameters. 36 | * 37 | * @param Builder $builder query builder 38 | * @param array $query query parameters 39 | * 40 | * @return array 41 | */ 42 | protected function getCriteria(Builder $builder, array $query) 43 | { 44 | $criteria = []; 45 | foreach ($query as $value) { 46 | $criterion = Criterion::make($value, $this->_getDefaultSortOrder()); 47 | if ($this->isFieldSortable($builder, $criterion->getField())) { 48 | $criteria[] = $criterion; 49 | } 50 | } 51 | 52 | return $criteria; 53 | } 54 | 55 | /** 56 | * Check if field is sortable for given model. 57 | * 58 | * @param Builder $builder query builder 59 | * @param string $field field name 60 | * 61 | * @return bool 62 | */ 63 | protected function isFieldSortable(Builder $builder, $field) 64 | { 65 | $sortable = $this->_getSortableAttributes($builder); 66 | 67 | return in_array($field, $sortable) || in_array('*', $sortable); 68 | } 69 | 70 | /** 71 | * Applies criteria to query 72 | * 73 | * @param Builder $builder query builder 74 | * @param Criterion[] $criteria sorting criteria 75 | */ 76 | protected function applyCriteria(Builder $builder, array $criteria) 77 | { 78 | foreach ($criteria as $criterion) { 79 | $criterion->apply($builder); 80 | } 81 | } 82 | 83 | /** 84 | * @param Builder $builder 85 | * 86 | * @return array list of sortable attributes 87 | */ 88 | protected function _getSortableAttributes(Builder $builder) 89 | { 90 | if (method_exists($builder->getModel(), 'getSortableAttributes')) { 91 | return $builder->getModel()->getSortableAttributes(); 92 | } 93 | 94 | if (property_exists($builder->getModel(), 'sortable')) { 95 | return $builder->getModel()->sortable; 96 | } 97 | 98 | throw new RuntimeException(sprintf('Model %s must either implement getSortableAttributes() or have $sortable property set', get_class($builder->getModel()))); 99 | } 100 | 101 | protected function _getSortParameterName() { 102 | return isset($this->sortParameterName) ? $this->sortParameterName : 'sort'; 103 | } 104 | 105 | protected function _getDefaultSortCriteria() { 106 | return isset($this->defaultSortCriteria) ? $this->defaultSortCriteria : []; 107 | } 108 | 109 | protected function _getDefaultSortOrder() { 110 | return isset($this->defaultSortOrder) ? $this->defaultSortOrder : Criterion::ORDER_ASCENDING; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | specify("correct value is parsed", function () { 14 | $this->assertEquals('aBc', Criterion::make('aBc,asc')->getField()); 15 | $this->assertEquals('cde', Criterion::make('cde,desc')->getField()); 16 | $this->assertEquals('asc', Criterion::make('abc,asc')->getOrder()); 17 | $this->assertEquals('desc', Criterion::make('cde,desc')->getOrder()); 18 | $this->assertEquals('a.b', Criterion::make('a.b,asc')->getField()); 19 | $this->assertEquals('a_b', Criterion::make('a_b,asc')->getField()); 20 | $this->assertEquals('a-b', Criterion::make('a-b,asc')->getField()); 21 | $this->assertEquals('a:b', Criterion::make('a:b,asc')->getField()); 22 | 23 | }); 24 | 25 | $this->specify("ascending order is used by default", function () { 26 | $this->assertEquals('abc', Criterion::make('abc')->getField()); 27 | $this->assertEquals('asc', Criterion::make('abc')->getOrder()); 28 | }); 29 | 30 | $this->specify("incorrect value results in exception being thrown", function () { 31 | try { 32 | Criterion::make('a,b,c'); 33 | $this->fail('Expected exception was not thrown'); 34 | } catch (InvalidArgumentException $e) { 35 | // 36 | } 37 | 38 | try { 39 | Criterion::make('abc,dasc'); 40 | $this->fail('Expected exception was not thrown'); 41 | } catch (InvalidArgumentException $e) { 42 | // 43 | } 44 | 45 | try { 46 | Criterion::make(',asc'); 47 | $this->fail('Expected exception was not thrown'); 48 | } catch (InvalidArgumentException $e) { 49 | // 50 | } 51 | }); 52 | 53 | $this->specify("field name is validated", function () { 54 | try { 55 | Criterion::make('a!b,asc'); 56 | $this->fail('Expected exception was not thrown'); 57 | } catch (InvalidArgumentException $e) { 58 | // 59 | } 60 | 61 | try { 62 | Criterion::make('a b,asc'); 63 | $this->fail('Expected exception was not thrown'); 64 | } catch (InvalidArgumentException $e) { 65 | // 66 | } 67 | 68 | try { 69 | Criterion::make('a#b,asc'); 70 | $this->fail('Expected exception was not thrown'); 71 | } catch (InvalidArgumentException $e) { 72 | // 73 | } 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/unit/SortableTraitTest.php: -------------------------------------------------------------------------------- 1 | specify("sort criterion is applied when only one is given", function () { 15 | $this->assertCount(1, (array)TestModelWithSortableMethod::sorted(['sort' => 'field1,asc'])->getQuery()->orders); 16 | }); 17 | 18 | $this->specify("sort criteria are applied when array is given", function () { 19 | $this->assertCount(2, (array)TestModelWithSortableMethod::sorted(['field1,asc', 'field2,desc'])->getQuery()->orders); 20 | }); $this->specify("sort criterion is applied when only one is given", function () { 21 | $this->assertCount(1, (array)TestModelWithSortableMethod::sorted(['sort' => 'field1,asc'])->getQuery()->orders); 22 | }); 23 | 24 | $this->specify("sort criteria are applied when array is given", function () { 25 | $this->assertCount(2, (array)TestModelWithSortableMethod::sorted(['field1,asc', 'field2,desc'])->getQuery()->orders); 26 | }); 27 | 28 | $this->specify("criteria are applied only to sortable parameters", function () { 29 | $this->assertCount(0, (array)TestModelWithSortableMethod::sorted('field0,asc')->getQuery()->orders); 30 | $this->assertCount(1, (array)TestModelWithSortableMethod::sorted(['field0,asc', 'field1,desc'])->getQuery()->orders); 31 | $this->assertCount(2, (array)TestModelWithSortableMethod::sorted(['sort' => ['field0,asc', 'field1,desc', 'field2,desc']])->getQuery()->orders); 32 | $this->assertCount(2, (array)TestModelWithSortableMethod::sorted(['field0,asc', 'field1,desc', 'field2,desc', 'field3,desc'])->getQuery()->orders); 33 | }); 34 | 35 | $this->specify("criteria are applied to columns by name", function () { 36 | $criterion = (array)TestModelWithSortableMethod::sorted('field1,asc')->getQuery()->orders[0]; 37 | $this->assertEquals('field1', $criterion['column']); 38 | }); 39 | 40 | $this->specify("criteria are applied in the same order as specified", function () { 41 | $criteria = (array)TestModelWithSortableMethod::sorted(['field1,desc', 'field2,desc'])->getQuery()->orders; 42 | $this->assertEquals('field1', $criteria[0]['column']); 43 | $this->assertEquals('field2', $criteria[1]['column']); 44 | 45 | $criteria = (array)TestModelWithSortableMethod::sorted(['field2,desc', 'field1,desc'])->getQuery()->orders; 46 | $this->assertEquals('field2', $criteria[0]['column']); 47 | $this->assertEquals('field1', $criteria[1]['column']); 48 | }); 49 | 50 | $this->specify('getSearchableAttribues is not required, if $searchable property exists', function() { 51 | $criteria = (array)TestModelWithSortableProperty::sorted(['field2,desc', 'field1,desc'])->getQuery()->orders; 52 | $this->assertEquals('field2', $criteria[0]['column']); 53 | $this->assertEquals('field1', $criteria[1]['column']); 54 | }); 55 | 56 | $this->specify('model must implement getSortableAttributes() or have $sortable property', function() { 57 | $this->assertThrows(RuntimeException::class, function() { 58 | TestModel::sorted(['field1,desc', 'field2,desc']); 59 | }); 60 | }); 61 | 62 | $this->specify('* in searchable field list makes all fields searchable', function() { 63 | $criteria = (array)TestModelWithAllFieldsSortable::sorted(['field2,desc', 'field42,desc'])->getQuery()->orders; 64 | $this->assertEquals('field2', $criteria[0]['column']); 65 | $this->assertEquals('field42', $criteria[1]['column']); 66 | }); 67 | 68 | $this->specify('available callback method is used in lieu of standard sorting', function() { 69 | $criteria = (array)TestModelWithSortableCallbackMethod::sorted(['created_at,desc'])->getQuery()->orders; 70 | $this->assertEquals('created', $criteria[0]['column']); 71 | $this->assertEquals('desc', $criteria[0]['direction']); 72 | }); 73 | 74 | $this->specify('default sorting criteria are applied', function() { 75 | Request::clearResolvedInstances(); 76 | Request::shouldReceive('input')->andReturn(null); 77 | $criteria = (array)TestModelWithDefaultSortingCriteria::sorted()->getQuery()->orders; 78 | $this->assertEquals('column1', $criteria[0]['column']); 79 | $this->assertEquals('desc', $criteria[0]['direction']); 80 | $this->assertEquals('column2', $criteria[1]['column']); 81 | $this->assertEquals('asc', $criteria[1]['direction']); 82 | }); 83 | 84 | $this->specify('default sorting order is applied', function() { 85 | Request::clearResolvedInstances(); 86 | Request::shouldReceive('input')->andReturn(['sort' => 'column1']); 87 | $criteria = (array)TestModelWithDefaultSortOrder::sorted()->getQuery()->orders; 88 | $this->assertEquals('column1', $criteria[0]['column']); 89 | $this->assertEquals('desc', $criteria[0]['direction']); 90 | 91 | Request::clearResolvedInstances(); 92 | Request::shouldReceive('input')->andReturn(['sort' => 'column1']); 93 | $criteria = (array)TestModelWithAllFieldsSortable::sorted()->getQuery()->orders; 94 | $this->assertEquals('column1', $criteria[0]['column']); 95 | $this->assertEquals('asc', $criteria[0]['direction']); 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/unit/TestBuilder.php: -------------------------------------------------------------------------------- 1 | orderBy('created', $direction); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/unit/TestModelWithSortableMethod.php: -------------------------------------------------------------------------------- 1 |