rebeccathedev/search-parser
最新稳定版本:v0.3
Composer 安装命令:
composer require rebeccathedev/search-parser
包简介
A parser that converts a freeform query into an intermediate object, that can be converted to query many backends (SQL, ElasticSearch, etc).
README 文档
README
A powerful search query parser that transforms freeform search queries into structured objects for querying SQL databases, Eloquent models, or other backends.
✨ Features
- 🎯 Parse Google-style search queries with field names, ranges, negation, and more
- 🗄️ Built-in transforms for SQL (PDO) and Laravel Eloquent
- 🔌 Extensible parser system for custom query types
- 🔒 Field filtering and mapping for security
- 🔎 Loose mode for fuzzy matching
- 🚀 PHP 8.2+ with modern type safety
🚀 Quick Example
use RebeccaTheDev\SearchParser\SearchParser; $parser = new SearchParser(); $query = $parser->parse('from:foo@example.com "bar baz" !meef date:2018/01/01-2018/08/01');
This tokenizes the search into a SearchQuery object with structured components:
RebeccaTheDev\SearchParser\SearchQuery Object
(
[position:RebeccaTheDev\SearchParser\SearchQuery:private] => 0
[data:protected] => Array
(
[0] => RebeccaTheDev\SearchParser\SearchQueryComponent Object
(
[type] => field
[field] => from
[value] => foo@example.com
[firstRangeValue] =>
[secondRangeValue] =>
[negate] =>
)
[1] => RebeccaTheDev\SearchParser\SearchQueryComponent Object
(
[type] => text
[field] =>
[value] => bar baz
[firstRangeValue] =>
[secondRangeValue] =>
[negate] =>
)
[2] => RebeccaTheDev\SearchParser\SearchQueryComponent Object
(
[type] => text
[field] =>
[value] => meef
[firstRangeValue] =>
[secondRangeValue] =>
[negate] => 1
)
[3] => RebeccaTheDev\SearchParser\SearchQueryComponent Object
(
[type] => range
[field] => date
[value] =>
[firstRangeValue] => 2018/01/01
[secondRangeValue] => 2018/08/01
[negate] =>
)
[4] => RebeccaTheDev\SearchParser\SearchQueryComponent Object
(
[type] => text
[field] =>
[value] => #hashtag
[firstRangeValue] =>
[secondRangeValue] =>
[negate] =>
)
)
)
📦 Installation
composer require rebeccathedev/search-parser
Requirements: PHP 8.2+
No external dependencies required for core functionality. Eloquent transform requires illuminate/database.
📖 Usage
Basic Parsing
use RebeccaTheDev\SearchParser\SearchParser; $parser = new SearchParser(); $query = $parser->parse('from:foo@example.com "exact phrase" !excluded');
The SearchQuery object is iterable:
foreach ($query as $component) { echo $component->type; // 'field', 'text', 'range' echo $component->value; }
🔧 Custom Parsers
Extend the parser by implementing the Parser interface:
use RebeccaTheDev\SearchParser\Parsers\Parser; use RebeccaTheDev\SearchParser\SearchQueryComponent; class HashtagParser implements Parser { public function parsePart(string $part): SearchQueryComponent { $component = new SearchQueryComponent(); if (preg_match('!#(.*)!', $part, $match)) { $component->type = 'hashtag'; $component->value = $match[1]; } return $component; } } // Use it $parser = new SearchParser(); $parser->addParser(new HashtagParser()); $query = $parser->parse('search #trending');
See src/Parsers/Hashtag.php for a working example. Note: parsers don't fall through - if your parser handles a part, processing moves to the next part.
🔄 Transforms
Transform parsed queries into SQL WHERE clauses or Eloquent query builders.
💾 SQL Transform
use RebeccaTheDev\SearchParser\Transforms\SQL\SQL; $pdo = new PDO("sqlite:/tmp/database.db"); $transform = new SQL('default_field', $pdo); $query = $parser->parse('from:foo@example.com "bar baz" !meef date:2018/01/01-2018/08/01'); $where = $transform->transform($query); // Result: // `from` = 'foo@example.com' and `default_field` = 'bar baz' and // `default_field` != 'meef' and (`date` between '2018/01/01' and '2018/08/01')
✨ Eloquent Transform
use RebeccaTheDev\SearchParser\Transforms\Eloquent\Eloquent; $users = User::query(); $transform = new Eloquent('name', $users); $query = $parser->parse('status:active age:25-35'); $users = $transform->transform($query)->get();
🔎 Loose Mode
Enable fuzzy matching with LIKE queries:
$transform = new SQL('default_field', $pdo); $transform->looseMode = true; $where = $transform->transform($query); // Result: // `from` = 'foo@example.com' and `default_field` like '%bar baz%' and // `default_field` not like '%meef%' and (`date` between '2018/01/01' and '2018/08/01')
🎨 Custom Component Transforms
Add custom transforms for your custom parsers:
use RebeccaTheDev\SearchParser\Transforms\SQL\Hashtag; $pdo = new PDO("sqlite:/tmp/database.db"); $transform = new SQL('default_field', $pdo); $transform->addComponentTransform(new Hashtag('default_field', $pdo)); $query = $parser->parse('search #trending'); $where = $transform->transform($query); // Result: `default_field` = 'search' and `hashtag` = 'trending'
See src/Transforms/SQL/Hashtag.php for a working example.
🛡️ Filters
⚠️ Security Note
Important: The SQL transform escapes values but not field names. Always allowlist allowed fields before passing queries to transforms. Never trust user input for field names.
🎯 FieldFilter
Allowlist allowed fields for security:
use RebeccaTheDev\SearchParser\Filters\{Filter, FieldFilter}; $filter = new Filter(); $fieldFilter = new FieldFilter(); $fieldFilter->validFields = ['from', 'to', 'subject', 'date']; $filter->addFilter($fieldFilter); $query = $parser->parse('from:foo@example.com invalid:malicious subject:test'); $filtered = $filter->filter($query); // Only 'from' and 'subject' fields are kept, 'invalid' is removed
🗺️ FieldNameMapper
Map user-facing field names to database column names:
use RebeccaTheDev\SearchParser\Filters\{Filter, FieldNameMapper}; $filter = new Filter(); $mapper = new FieldNameMapper(); $mapper->mappingFields = [ 'date' => 'created_at', 'author' => 'user_id' ]; $filter->addFilter($mapper); $query = $parser->parse('date:2024-01-01-2024-12-31 author:123'); $filtered = $filter->filter($query); // 'date' becomes 'created_at', 'author' becomes 'user_id'
⚙️ Custom Filters
Implement the FiltersQueries interface:
use RebeccaTheDev\SearchParser\Filters\FiltersQueries; use RebeccaTheDev\SearchParser\SearchQuery; class MyCustomFilter implements FiltersQueries { public function filter(SearchQuery $query): SearchQuery { foreach ($query as $component) { // Your custom filtering logic } return $query; } } $filter = new Filter(); $filter->addFilter(new MyCustomFilter());
Useful SearchQuery methods:
remove(SearchQueryComponent $item)- Remove a componentreplace(SearchQueryComponent $old, SearchQueryComponent $new)- Replace a componentmerge(SearchQuery $query)- Merge two queries
🧪 Testing
composer install ./vendor/bin/phpunit
Some tests may be skipped if optional dependencies (like Eloquent) aren't installed.
📄 License
MIT License - see LICENSE file for details.
👩💻 Author
Made with 🩷 by Rebecca Peck
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 3
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-12-13