12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel #2Advent Calendar 2019

Day 1

【Laravel】独自のDB用クエリ関数を実装する

Last updated at Posted at 2019-11-30

この記事でやりたいこと

こんな独自のクエリ関数を実装する手順です。

\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

12
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?