rabbit-slide-kou-php-conference-2017 2017.10.08.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rabbit +1 -0
- data/README.rd +48 -0
- data/Rakefile +17 -0
- data/config.yaml +26 -0
- data/images/php-document-search-search.png +0 -0
- data/images/php-document-search-similar-search.png +0 -0
- data/images/php-document-search-similar-search.xcf +0 -0
- data/images/php-document-search-synonym.png +0 -0
- data/images/php-document-search-synonym.xcf +0 -0
- data/images/php-document-search.png +0 -0
- data/images/php-manual-ja-full-text-search-by-at-not-found.png +0 -0
- data/images/php-manual-ja-full-text-search-by-at-not-found.xcf +0 -0
- data/images/php-manual-ja-full-text-search-link.png +0 -0
- data/images/php-manual-ja-full-text-search-link.xcf +0 -0
- data/images/php-manual-ja-search-by-at-not-found.png +0 -0
- data/images/php-manual-ja-search-by-at-not-found.xcf +0 -0
- data/images/php-manual-ja-search-by-at.png +0 -0
- data/images/php-manual-ja-search-by-at.xcf +0 -0
- data/images/php-manual-ja.png +0 -0
- data/images/search-pg-bigm.pdf +0 -0
- data/images/search-pgroonga-pg-bigm.pdf +0 -0
- data/pdf/php-conference-2017-php-document-fast-full-text-search-system-with-postgresql-and-pgroonga.pdf +0 -0
- data/php-document-fast-full-text-search-system-with-postgresql-and-pgroonga.rab +871 -0
- data/theme.rb +5 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ec4cae3a085fc043e16dd908ef24ffced568281c
|
4
|
+
data.tar.gz: 8915e502b0374beb084d775d5ac7ac5d32cb8de3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 82e3b43f0b8d7e0307ebcbb7906a4a92378b1c79ac3b60b94361b85ce177cfd0856b5402f02428279b0e1add8f365924bb8daabe899f4e463bd58988ab5fa0a2
|
7
|
+
data.tar.gz: 1a0d1fb95de9f6b3a38ff333cbe30420100c7d7a67a7dab7d8c50d91e5405d67230216da765df7fb0f1602249aa805530992b9e3774a1aeb25fc8d2b2d625bf8
|
data/.rabbit
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
php-document-fast-full-text-search-system-with-postgresql-and-pgroonga.rab
|
data/README.rd
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= PostgreSQLとPGroongaで作るPHPマニュアル高速全文検索システム
|
2
|
+
|
3
|
+
PHPマニュアルは日本語にも翻訳されていますが、検索機能が限定的なため、せっかくの説明を活用しきれません。基本の検索機能は関数やクラス名の検索機能しかないため「正規表現」で正規表現の使い方を検索できません。サイト内検索機能では「@」でエラー制御演算子を検索できません。
|
4
|
+
|
5
|
+
この講演ではPHPマニュアルをより活用するためのPHPマニュアル高速全文検索システムを紹介します。このシステムはPostgreSQLとPGroongaを利用して実現しています。
|
6
|
+
|
7
|
+
== ライセンス
|
8
|
+
|
9
|
+
=== スライド
|
10
|
+
|
11
|
+
CC BY-SA 4.0
|
12
|
+
|
13
|
+
原著作者名は以下の通りです。
|
14
|
+
|
15
|
+
* 須藤功平(またはKouhei Sutou)
|
16
|
+
|
17
|
+
=== 画像
|
18
|
+
|
19
|
+
==== Groonga・Mroonga・PGroongaのロゴ
|
20
|
+
|
21
|
+
CC BY 3.0
|
22
|
+
|
23
|
+
原著作者名は以下の通りです。
|
24
|
+
|
25
|
+
* Groongaプロジェクト
|
26
|
+
* Mroongaプロジェクト
|
27
|
+
* PGroongaプロジェクト
|
28
|
+
|
29
|
+
== 作者向け
|
30
|
+
|
31
|
+
=== 表示
|
32
|
+
|
33
|
+
rake
|
34
|
+
|
35
|
+
=== 公開
|
36
|
+
|
37
|
+
rake publish
|
38
|
+
|
39
|
+
== 閲覧者向け
|
40
|
+
|
41
|
+
=== インストール
|
42
|
+
|
43
|
+
gem install rabbit-slide-kou-php-conference-2017
|
44
|
+
|
45
|
+
=== 表示
|
46
|
+
|
47
|
+
rabbit rabbit-slide-kou-php-conference-2017.gem
|
48
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rabbit/task/slide"
|
2
|
+
|
3
|
+
# Edit ./config.yaml to customize meta data
|
4
|
+
|
5
|
+
spec = nil
|
6
|
+
Rabbit::Task::Slide.new do |task|
|
7
|
+
spec = task.spec
|
8
|
+
spec.files += Dir.glob("images/**/*.*")
|
9
|
+
# spec.files -= Dir.glob("private/**/*.*")
|
10
|
+
spec.add_runtime_dependency("rabbit-theme-groonga")
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Tag #{spec.version}"
|
14
|
+
task :tag do
|
15
|
+
sh("git", "tag", "-a", spec.version.to_s, "-m", "Publish #{spec.version}")
|
16
|
+
sh("git", "push", "--tags")
|
17
|
+
end
|
data/config.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
id: php-conference-2017
|
3
|
+
base_name: php-document-fast-full-text-search-system-with-postgresql-and-pgroonga
|
4
|
+
tags:
|
5
|
+
- rabbit
|
6
|
+
- php
|
7
|
+
- postgresql
|
8
|
+
- pgroonga
|
9
|
+
- phpcon2017
|
10
|
+
presentation_date: '2017-10-08'
|
11
|
+
version: 2017.10.08.0
|
12
|
+
licenses:
|
13
|
+
- CC BY 3.0
|
14
|
+
- CC BY-SA 4.0
|
15
|
+
slideshare_id: phpconference2017
|
16
|
+
speaker_deck_id:
|
17
|
+
ustream_id:
|
18
|
+
vimeo_id:
|
19
|
+
youtube_id:
|
20
|
+
author:
|
21
|
+
markup_language: :rd
|
22
|
+
name: Kouhei Sutou
|
23
|
+
email: kou@clear-code.com
|
24
|
+
rubygems_user: kou
|
25
|
+
slideshare_user: kou
|
26
|
+
speaker_deck_user:
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,871 @@
|
|
1
|
+
= PostgreSQLとPGroongaで\n作る\nPHPマニュアル\n高速全文検索システム
|
2
|
+
|
3
|
+
: author
|
4
|
+
須藤功平
|
5
|
+
: institution
|
6
|
+
クリアコード
|
7
|
+
: content-source
|
8
|
+
PHPカンファレンス2017
|
9
|
+
: date
|
10
|
+
2017-10-08
|
11
|
+
: start-time
|
12
|
+
2017-10-08T11:00:00+09:00
|
13
|
+
: end-time
|
14
|
+
2017-10-08T12:00:00+09:00
|
15
|
+
: theme
|
16
|
+
.
|
17
|
+
|
18
|
+
= PHPマニュアル
|
19
|
+
|
20
|
+
# image
|
21
|
+
# src = images/php-manual-ja.png
|
22
|
+
# relative_width = 80
|
23
|
+
|
24
|
+
(('tag:center'))
|
25
|
+
http://php.net/manual/ja/
|
26
|
+
|
27
|
+
= @で検索
|
28
|
+
|
29
|
+
# image
|
30
|
+
# src = images/php-manual-ja-search-by-at.png
|
31
|
+
# relative_width = 80
|
32
|
+
|
33
|
+
(('tag:center'))
|
34
|
+
@:エラー制御演算子
|
35
|
+
|
36
|
+
= @でnot found
|
37
|
+
|
38
|
+
# image
|
39
|
+
# src = images/php-manual-ja-search-by-at-not-found.png
|
40
|
+
# relative_width = 80
|
41
|
+
|
42
|
+
(('tag:center'))
|
43
|
+
関数・クラス・例外のみ検索対象
|
44
|
+
|
45
|
+
= 全文検索あり!
|
46
|
+
|
47
|
+
# image
|
48
|
+
# src = images/php-manual-ja-full-text-search-link.png
|
49
|
+
# relative_height = 80
|
50
|
+
|
51
|
+
(('tag:center'))
|
52
|
+
Googleカスタム検索
|
53
|
+
|
54
|
+
= @でnot found
|
55
|
+
|
56
|
+
# image
|
57
|
+
# src = images/php-manual-ja-full-text-search-by-at-not-found.png
|
58
|
+
# relative_width = 80
|
59
|
+
|
60
|
+
(('tag:center'))
|
61
|
+
Googleは自然言語向けだから
|
62
|
+
|
63
|
+
= マニュアル検索
|
64
|
+
|
65
|
+
* 自然言語向けと傾向が違う
|
66
|
+
* @:自然言語ではノイズ\n
|
67
|
+
(('note:特に日本語ではノイズ'))
|
68
|
+
* @:マニュアルでは重要語
|
69
|
+
* プログラミング言語用の\n
|
70
|
+
チューニングが必要
|
71
|
+
* 🔍欲しい情報が見つかる!
|
72
|
+
|
73
|
+
= マニュアル\n検索システムの\n作り方
|
74
|
+
|
75
|
+
= PHP\n+\nPostgreSQL
|
76
|
+
|
77
|
+
= PostgreSQLと全文検索
|
78
|
+
|
79
|
+
* LIKE:組込機能
|
80
|
+
* textsearch:組込機能
|
81
|
+
* pg_trgm:標準添付
|
82
|
+
* アーカイブには含まれている
|
83
|
+
* 別途インストールすれば使える
|
84
|
+
|
85
|
+
= LIKE
|
86
|
+
|
87
|
+
* 少ないデータ
|
88
|
+
* 十分実用的
|
89
|
+
* 400文字×20万件くらいなら1秒とか
|
90
|
+
* 少なくないデータ
|
91
|
+
* 性能問題アリ
|
92
|
+
|
93
|
+
= PHPマニュアルのデータ
|
94
|
+
|
95
|
+
# RT
|
96
|
+
|
97
|
+
件数, 13095
|
98
|
+
平均文字数, 871文字
|
99
|
+
|
100
|
+
= PHPマニュアルでLIKE
|
101
|
+
|
102
|
+
* 👍速度は十分実用的
|
103
|
+
* (({LIKE '%@%'}))で約100ms
|
104
|
+
* 👎それっぽい順のソート不可
|
105
|
+
* 全文検索ではソート順が重要
|
106
|
+
* ユーザーは先頭n件しか見ない
|
107
|
+
|
108
|
+
= textsearch
|
109
|
+
|
110
|
+
* インデックスを作るので速い
|
111
|
+
* 言語毎にモジュールが必要
|
112
|
+
* 英語やフランス語などは組込
|
113
|
+
* 日本語は別途必要
|
114
|
+
* 日本語用モジュール
|
115
|
+
* 公式にはメンテナンスされていない\n
|
116
|
+
(('note:forkして動くようにしている人はいる'))
|
117
|
+
|
118
|
+
= pg_trgm
|
119
|
+
|
120
|
+
* インデックスを作るので速い
|
121
|
+
* 注:ヒット件数が増えると遅い
|
122
|
+
* 注:テキスト量が多いと遅い
|
123
|
+
* 注:1,2文字の検索は遅い(('note:(米・日本)'))
|
124
|
+
|
125
|
+
* 日本語を使うにはひと工夫必要
|
126
|
+
* C.UTF-8を使う
|
127
|
+
* ソースを変更してビルド
|
128
|
+
|
129
|
+
= プラグイン
|
130
|
+
|
131
|
+
* pg_bigm
|
132
|
+
* pg_trgmの日本語対応強化版
|
133
|
+
* それっぽい順のソート不可
|
134
|
+
* PGroonga
|
135
|
+
* 本気の全文検索エンジンを利用
|
136
|
+
* 速いし日本語もバッチリ!
|
137
|
+
* それっぽい順のソート可
|
138
|
+
|
139
|
+
= ベンチマーク:pg_bigm
|
140
|
+
|
141
|
+
# image
|
142
|
+
# src = images/search-pg-bigm.pdf
|
143
|
+
# relative_height = 100
|
144
|
+
|
145
|
+
= ベンチマーク:PGroonga
|
146
|
+
|
147
|
+
# image
|
148
|
+
# src = images/search-pgroonga-pg-bigm.pdf
|
149
|
+
# relative_height = 100
|
150
|
+
|
151
|
+
= PostgreSQLで全文検索システム
|
152
|
+
|
153
|
+
* PostgreSQLで全文検索
|
154
|
+
* PGroongaがベスト!💯
|
155
|
+
* PGroonga
|
156
|
+
* 高速
|
157
|
+
* 日本語対応
|
158
|
+
* それっぽい順でソート可
|
159
|
+
|
160
|
+
= PHP document search
|
161
|
+
|
162
|
+
# image
|
163
|
+
# src = images/php-document-search.png
|
164
|
+
# relative_height = 80
|
165
|
+
|
166
|
+
(('tag:center'))
|
167
|
+
PHP + PostgreSQL + PGroonga
|
168
|
+
|
169
|
+
= 基本機能
|
170
|
+
|
171
|
+
* 高速全文検索+ソート
|
172
|
+
* 検索キーワードハイライト
|
173
|
+
* キーワード周辺テキスト表示
|
174
|
+
|
175
|
+
= 高度な機能
|
176
|
+
|
177
|
+
* オートコンプリート
|
178
|
+
* ローマ字対応(seiki→正規表現)
|
179
|
+
* 類似マニュアル検索
|
180
|
+
* 同義語展開
|
181
|
+
* 「@」→「@ OR エラー制御演算子」
|
182
|
+
|
183
|
+
= 作り方:ツール
|
184
|
+
|
185
|
+
* フレームワーク
|
186
|
+
* Laravel
|
187
|
+
* RDBMS
|
188
|
+
* PostgreSQL
|
189
|
+
* 高速日本語全文検索機能
|
190
|
+
* PGroonga
|
191
|
+
|
192
|
+
= 作り方:インストール
|
193
|
+
|
194
|
+
* Laravel
|
195
|
+
* 省略
|
196
|
+
* PostgreSQL
|
197
|
+
* パッケージで
|
198
|
+
* PGroonga
|
199
|
+
* パッケージで\n
|
200
|
+
(('tag:x-small:https://pgroonga.github.io/ja/install/'))
|
201
|
+
|
202
|
+
= 初期化:Laravel
|
203
|
+
|
204
|
+
# coderay console
|
205
|
+
|
206
|
+
% laravel new php-document-search
|
207
|
+
% cd php-document-search
|
208
|
+
% editor .env
|
209
|
+
|
210
|
+
= 初期化:データベース
|
211
|
+
|
212
|
+
# coderay console
|
213
|
+
|
214
|
+
% sudo -u postgres -H \
|
215
|
+
createdb php_document_search
|
216
|
+
|
217
|
+
= 初期化:PGroonga
|
218
|
+
|
219
|
+
# rouge sql
|
220
|
+
-- ↓を実行する必要がある
|
221
|
+
CREATE EXTENSION pgroonga;
|
222
|
+
|
223
|
+
= 初期化:PGroonga
|
224
|
+
|
225
|
+
(('tag:center'))
|
226
|
+
マイグレーションファイル作成
|
227
|
+
|
228
|
+
# coderay console
|
229
|
+
|
230
|
+
% php artisan \
|
231
|
+
make:migration enable_pgroonga
|
232
|
+
|
233
|
+
= マイグレーション
|
234
|
+
|
235
|
+
# coderay php
|
236
|
+
|
237
|
+
public function up()
|
238
|
+
{
|
239
|
+
DB::statement("CREATE EXTENSION pgroonga;");
|
240
|
+
// CREATE EXTENSION IF NOT EXISTS ...の方がよい
|
241
|
+
}
|
242
|
+
|
243
|
+
public function down()
|
244
|
+
{
|
245
|
+
DB::statement("DROP EXTENSION pgroonga;");
|
246
|
+
}
|
247
|
+
|
248
|
+
= モデル作成
|
249
|
+
|
250
|
+
* マニュアルをモデルにする
|
251
|
+
* 名前:(({Entry}))
|
252
|
+
* 1ページ1インスタンス
|
253
|
+
|
254
|
+
= モデル作成
|
255
|
+
|
256
|
+
# coderay console
|
257
|
+
|
258
|
+
% php artisan \
|
259
|
+
make:model \
|
260
|
+
--migration \
|
261
|
+
--controller \
|
262
|
+
--resource \
|
263
|
+
Entry
|
264
|
+
|
265
|
+
= マイグレーション
|
266
|
+
|
267
|
+
# coderay php
|
268
|
+
|
269
|
+
Schema::create("entries", function ($table) {
|
270
|
+
$table->increments("id");
|
271
|
+
$table->text("url");
|
272
|
+
$table->text("title");
|
273
|
+
$table->text("content");
|
274
|
+
// PGroongaインデックス(デフォルト:全文検索用)
|
275
|
+
// 主キー(id)も入れるのが大事!
|
276
|
+
// それっぽい順のソートで必要
|
277
|
+
$table->index(
|
278
|
+
["id", "title", "content"], null, "pgroonga");
|
279
|
+
});
|
280
|
+
|
281
|
+
= データ登録
|
282
|
+
|
283
|
+
(1) PHPのドキュメントを\n
|
284
|
+
ローカルで生成
|
285
|
+
* PHPのドキュメントの作り方\n
|
286
|
+
http://doc.php.net/tutorial/
|
287
|
+
* フィードバックチャンスが\n
|
288
|
+
いろいろあったよ!
|
289
|
+
(2) ページ毎にPostgreSQLに挿入
|
290
|
+
|
291
|
+
= コマンド作成
|
292
|
+
|
293
|
+
# coderay console
|
294
|
+
|
295
|
+
% php artisan \
|
296
|
+
make:command \
|
297
|
+
--command=doc:register \
|
298
|
+
RegisterDocuments
|
299
|
+
|
300
|
+
= 登録コマンド実装(一部)
|
301
|
+
|
302
|
+
# coderay php
|
303
|
+
|
304
|
+
public function handle()
|
305
|
+
{
|
306
|
+
foreach (glob("public/doc/*.html") as $html_path) {
|
307
|
+
$document = new \DOMDocument();
|
308
|
+
@$document->loadHTMLFile($html_path);
|
309
|
+
$xpath = new \DOMXPath($document);
|
310
|
+
$entry = new Entry();
|
311
|
+
$entry->url = "/doc/" . basename($html_path);
|
312
|
+
// XPathでテキスト抽出
|
313
|
+
$this->extract_title($entry, $xpath);
|
314
|
+
$this->extract_content($entry, $xpath);
|
315
|
+
$entry->save();
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
= 登録
|
320
|
+
|
321
|
+
# coderay console
|
322
|
+
|
323
|
+
% php artisan doc:register
|
324
|
+
|
325
|
+
= 検索用コントローラー
|
326
|
+
|
327
|
+
# coderay php
|
328
|
+
|
329
|
+
public function index(Request $request)
|
330
|
+
{
|
331
|
+
$query = $request["query"];
|
332
|
+
$entries = Entry::query()
|
333
|
+
// ↓はモデルに作る(後述)
|
334
|
+
->fullTextSearch($query)
|
335
|
+
->limit(10)
|
336
|
+
->get();
|
337
|
+
return view("entry.search.index",
|
338
|
+
["entries" => $entries,
|
339
|
+
"query" => $query]);
|
340
|
+
}
|
341
|
+
|
342
|
+
= 検索対象モデル
|
343
|
+
|
344
|
+
# coderay php
|
345
|
+
|
346
|
+
public function
|
347
|
+
scopeFullTextSearch($query, $search_query)
|
348
|
+
{
|
349
|
+
if ($search_query) {
|
350
|
+
return ...; // クエリーがあったら検索
|
351
|
+
} else {
|
352
|
+
return ...; // なかったら適当に返す(省略)
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
= 検索対象モデル:検索
|
357
|
+
|
358
|
+
# coderay php
|
359
|
+
|
360
|
+
return $query
|
361
|
+
->select("id", "url")
|
362
|
+
// それっぽさの度合い
|
363
|
+
->selectRaw("pgroonga_score(entries) AS score")
|
364
|
+
// キーワードハイライト
|
365
|
+
->highlightHTML("title", $search_query)
|
366
|
+
// キーワード周辺のテキスト(キーワードハイライト付き)
|
367
|
+
->snippetHTML("content", $search_query)
|
368
|
+
// タイトルと本文を全文検索(タイトルの方が重要)
|
369
|
+
->whereRaw("title &@~ ? OR content &@~ ?",
|
370
|
+
[">($search_query)", $search_query])
|
371
|
+
// それっぽい順に返す
|
372
|
+
->orderBy("score", "DESC");
|
373
|
+
|
374
|
+
= キーワードハイライト
|
375
|
+
|
376
|
+
# coderay php
|
377
|
+
|
378
|
+
public function scopeHighlightHTML($query,
|
379
|
+
$column,
|
380
|
+
$search_query)
|
381
|
+
{
|
382
|
+
return $query
|
383
|
+
// PGroonga提供ハイライト関数
|
384
|
+
->selectRaw("pgroonga_highlight_html($column, " .
|
385
|
+
// PGroonga提供クエリーからキーワードを抽出する関数
|
386
|
+
"pgroonga_query_extract_keywords(?)) " .
|
387
|
+
"AS highlighted_$column",
|
388
|
+
[$search_query]);
|
389
|
+
}
|
390
|
+
|
391
|
+
= 検索結果
|
392
|
+
|
393
|
+
# coderay php
|
394
|
+
|
395
|
+
<div class="entries">
|
396
|
+
@foreach ($entries as $entry)
|
397
|
+
<a href="{{ $entry->url }}">
|
398
|
+
<h4>
|
399
|
+
{{-- マークアップ済み! --}}
|
400
|
+
{!! $entry->highlighted_title !!}
|
401
|
+
<span class="score">{{ $entry->score }}</span>
|
402
|
+
</h4>
|
403
|
+
{{-- 周辺テキストはtext[](後で補足) --}}
|
404
|
+
@foreach ($entry->content_snippets as $snippet)
|
405
|
+
<pre class="snippet">{!! $snippet !!}</pre>
|
406
|
+
@endforeach
|
407
|
+
</a>
|
408
|
+
@endforeach
|
409
|
+
</div>
|
410
|
+
|
411
|
+
= 検索対象モデル:配列
|
412
|
+
|
413
|
+
# coderay php
|
414
|
+
|
415
|
+
public function getContentSnippetsAttribute($value)
|
416
|
+
{ // PostgreSQLは配列をサポートしているがPDOは未サポート
|
417
|
+
// '["...","..."]'という文字列になるのでそれを配列に変換
|
418
|
+
// ※これは回避策なのでPDOに配列サポートを入れたい!
|
419
|
+
return array_map(
|
420
|
+
function ($e) {
|
421
|
+
// 「"」が「\"」になっているので戻す
|
422
|
+
return preg_replace('/\\\\(.)/', '$1', $e);
|
423
|
+
},
|
424
|
+
explode('","', substr($value, 2, -2)));
|
425
|
+
}
|
426
|
+
|
427
|
+
= 高速日本語全文検索!
|
428
|
+
|
429
|
+
# image
|
430
|
+
# src = images/php-document-search-search.png
|
431
|
+
# relative_height = 100
|
432
|
+
|
433
|
+
= オートコンプリート
|
434
|
+
|
435
|
+
* 必要なもの
|
436
|
+
* 候補用テーブル
|
437
|
+
* 候補のヨミガナ(カタカナ)
|
438
|
+
* PGroonga!!!
|
439
|
+
|
440
|
+
= モデル作成
|
441
|
+
|
442
|
+
# coderay console
|
443
|
+
|
444
|
+
% php artisan \
|
445
|
+
make:model \
|
446
|
+
--migration \
|
447
|
+
--controller \
|
448
|
+
--resource \
|
449
|
+
Term
|
450
|
+
|
451
|
+
= マイグレーション:カラム
|
452
|
+
|
453
|
+
# coderay php
|
454
|
+
|
455
|
+
Schema::create("terms", function ($table) {
|
456
|
+
$table->increments("id");
|
457
|
+
$table->text("term");
|
458
|
+
$table->text("label");
|
459
|
+
$table->text("reading"); // 本当は配列にしたい
|
460
|
+
$table->timestamps();
|
461
|
+
// インデックス定義(後述)
|
462
|
+
});
|
463
|
+
|
464
|
+
= マイグレーション\nインデックス1
|
465
|
+
|
466
|
+
# coderay php
|
467
|
+
|
468
|
+
$table->index([
|
469
|
+
// ヨミガナに対する前方一致RK検索用
|
470
|
+
// RK:ローマ字・カナ(後述)
|
471
|
+
DB::raw("reading " .
|
472
|
+
"pgroonga_text_term_search_ops_v2"),
|
473
|
+
], null, "pgroonga");
|
474
|
+
|
475
|
+
= マイグレーション\nインデックス2
|
476
|
+
|
477
|
+
# coderay php
|
478
|
+
|
479
|
+
// 候補に対するゆるい全文検索用(中間一致用)
|
480
|
+
DB::statement(
|
481
|
+
"CREATE INDEX terms_term_index " .
|
482
|
+
"ON terms " .
|
483
|
+
"USING pgroonga (term) " .
|
484
|
+
// ↓がポイント
|
485
|
+
// ※LaravelがWITHを未サポートなのでSQLで書いている
|
486
|
+
// ※回避策なのでLaravelにWITHサポートを入れたい!
|
487
|
+
"WITH (tokenizer='TokenBigramSplitSymbolAlphaDigit')");
|
488
|
+
|
489
|
+
= 前方一致RK検索
|
490
|
+
|
491
|
+
* 日本語特化の前方一致検索
|
492
|
+
* ローマ字・ひらがな・カタカナで\n
|
493
|
+
カタカナを前方一致検索できる
|
494
|
+
* gy→ギュウニュウ
|
495
|
+
* ぎ→ギュウニュウ
|
496
|
+
* ギ→ギュウニュウ
|
497
|
+
|
498
|
+
= 候補モデル:検索
|
499
|
+
|
500
|
+
# coderay php
|
501
|
+
|
502
|
+
public function scopeComplete($query, $search_query)
|
503
|
+
{
|
504
|
+
return $query
|
505
|
+
->select("label")
|
506
|
+
->highlightHTML("label", $search_query)
|
507
|
+
// 前方一致RK検索
|
508
|
+
->whereRaw("reading &^~ :query OR " .
|
509
|
+
// ゆるい全文検索
|
510
|
+
"term &@~ :query",
|
511
|
+
["query" => $search_query])
|
512
|
+
->orderBy("label")
|
513
|
+
->limit(10);
|
514
|
+
}
|
515
|
+
|
516
|
+
= コントローラー
|
517
|
+
|
518
|
+
# coderay php
|
519
|
+
|
520
|
+
public function index(Request $request)
|
521
|
+
{
|
522
|
+
$query = $request["query"];
|
523
|
+
// モデルに実装した検索処理を呼び出し
|
524
|
+
$terms = Term::query()->complete($query);
|
525
|
+
$data = [];
|
526
|
+
foreach ($terms->get() as $term) {
|
527
|
+
$data[] = [
|
528
|
+
"value" => $term->label,
|
529
|
+
"label" => $term->highlighted_label,
|
530
|
+
];
|
531
|
+
}
|
532
|
+
// JSONで候補を返す
|
533
|
+
return response()->json($data);
|
534
|
+
}
|
535
|
+
|
536
|
+
= UI
|
537
|
+
|
538
|
+
# coderay javascript
|
539
|
+
|
540
|
+
$("#query").autocomplete({
|
541
|
+
source: function(request, response) {
|
542
|
+
$.ajax({
|
543
|
+
url: "/terms/", // コントローラー呼び出し
|
544
|
+
dataType: "json",
|
545
|
+
data: {query: this.term},
|
546
|
+
success: response
|
547
|
+
});
|
548
|
+
}
|
549
|
+
}).autocomplete("instance")._renderItem = function(ul, item) {
|
550
|
+
return $("<li>")
|
551
|
+
.attr("data-value", item.value) // 候補には生データを使う
|
552
|
+
.append(item.label) // ハイライトしたデータを表示
|
553
|
+
.appendTo(ul);
|
554
|
+
};
|
555
|
+
|
556
|
+
= オートコンプリート!
|
557
|
+
|
558
|
+
# image
|
559
|
+
# src = images/php-document-search.png
|
560
|
+
# relative_height = 100
|
561
|
+
|
562
|
+
= 類似マニュアル検索
|
563
|
+
|
564
|
+
# image
|
565
|
+
# src = images/php-document-search-similar-search.png
|
566
|
+
# relative_height = 100
|
567
|
+
|
568
|
+
= 実現方法
|
569
|
+
|
570
|
+
* 類似検索用インデックスが必要
|
571
|
+
* 自然言語に合わせた処理で精度向上
|
572
|
+
* 日本語ならMeCabを活用
|
573
|
+
* 類似検索用の演算子を使う
|
574
|
+
* 類似検索クエリー\n
|
575
|
+
→対象マニュアルのテキスト全体
|
576
|
+
* 参考:全文検索クエリー\n
|
577
|
+
→キーワード
|
578
|
+
|
579
|
+
= インデックス作成
|
580
|
+
|
581
|
+
(('tag:center'))
|
582
|
+
マイグレーションファイル作成
|
583
|
+
|
584
|
+
# coderay console
|
585
|
+
|
586
|
+
% php artisan \
|
587
|
+
make:migration \
|
588
|
+
add_similar_search_index
|
589
|
+
|
590
|
+
= マイグレーション
|
591
|
+
|
592
|
+
# coderay php
|
593
|
+
|
594
|
+
public function up()
|
595
|
+
{ // WITHを使っているのでDB::statementを使用
|
596
|
+
DB::statement(
|
597
|
+
"CREATE INDEX similar_search_index " .
|
598
|
+
"ON entries " .
|
599
|
+
// タイトルと内容を合わせたテキストをインデックス
|
600
|
+
// 理由1:タイトルも重要→対象に加えて精度向上
|
601
|
+
// 理由2:PostgreSQLが全文検索インデックスと
|
602
|
+
// 区別できるように
|
603
|
+
"USING pgroonga (id, (title || ' ' || content)) " .
|
604
|
+
// ポイント:MeCabを使う
|
605
|
+
"WITH (tokenizer='TokenMecab')");
|
606
|
+
}
|
607
|
+
|
608
|
+
= 類似検索:スコープ
|
609
|
+
|
610
|
+
# coderay php
|
611
|
+
|
612
|
+
public function scopeSimilarSearch($query, $text)
|
613
|
+
{
|
614
|
+
return $query
|
615
|
+
->select("id", "url", "title")
|
616
|
+
->selectRaw("pgroonga_score(entries) AS score")
|
617
|
+
// インデックス定義と同じ式↓を指定すること!
|
618
|
+
// title || ' ' || content
|
619
|
+
// &@*が類似検索の演算子
|
620
|
+
->whereRaw("(title || ' ' || content) &@* ?",
|
621
|
+
[$text])
|
622
|
+
->orderBy("score", "DESC");
|
623
|
+
}
|
624
|
+
|
625
|
+
= 類似検索:インスタンスメソッド
|
626
|
+
|
627
|
+
# coderay php
|
628
|
+
|
629
|
+
public function similarEntries()
|
630
|
+
{
|
631
|
+
return Entries::query()
|
632
|
+
// タイトルと内容がクエリー
|
633
|
+
->similarSearch("{$this->title} {$this->content}")
|
634
|
+
// 自分自身を除くこと!
|
635
|
+
->where("id", "<>", $this->id)
|
636
|
+
// 最も類似している3件のみ取得
|
637
|
+
->limit(3)
|
638
|
+
->get();
|
639
|
+
}
|
640
|
+
|
641
|
+
= 類似検索:使い方
|
642
|
+
|
643
|
+
# coderay php
|
644
|
+
|
645
|
+
@foreach ($entries as $entry)
|
646
|
+
<ol> {{-- ↓マニュアル毎に類似文書検索 --}}
|
647
|
+
@foreach ($entry->similarEntries() as $similarEntry)
|
648
|
+
<li>
|
649
|
+
<a href="{{ $similarEntry->url }}">
|
650
|
+
{{ $similarEntry->title }}
|
651
|
+
<span class="score">{{ $similarEntry->score }}</span>
|
652
|
+
</a>
|
653
|
+
</li>
|
654
|
+
@endforeach
|
655
|
+
</ol>
|
656
|
+
@endforeach
|
657
|
+
|
658
|
+
= 類似マニュアル検索
|
659
|
+
|
660
|
+
# image
|
661
|
+
# src = images/php-document-search-similar-search.png
|
662
|
+
# relative_height = 100
|
663
|
+
|
664
|
+
= 同義語展開
|
665
|
+
|
666
|
+
# image
|
667
|
+
# src = images/php-document-search-synonym.png
|
668
|
+
# relative_height = 100
|
669
|
+
|
670
|
+
= 実現方法
|
671
|
+
|
672
|
+
* 同義語管理テーブルを作成
|
673
|
+
* 同義語を登録
|
674
|
+
* 同義語を展開して検索
|
675
|
+
|
676
|
+
= モデル作成
|
677
|
+
|
678
|
+
# coderay console
|
679
|
+
|
680
|
+
% php artisan \
|
681
|
+
make:model \
|
682
|
+
--migration \
|
683
|
+
--controller \
|
684
|
+
--resource \
|
685
|
+
Synonym
|
686
|
+
|
687
|
+
= マイグレーション:カラム
|
688
|
+
|
689
|
+
# coderay php
|
690
|
+
|
691
|
+
public function up() {
|
692
|
+
Schema::create('synonyms', function ($table) {
|
693
|
+
$table->increments('id');
|
694
|
+
$table->text('term'); // 展開対象の語
|
695
|
+
$table->text('synonym'); // 展開後の語
|
696
|
+
// 例:term: @, synonym: @
|
697
|
+
// 例:term: @, synonym: エラー制御演算子
|
698
|
+
// 「@」→「@ OR エラー制御演算子」
|
699
|
+
$table->timestamps();
|
700
|
+
// インデックス定義(後述)
|
701
|
+
});
|
702
|
+
}
|
703
|
+
|
704
|
+
= マイグレーション\nインデックス
|
705
|
+
|
706
|
+
# coderay php
|
707
|
+
|
708
|
+
$table->index(
|
709
|
+
// termで完全一致できるようにする設定
|
710
|
+
[DB::raw(
|
711
|
+
"term pgroonga_text_term_search_ops_v2")],
|
712
|
+
null,
|
713
|
+
"pgroonga");
|
714
|
+
|
715
|
+
= 同義語登録
|
716
|
+
|
717
|
+
(('tag:center'))
|
718
|
+
シーダー作成
|
719
|
+
|
720
|
+
# coderay console
|
721
|
+
|
722
|
+
% php artisan \
|
723
|
+
make:seeder \
|
724
|
+
SynonymsTableSeeder
|
725
|
+
|
726
|
+
= シーダー
|
727
|
+
|
728
|
+
# coderay php
|
729
|
+
|
730
|
+
public function run()
|
731
|
+
{
|
732
|
+
$synonyms = [
|
733
|
+
// @そのもので検索させないならこれはいらない
|
734
|
+
["term" => "@", "synonym" => "@"],
|
735
|
+
["term" => "@",
|
736
|
+
// synonymでは演算子を使える
|
737
|
+
// ">": 重要度を上げる演算子
|
738
|
+
"synonym" => ">エラー制御演算子"],
|
739
|
+
];
|
740
|
+
DB::table("synonyms")->insert($synonyms);
|
741
|
+
}
|
742
|
+
|
743
|
+
= 動作確認
|
744
|
+
|
745
|
+
# coderay sql
|
746
|
+
|
747
|
+
SELECT pgroonga_query_expand(
|
748
|
+
'synonyms', -- テーブル名
|
749
|
+
'term', -- 展開対象語のカラム名
|
750
|
+
'synonym', -- 展開後の語のカラム名
|
751
|
+
'@'); -- 展開対象のクエリー
|
752
|
+
-- pgroonga_query_expand
|
753
|
+
-- ------------------------------
|
754
|
+
-- ((@) OR (>エラー制御演算子))
|
755
|
+
-- (1 row)
|
756
|
+
|
757
|
+
= 検索
|
758
|
+
|
759
|
+
# coderay php
|
760
|
+
|
761
|
+
whereRaw("title &@~ ? OR content &@~ ?",
|
762
|
+
[">({$search_query})", $search_query]);
|
763
|
+
// ↓クエリーをpgroonga_query_expand()で展開して利用
|
764
|
+
whereRaw(
|
765
|
+
"title &@~ pgroonga_query_expand(?, ?, ?, ?) OR " .
|
766
|
+
"content &@~ pgroonga_query_expand(?, ?, ?, ?)",
|
767
|
+
["synonyms", "term", "synonym", ">({$search_query})",
|
768
|
+
"synonyms", "term", "synonym", $search_query]);
|
769
|
+
|
770
|
+
= 同義語展開
|
771
|
+
|
772
|
+
# image
|
773
|
+
# src = images/php-document-search-synonym.png
|
774
|
+
# relative_height = 100
|
775
|
+
|
776
|
+
= おさらい:基本機能
|
777
|
+
|
778
|
+
* 高速全文検索+ソート
|
779
|
+
* 検索キーワードハイライト
|
780
|
+
* キーワード周辺テキスト表示
|
781
|
+
|
782
|
+
= おさらい:高度な機能
|
783
|
+
|
784
|
+
* オートコンプリート
|
785
|
+
* ローマ字対応(seiki→正規表現)
|
786
|
+
* 類似マニュアル検索
|
787
|
+
* 同義語展開
|
788
|
+
* 「@」→「@ OR エラー制御演算子」
|
789
|
+
|
790
|
+
= 開発者募集!
|
791
|
+
|
792
|
+
* 公式検索システム置き換え!?
|
793
|
+
* 必要そう:複数バージョン対応
|
794
|
+
* 必要そう:複数言語対応
|
795
|
+
* マニュアルをさらによく!
|
796
|
+
* 検索を便利に!→ユーザー増加!
|
797
|
+
* →フィードバックする人も増加!
|
798
|
+
|
799
|
+
= 使いたい!
|
800
|
+
|
801
|
+
* WEICさんが運用予定!
|
802
|
+
* 2017年10月中にリリース予定
|
803
|
+
* URL: http://phpdocs.weic.co.jp/
|
804
|
+
* 宣伝(('note:(運用スポンサーの宣伝枠)'))
|
805
|
+
* PHPエンジニア大募集!
|
806
|
+
* 業務時間内にこれの開発もできる!?
|
807
|
+
|
808
|
+
= PHP document search情報
|
809
|
+
|
810
|
+
* ソース
|
811
|
+
* (('tag:x-small'))https://github.com/kou/php-document-search
|
812
|
+
* OSS
|
813
|
+
* MITライセンス
|
814
|
+
|
815
|
+
= まとめ
|
816
|
+
|
817
|
+
* PostgreSQL + PGroonga
|
818
|
+
* 高速日本語全文検索サービスを\n
|
819
|
+
((*PHP*))で簡単に作れる!
|
820
|
+
* 開発者募集!
|
821
|
+
* サービス提供予定 by WEIC!
|
822
|
+
* URL: http://phpdocs.weic.co.jp/
|
823
|
+
|
824
|
+
= MySQLでもできる?
|
825
|
+
|
826
|
+
* MySQL・PostgreSQLだけで作る\n
|
827
|
+
高速でリッチな\n
|
828
|
+
全文検索システム
|
829
|
+
* db tech showcase Tokyo 2017の資料
|
830
|
+
* PGroongaの代わりにMroongaを使う
|
831
|
+
* SQLでの書き方だけでPHPの話はない
|
832
|
+
|
833
|
+
(('tag:center'))
|
834
|
+
(('tag:xx-small'))
|
835
|
+
https://slide.rabbit-shocker.org/authors/kou/db-tech-showcase-tokyo-2017/
|
836
|
+
|
837
|
+
= PHP+MySQL+Mroonga入門
|
838
|
+
|
839
|
+
* Groongaではじめる全文検索
|
840
|
+
* https://grnbook-ja.tumblr.com/
|
841
|
+
* 著者:北市真
|
842
|
+
* PHP+Mroonga入門の電子書籍
|
843
|
+
* 今はまだ無料!
|
844
|
+
|
845
|
+
= PHPの開発へ参加!
|
846
|
+
|
847
|
+
* PHPの開発に参加しませんか?
|
848
|
+
* 例:PDO/LaravelのPostgreSQL関連
|
849
|
+
* 例:マニュアル生成まわり
|
850
|
+
* 例:PHP document search関連
|
851
|
+
* (('wait'))やりたいけど自分はムリそう…
|
852
|
+
* そんなことはないんですよ!
|
853
|
+
|
854
|
+
= OSS Gate
|
855
|
+
|
856
|
+
* OSS Gate
|
857
|
+
* OSS開発者を増やす取り組み
|
858
|
+
* OSS Gateワークショップ
|
859
|
+
* OSS開発未経験者を経験者にする\n
|
860
|
+
ワークショップ
|
861
|
+
* PHPもOSS!
|
862
|
+
|
863
|
+
= ワークショップ
|
864
|
+
|
865
|
+
* このカンファレンス内で開催!
|
866
|
+
* 午後にこの部屋で開催(('note:(2時間45分)'))
|
867
|
+
* 参加希望者は私に声をかけて!
|
868
|
+
* PHP関連のOSSの開発に\n
|
869
|
+
参加する人を増やそう!
|
870
|
+
* 今回だけで終わりにしないで\n
|
871
|
+
((*今回を始まりにしたい!*))
|
data/theme.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabbit-slide-kou-php-conference-2017
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2017.10.08.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kouhei Sutou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rabbit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rabbit-theme-groonga
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |-
|
42
|
+
PHPマニュアルは日本語にも翻訳されていますが、検索機能が限定的なため、せっかくの説明を活用しきれません。基本の検索機能は関数やクラス名の検索機能しかないため「正規表現」で正規表現の使い方を検索できません。サイト内検索機能では「@」でエラー制御演算子を検索できません。
|
43
|
+
|
44
|
+
この講演ではPHPマニュアルをより活用するためのPHPマニュアル高速全文検索システムを紹介します。このシステムはPostgreSQLとPGroongaを利用して実現しています。
|
45
|
+
email:
|
46
|
+
- kou@clear-code.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- ".rabbit"
|
52
|
+
- README.rd
|
53
|
+
- Rakefile
|
54
|
+
- config.yaml
|
55
|
+
- images/php-document-search-search.png
|
56
|
+
- images/php-document-search-similar-search.png
|
57
|
+
- images/php-document-search-similar-search.xcf
|
58
|
+
- images/php-document-search-synonym.png
|
59
|
+
- images/php-document-search-synonym.xcf
|
60
|
+
- images/php-document-search.png
|
61
|
+
- images/php-manual-ja-full-text-search-by-at-not-found.png
|
62
|
+
- images/php-manual-ja-full-text-search-by-at-not-found.xcf
|
63
|
+
- images/php-manual-ja-full-text-search-link.png
|
64
|
+
- images/php-manual-ja-full-text-search-link.xcf
|
65
|
+
- images/php-manual-ja-search-by-at-not-found.png
|
66
|
+
- images/php-manual-ja-search-by-at-not-found.xcf
|
67
|
+
- images/php-manual-ja-search-by-at.png
|
68
|
+
- images/php-manual-ja-search-by-at.xcf
|
69
|
+
- images/php-manual-ja.png
|
70
|
+
- images/search-pg-bigm.pdf
|
71
|
+
- images/search-pgroonga-pg-bigm.pdf
|
72
|
+
- pdf/php-conference-2017-php-document-fast-full-text-search-system-with-postgresql-and-pgroonga.pdf
|
73
|
+
- php-document-fast-full-text-search-system-with-postgresql-and-pgroonga.rab
|
74
|
+
- theme.rb
|
75
|
+
homepage: http://slide.rabbit-shocker.org/authors/kou/php-conference-2017/
|
76
|
+
licenses:
|
77
|
+
- CC BY 3.0
|
78
|
+
- CC BY-SA 4.0
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.5.2
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: PostgreSQLとPGroongaで作るPHPマニュアル高速全文検索システム
|
100
|
+
test_files: []
|