diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61ead86 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/README.md b/README.md new file mode 100644 index 0000000..274a89e --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# Laravel Meta data + +A php trait for searching on laravel eloquent models + +## Requirements + +- PHP >= 5.4 +- Laravel >= 5.0 + + +## Installation + +Add laravel-searchable to your composer.json file: + +```json +"require": { + "astritzeqiri/laravel-searchable": "~1.0" +} +``` + +Get composer to install the package: + +``` +$ composer require astritzeqiri/laravel-searchable +``` + +## Usage + +### Examples + +First you need to go to your model and use the Searchable: + +```php +// E.x. User.php +// add this before the class declaration +use AstritZeqiri\LaravelSearchable\Traits\Searchable; + +// after the class declaration add this code snippet: +use Searchable; +``` + +Basic search: + +```php +// This gives you a list of users that match the name john +$users = App\User::search('John', ['name'])->get(); + +// if you want the search to be exact you pass a third attribute +$users = App\User::search('John', ['name'], true)->get(); +``` + +The array of search fields can also be set on the Model itself E.x. User.php: + +```php + +class User extends Model +{ + // These are the default search fields. + protected static $searchOn = ['first_name', 'last_name']; + +} + +// Now you can do this. +// That gives you the users with the first_name or last_name Doe +$users = App\User::search('Doe')->get(); + +// Of course if you give it the second attribute it ignores the model fields. +// Now it only searches on the first_name not the last_name +$users = App\User::search('Doe', ['first_name'])->get(); + + +``` + + +Sometimes you may want to search on some other fields that are not on the user model but in some other table related to the user: + +```php + +// Ex. You want to search users profile description which is contained in the profiles table, +// you can do that by giving for example profile_description asf field. +$users = App\User::search('Texas', ['profile_description'])->get(); + +// And then you'll have to declare a scope function on you User model for that field. +// The function has to be called 'scopeSearchOn' and your field on studly_case +// in this example it needs to be 'scopeSearchOnProfileDescription' +class User extends Model +{ + /** + * Search on the users profile description. + * + * @param QueryBuilder $query + * @param string $search [the string that we are searching for] + * @param string $exact [if exact searching has been required] + * + * @return QueryBuilder $query + */ + public function scopeSearchOnProfileDescription($query, $search, $exact) + { + return $query->whereHas('profile', function($query) use($search) { + return $query->where('description', 'LIKE', $search); + }); + } + +} + +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4ecf4a5 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "astritzeqiri/laravel-searchable", + "description": "A php trait for searching on laravel eloquent models", + "type": "laravel-plugin", + "keywords": ["search", "laravel", "larave-search"], + "homepage": "https://github.com/astritzeqiri/laravel-searchable", + "license": "MIT", + "authors": [ + { + "name": "Astrit Zeqiri", + "email": "astritzeqiri5@gmail.com", + "homepage": "http://astritzeqiri.com", + "role": "Developer" + } + ], + "require": { + "php": ">=5.4.0", + "laravel/framework": "5.*" + }, + "autoload": { + "psr-4": { + "AstritZeqiri\\Metadata\\": "src/" + } + } +} diff --git a/src/Exceptions/SearchException.php b/src/Exceptions/SearchException.php new file mode 100644 index 0000000..f9134c4 --- /dev/null +++ b/src/Exceptions/SearchException.php @@ -0,0 +1,16 @@ +className = $className; + $this->builder = $builder; + $this->fields = $fields; + $this->search = $search; + $this->exact = $exact; + + $this->comparator = $this->exact ? "=" : "LIKE"; + } + + /** + * Build the query and return it. + * + * @return Illuminate\Database\Eloquent\Builder $this->builder + */ + public function build() + { + $this->checkForNoBuilderGiven(); + + $this->buildQuery(); + + return $this->builder; + } + + /** + * Build the query. + * + * @throws SearcherException [if the builder has not been provided] + * + * @return void + */ + public function buildQuery() + { + $this->builder->where(function ($query) { + $boolean = 'and'; + foreach ($this->fields as $field) { + $this->makeQueryOnField($query, $field, $boolean); + + $boolean = 'or'; + } + }); + } + + /** + * Make the query for a single field. + * + * @param Illuminate\Database\Eloquent\Builder $query + * @param string|Closure $field + * @param string $boolean [tells if the function has to be E.x. 'where' if false 'orWhere' if true] + * + * @return void + */ + private function makeQueryOnField($query, $field, $boolean = 'or') + { + if (is_string($field) && $this->hasScopeForSearchOn($field)) { + $method = $this->getScopeMethodNameFromField($field, false); + + return $this->makeQueryOnField($query, function($query) use ($method) { + $query->$method( + $this->search, + $this->exact + ); + }, $boolean); + } + + $query->where($field, $this->comparator, $this->getSearchString(), $boolean); + } + + /** + * Check if the class has a scopeMethod with the name of the field + * + * E.x. + * $field = 'name' + * The method has to be: scopeSearchOnName + * + * @param string $field + * @return boolean + */ + private function hasScopeForSearchOn($field = '') + { + return + class_exists($this->className) && + method_exists( + $this->className, + $this->getScopeMethodNameFromField($field) + ); + } + + /** + * Get the scope method name from the given field. + * + * @param string $field + * @param boolean $prependScope [if this is true it appends the scope to the method name] + * + * @return string + */ + private function getScopeMethodNameFromField($field = '', $prependScope = true) + { + $field = studly_case($field); + + return $prependScope ? 'scopeSearchOn' . $field : 'searchOn' . $field; + } + + /** + * Check if we need to add percentages to the string if the user wanted exact search or not + * + * @return string + */ + public function getSearchString() + { + return $this->appendSign() . $this->search . $this->prependSign(); + } + + /** + * If the user requested exact searching then return empty else return % + * + * @return string + */ + private function appendSign() + { + return $this->exact ? "" : "%"; + } + + /** + * If the user requested exact searching then return empty else return % + * + * @return string + */ + private function prependSign() + { + return $this->exact ? "" : "%"; + } + + /** + * Set the classname. + * + * @param string $className + * + * @return $this + */ + public function className($className) + { + $this->className = $className; + + return $this; + } + + /** + * Set the query builder. + * + * @param Illuminate\Database\Eloquent\Builder $query + * + * @return $this + */ + public function query($query) + { + $this->builder = $query; + + return $this; + } + + /** + * Set the fields. + * + * @param array $fields + * + * @return $this + */ + public function onFields(array $fields) + { + $this->fields = $fields; + + return $this; + } + + /** + * Set the search string. + * + * @param string $search + * + * @return $this + */ + public function searchFor($search) + { + $this->search = trim($search); + + return $this; + } + + /** + * Set the search to exact. + * + * @param boolean $exact + * + * @return $this + */ + public function exactSearch($exact = true) + { + $this->exact = $exact; + + return $this; + } + + /** + * If there is not a given builder it thrown an exception. + * + * @throws SearcherException [if the builder has not been provided] + * @return void + */ + private function checkForNoBuilderGiven() + { + if (! $this->builder) { + throw SearcherException::builderNotFound(); + } + } +} diff --git a/src/Traits/Searchable.php b/src/Traits/Searchable.php new file mode 100644 index 0000000..8c12b09 --- /dev/null +++ b/src/Traits/Searchable.php @@ -0,0 +1,47 @@ +className(static::class) + ->searchFor($search) + ->onFields($fields) + ->exactSearch($exact) + ->build(); + } +}