circle 4年前

修改理由:

错别字:神器 --> 神奇

此投稿已在 4年前 合并。

内容修改:

红色背景 为原始内容

绿色背景 为新增或者修改的内容

OldNewDifferences
1  
21![](https://iocaffcdn.phphub.org/uploads/images/201903/19/1/jwR0XOzh4D.png!large)
32
43最近,我需要在开发的事件管理系统中实现搜索功能。 一开始只是简单的几个选项 (通过名称,邮箱等搜索),到后面参数变得越来越多。
54
65今天,我会介绍整个过程以及如何构建灵活且可扩展的搜索系统。如果你想查看代码,请访问 [Git 仓库](https://github.com/drawmyattention/advanced-eloquent-search-filters/tree/master/app/UserSearch) 。
76
8 
 7
98#### 我们将创造什么
109
1110我们公司需要一种跟踪我们与世界各地客户举办的各种活动和会议的方式。我们目前的唯一方法是让每位员工在 Outlook 日程表上存储会议的详细信息。可拓展性较差!
 
3837
3938前端的条件过滤的截图。
4039
41 
 40
4241#### 模型及模型关联
4342
4443在这个例子中我们回用到很多模型:
 
7372
7473> 客人的名字是 'Billy',来自 'Google' 公司,目前居住在 'London',已经对 'key-note-presentation-new-york-05--2016' 的活动邀请做出了回复,并且回复的内容是 'I will be attending',负责跟进这位客人的销售经理是 'Tom Jones' 或者 'Joe Bloggs'。
7574
76 
 75
7776#### 开始 --- 最佳实践
7877
7978我是一个坚定不移的极简主义者,我坚信少即是多。下面就让我们以最简单的方式探索出解决这个需求的最佳实践。
 
112111
113112由于我们需要在 filter 方法中处理请求提交的数据,所以我把 Request 类做了依赖注入。[Laravel 的服务容器](https://laravel.com/docs/5.2/container#resolving) 会解析这个依赖,我们可以在方法中直接使用 Request 的实例,也就是 $request。User 类也是同样道理,我们需要从中检索一些数据。
114113
115 
 114
116115
117116这个搜索需求有一点比较麻烦的是,每个参数都是可选的。所以我们要先写一系列的条件语句来判断每个参数是否存在:
118117
 
150149
151150首先,它只会根据一个条件去检索用户表,然后就返回了。所以,通过上面的代码逻辑,我们根本无法获得姓名为 'Billy', 而且住在 'London' 的用户。
152151
153 
 152
154153实现这种目的的一种方式是嵌套条件:
155154
156155```
 
170169我确信你可以看到这在两个或者三个参数的时候起作用,但是一旦我们添加更多选项,这将会难以管理。
171170
172171
173 
 172
174173
175174
176175#### 改进我们的搜索 api
 
208207
209208好多了!我们现在可以将每个搜索参数做为修饰符添加到从 * $user->newQuery()* 返回的查询实例中。
210209
211 
 210
212211我们现在可以根据所有的参数来做搜索了, 再多参数都不怕.
213212
214213一起来实践吧:
 
269268搞定,棒极了!
270269
271270
272 
 271
273272
274273
275274#### 是否还需要重构?
 
282281
283282然而,如果你是在构建一个比较复杂的项目,那么我们还是需要更加优雅且扩展性好的解决方案。
284283
285 
 284
286285
287286#### 编写新的搜索 api
288287
 
294293
295294根据我的经验,这个方法能帮助我写出可读性更强,更加优雅的程序。还有一个很大的额外收获就是,通过这种阶段性的验收测试,我能更好地抓住商业需求。因此,我可以很自信地说我写的程序可以很好地满足市场的需求,具有很高商业价值。
296295
297 
 296
298297
299298以下添加到搜索功能的代码中,我希望我的搜索 api 是这样写的:
300299
 
323322}
324323```
325324
326 
 325
327326
328327最简单的方式,让我们把控制器中的代码复制到 apply 函数中:
329328
 
406405
407406
408407
409 
 408
410409
411410其次,由于 `newQuery()` 方法不是静态方法,无法通过 User 类静态调用,所以我们需要先创建一个 User 对象,再调用这个方法:
412411
 
437436
438437好多了,是不是?把一系列的条件判断交给专门的类处理,使控制器的代码简介清新。
439438
440 
 439
441440
442441#### 下面进入见证奇迹的时刻
443442
 
451450
452451先从 `Name` 条件开始吧。但是,就像我们前面讲的,还是想一下我们需要怎样使用这种单一条件过滤的接口。
453452
454 
 453
455454我希望可以这样调用这个接口:
456455
457456```
 
511510
512511如你所见,City 类的代码逻辑跟 Name 类相同,只是过滤条件变成了 'city'。让每个条件过滤类都只有一个简单的 `apply()` 方法,而且方法接收的参数和处理的逻辑都相同,我们可以把这看成一个协议,这一点很重要,下面我会具体说明。
513512
514 
 513
515514为了确保每个条件过滤类都能遵循这个协议,我决定写一个接口,让每个类都实现这个接口。
516515
517516```
 
596595
597596我把所有的条件过滤类的文件放在一个单独的文件夹里,这让我对已有的过滤条件一目了然。
598597
599 
 598
600599#### 使用新的过滤器
601600
602601现在回过头来看 UserSearch 类的 applyFiltersToQuery() 方法,发现我们可以再做一些优化了。
 
615614}
616615```
617616
618 
 617
619618
620619现在根据过滤条件构建查询语句的工作已经转给各个相应的过滤类了,但是判断每个过滤条件是否存在的工作,还是通过一系列的条件判断语句完成的。而且条件判断的参数都是写死的,一个参数对应一个过滤类。这样我每增加一个新的过滤条件,我都要重新修改 `UserSearch` 类的代码。这显然是一个需要解决的问题。
621620
 
649648}
650649```
651650
652 
 651
653652
654653下面逐行分析这段代码:
655654
 
687686$query = $decorator::apply($query, $value);
688687```
689688
690 这里就是神的地方了,PHP 允许把变量 `$decorator` 作为类,并调用其方法(在这里就是 apply() 方法了)。现在再看这个接口的代码,发现我们再次实力证明了磨刀不误砍柴工。现在我们可以确保每个过滤类对外响应一致,内部又可以分别处理各自的逻辑。
691 
692 
693 
 689这里就是神的地方了,PHP 允许把变量 `$decorator` 作为类,并调用其方法(在这里就是 apply() 方法了)。现在再看这个接口的代码,发现我们再次实力证明了磨刀不误砍柴工。现在我们可以确保每个过滤类对外响应一致,内部又可以分别处理各自的逻辑。
 690
 691
 692
694693
695694
696695#### 最后的优化
 
758757
759758这样,即便要扩展搜索接口,我也不需要再去反复修改 `UserSearch` 类里的代码了。需要增加新的过滤条件吗?简单,只要在 `App\UserSearch\Filters` 目录下创建一个过滤类,并使之实现 `Filter` 接口就 OK 了。
760759
761 
 760
762761
763762#### 结论
764763