24
20

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 5 years have passed since last update.

オールアバウトAdvent Calendar 2016

Day 21

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

Last updated at Posted at 2016-12-21

この記事は、オールアバウト 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用に書き直した内容で動作確認をしていないので、もし動かない場合は、コメントしてください。

24
20
2

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
24
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?