diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..df55cd7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false + +[*.{vue,js,scss}] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9af3157 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +/tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.scrutinizer.yml export-ignore +.travis.yml export-ignore +phpunit.php export-ignore +phpunit.xml.dist export-ignore +phpunit.xml export-ignore +.php_cs export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..497c4e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea +*.DS_Store +/vendor +/coverage +sftp-config.json +composer.lock +.subsplit +.php_cs.cache diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..55a35ff --- /dev/null +++ b/.php_cs @@ -0,0 +1,27 @@ + + +This source file is subject to the MIT license that is bundled. +EOF; + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules(array( + '@Symfony' => true, + 'header_comment' => array('header' => $header), + 'array_syntax' => array('syntax' => 'short'), + 'ordered_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'php_unit_construct' => true, + 'php_unit_strict' => true, + )) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in(__DIR__) + ) +; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a3e6e2 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +

laravel-eloquent-filter

+ +

An Eloquent way to filter Eloquent Models.

+ + +## Installing + +```shell +$ composer require huangbule/laravel-eloquent-filter -vvv +``` + +## Usage + +TODO + +## Contributing + +You can contribute in one of three ways: + +1. File bug reports using the [issue tracker](https://github.com/huangbule/laravel-eloquent-filter/issues). +2. Answer questions or fix bugs on the [issue tracker](https://github.com/huangbule/laravel-eloquent-filter/issues). +3. Contribute new features or update the wiki. + +_The code contribution process is not very formal. You just need to make sure that you follow the PSR-0, PSR-1, and PSR-2 coding guidelines. Any new code contributions must be accompanied by unit tests where applicable._ + +## License + +MIT \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..497f33e --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "huangbule/laravel-eloquent-filter", + "description": "An Eloquent way to filter Eloquent Models", + "license": "MIT", + "authors": [ + { + "name": "huangbule", + "email": "3314308761@qq.com" + } + ], + "require": {}, + "autoload": { + "psr-4": { + "Huangbule\\LaravelEloquentFilter\\": "src" + } + }, + "extra": { + "laravel": { + "providers": [ + "Huangbule\\LaravelEloquentFilter\\EloquentFilterProvider" + ], + "aliases": { + + } + } + } +} \ No newline at end of file diff --git a/config/filter.php b/config/filter.php new file mode 100644 index 0000000..209c010 --- /dev/null +++ b/config/filter.php @@ -0,0 +1,11 @@ + '$like', + + 'rule' => [ + 'uuid' => 'uuid|$eq', + 'department_name' => '#department', + ] + +]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..e47284c --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + ./tests/ + + + + + src/ + + + diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Contracts/Ipreprocess.php b/src/Contracts/Ipreprocess.php new file mode 100644 index 0000000..d9007ad --- /dev/null +++ b/src/Contracts/Ipreprocess.php @@ -0,0 +1,7 @@ +mergeConfigFrom(__DIR__.'/../config/filter.php', 'filter'); + } + + public function boot() + { + if ($this->app->runningInConsole()) { + $this->publishes([ + __DIR__.'/../config/filter.php' => config_path('filter.php'), + ], 'filter'); + + } + } + + +} \ No newline at end of file diff --git a/src/Exceptions/InvalidArgumentException.php b/src/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..7701c40 --- /dev/null +++ b/src/Exceptions/InvalidArgumentException.php @@ -0,0 +1,8 @@ +input() + * @param array $arr_filter + */ + public function scopeFilter($query, $param = [], $arr_filter = []) + { + foreach ($arr_filter as $key => $column) { + if (Str::contains($column, ':')) { + list($column, $operator) = explode(":", $column); + } else { + $operator = config('filter.rule.' . $column); + } + $operator ??= config('filter.default'); + $arr_operator = explode("|", $operator); + + $relation = null; + $filter_command = config('filter.default'); + + foreach ($arr_operator as $command) { + if (!$command) + throw new InvalidArgumentException($arr_filter[$key] . " format not valid"); + + //check rename or not + if (isset($this->renamedFilterFields[$column]) && !empty($param[$column])) { + $old_column = $column; + $column = $this->renamedFilterFields[$column]; + $param[$column] = $param[$old_column]; + } + + if (Str::startsWith($command, '$')) { + $filter_command = $command; + } elseif (Str::startsWith($command, '#')) { + $relation = Str::substr($command, 1); + + if (!method_exists(static::class, $relation)) { + throw new NotFoundException("'" . get_class(new static()) . "' has not found relation : " . $relation); + } + } else { + $preprocess_macro = static::$preprocessMacros[$command] ?? null; + if ($preprocess_macro) { + if (!$preprocess_macro instanceof Ipreprocess) + throw new NotInstanceOfInterfaceException(get_class($preprocess_macro) . " not implements Ipreprocess "); + + } else { + $process_class = Str::beforeLast(__NAMESPACE__, "Traits") . "Preprocess\\" . Str::studly($command . "Preprocess"); + if (!class_exists($process_class)) { + throw new NotFoundException($process_class . " not found"); + } + (new $process_class)->handle($column, $param); + } + } + } + + if ($filter_command) { + $filter = Str::of($filter_command . "Filter")->substr(1)->studly()->__toString(); + $filter = lcfirst($filter); + $macro = static::$filterMacros[$filter] ?? null; + if ($macro) { + if ($macro instanceof \Closure) { + $macro = $macro->bindTo(null, static::class); + $macro($query, $column, $param); + } + } else { + $reflection_trait = new \ReflectionClass(self::class); + $method_exists = $reflection_trait->hasMethod($filter); + if (!$method_exists) + throw new NotFoundException($filter . " not found"); + + if (!empty($param[$column])) { + $callback = $this->createFilterClosure($filter, $column, $param); + if ($relation) { + $query->whereHas($relation, $callback); + } else { + $callback($query); + } + } + } + } + } + } + + private function createFilterClosure($filter, $column, $param) + { + return function ($qr) use ($filter, $column, $param) { + return $this->{$filter}($qr, $column, $param); + }; + } + + private function likeFilter($qr, $column, $param) + { + return $qr->where($column, 'like', '%' . $param[$column] . '%'); + } + + private function eqFilter($qr, $column, $param) + { + return $qr->where($column, $param[$column]); + } + + private function neqFilter($qr, $column, $param) + { + return $qr->where($column, '!=', $param[$column]); + } + + private function lteFilter($qr, $column, $param) + { + return $qr->where($column, '<=', $param[$column]); + } + + private function ltFilter($qr, $column, $param) + { + return $qr->where($column, '<', $param[$column]); + } + + private function gteFilter($qr, $column, $param) + { + return $qr->where($column, '>=', $param[$column]); + } + + private function gtFilter($qr, $column, $param) + { + return $qr->where($column, '>', $param[$column]); + } + + private function inFilter($qr, $column, $param) + { + if (!is_array($arr = $param[$column])) { + $arr = explode(',', $arr); + } + return $qr->whereIn($column, $arr); + } + + private function notInFilter($qr, $column, $param) + { + if (!is_array($arr = $param[$column])) { + $arr = explode(',', $arr); + } + return $qr->whereNotIn($column, $arr); + } + + private function betweenFilter($qr, $column, $param) + { + if (!is_array($arr = $param[$column])) { + $arr = explode(',', $arr); + } + return $qr->whereBetween($column, $arr); + } + + private function halfOpenFilter($qr, $column, $param) + { + if (!is_array($arr = $param[$column])) { + $arr = explode(',', $arr); + } + if (count($arr) != 2) + throw new InvalidArgumentException($column . " "); + throw_api_exception($column . " corresponding values must be throughput or comma separated."); + + return $qr->where($column, '>=', $arr[0])->where($column, '<', $arr[1]); + } + +} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29