素のPHPでEloquentを使う(illuminate/databaseパッケージの単体利用)

カテゴリ: composer | タグ: , ,

PHPのフレームワークであるLaravelでは、DBアクセスを行うのにEloquentやQueryBuilderという便利なモジュールがあります。

ですが、プロジェクトによってはLaravel以外のマイクロフレームワークや、フレームワークなしの環境だけども、ORマッパーのEloquentだけは使いという状況もあります。

そこで今回は、Laravelフレームワークを使わずに、Eloquentを利用する方法を説明します。

LaravelとEloquentの関係

EloquentはLaravel向けののORマッパーライブラリとして有名ですが、Eloquent自体はLaravelと一緒に使わなくても、composerのilluminate/databaseという独立したパッケージとして提供されています。

ですので、illuminate/databaseパッケージだけを個別で取得すれば、Laravel以外の環境でもEloquentを利用できます。

Composerでilluminate/databaseパッケージを取得する

それでは、まずComposerを利用してilluminate/databaseパッケージを取得します。

Composerがインストールされた環境で、以下のようにcomposer requireコマンドを実行してilluminate/databaseパッケージをダウンロードします。

$ composer require illuminate/database

Using version ^5.5 for illuminate/database
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 10 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-mbstring (v1.5.0): Loading from cache
  - Installing symfony/translation (v3.3.10): Downloading (100%)
  - Installing nesbot/carbon (1.22.1): Loading from cache
  ...
  - Installing illuminate/container (v5.5.2): Downloading (100%)
  - Installing illuminate/database (v5.5.2): Downloading (100%)

Writing lock file
Generating autoload files

composer requireの完了後にカレントディレクトリを見ると、ファイルcomposer.json, composer.lockとvendorディレクトリができていることが分かります。

$ ls -l
total 25
-rw-r--r-- 1 aka 197121    65 Oct 15 20:02 composer.json
-rw-r--r-- 1 aka 197121 19531 Oct 15 20:03 composer.lock
drwxr-xr-x 1 aka 197121     0 Oct 15 20:03 vendor/

※今回はcomposer requireコマンドでカレントディレクトリの下にローカルインストールしているので、もし、illuminate/databaseパッケージが不要になったら、下記の3つを消せばアンインストールは完了です。

ダウンロードが終わったら、composer showで何のパッケージがインストールされたか一覧を確認します。

illuminate/databaseが依存しているパッケージも含めて、合計で10のパッケージが入りました。

$ composer show
doctrine/inflector        v1.2.0  Common String Manipulations with regard to casing and singular/plural rules.
illuminate/container      v5.5.2  The Illuminate Container package.
illuminate/contracts      v5.5.2  The Illuminate Contracts package.
illuminate/database       v5.5.2  The Illuminate Database package.
illuminate/support        v5.5.2  The Illuminate Support package.
nesbot/carbon             1.22.1  A simple API extension for DateTime.
psr/container             1.0.0   Common Container Interface (PHP FIG PSR-11)
psr/simple-cache          1.0.0   Common interfaces for simple caching
symfony/polyfill-mbstring v1.5.0  Symfony polyfill for the Mbstring extension
symfony/translation       v3.3.10 Symfony Translation Component

念のため、composer licensesでライセンスをチェックします。
すべてMITライセンスでした。

$ composer licenses
Name: __root__
Version: No version set (parsed as 1.0.0)
Licenses: none
Dependencies:

Name                       Version  License
doctrine/inflector         v1.2.0   MIT
illuminate/container       v5.5.2   MIT
illuminate/contracts       v5.5.2   MIT
illuminate/database        v5.5.2   MIT
illuminate/support         v5.5.2   MIT
nesbot/carbon              1.22.1   MIT
psr/container              1.0.0    MIT
psr/simple-cache           1.0.0    MIT
symfony/polyfill-mbstring  v1.5.0   MIT
symfony/translation        v3.3.10  MIT

これでパッケージのダウンロードと、確認は完了です。

パッケージがロードできるか確認

EloquentのORマッパーを単独で使う場合は、DBの接続管理をIlluminate\Database\Capsule\Managerクラスで行います。

ですので、まずはCompsoserのautoloadの機能を使って、Illuminate\Database\Capsule\Managerクラスが正しく利用できるか動作確認してみます。

<?php
require_once 'vendor/autoload.php';

$db = new Illuminate\Database\Capsule\Manager;
echo get_class($db) . PHP_EOL;

このスクリプトを実行して、以下のようにクラス名が出力されればOKです。

$ php dbtest.php
Illuminate\Database\Capsule\Manager

データベースに接続してみる

次はMySQLにアクセスできる事を確認します。
EloquentのORマッパーを使うと処理が複雑になって問題が発生したときの切り分けが難しくになるので、まずはOR間マッパーの機能を使わずに、select()メソッドで直にSQLを発行してみます。

以下のコードで、MySQLに接続しshow databasesコマンドでデータベースの一覧を取得できるか確認します。

※スクリプト中のaddConnection() host, database, username, passwordは実際の環境に合わせて書き換えてください。

<?php
require_once 'vendor/autoload.php';

$db = new Illuminate\Database\Capsule\Manager;
$db->addConnection([
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'database'  => '',
    'username'  => 'root',
    'password'  => 'passw0rd',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => '',
]);

$db->setAsGlobal();
$db->bootEloquent();

// DB一覧を取得
$rows = $db::select('show databases');
foreach($rows as $row) {
    echo $row->Database . PHP_EOL;
}

スクリプトを実行し、以下のようにDBの一覧が出力されればOKです。(実際に出力されるDBの一覧は、接続先MySQLサーバの内容に応じて異なります)

$ php test01.php
information_schema
mysql
performance_schema

接続先のDBを作る

DBの接続確認がができたら、MySQLにテーブルを作ってアクセスしてみます。

PHPのプログラムを作る前に、まずはMySQL上でテーブルを作成します。MySQLが動く環境はWindowsならxamppなどで用意してもらってよいですが、今回はVagrantを使ってLinuxのVMを用意して動作確認しています。

VagrantでのMySQL環境作成はこの記事の手順で作っています。
- VagrantでCentOS7.3+MySQL5.6環境をコマンド1つで構築する

(xamppやVagrantなどで)MySQLのサーバを用意したら、rootユーザでDBに接続します。

# mysql --user=root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.1.10-MariaDB mariadb.org binary distribution

Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

CREATE DATABASEでデータベースを作成します。今回はデータベース名をtest_dbとしました。

> CREATE DATABASE test_db;
Query OK, 1 row affected (0.00 sec)

作成したデータベースに切り替え、以下の列定義でusersテーブルを作ります。

use test_db;
Database changed

CREATE TABLE users (
  id         int(10)       unsigned NOT NULL AUTO_INCREMENT,
  name       varchar(255)  NOT NULL,
  deleted_at timestamp     NULL DEFAULT NULL,
  created_at timestamp     NULL DEFAULT NULL,
  updated_at timestamp     NULL DEFAULT NULL,
  PRIMARY KEY (id)    
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Query OK, 0 rows affected (0.07 sec)

テーブルが作成できたか確認します。

> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| users             |
+-------------------+
1 row in set (0.00 sec)

> desc users;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255)     | NO   |     | NULL    |                |
| deleted_at | timestamp        | YES  |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
5 rows in set (0.18 sec)

この後の動作確認を簡単にするために、テストデータを何件か入れておきます。

> insert into users values(NULL, 'alice', null, null, null);
Query OK, 1 row affected (0.01 sec)

> insert into users values(NULL, 'bob', null, null, null);
Query OK, 1 row affected (0.00 sec)

> select * from users;
+----+-------+------------+------------+------------+
| id | name  | deleted_at | created_at | updated_at |
+----+-------+------------+------------+------------+
|  1 | alice | NULL       | NULL       | NULL       |
|  2 | bob   | NULL       | NULL       | NULL       |
+----+-------+------------+------------+------------+
2 rows in set (0.00 sec)

これでデータベースの準備は完了です。

Eloquentのモデルクラスを作ってデータをselectする

次は、Eloquentのモデルクラスを作成します。
モデルクラスはIlluminate\Database\Eloquent\Modelを継承します。
また、クラス名はMySQLのテーブル名を単数形にすればよいです。

まずは中身が空っぽのクラスを作ってみます。

class User extends Illuminate\Database\Eloquent\Model
{
}

次にこのモデルクラスを使って、データを全件selectし、結果を出力します。

// usersテーブルの全データをダンプする
$users = User::all();
foreach($users as $user) {
    printf( "[%d] [%s] [%s]\n", $user->id, $user->name, $user->created_at);
}

先ほどのコネクション作成処理も必要なので、コードの全体は以下のようになります。データベースを作成したのでaddConnection()メソッドの'database'でDB名を指定しています。

<?php
require_once 'vendor/autoload.php';

$db = new Illuminate\Database\Capsule\Manager;
$db->addConnection([
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'database'  => 'test_db',
    'username'  => 'root',
    'password'  => '',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => '',
]);
$db->setAsGlobal();
$db->bootEloquent();

// モデルクラスを作る
class User extends Illuminate\Database\Eloquent\Model
{
}

// usersテーブルの全データをダンプする
$users = User::all();
foreach($users as $user) {
    printf( "[%d] [%s] [%s]\n", $user->id, $user->name, $user->created_at);
}

このスクリプトを実行すると、以下のようにデータが取得できます。3つ目の値が出力されていない理由は、テストデータを作ったときcreated_atはnullだった為です。

$ php dbtest.php
[1] [alice] []
[2] [bob] []

これで、Eloquentのモデルクラスを利用したデータ検索が行えることが確認できました。
Userクラスは今回テストのために、メインのスクリプトに書きましたが、実際は暮らす単位で別のファイルに分けて書く形になります。

Eloquentのモデルクラスを作ってデータをinsertする

次は、データのisnertを行ってみます。

データの登録は、以下のようなコードになります。(DBへの接続処理はもちろん必要ですが、同じなので省略します)

$user = new User;
$user->name = "charlie";
$user->save();

上記のコードを実行し、DBの内容を確認すると以下のようになります。id=3でcharlieが登録されていることが核にできます。

> select * from users;
+----+---------+------------+---------------------+---------------------+
| id | name    | deleted_at | created_at          | updated_at          |
+----+---------+------------+---------------------+---------------------+
|  1 | alice   | NULL       | NULL                | NULL                |
|  2 | bob     | NULL       | NULL                | NULL                |
|  3 | charlie | NULL       | 2017-01-31 07:58:58 | 2017-01-31 07:58:58 |
+----+---------+------------+---------------------+---------------------+

Eloquentのモデルクラスを作ってデータをdeleteする

select,insertが終わったら次はDeleteです。

データの削除方法はいろいろありますが、例えばid=1のデータを検索して削除する場合は、以下のコードになります。

$user = User::find(1);
$user->delete();

スクリプトを実行後にMySQLに接続してselectしてみると、id=1のデータがdeleteされており、2,3だけになりました。

> select * from users;
+----+---------+------------+---------------------+---------------------+
| id | name    | deleted_at | created_at          | updated_at          |
+----+---------+------------+---------------------+---------------------+
|  2 | bob     | NULL       | NULL                | NULL                |
|  3 | charlie | NULL       | 2017-10-31 07:58:58 | 2017-10-31 07:58:58 |
+----+---------+------------+---------------------+---------------------+

Eloquentのモデルクラスを作ってデータを論理削除(soft delete)する

先ほどの例ではデータの削除時にdelete文が発行されましたが、システムによっては、データを削除する時に実際にデータをdeleteするのではなく、削除フラグを立てて論理削除したい場合もあります。

このような場合に備えてEloquentではSoft Delete機能というものがあります。

SoftDeleteを使うためには、モデルクラス内で、use Illuminate\Database\Eloquent\SoftDeletes;を記載すればよいです。

データの削除処理は先ほどの例と同じです(id=1のデータはdeleteしてしまったので、id=2を指定しています)

class User extends Illuminate\Database\Eloquent\Model
{
    use Illuminate\Database\Eloquent\SoftDeletes;
}

$user = User::find(2);
$user->delete();

上記のコードを実行すると、以下のようにdeleted_atに削除時刻が登録されるだけで、MySQLでselect文を叩いて直接確認すると実際のデータはdeleteされていません。

> select * from users;
+----+---------+---------------------+---------------------+---------------------+
| id | name    | deleted_at          | created_at          | updated_at          |
+----+---------+---------------------+---------------------+---------------------+
|  2 | bob     | 2017-10-31 08:03:07 | NULL                | 2017-10-31 08:03:07 |
|  3 | charlie | NULL                | 2017-10-31 07:58:58 | 2017-10-31 07:58:58 |
+----+---------+---------------------+---------------------+---------------------+
2 rows in set (0.00 sec)

この後、PHPのスクリプトから再度all()メソッドで全データを取得するとどうなるでしょうか?

$users = User::all();
foreach($users as $user) {
    printf( "[%d] [%s] [%s]\n", $user->id, $user->name, $user->created_at);
}

実行してみると、deleted_atがnullで無いデータは検索対象から除外されます。

$ php dbtest.php
[3] [charlie] [2017-10-31 07:58:58]

論理削除されたデータも含めて取得したい場合は、withTrashed()メソッドを利用します。

$users = User::withTrashed()->get();
foreach($users as $user) {
    printf( "[%d] [%s] [delete=%s][create=%s][update=%s]\n", 
            $user->id, $user->name, $user->deleted_at, $user->created_at, $user->updated_at);
}

実行結果を見ると、id=2,3のデータが両方出力されています。どのデータが論理削除されたレコードかは、取得したレコードのdeleted_atを確認すれば判断できます。

$ php dbtest.php
[2] [bob] [delete=2017-10-31 08:03:07][create=][update=2017-10-31 08:03:07]
[3] [charlie] [delete=][create=2017-10-31 07:58:58][update=2017-10-31 07:58:58]

古いデータの消込みなどで、あえて論理削除されたデータだけほしい場合はonlyTrashed()を使用します。

$users = User::onlyTrashed()->get();
foreach($users as $user) {
    printf( "[%d] [%s] [delete=%s][create=%s][update=%s]\n", 
            $user->id, $user->name, $user->deleted_at, $user->created_at, $user->updated_at);
}

論理削除済みであるid=2のデータだけが取得できました

$ php dbtest.php
[2] [bob] [delete=2017-10-31 08:03:07][create=][update=2017-10-31 08:03:07]

Eloquentが発行したSQLを確認する

EloquentのようなORマッパーを使っていると、DBの処理が遅い時にEloquentが生成した生のSQLを確認したいことがあります。

このような場合は、事前にenableQueryLog()を実行したうえで最後にgetQueryLog()をコールすれば、Eloquentが発行したSQLを確認できます。複数のSQLが発行された場合も全てのSQLを配列として取得できます。

\Illuminate\Database\Capsule\Manager::enableQueryLog();

User::find(3);
User::all();
User::withTrashed()->get();
User::onlyTrashed()->get();

$logs = \Illuminate\Database\Capsule\Manager::getQueryLog();
foreach($logs as $log) {
    echo "-----------------------------" . PHP_EOL;
    echo " query : " . $log['query'] . PHP_EOL;
    echo " param : " . json_encode($log['bindings']) . PHP_EOL;
    echo " time  : " . $log['time'] . 'mSec' . PHP_EOL;
}

実行結果は以下のようになります。各SQL文に加えて実行時間をミリ秒単位で取得することも可能です。

$ php dbtest.php
-----------------------------
 query : select * from `users` where `users`.`id` = ? and `users`.`deleted_at` is null limit 1
 param : [3]
 time  : 1021.38mSec
-----------------------------
 query : select * from `users` where `users`.`deleted_at` is null
 param : []
 time  : 1.42mSec
-----------------------------
 query : select * from `users`
 param : []
 time  : 1.17mSec
-----------------------------
 query : select * from `users` where `users`.`deleted_at` is not null
 param : []
 time  : 1.33mSec

まとめ

素のPHPからEloquentパッケージを使ったMySQLの接続、データ操作をいくつか行ってみました。

Eloquentは上記以外にももちろん、データのupdateやとトランザクション処理など、通常のORマッパーが持っていそうな機能は一式持っています。

Eloquent自体の機能詳細を知りたい場合はLaravel関係の書籍を参考にするとよいです。

以下に2冊紹介しますが、現時点だと青い表紙の「PHPフレームワーク Laravel入門」のほうが、Laravelの新しいバージョンをベースにしているのでお勧めです。


Amazonでおトクに買い物する方法
AmazonチャージでポイントGET


Amazonは買いもの前にAmazonギフト券をチャージしてポイントをゲットしないと損!

こちらもおススメ

One thought on “素のPHPでEloquentを使う(illuminate/databaseパッケージの単体利用)

  1. Slim初心者です。普段LaravelでEloquentを使っていて、Slimで使おうとしたら上手くいかず、困っていましたが、ここの情報がヒントになりました。ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。