この記事でやりたいこと
こんな独自のクエリ関数を実装する手順です。
\DB::canConnection(); //(1)
\Schema::getColumnDefinitions('user'); //(2)
User::getColumnDefinitions(); //(3)
\DB::table('user')->getColumnDefinitions(); //(4)
はじめに
以下のような関数は、Laravelですでに用意されています。
\DB::statement('drop table users');
\Schema::hasTable('testTable');
上記のような形式の、独自の関数を追加する方法です。
実装方法
それでは早速実装してみましょう。
なお、「この記事で書きたいこと」で書いた(1)~(4)の関数ですが、厄介なことに定義する場所は、それぞれ異なります。その点を考慮しておいてください。
今回作成するコードのフォルダ構成は、以下のようになります。
ServiceProvider.php
Database/
├ MySqlConnection.php
├ Eloquent/
│ └ MySqlBuilder.php
├ Query/
│ ├ MySqlBuilder.php
│ ├ Grammars
│ │ └ MySqlGrammar.php
│ └ Processors
│ └ MySqlGrammar.php
└ Schema/
├ MySqlBuilder.php
└ Grammars
└ MySqlGrammar.php
Model/
└ CustomBuilderTrait.php
※今回はMySQL前提で作成していきます。MySQL以外のデータベースを使用したい場合、複数のクラスを作成してください。
※今回の例では、以下の3つの関数を作成します。
- データベースに接続できるかを判定する関数「canConnection」
- データベースのバージョンを取得する関数「getVersion」
- 指定のテーブルの列の定義を一覧取得する関数「getColumnDefinitions」
実装
- ServiceProviderに、独自のMySqlConnectionを呼び出すための処理を追加します。
<?php
namespace CustomBuilder;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Connection;
class CustomBuilderServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
// 追加
Connection::resolverFor('mysql', function (...$parameters) {
return new Database\MySqlConnection(...$parameters);
});
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
//
}
}
- Database\MySqlConnectionを作成します。
この関数では、継承した「MySqlGrammar」などの定義、ならびに「\DB::XXXXXX」形式で呼び出す関数の定義を行います。
<?php
namespace CustomBuilder\Database;
use CustomBuilder\Database\Query\Grammars\MySqlGrammar as QueryGrammar;
use CustomBuilder\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar;
use CustomBuilder\Database\Schema\MySqlBuilder;
use CustomBuilder\Database\Query\MySqlBuilder as QueryBuilder;
use CustomBuilder\Database\Eloquent\MySqlBuilder as EloquentBuilder;
use CustomBuilder\Database\Query\Processors\MySqlProcessor;
use Illuminate\Database\MySqlConnection as BaseConnection;
class MySqlConnection extends BaseConnection
{
/**
* Get a schema builder instance for the connection.
*
* @return Builder
*/
public function getSchemaBuilder()
{
if (is_null($this->schemaGrammar)) {
$this->useDefaultSchemaGrammar();
}
return new MySqlBuilder($this);
}
/**
* Get the default schema grammar instance.
*
* @return MySqlGrammar
*/
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the default query grammar instance.
*
* @return QueryGrammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new QueryGrammar);
}
/**
* Get the default post processor instance.
*
* @return MySqlProcessor
*/
protected function getDefaultPostProcessor()
{
return new MySqlProcessor;
}
/**
* Get a new query builder instance.
*
* @return \Illuminate\Database\Query\Builder
*/
public function query()
{
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
/**
* Get a new eloquent query builder instance.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function eloquentBuilder($query)
{
return new EloquentBuilder($query);
}
// \DB::XXXXXX()関数はここで記載 -------------------------------------------
public function canConnection()
{
try {
$this->getSchemaBuilder()->getTableListing();
return true;
} catch (\Exception $ex) {
return false;
}
}
/**
* Get database version.
*
* @return void
*/
public function getVersion()
{
return $this->getSchemaBuilder()->getVersion();
}
}
- Database\Schema\MySqlBuilderを作成します。
このクラスに、\Schema::XXXXXX()で呼び出す関数の定義を行います。
<?php
namespace CustomBuilder\Database\Schema;
use Illuminate\Database\Schema\MySqlBuilder as BaseBuilder;
class MySqlBuilder extends BaseBuilder
{
// \Schema::XXXXXX()関数の形式 -------------------------------------------
/**
* Get database version.
*
* @return void
*/
public function getVersion()
{
$results = $this->connection->selectFromWriteConnection($this->grammar->compileGetVersion());
return $this->connection->getPostProcessor()->processGetVersion($results);
}
/**
* Get the table listing
*
* @return array
*/
public function getTableListing()
{
$results = $this->connection->selectFromWriteConnection($this->grammar->compileGetTableListing());
return $this->connection->getPostProcessor()->processTableListing($results);
}
/**
* Get column difinitions
*
* @return array
*/
public function getColumnDefinitions($table)
{
$baseTable = $table;
$table = $this->connection->getTablePrefix().$table;
$results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnDefinitions($table));
return $this->connection->getPostProcessor()->processColumnDefinitions($baseTable, $results);
}
}
- Database\Schema\Grammars\MySqlGrammarを作成します。
このクラスで、主に独自クラスで実行するSQLを定義します。
<?php
namespace CustomBuilder\Database\Schema\Grammars;
use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseGrammar;
class MySqlGrammar extends BaseGrammar
{
/**
* Compile the query to get version
*
* @return string
*/
public function compileGetVersion()
{
return "select version()";
}
/**
* Compile the query to show tables
*
* @return string
*/
public function compileGetTableListing()
{
return "show tables";
}
/**
* Compile the query to get column difinitions
*
* @return string
*/
public function compileColumnDefinitions($tableName)
{
return "show columns from {$this->wrapTable($tableName)}";
}
}
- Database\Schema\Grammars\MySqlGrammarを作成します。
このクラスで、主に独自クラスで実行するSQLを定義します。
<?php
namespace CustomBuilder\Database\Schema\Grammars;
use Illuminate\Database\Schema\Grammars\MySqlGrammar as BaseGrammar;
class MySqlGrammar extends BaseGrammar
{
/**
* Compile the query to get version
*
* @return string
*/
public function compileGetVersion()
{
return "select version()";
}
/**
* Compile the query to show tables
*
* @return string
*/
public function compileGetTableListing()
{
return "show tables";
}
/**
* Compile the query to get column difinitions
*
* @return string
*/
public function compileColumnDefinitions($tableName)
{
return "show columns from {$this->wrapTable($tableName)}";
}
}
- Database\Query\Processors\MySqlProcessorを作成します。
このクラスで、SQLを実行して取得した結果を、配列やCollectionに加工するなどを行います。
また、このクラス内の関数によって、各データベース種類(MySQL、SQL Serverなど)によって列名などが異なるものを、すべて揃える目的があります。
<?php
namespace CustomBuilder\Database\Query\Processors;
use Illuminate\Database\Query\Processors\MySqlProcessor as BaseMySqlProcessor;
class MySqlProcessor extends BaseMySqlProcessor
{
/**
* Process the results of a get version.
*
* @param array $results
* @return array
*/
public function processGetVersion($results)
{
$versionArray = $this->versionAray($results);
return $versionArray[0];
}
protected function versionAray($results)
{
$version = collect(collect($results)->first())->first();
return explode('-', $version);
}
/**
* Process the results of a table listing query.
*
* @param array $results
* @return array
*/
public function processTableListing($results)
{
return array_map(function ($result) {
return collect((object) $result)->first();
}, $results);
}
/**
* Process the results of a Column Definitions query.
*
* @param array $results
* @return array
*/
public function processColumnDefinitions($tableName, $results)
{
return collect($results)->map(function ($result) use ($tableName) {
return [
'table_name' => $tableName,
'column_name' => $result->Field,
'type' => $result->Type,
'nullable' => boolval($result->Null),
'virtual' => strtoupper($result->Extra) == 'VIRTUAL GENERATED',
];
})->toArray();
}
}
- Database\Query\MySqlBuilderを作成します。
このクラスに、\DB::table('user')::XXXXXX()で呼び出す関数の定義を行います。
<?php
namespace CustomBuilder\Database\Query;
use Illuminate\Database\Query\Builder as BaseBuilder;
class MySqlBuilder extends BaseBuilder
{
// \DB::table('user')::XXXXXX()関数の形式 -------------------------------------------
/**
* Get column difinitions
*
* @return array
*/
public function getColumnDefinitions()
{
$results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnDefinitions($this->from));
return $this->connection->getPostProcessor()->processColumnDefinitions($this->from, $results);
}
}
- また、\Database\Query\Grammars\MySqlGrammarで、上記のDatabase\Query\MySqlBuilderで呼び出すGrammerを作成します。
(\Database\Schema\Grammars\MySqlGrammarとまとめられないかな?という思いもありつつ)
<?php
namespace CustomBuilder\Database\Query\Grammars;
use Illuminate\Database\Query\Grammars\MySqlGrammar as BaseGrammar;
class MySqlGrammar extends BaseGrammar
{
/**
* Compile the query to get column difinitions
*
* @return string
*/
public function compileColumnDefinitions($tableName)
{
return "show columns from {$this->wrapTable($tableName)}";
}
}
- Database\Eloquent\MySqlBuilderを作成します。
このクラスに、\User::XXXXXX()で呼び出す関数の定義を行います。
<?php
namespace CustomBuilder\Database\Eloquent;
use Illuminate\Database\Eloquent\Builder as BaseBuilder;
class MySqlBuilder extends BaseBuilder
{
// \User::XXXXXX()関数の形式 -------------------------------------------
/**
* Get column difinitions
*
* @return array
*/
public function getColumnDefinitions()
{
$table = $this->model->getTable();
$connection = $this->query->connection;
$results = $connection->selectFromWriteConnection($connection->getQueryGrammar()->compileColumnDefinitions($table));
return $connection->getPostProcessor()->processColumnDefinitions($table, $results);
}
}
- Model\CustomBuilderTraitを作成します。
このTraitでは、各Modelで独自のEloquent\Builder、Query\Builderを呼び出すために必要となります。
<?php
namespace CustomBuilder\Model;
trait CustomBuilderTrait
{
/**
* Get a new query builder instance for the connection.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return $connection->query();
}
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
$connection = $this->getConnection();
return $connection->eloquentBuilder($query);
}
}
独自関数を呼び出すModelで、このTraitをuseしてください。
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use CustomBuilder\Model\CustomBuilderTrait;
class User extends Authenticatable
{
use Notifiable;
// 追加
use CustomBuilderTrait;
// 略
}
まとめ
以上です!
Helperクラス的な関数を作ってもいいですが、出来ればこのように、クエリビルダを拡張することを行いたいですよね。
その場合は是非、こちらのコードを使用してください!
ちなみに今回のコードのGitHubはこちらです。
https://github.com/hirossyi73/custom-builder