最近在浏览社区话题的时候,看到了一位同仁发表的一篇教程:Laravel 5.3 下通过 migrate 添加 “全文索引” 的方法,突然想到自己之前也研究过这一话题,所以今天就和大家分享一下我的实现思路:如何更优雅地创建fulltext索引。

我非常喜欢Laravel框架的原因之一就是它的拓展性简直太赞了,所有框架本身未实现的功能你都可以自定义实现,而且可以实现得非常优雅。

其实,要想让添加全文索引这个动作变得更“优雅”一点,不外乎为Blueprint类添加一个fulltext方法,操作起来就像这样$table->fulltext('column');,这就与我们平时为字段添加unique索引一样方便了。我们先来看看预想效果:

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::table('posts', function (Blueprint $table) {

$table->fulltext(['title', 'content']);

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::dropFulltext(['title', 'content']);

}

Step 1 拓展Blueprint类

为了实现上述所预想的效果,显然我们要对Laravel的Illuminate\Database\Schema\Blueprint类进行拓展。这里所说的拓展,实际就是在继承原Blueprint的前提下定义一个新的Blueprint,并且在新的Blueprint类里面添加我们想要拓展的fulltext方法:

namespace Vendor\Package;

use Illuminate\Database\Schema\Blueprint as BaseBlueprint;

class Blueprint extends BaseBlueprint

{

/**

* Specify a fulltext index for the table.

*

* @param string|array $columns

* @param string $name

* @return \Illuminate\Support\Fluent

*/

public function fulltext($columns, $name = null)

{

return $this->indexCommand('fulltext', $columns, $name);

}

/**

* Indicate that the given fulltext key should be dropped.

*

* @param string|array $index

* @return \Illuminate\Support\Fluent

*/

public function dropFulltext($index)

{

return $this->dropIndexCommand('dropFulltext', 'fulltext', $index);

}

}

Step 2 拓展MySqlGrammar类

光实现一个拓展好的Blueprint肯定还不行,我们追踪一下Laravel的源码就能知道,它的底层是通过Illuminate\Database\Schema\Grammars\Grammar来编译SQL语法的,相应地MySQL的语法编译则由Illuminate\Database\Schema\Grammars\MySqlGrammar完成,因此我们还需要拓展一下MySqlGrammar,否则系统就会因为找不到编译fulltext索引的方法,而导致创建索引不成功。拓展MySqlGrammar时我们需要添加两个新的方法compileFulltext和compileDropFulltext,分别用于创建与删除全文索引,参考以下代码:

namespace Vendor\Package;

use Illuminate\Support\Fluent;

use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseMySqlGrammar;

class MySqlGrammar extends BaseMySqlGrammar

{

/**

* Compile a fulltext key command.

*

* @param \Vendor\Package\Blueprint $blueprint

* @param \Illuminate\Support\Fluent $command

* @return string

*/

public function compileFulltext(Blueprint $blueprint, Fluent $command)

{

return $this->compileKey($blueprint, $command, 'fulltext');

}

/**

* Compile a drop unique key command.

*

* @param \Vendor\Package\Blueprint $blueprint

* @param \Illuminate\Support\Fluent $command

* @return string

*/

public function compileDropFulltext(Blueprint $blueprint, Fluent $command)

{

$table = $this->wrapTable($blueprint);

$index = $this->wrap($command->index);

return "alter table {$table} drop index {$index}";

}

}

Step 3 注入新的MySqlGrammar

实现了以上两个拓展,我们的准备工作就已完毕,接下来就要将这两个拓展好的类注入到系统当中去。究竟如何注入。首先我们要来认识一下Illuminate\Support\Facades\Schema:

namespace Illuminate\Support\Facades;

/**

* @see \Illuminate\Database\Schema\Builder

*/

class Schema extends Facade

{

/**

* Get a schema builder instance for a connection.

*

* @param string $name

* @return \Illuminate\Database\Schema\Builder

*/

public static function connection($name)

{

return static::$app['db']->connection($name)->getSchemaBuilder();

}

/**

* Get a schema builder instance for the default connection.

*

* @return \Illuminate\Database\Schema\Builder

*/

protected static function getFacadeAccessor()

{

return static::$app['db']->connection()->getSchemaBuilder();

}

}

不难看出,这个Facade实际上返回由数据库连接的getSchemaBuilder方法生成的Illuminate\Database\Schema\Builder实例,我们继续追踪Illuminate\Database\Connection类,找到getSchemaBuilder方法:

/**

* Get a schema builder instance for the connection.

*

* @return \Illuminate\Database\Schema\Builder

*/

public function getSchemaBuilder()

{

if (is_null($this->schemaGrammar)) {

$this->useDefaultSchemaGrammar();

}

return new SchemaBuilder($this);

}

可以看出,我们应该在Laravel实例化SchemaBuilder之前注入拓展好的MySqlGrammar,而伟大的作者早已为我们准备好了接入方法setSchemaGrammar,因此我们只要这样操作就能轻松地完成注入:

use Vendor\Package\MySqlGrammar;

app('db')->connection()->setSchemaGrammar(new MySqlGrammar);

Step 4 注入新的Blueprint

现在只剩Blueprint还没被注入了,继续追踪\Illuminate\Database\Schema\Builder,通读一遍源代码,我们可以发现,我们平时写migration文件时所用的table()、create()、drop()等方法都会调用同一个方法createBlueprint:

/**

* Create a new command set with a Closure.

*

* @param string $table

* @param \Closure|null $callback

* @return \Illuminate\Database\Schema\Blueprint

*/

protected function createBlueprint($table, Closure $callback = null)

{

if (isset($this->resolver)) {

return call_user_func($this->resolver, $table, $callback);

}

return new Blueprint($table, $callback);

}

它会先判断是否存在自定义的resolver,而伟大的作者也为我们提供了接入方法blueprintResolver,所以我们可以这样注入拓展好的Blueprint:

use Vendor\Package\Blueprint;

app('db')->connection()->getSchemaBuilder()->blueprintResolver(function ($table, $callback) {

return new Blueprint($table, $callback);

});

至此,我们应该可以结束工作了,但事实不是这样的,如果你按照以下的方式来组织你的代码,你会发现,它并不能得到你预想的结果:

use Vendor\Package\Blueprint;

use Vendor\Package\MySqlGrammar;

use Illuminate\Support\Facades\Schema;

use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

app('db')->connection()->setSchemaGrammar(new MySqlGrammar);

app('db')->connection()->getSchemaBuilder()->blueprintResolver(new Blueprint);

Schema::table('posts', function (Blueprint $table) {

$table->fulltext(['title', 'content']);

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

app('db')->connection()->setSchemaGrammar(new MySqlGrammar);

app('db')->connection()->getSchemaBuilder()->blueprintResolver(new Blueprint);

Schema::dropFulltext(['title', 'content']);

}

}

具体原因是什么呢?这里保留一点想象空间,让大家自己去探寻一下结果,有想法的可以一起在回复区讨论。

Step 5 实现新的Schema Facade

这里我提供了一种解决方案,就是另行实现一个Facade:

namespace Vendor\Package;

use Illuminate\Support\Facades\Facade;

class Schema extends Facade

{

/**

* Get a schema builder instance for the default connection.

*

* @return \Illuminate\Database\Schema\Builder

*/

protected static function getFacadeAccessor()

{

$connection = static::$app['db']->connection();

$connection->setSchemaGrammar(new MySqlGrammar);

$schema = $connection->getSchemaBuilder();

$schema->blueprintResolver(function ($table, $callback) {

return new Blueprint($table, $callback);

});

return $schema;

}

}

这样,我们就可以优雅地添加fulltext索引了:

use Vendor\Package\Blueprint;

use Vendor\Package\Schema;

use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('posts', function (Blueprint $table) {

$table->fulltext(['title', 'content']);

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::dropFulltext(['title', 'content']);

}

}

整理好的拓展包

这是我个人整理的一个Package,有需求的可以直接使用,感兴趣的读者也可以下载源码继续研究:mysql-fulltext-laravel。

这篇文章我们并不关注MySQL全文索引对中文的支持,所以请读者选对你的应用场景。

其实正如@wyg27 所写教程呈现的,一行代码就能解决的问题,为什么还要如此大费周章?仁者见仁,或许你能从这里学会如何按需拓展你的Laravel应用。

这篇帖子没有半点反驳@wyg27 的意思,他的方法已然是最快速有效的了,我只是在此和大家分享一种新思路而已,所以不喜勿喷,2333333

我是黄毅,欢迎关注我的 Github 和 博客

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐