Edited at

LaravelのModelをパッケージ化してCMSとフロントのアプリケーション実装を楽にする

More than 1 year has passed since last update.

この記事は、オールアバウト Advent Calendar 2016の21日目の記事です。

オールアバウトでエンジニアをしている@pakkunです。

LaravelでCMSとフロントでアプリケーションを分ける際に、Modelをパッケージ化したいなんていう悩みを持ったことはありませんか?

というわけで、オールアバウトでは、Modelをパッケージ化して運用している場合があるので、どんな感じで運用しているのかざっくりまとめます。

※Laravel5.3前提で書きますので、バージョン違いでできないこともあるかも?


Modelのパッケージ化


Gitにパッケージ用のリポジトリを作る

GitHubでもBitbucketでも、なんでも良いのでリポジトリを作りましょう!


【パッケージ】パッケージの構造を作る

良い感じに作りましょう!

vendor

└ all-about/
└ model-package
├ config
├ database
│ ├ factories
│ ├ migrations
│ └ seeds
├ src
│ ├ Models
│ │ └ Eloquent // ここらへんにModelを配置すると良さげな気がする
│ │ ├ User
│ │ └ Content
│ └ Providers
├ tests
├ composer.json
├ gulpfile.js
├ package.json
├ phpcs.xml
├ phpunit.xml
└ readme.md


【パッケージ】Composer.jsonを定義する

良い感じに定義しましょう!


vendor/all-about/model-package/composer.json

{

"name": "all-about/model-package",
"version": "0.0.1",
"description": "",
"keywords": [],
"license": "",
"type": "package",
"require": {
"php": ">=5.6.4",
"laravel/framework": "5.3.*"
},
"autoload": {
"psr-4": {
"AllAbout\\ModelPackage\\": "src/",
"AllAbout\\ModelPackage\\Database\\": "database/"
}
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
]
},
"config": {
"preferred-install": "dist"
},
"minimum-stability": "stable"
}


【CMS or フロント】Composer.jsonに定義する

良い感じに定義しましょう!

業務で使うならプライベートリポジトリだと思うので、下記のような感じになりそうですね!


laravel/composer.json

{

"repositories": [
{
"type": "vcs",
"url": "https://example.com/all-about/model-package"
}
],
"require": {
"all-about/model-package": "dev-master"
}
}


【CMS or フロント】Modelを呼び出す

呼び出しましょう!


laravel/app/Http/HogeController.php

use AllAbout\ModelPackage\Models\Eloquent\User;


以上で、Modelのパッケージ化が完了しました。

ついでなので、テーブル定義もパッケージ内で管理してみましょう!


テーブル定義

artisanコマンドによるマイグレーションでは、make:migration、migrate、migrate:refreshで、パス指定ができます。


【CMS・フロント】テーブル定義

CMS・フロントのアプリケーションのルートディレクトリで下記artisanコマンドを実行して、パッケージ内にマイグレーションファイルを作成しましょう!

php artisan make:migration create_contents_table --path=vendor/all-about/model-package/database/migrations/


【CMS・フロント】マイグレーション実行

作成されたファイルにテーブル定義を行ったら、パスを指定してマイグレーションを実行しましょう!

初期開発だとテーブル構造が何回も変わったりするので、潔くリフレッシュ(削除&実行)しましょう。

開発で本番DBに向けるなんていう人はいないよね??

php artisan migrate:refresh --path=vendor/all-about/model-package/database/migrations/

以上で、データベースマイグレーションが完了です。

CMS・フロントのアプリケーションからマイグレーションし放題ですね!

artisanコマンドは、readme.mdに書いておくと使いやすいです。

ただ、これだけだとパッケージ内のテストができないので、設定していきます。


パッケージのテスト定義


【パッケージ】composer.jsonにテスト関連のパッケージを追加する

テストを書くなら、とりあえず、下記パッケージをインストールしましょう!

  "require-dev": {

"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~5.0",
"orchestra/testbench": "~3.3"
}


【パッケージ】phpunit.xmlの設定

よしなに設定しましょう!


vendor/all-about/model-package/phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
</php>
</phpunit>



【パッケージ】orchestra/testbenchでテストのベースクラスの作成

Laravelパッケージ用のテストサポートパッケージ「orchestra/testbench」をインストールすることにより、Laravelに依存した機能が担保されます。

testsディレクトリにorchestra/testbenchベースのTestCase.phpを作成しましょう!

必要最低限なら、継承元のクラスで設定済みなので、下記のようなクラス定義のファイルだけを作ればOK!


vendor/all-about/model-package/tests/TestCase.php

<?php

class TestCase extends \Orchestra\Testbench\TestCase
{
//
}



【パッケージ】テストを書く

下記testsディレクトリで、orchestra/testbenchで作成したベースクラスを継承してテストを書きまくろう!


vendor/all-about/model-package/tests/HogeTest.php

<?php

class HogeTest extends TestCase
{
public function testHoge()
{
$this->assertTrue(true);
}
}



【パッケージ】テストを実行

下記コマンドを実行して、動けばOK!

cd vendor/all-about/model-package

./vendor/bin/phpunit

以上で、パッケージ内のテストの設定が完了です。

でも、手動でテストを回すのはつらいですよね?自動化しましょう!


【パッケージ】gulpfile.jsを設定する。

nodejs、gulp、gulp-phpunitは事前にインストールしておきましょう。

var gulp = require('gulp');

var phpunit = require('gulp-phpunit');

var targets = [
'./config/**/*.php',
'./database/**/*.php',
'./src/**/*.php',
'./tests/**/*.php',
'!node_modules/', '!vendor/**/*'
];

gulp.task('default', [
'phpunit',
]);

gulp.task("watch", function () {
var gaze_option = {
debounceDelay: 1000 // wait 1 sec after the last run
};
gulp.watch(targets, [
'phpunit',
], gaze_option);
});

// shellだと色がつかないので・・・。
gulp.task('phpunit', function () {
var options = {debug: false};
gulp.src('phpunit.xml')
.pipe(phpunit('./vendor/bin/phpunit', options)).on('error', function (err) {});
});


【パッケージ】gulpでファイル監視しながらテストを書く

nodejs、gulp、gulp-phpunitがインストールできていれば、あとは下記コマンドをEnterキーでターンッ!

gulp watch

ファイル監視されているので、ファイル更新のたびにテストが実行されるようになりました!

以上でパッケージの自動テストが完了です。

でも、CMS・フロントのアプリケーションで、それぞれのSeederデータ、テストクラスで使うデータを作るなんてめんどくさいですよね?

なんとかしましょう!


モデルファクトリー


【パッケージ】モデルファクトリーを定義

CMS・フロントで同じSeederデータの定義・テストデータの定義をまとめて使えるように、Laravelのモデルファクトリーを使いましょう!

下記ディレクトリにModelFactory.phpを配置しましょう。


vendor/all-about/model-package/database/factories/ModelFactory.php

<?php

use Faker\Factory as FakerFactory;

$factory->define(\AllAbout\ModelPackage\Models\Eloquent\Content::class, function (Faker\Generator $faker) {
// Fakerを使うとダミーデータの生成が楽。
$faker = FakerFactory::create('ja_JP');

$data = [
'title' => $faker->word,
'description' => $faker->sentence(),
'body' => $faker->sentence(),
];

// DB用格納時に生成されているデータをテスト時には、ダミーで作成
if (app()->environment() === 'testing') {
$data['id'] = crc32(uniqid());
$data['created_at'] = $faker->dateTime();
$data['updated_at'] = $faker->dateTime();
}

return $data;
});


これだけで、Seeder、テストのデータ作成が簡単にできるよう仕組みができました。

Seederから設定していきましょう!


Seeder


【パッケージ】Seederクラスを定義

作成したモデルファクトリーを読み込んで、Seederデータを作成しましょう。


vendor/all-about/model-package/database/seeds/HogeSeeder.php

<?php

namespace AllAbout\ModelPackage\Database\Seeds;

use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
use Illuminate\Database\Seeder;

class HogeSeeder extends Seeder
{
protected $factory_path = 'vendor/all-about/model-package/database/factories';

public function setContainer(Container $container)
{
// モデルファクトリー読み込み
$container->make(EloquentFactory::class)->load($this->factory_path);
return parent::setContainer($container);
}

public function run()
{
// DBにContentデータが3件生成される処理。なんて便利なんだ、モデルファクトリー。
factory(\AllAbout\ModelPackage\Models\Eloquent\Content::class, 3)->create();
}
}



【CMS・フロント】Seederを実行

CMS・フロントのルートディレクトリに移動して下記artisanコマンドを実行!

php artisan db:seed --class="AllAbout\ModelPackage\Database\Seeds\HogeSeeder"

これ一つでCMS・フロントで作るダミーデータをパッケージで一元管理することが可能になりました!

Seederデータの設定は完了しましたが、CMS・フロントでのテストを書く際にモデルファクトリーが呼べないので、設定をしましょう。


CMS・フロントのテスト


【CMS・フロント】 tests配下にModelPackageTestCase.phpを作成する。

CMS・フロントで、モデルファクトリーを呼ぶために、継承クラス「ModelPackageTestCase」を作成しましょう。

私は別クラスで定義したい派なので継承クラス「ModelPackageTestCase」を作成しましたが、TestCase.phpに書いても良いと思います。


laravel/tests/ModelPackageTestCase.php

<?php

use Illuminate\Database\Eloquent\Factory as EloquentFactory;

abstract class ModelPackageTestCase extends TestCase
{
// パッケージのfacitoriesのディレクトリまでを定義
protected $factory_path = 'vendor/all-about/model-package/database/factories';

/**
* Setup the test environment.
*
* @return void
*/

public function setUp()
{
parent::setUp();
$this->app->make(EloquentFactory::class)->load(base_path($this->factory_path));
}
}



【CMS・フロント】composer.jsonにModelPackageTestCase.phpのautolaod定義を追加

別クラスにした場合、定義しないと読み込めないので定義しましょう!


laravel/composer.json

"autoload-dev": {

"classmap": [
"tests/ModelPackageTestCase.php", // 追加
"tests/TestCase.php"
]
},


【CMS・フロント】 ModelPackageTestCase.phpを継承してテストを作成する。

モデルファクトリーによって簡単に定義できるようになったテストデータを使用して、モックしまくろう!

<?php

class ContentControllerTest extends ModelPackageTestCase {
public function testIndex()
{
$content_model_mock = Mockery::mock(Content::class);
// テスト時に近しいデータを、モデルファクトリーによって、生成
// make()を使用しているのでDBアクセスしていない
$content = factory(\AllAbout\ModelPackage\Models\Eloquent\Content::class)->make();
$content_model_mock->shouldReceive('find')->andReturn($content);

$this->app->instance(\AllAbout\ModelPackage\Models\Eloquent\Content::class, $content_model_mock);
// 省略
}
}

これでテストが簡単にかけるようになりました!

CMS・フロントのアプリケーションでのphpunit、phpcsの設定・実行・自動化は、パッケージのところで書いたので、省略します・・・。

以上が、オールアバウトで行っているModelのパッケージ化でした。

案外簡単にできるのでぜひお試しをー!

※今回、Qiita用に書き直した内容で動作確認をしていないので、もし動かない場合は、コメントしてください。