- 投稿日:2019-12-13T23:58:14+09:00
Laravel の認可(Policy)にモデル以外のパラメータを与える
この記事について
Laravel #2 Advent Calendar 2019 - Qiita 14日目の記事です。
表題の通りではあるんですが、意外と使われてないなぁと思ったので記事にすることにしました。
はじめに
環境
- Laravel 6.6.0
5.x でも同じです。
Policy について
特定のモデルに対し、ユーザーが操作権限があるかどうかを判定する仕組みで、公式ドキュメントには以下のような使用例が載っています。
class PostPolicy { public function update(User $user, Post $post) { return $user->id === $post->user_id; } }呼び出し側は、
class PostController extends Controller { public function update(Request $request, Post $post) { $this->authorize('update', $post); } }みたいになります。
この仕組みを利用するメリットは、
- 渡されたモデルのインスタンスに応じて Policy クラスをよしなに選択してくれる
- User インスタンスを自動でバインドしてくれる(デフォルトでは現在ログインしているユーザー)
- 権限がない場合は勝手に 403 エラーにしてくれる
あたりでしょうか。
権限管理が複雑なアプリケーションだと、アプリケーション独自で認可機構を実装したほうが柔軟にできると思うので、無理に使う必要はないと思いますが、そこはチームのポリシー次第、ということで。
ユースケース
- 他のモデルの状態が必要
- 静的な権限テーブルみたいなやつがある
- 動的にポリシーを差し替えたい
ざっと思いついたものを挙げましたが、基本的にはぜんぶ一緒です。
結論
authorize メソッドに配列で引数を渡すと、Policy の各メソッドには、スプレッド演算子によって展開された形で渡ってくるので、それらを使って複雑な認可ルールに対処できます。
呼び出し側でこのように呼ぶと、
$this->authorize('update', [$mainModel, $subModel]);Policy のメソッドにはこのように渡ってきます。
public function update(User $user, MainModel $main, SubModel $sub) {使用例
1. 他のモデルの状態が必要
ユーザーにも対象のモデルにもひもづかない、別のモデルの状態が必要になったとき、Controller でいったんそのモデルのインスタンスを取得して、authorize メソッドに渡してやります(ちょっといい例が浮かばなかったので適当なモデル名になっていますが、ご容赦ください)。
public function update(Request $request, Post $post) { $someModel = SomeModel::findOrFail($request->some_model_id); $this->authorize('update', [$post, $someModel]); }Policy 側では以下のように状態を参照できます(メソッドを呼んでもいいでしょう)。
public function update(User $user, Post $post, SomeModel $someModel) { return $user->id === $post->user_id && $someModel->acceptable; }2. 静的な権限テーブルみたいなやつがある
たとえばユースケースごとに操作可能な権限のリストを持っていて、それに合致しない場合は弾く、みたいなケースです。
ユースケースに決め打ちで(もしくはなんらかのルールに基づいてデータベースから取ってくるとかでも)、
class UpdateTask extends UseCase { public function validRoles(): array { return [Role::Admin, Role::Wheel]; } public function invoke() {...} }みたいに権限のリストがあり、それを以下のように authorize に渡してやります。
public function update(UpdateTask $useCase, Post $post) { $this->authorize('update', [$post, $useCase->validRoles()]); }Policy 側はこうなります。
public function update(User $user, Post $post, array $roles) { return $user->hasRole($roles) || $user->id === $post->user_id; }3. 動的にポリシーを差し替えたい
ユースケースごとに独自で認可ルール(関数)を持っていて、そいつをなんらかのルールで動的に生成する、といったようなケースです。
class UpdateTask extends UseCase { public function authorizeRule(): \Closure { return function (User $user, Task $task): bool { // ... }; } public function invoke() {...} }呼び出し側は、
public function update(UpdateTask $useCase, Post $post) { $this->authorize('update', [$post, $useCase->authorizeRule()]); }で、Policy 側はこうなります。
public function update(User $user, Post $post, \Closure $rule) { return $rule($user, $task); }ここまでくると、Policy 使う必要ない気もしてきますが、認可の仕組み自体は Laravel に任せて、ドメインロジックはドメインモデルに任せる、という分担もやりやすいと思うので、検討する価値はあるんじゃないかと思います。
おわりに
上記に挙げた以外にもユースケースがありましたら、コメント欄にて教えていただけると助かります
![]()
- 投稿日:2019-12-13T22:43:42+09:00
【Laravel】保守性をアップさせるかもしれない 3 つのテクニック
はじめに
初めまして、MasaKu です。
今年も残すところわずかですね。
今年もたくさんのコードを書いてきたことだと思います。
中には埃をかぶりまくった個人開発のコードもあるかと思いますが、年末のこの時期にちょっとだけリファクタリングとして手を入れていみませんか?
今回は、Laravel で作成されたアプリケーションを、ちょこっとした工夫だけで可読性をアップできるかもしれないテクニックをご紹介します。
その1:ルートグループ
Laravel では
web.php
にルーティングする処理を記載します。超ざっくりしたブログページのサンプルを作成しました。
// ホーム画面 Route::get('/home', 'HomeController@home'); // ブログページ Route::get('/blog', 'BlogController@blog'); Route::get('/create', 'BlogController@create'); Route::get('/update', 'BlogController@update'); Route::get('/delete', 'BlogController@delete');この時、ブログページにアクセスする際は、twitter の OAuth でログイン認証している状態にさせたいという場合は各ルーティングにミドルウェアを設定をするのが便利かと思います。
// ブログページ Route::get('/blog', 'BlogController@blog')->middleware('oAuth'); Route::get('/create', 'BlogController@create')->middleware('oAuth'); Route::get('/update', 'BlogController@update')->middleware('oAuth'); Route::get('/delete', 'BlogController@delete')->middleware('oAuth');もちろん、このように1件ずつ Middleware を設定することもできるのですが、これだと Middleware を差し替えようとした際に、全ての Middleware の定義を修正しなければならなくなります。
それに、こういったコードがたくさん並ぶとパッと見たときに、どの Middleware を設定しているのかがわかりづらいし、Middleware を付け忘れてしまうことにもなりかねません。
こういう時は、ルーティングをグループ化して、そのグループに Middleware を設定するとコードがスッキリしてわかりやすくなります。
// ブログページ Route::middleware(['oAuth'])->group(function(){ Route::get('/blog', 'BlogController@blog'); Route::get('/create', 'BlogController@create'); Route::get('/update', 'BlogController@update'); Route::get('/delete', 'BlogController@delete'); });このようにすれば、パッと見ただけで Middleware をかける処理が一覧として理解しやすく付け忘れるということもなくなると思います。
ちなみに、グループ化しておくことのメリットとして、グループ内のコントローラ対して名前空間を指定することもできるということもあります。
今後コントローラの名前空間を変更した場合なども一括で設定することができるので非常に便利です。
// ブログページ Route::namespace('Blog')->middleware(['oAuth'])->group(function(){ Route::get('/blog', 'BlogController@blog'); Route::get('/create', 'BlogController@create'); Route::get('/update', 'BlogController@update'); Route::get('/delete', 'BlogController@delete'); });その2:サービスコンテナからサービスクラスを利用する
Laravel ではコントローラ内に処理を記載して、処理結果を View に渡すようにしていきます。
そのため、コントローラ内が複雑になってしまうことをできるだけ避けたいのではないでしょうか。
特に、似たような処理がほかのコントローラ内にあるような状態はできるだけ避けたいです。
そんなときは、共通処理をサービスクラスに書き出してサービスコンテナに登録し、コントローラ内でそのクラスのインスタンスを利用するようにしましょう。
Laravel プロジェクトの
app
フォルダ内にOriginClasses
というフォルダを作成します。その後、
OriginService.php
というファイルを作成し、以下のように入力してください。<?php namespace App\OriginClasses; class OriginService { private $msg; private $data; public function __construct() { $this->msg = 'サービスクラスを取得'; $this->data = ['ホーム', 'ブログ']; } public function getMessage() { return $this->msg; } public function getData() { return $this->data; } }メッセージとデータを取得する共通処理です。
この処理を各コントローラ内で呼び出して利用できるようにしていきます。
Laravel では
app()
というメソッドを通してサービスコンテナからインスタンスを取得することができます。以下のようにコントローラ内に
app('App\OriginClasses\OriginService')
を記載することで、インスタンスを取得することができます。public function blog() { // OriginServiceクラスのインスタンスを取得 $originService = app('App\OriginClasses\OriginService'); echo $originService->getMessage(); return view('blog'); }このようにしておくことで、コントローラ内に共通の処理がいたるところにベタ書きされることを防ぐことができます。
今後、同じ処理を別のコントローラに埋め込みたくなったときや、これまで定義していた共通処理を修正する場合も、クラスファイルのメソッドを修正するだけですべてのコントローラ内の処理を修正することができるので非常に便利です。
その3:ファサードを使う
先程はサービスコンテナからインスタンスを取得する方法をご紹介しましたが、サービスを利用する入口だけを使ってサービスを利用する方法もがります。
ファサードではコントローラ側でインスタンスを取得するのではなく、サービスの入口だけを利用して処理することができます。
まずは、Laravel プロジェクト内の
app
フォルダにFacades
というフォルダを作成します。その後、
Facades
フォルダ内にOriginService.php
というファイルを作成して以下のように入力してください。<?php namespace App\Facades; use Illumination\Support\Facades\Facade; class OriginService extends Facade { protected static function getFacadeAccessor() { return 'originservice'; } }それでは、作成したファサードを
/config/app.php
のaliases
配列に追加していきます。'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Arr' => Illuminate\Support\Arr::class, // 一部省略 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, 'originservice' => App\Facades\MyService::class, //この行を追加 ],最後に
aliases
に登録したファサードを Laravel のサービスプロバイダに設定して利用できるようにしましょう。サービスプロバイダは以下のコマンドで作成できます。
php artisan make:provide OriginServiceProvider作成した
OriginServiceProvider.php
を以下の通り修正してください。<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class OriginServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { app()->singleton('originservice', 'App\OriginClasses\OriginSevice'); } /** * Bootstrap services. * * @return void */ public function boot() { // } }作成したサービスプロバイダを
/config/app.php
に設定します。'providers' => [ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, // 一部省略 App\Providers\RouteServiceProvider::class, App\Providers\OriginServiceProvider::class, //この行を追加 ]これでファサードを通してサービスを利用できるようになりました。
あとは以下のようにすることでコントローラ側からサービスを利用することができます。
use App\Facades\OriginService; class BlogController extends Controller { public function blog() { OriginService::getMessage(); return view('blog'); }ファサードの処理の流れを整理しますと以下のとおりになります
- ファサードを作成し
getFacadeAccessor()
メソッドに利用するサービスのファサード(入口)の名前を返すようにするconfig/app.php
にファサードを登録する(エイリアスとファサードの実態を登録)- サービスプロバイダを作成し、利用するサービス(コントローラ側で呼び出したい処理)をファサードの名前で設定する
- コントローラ側でファサードを経由してサービスの処理を実行する
おわりに
いかがでしたでしょうか。
いずれも基本的な内容にはなりますが、もし取り入れていなくてもちょっとした工夫だけで直ぐに取り入れることができる内容なので、リファクタリングとしてちょうど良い内容なのではないかと思いました。
ぜひお試しあれ!
参考資料
- 投稿日:2019-12-13T19:04:17+09:00
Laravel Seeding で Users, Posts, Comments テーブルの値を埋める方法
Laravel の Seeding 機能を利用して、
users
,posts
,comments
テーブルの値を埋める方法を書きます。各テーブルのカラム
各テーブルのカラムは以下の通りです。
users posts comments id id id name user_id user_id title post_id email_verified_at text text password created_at created_at remember_token updated_at updated_at created_at updated_at 手順
UsersTableSeeder を作成する
これは
UserFactory
を実行するために使うファイル(クラス)です。php artisan make:seeder UsersTableSeederdatabase/seeds/UsersTableSeeder.php<?php use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { public static $nbPosts = 0; public static function count($nb) { self::$nbPosts += $nb; //echo self::$nbUsers."\n"; } /** * Run the database seeds. * * @return void */ public function run() { factory(App\User::class, 50)->create(); } }PostsTableSeeder を作成する
今回はこのファイル(クラス)は使いませんが、一応、作っておきます。
php artisan make:seeder PostsTableSeederCommentsTableSeeder を作成する
これは
comments
テーブルのpost_id
にランダムな値を入れるためのファイルです。php artisan make:seeder CommentsTableSeederdatabase/seeds/CommentsTableSeeder.php<?php use Illuminate\Database\Seeder; use Faker\Generator as Faker; use UsersTableSeeder as Start; class CommentsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run(Faker $faker) { $comments = App\Comment::all(); foreach($comments as $comment) { $comment->post_id = $faker->numberBetween(1, Start::$nbPosts); $comment->save(); } } }DatabaseSeeder に上記の3つの Seeder を設置する
DatabaseSeeder
のcall
メソッドに名称を渡すことで、ユーザー自作のSeeder
ファイルを実行します。database/seeds/DatabaseSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call([ UsersTableSeeder::class, PostsTableSeeder::class, CommentsTableSeeder::class ]); } }UserFactory を作成します
User
モデルに対応するテーブル(=users
)の「行」を自動的に作成する際の参考となるファイルです。php artisan make:factory UserFactory --model=User<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\User; use Faker\Generator as Faker; use Illuminate\Support\Str; use UsersTableSeeder as Seeder; /* |-------------------------------------------------------------------------- | Model Factories |-------------------------------------------------------------------------- | | This directory should contain each of the model factory definitions for | your application. Factories provide a convenient way to generate new | model instances for testing / seeding your application's database. | */ $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; }); $factory->afterCreating(User::class, function ($user, $faker) { $nb = $faker->numberBetween(1, 3); Seeder::count($nb); $user->posts()->createMany(factory(App\Post::class, $nb)->make()->toArray()); $nb = $faker->numberBetween(4, 6); $user->comments()->createMany(factory(App\Comment::class, $nb)->make()->toArray()); });PostsFactory を作成します
Post
モデルに対応するテーブル(=posts
)の「行」を自動的に作成する際の参考となるファイルです。php artisan make:factory PostFactory --model=Post<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\Post; use Faker\Generator as Faker; $factory->define(Post::class, function (Faker $faker) { return [ 'title' => $faker->words(4, true), 'text' => $faker->paragraph(1), 'user_id' => 1 ]; });CommentFactory を作成します
Comment
モデルに対応するテーブル(=comments
)の「行」を自動的に作成する際の参考となるファイルです。php artisan make:factory CommentFactory --model=Comment<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\Comment; use Faker\Generator as Faker; $factory->define(Comment::class, function (Faker $faker) { return [ 'text' => $faker->paragraph(1), 'user_id' => 1, 'post_id' => 1 ]; });autoload ファイルを作成します
私にはこの役割がよくわかりませんが、これも
Seeding
に必要だそうです。composer dump-autoloadSeeding を実行する
データベースのデータをすべて消去した上で、
Seeding
を実行します。php artisan migrate:fresh --seed補足
user_id
posts
テーブルとcomments
テーブルのuser-id
カラムをafterCreating
メソッドのコールバック関数で埋めています。<?php $factory->afterCreating(User::class, $cb);上記のようにすると、
User
モデルを作成するたびに、その直後に、$cb
を実行できます。今回の場合は、この
$cb
の中でUser
モデルのposts
とcomments
メソッド( Relationship )を使ってuser-id
カラムを埋めています。post_id
上記のように
afterCreating
メソッドを利用するだけでは、comments
テーブルのpost_id
カラムにランダムな値を埋めることができません。今回の場合は、
PostFactory
で作成したposts
テーブルの行数をUsersTableSeeder
で記録し、それを参考にCommentsTableSeeder
でcomments
テーブルの各行のpost_id
の値を記録した行数以下の値の整数に更新しています。感想
UsersTableSeeder
でusers
テーブルの行だけを作成し、作成した行数をそのクラスのstatic property
として記録し、PostsTableSeeder
とCommentsTableSeeder
でも同様にするほうが柔軟に開発できるまもしれません…。参考
https://laravel.com/docs/6.x/seeding
https://laravel.com/docs/6.x/database-testing#writing-factories
https://laravel.com/docs/6.x/eloquent#retrieving-models:title
- 投稿日:2019-12-13T18:31:25+09:00
フロントVue.js、サーバーサイドLaravelの実装方法
概要
1つのアプリを、フロントはVue.js、サーバーサイドはLaravelで作成し、Vue.js側でサーバーサイドをAPIとして引っ張ってきて使用したい。
axios
store/index.jsimport Vue from 'vue' import Vuex from 'vuex' import axios from 'axios' Vue.use(Vuex) const URL_BASE = '全ページ共通のホスト部分URL'; var token = "生成されたトークン"; //新しいVuexを生成 export default new Vuex.Store({ state: { list: [] }, actions: { //このcontextがないと、urlが上手く渡って来ないことがある。 get_ajax(context, url) { //変数urlはviewで定義する return axios.get(URL_BASE + url, { //このheadersは、認証が必要なページの時に記載する。 headers: { "Content-Type": "application/json", "Authorization": 'Bearer ' + token }, responseType: 'json', }) //resに送られてきたデータが入っている。 .then((res) => { //下記の記載で、ここの配列listにres.dataをsetしている。 Vue.set(this, 'list', res.data); }); }, } });views/index.js//ここら辺は今回関係ないとこ <template> <div> <Template/> </div> </template> <script> //これは今回関係なし。 import Template from "@/common/Template.vue" //vuexのmapActionsが使えるようになる。 import { mapActions } from 'vuex' var url = 'このサーバーサイドで実装した、このviewページのurl'; export default { data() { return { list: [] } }, //ここら辺も今回関係なし components: { Template }, //mapActionsにstoreで定義したget_ajaxが使えるように記載する。 methods: { ...mapActions([ 'get_ajax' ]), }, //asyncとawaitで非同期通信となる。 async mounted() { await this.get_ajax(url).then(()=>{ //ここの記載でdataの中の配列listにstoreで定義されたlistを代入している。 this.list = this.$store.list; }); } } </script>
- 投稿日:2019-12-13T17:38:00+09:00
LaravelのAPIのテストにおいて、APIのリクエスト手法を間違えてテストに失敗した話
Laravelの更新系APIのテストにおいて、APIのリクエスト手法を間違えたばかりにテストが失敗して悩んだ話を書きます。
ちゃんと公式ドキュメントを読めば当たり前のことなのですが、APIのテストにはjson
メソッドを使いましょう。APIに関連付けられたフォームリクエストが実行されるとき、
例えばform
タグをsubmit
して、バリデーションエラーが発生したら入力画面にリダイレクトしてほしい。
また、AJAXのAPIとしてリクエストし、バリデーションエラーならばJSONでエラーメッセージを返してほしい。
HTTPステータスコードでいうと、リダイレクトは302、バリデーションエラーのJSONレスポンスは422となります。
Laravelでバリデーションするときに使われるフォームリクエストは、この辺りの処理を自動でやってくれます。ありがたい
公式ドキュメントにもちゃんと書かれています。If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.
https://laravel.com/docs/6.x/validation#form-request-validation
そのHTTPステータスコードの切り替えに関わるAPIの呼び出し方をテストで再現しなかったばかりにテストが失敗して悩んでしまいました。
再現するために、ちゃちゃっとLaravelで更新系のAPIを作ってみます。使うLaravelのバージョンは6.xです。$ composer create-project --prefer-dist laravel/laravel apisample $ cd apisample $ php artisan make:request UpdateUserNameRequest $ php artisan make:controller UpdateUserNameApi $ php artisan make:test UpdateUserNameApiTestroutes/api.php<?php use Illuminate\Http\Request; Route::patch('/update_user_name', 'UpdateUserNameApi');app/Http/Requests/UpdateUserNameRequest.php<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class UpdateUserNameRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'name' => 'required', ]; } }名前は必須だよというフォームリクエストです。
app/Http/Controllers/UpdateUserNameApi.php<?php namespace App\Http\Controllers; use App\Http\Requests\UpdateUserNameRequest; class UpdateUserNameApi extends Controller { public function __invoke(UpdateUserNameRequest $request) { // 更新処理 return response()->json(['message' => '更新完了']); } }とりあえずの更新APIなので、どのユーザの名前を更新するんやい!! という点は置いといてください
次にテストです。まずは私が最初に書いて失敗したテストです。
tests/Feature/UpdateUserNameApiTest.php<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class UpdateUserNameApiTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testApi() { // エラー $this->patch('/api/update_user_name', [])->assertStatus(422); // 成功 $this->patch('/api/update_user_name', ['name' => 'Takashi'])->assertStatus(200); } }
patch
でリクエストして、エラーだと422を想定してassertStatus(422)
してます。
これを実行すると、$ vendor/bin/phpunit tests/Feature/UpdateUserNameApiTest.php PHPUnit 8.5.0 by Sebastian Bergmann and contributors. F 1 / 1 (100%) Time: 383 ms, Memory: 18.00 MB There was 1 failure: 1) Tests\Feature\UpdateUserNameApiTest::testApi Expected status code 422 but received 302. Failed asserting that false is true. apisample/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:183 apisample/tests/Feature/UpdateUserNameApiTest.php:19 FAILURES! Tests: 1, Assertions: 1, Failures: 1.422じゃなくて302だよと言われ、テストが失敗してしまいました。
よくよく考えると、このテストで実行しているリクエストはAJAXリクエストではありません。
なので422ではなくて、リダイレクトの302が返ってくるわけです。それじゃあ、実際の運用と同じようにAPIにリクエストするにはどうしたらよいかというと、公式ドキュメントにしっかり載っていました。
https://laravel.com/docs/6.x/http-tests#testing-json-apis
tests/Feature/UpdateUserNameApiTest.php<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class UpdateUserNameApiTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testApi() { // エラー $this->patchJson('/api/update_user_name', [])->assertStatus(422); // 成功 $this->patchJson('/api/update_user_name', ['name' => 'Takashi'])->assertStatus(200); } }$ vendor/bin/phpunit tests/Feature/UpdateUserNameApiTest.php PHPUnit 8.5.0 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 112 ms, Memory: 18.00 MB OK (1 test, 2 assertions)公式ドキュメントをちゃんと読みましょう自分
- 投稿日:2019-12-13T15:52:06+09:00
Azure Key Vault を利用した .env 内の機密情報の管理
.env と機密情報
Laravel を利用したプロジェクトは通常
.env
を使って環境変数を管理しています。.env
には各種認証情報などを含めることもあると思いますが、それらは Git などのバージョン管理システムに平文で保存するべきではありません。
かと言って、どこにも管理されておらず稼動している環境に置いてあるだけの状態というのも心許無さがあります。そこで Azure Key Vault (和名:キー コンテナー)のシークレットと、Go で書いた簡素な vaultenv というツールで、
.env
に直接機密情報を記述することなく管理できるようにしました。以下では Azure VM を利用していることを前提に、例を紹介します。Azure Key Vault の準備
- まずは Key Vault を作成します。(ここでは Azure CLI を使った例で説明します)
az keyvault create --location japaneast --name <YourKeyVaultName> --resource-group <YourResourceGroupName>
- 開発者のグループやユーザーに
セット
(set)と一覧取得
(list)の権限を付与します。格納されているデータは取得できないようにします。az keyvault set-policy --resource-group <YourResourceGroupName> --name <YourKeyVaultName> --secret-permissions set list --object-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- シークレットを復元したいデプロイ用途などの VM に対して、
取得
の権限を付与します。az vm identity assign --name <NameOfYourVirtualMachine> --resource-group <YourResourceGroupName> { "systemAssignedIdentity": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "userAssignedIdentities": {} }az keyvault set-policy --name <YourKeyVaultName> --object-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --secret-permissions getKey Vault へ機密情報の格納
- 機密情報を Key Vault のシークレットに保存します。
$ az keyvault secret set --vault-name <YourKeyVaultName> -n example-password --value "naisho" { "attributes": { "created": "2019-12-13T03:41:15+00:00", "enabled": true, "expires": null, "notBefore": null, "recoveryLevel": "Purgeable", "updated": "2019-12-13T03:41:15+00:00" }, "contentType": null, "id": "https://<YourKeyVaultName>.vault.azure.net/secrets/example-password/97a8cfac350c4b67b1f3510b1598cdce", "kid": null, "managed": null, "tags": { "file-encoding": "utf-8" }, "value": "naisho" }
- 登録時に出力された id を
{{ kv < id > }}
の形式で任意のテキストファイルに埋め込みます。example-password 以下(/97a8c...
) も含めるとバージョンを固定することができます。含めない場合は最新の値を取得します。.env.templateUSER=user1 PASSWORD={{ kv "https://<YourKeyVaultName>.vault.azure.net/secrets/example-password" }}.env への展開
- シークレットの取得を許可した VM 上で行います。上記のファイルを vaultenv を通すことにより
{{ kv < id > }}
で記述された部分が Key Vault に保存したデータに置換されます。実際にはデプロイ時に自動実行されるスクリプト内で行っています。$ go get github.com/sensyn-robotics/vaultenv $ vaultenv < .env.template > .env $ cat .env USER=user1 PASSWORD=naishoまとめ
Key Vault のアクセスポリシーを設定するすることにより、開発者自身のアカウントでは保存されたデータを参照せずに、登録のみが行えるようにすることができます。さらに、データの取得権限を特定の VM に限定することで、(VM に対するアクセスコントロールを適切に行えていれば)機密情報が漏洩してしまうリスクを低減できます。
参考リンク
- 投稿日:2019-12-13T12:59:26+09:00
php-fpmで動くLaravelのdocker imageをgithub actions使ってECRへデプロイ
laravelのアプリをphp-fpmでsocket使ってgithub actionsでECSにデプロイしたのでメモ。
php&laravel知識は私にはほぼないです。DB使ってないアプリなのでDB設定なし。雑において使えればよかったのでいろいろ適当です。
だったらphp-fpmにする必要もなかったんですができるのかなと思ってやったら出来たので記録。前提
- Docker
- docker-compose
- デプロイできるAWS ECSクラスタが存在する
- AWS ECR
- github actions
- Terraform
dockerの作成
ローカルで確認できるように&pushするときのタグを保存できるのでdocker-compose.ymlでbuildするようにする。
環境変数で基本必要なものを
.env.docker
に作っておいてsecretな情報は引き数で入れる方針で。docker-compose.ymlversion: '3.7' volumes: sock: name: sock services: nginx: image: 012345678901.dkr.ecr.us-east-1.amazonaws.com/app-nginx container_name: app-nginx build: context: . dockerfile: ./docker/nginx/Dockerfile volumes: - sock:/sock depends_on: - php ports: - "8080:80" php: image: 012345678901.dkr.ecr.us-east-1.amazonaws.com/app-php container_name: app-php build: context: . args: SECRET_TOKEN: "$SECRET_TOKEN" volumes: - sock:/sockphp-fpm
FROM php:7.4-fpm-alpine ENV COMPOSER_ALLOW_SUPERUSER 1 ARG SECRET_TOKEN COPY --from=composer /usr/bin/composer /usr/bin/composer RUN apk add --no-cache zip unzip WORKDIR /code COPY --chown=www-data:www-data . . RUN composer install COPY .env.docker .env COPY docker/php-fpm.d /usr/local/etc/php-fpm.d RUN echo SECRET_TOKEN="$SECRET_TOKEN" >> .env RUN php artisan key:generate RUN chown www-data /usr/local/var/log/ WORKDIR /code/publicphp-fpmのコンテナだとzz-docker.confにlistenが入っているので上書きしないとsocketが書き込まれないので上書き
docker/php-fpm.d/zz-docker.conf[global] daemonize = no [www] listen = /sock/php-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0666他php-fpmで変えたい項目があるなら、
www.conf
も上書きすればよいnginx
public/
にあるファイルは必要なので配置FROM nginx:stable-alpine ADD docker/nginx/conf.d/ /etc/nginx/conf.d ADD public/ /code/public EXPOSE 80 STOPSIGNAL SIGTERM CMD ["nginx", "-g", "daemon off;"]
/sock/php-fpm.sock
でボリューム共有するのでそこをfastcgi_passに指定docker/nginx/conf.d/default.confserver { listen 80 default_server; server_name _; charset utf-8; client_max_body_size 75M; gzip on; gzip_types text/plain application/xml text/css application/javascript; gzip_min_length 1000; index index.php index.html; root /code/public; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/sock/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } }下記実行して
127.0.0.1:8080
がひらけたら成功docker-compose build docker-compose upTerraformでECR と ECS service用意
networkModeをawsvpcにしてdiscovery serviceで参照する設定いれてます。
security group、vpcとdiscovery serviceの設定は別のところでやっているとして省略。github actions用のユーザterraformで作って
ACCESS_KEY
とSECRET_ACCESS_KEY
はtfstateにいれたくないからWebコンソールからとってます。resource "aws_cloudwatch_log_group" "app" { name = "/app" retention_in_days = 60 tags = { Environment = "app" } } resource "aws_ecs_task_definition" "app" { family = "app" volume { name = "sock" docker_volume_configuration { scope = "task" driver = "local" } } container_definitions = file("task-definition/app.json") network_mode = "awsvpc" } resource "aws_ecs_service" "app" { name = "app" cluster = "cluster" task_definition = aws_ecs_task_definition.app.arn desired_count = 1 deployment_minimum_healthy_percent = 0 deployment_maximum_percent = 100 network_configuration { subnets = aws_subnet.vpc.*.id security_groups = [ aws_security_group.output.id aws_security_group.app.id ] } service_registries { registry_arn = aws_service_discovery_service.app.arn } lifecycle { ignore_changes = [desired_count] } } resource "aws_service_discovery_service" "app" { name = "app" dns_config { namespace_id = aws_service_discovery_public_dns_namespace.main.id dns_records { ttl = 10 type = "A" } routing_policy = "MULTIVALUE" } health_check_custom_config { failure_threshold = 1 } } # ECR resource "aws_ecr_repository" "app-nginx" { name = "app-nginx" } resource "aws_ecr_repository" "app-php" { name = "app-php" } resource "aws_ecr_lifecycle_policy" "app-nginx" { repository = aws_ecr_repository.app-nginx.name policy = file("ecr/lifecycle-policy.json") } resource "aws_ecr_lifecycle_policy" "app-php" { repository = aws_ecr_repository.app-php.name policy = file("ecr/lifecycle-policy.json") } resource "aws_iam_user" "github-actions-app" { name = "github-actions-app" path = "/" } resource "aws_iam_user_policy" "github-actions-app" { name = "github-actions-app" user = "${aws_iam_user.github-actions-app.name}" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecs:UpdateService", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:CompleteLayerUpload", "ecr:DescribeRepositories", "ecr:UploadLayerPart", "ecr:InitiateLayerUpload", "ecr:BatchCheckLayerAvailability", "ecr:PutImage" ], "Resource": [ "arn:aws:ecs:us-east-1:012345678901:service/cluster/app", "arn:aws:ecr:us-east-1:012345678901:repository/app-*" ] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" } ] } EOF }volumeをmountしてsocketファイルをコンテナ間で共有
task-definition/app.tf[ { "mountPoints": [ { "sourceVolume": "sock", "containerPath": "/sock" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/app", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "nginx" } }, "cpu": 32, "memoryReservation": 32, "image": "012345678901.dkr.ecr.us-east-1.amazonaws.com/app-nginx:latest", "portMappings": [ { "hostPort": 80, "containerPort": 80, "protocol": "tcp" } ], "name": "nginx" }, { "mountPoints": [ { "sourceVolume": "sock", "containerPath": "/sock" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/app", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "php" } }, "cpu": 64, "memoryReservation": 64, "image": "012345678901.dkr.ecr.us-east-1.amazonaws.com/app-php:latest", "name": "php" } ]削除は単純なポリシーで
ecr/lifecycle-policy.json{ "rules": [ { "rulePriority": 1, "description": "Expire images older than 7 count", "selection": { "tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7 }, "action": { "type": "expire" } } ] }imageがない状態で
terraform apply
するとECS Serviceが起動でエラーでますが気にしない。
するなら、ECRのところだけterraform apply
してdocker-compose push
してからやるとECS Serviceも起動する。github actions
githubのsecretsに
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
SECRET_TOKEN
を作ってます。自動でデプロイされてくないので tagに
v*
とつけたらデプロイされるようにしてます。github/workflows/deploy.ymlname: Deploy on: push: tags: - v* jobs: deploy_production: name: deploy production runs-on: ubuntu-latest steps: - name: AWS ECR login run: $(aws ecr get-login --no-include-email) env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-east-1 - uses: actions/checkout@master - name: Get Composer Cache Directory id: composer-cache run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - uses: actions/cache@v1 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-composer- - name: build base run: docker-compose build env: SECRET_TOKEN: ${{ secrets.SECRET_TOKEN }} - name: push image run: docker-compose push - name: force deployment ecr run: aws ecs update-service --cluster cluster --service app --force-new-deployment env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-east-1これでgithubでreleaseからタグ打てばデプロイされて便利です。
- 投稿日:2019-12-13T12:18:03+09:00
[Laravel]RegisterController.phpさん、抽象化ダイエット大作戦!
Diverse Advent Calendar 2019 14日目の記事です。
こんにちは! @gatapon です!
エンジニア歴1年未満ですが無事生きてます。元気にしてます!
業務では使っているわけではないのですが、Laravelについて書いてみようと思います。概要
少し設計に興味を持った私が
RegisterController.php
をLaravelの機能を利用したり、レイヤー化させてみました。やったこと
RegisterController.php
の処理の分離- Service層の導入
- Repository層の導入
はじめに
「どうしてこんな記事を書くのか」という経緯をポエムっぽく書きます。
4月からエンジニアとして実務を経験してきましたが、入社前の私は個人で小さなポートフォリオを作った程度でした。実際に仕事で触れるプロダクトはポートフォリオと比べ物にならないくらい大きく、ちょっとした機能の修正であっても「どこに記述するべきか」、「どう記述するべきか」という事に悩む事が多かったです。
エンジニアになって3,4ヶ月程経ち、少しずつですが経験を重ね「ここはこう書いたほうが良いよな」みたいな勘が、なんとなくではありますが自分の中で形成されていきました。しかしそれはあくまで勘でしかなく、「なんとなく正しい」と思ってるだけであり、客観的に正しいかどうか自分には判断できませんでした。自分の中で言語化できておらず他人に伝えることも、なかなか大変でした。
そういった事が原因か判りませんが、抽象的なものを読みたいという欲求に駆られていろいろ読み物を漁りました。コードを『どこに記述するべきか』『どう記述するべきか』について読み漁っていくうちに「設計」を勉強することがが近道ではないかなと感じ、少しずつ興味を持つようになりました。
以前、Laravelを使ってポートフォリオを作っていたのもあり、たまたま持っていた本もLaravelのクリーンアーキテクチャについて詳細に記述されていたのでLaravelを使って勉強してみることにしました。
その中でもLaravelの
RegisterController.php
の書かれ方が気になったので、いろいろいじってみました。
それが割と面白かったので、簡単ではありますが内容をお伝えしたいと思います。以下、出てくるソースは実装をフェイズごとに区切っていますが、勉強時にディレクトリ構造やファイル名、テーブル名など命名にも試行錯誤重ね、度々変更していました。記事中のファイル名などに不整合が起こっているかもしれません。統一するよう見直しましたが、漏れがあったらご指摘下さい。
RegisterController.phpを手直し
まずはそのままの
RegisterController.php
を見てみます。app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/RegisterController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use App\User; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; class RegisterController extends Controller { /* |-------------------------------------------------------------------------- | Register Controller |-------------------------------------------------------------------------- | This controller handles the registration of new users as well as their | validation and creation. By default this controller uses a trait to | provide this functionality without requiring any additional code. | */ use RegistersUsers; /** * Where to redirect users after registration. * * @var string */ protected $redirectTo = RouteServiceProvider::HOME; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return \App\User */ protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); } }ユーザーの新規登録の処理がコントローラー内にまとめられています。これは各プロジェクトごとに拡張しやすいように、良い感じにしてくれているのでしょう。
RegisterController.php
にはvalidation, ユーザーの作成する処理はありますが、実体となるregister
メソッドはここにはありません。route/web.phpRoute::post('auth/register', 'Auth\RegisterController@register');しかし、それでもRouteで呼ぶことができています。
それはregister
メソッドを持つRegistersUsers
トレイトを継承しているからです。framework/src/illuminate/Foundation/Auth/RegistersUsers.php
これらソースをもとに下準備を行い記述します。
一部修正というか、別のディレクトリにてファイルを作り直しています。app/Http/Controllers/Api/Auth/RegisterController.php<?php namespace App\Http\Controllers\Api\Auth; use App\Http\Controllers\Controller; use App\Models\User; use App\Models\Profile; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use Illuminate\Auth\Events\Registered; use Illuminate\Foundation\Auth\RegistersUsers; class RegisterController extends Controller { use RegistersUsers; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } /** * register * * @param Request * @return JsonResponse */ public function register(Request $request) { $validate = $this->validator($request->all()); if ($validate->fails()) { return new JsonResponse($validate->errors()); } event(new Registered($user = $this->create($request->all()))); if (empty($user)) { return new JsonResponse('Error'); } return new JsonResponse($user); } /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); } /** * Transaction for create a new user and profile, after a valid registration. * * @param array $data * @return \App\Models\User */ protected function create(array $data) { DB::beginTransaction(); try { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); Profile::create([ 'user_id' => $user->id, 'country' => '', 'question' => '', 'answer' => '', ]); DB::commit(); return $user; } catch(\PDOException $e) { DB::rollBack(); Log::Debug('Transaction Error: '. print_r($e, true)); } } }
app/Http/Controllers/Api/RegisterController.php
を作成し、以下を実施しました。
RegistersUsers
のregister
メソッドをもってくる。- 手軽に検証できるように
register
メソッドはJsonを返すように修正create
メソッドにユーザー新規作成と同時にProfile
も作成するトランザクションを追加いい感じに肥えて来ましたね!
その他、Profile
モデルの実装やテーブル等の準備はありますが端折ります。作成したRegisterController.phpのダイエット
準備ができました。
早速以下2つを行いRegisterController.php
をスッキリさせます。
- LaravelのFormRequest機能を利用
- Service層を作成して処理層を分離
これらを行うことで、コントローラーは「リクエストを受け取り、要求されたデータを返す」だけのシンプルな形になります。
app/Http/Controllers/Api/Auth/RegisterController.php<?php namespace App\Http\Controllers\Api\Auth; use App\Http\Controllers\Controller; use App\Http\Requests\UserRegistPost; use App\Services\UserRegisterService; use Illuminate\Http\JsonResponse; use Illuminate\Auth\Events\Registered; use Illuminate\Foundation\Auth\RegistersUsers; class RegisterController extends Controller { use RegistersUsers; /** * Create a new controller instance. * * @return void */ public function __construct(UserRegisterService $service) { $this->middleware('guest'); $this->service = $service; } /** * register * @param UserRegistPost * @return JsonResponse */ public function register(UserRegistPost $request) { # userRegisterTransactionについては後述 event(new Registered($user = $this->service->userRegisterTransaction($request->all()))); if (!$user) { return new JsonResponse('Error'); } return new JsonResponse($user); } }FormRequest
LaravelにあるFormRequestはとても便利です。バリデーションのロジックをコントローラーから分離できます。
また、コントローラーのメソッドに依存注入することでコントローラーがリクエストを受け取った時点でバリデーションが行われます。
register
メソッド内にバリデーションエラー処理も記述しなくて済み、コントローラーがスッキリしました。app/Http/Requests/UserResistPost.php<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use App\Traits\ApiFormRequestTrait; class UserRegistPost extends FormRequest { use ApiFormRequestTrait; /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'name' => ['required', 'string', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]; } }今回のAPIのFormRequestのValidationエラーの実装は、以下リンクを参考にさせてもらいました。これ以外の方法もあるようです。脇道にそれるので、ここでは記述しません。
【Laravel5】FormRequestのバリデーション結果をJSON APIで返す
Service層の導入
MVCの下に作成されたフレームワークにはFatモデル、Fatコントローラー問題があります。
Service層の導入はその問題を緩和することが出来ます。
モデルやコントローラーからビジネスロジックを分離させることができるので、本来の責務だけ任せることが出来ます。app/Services/UserRegisterService.php<?php namespace App\Services; use App\Models\User; use App\Models\Profile; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Hash; class UserRegisterService { /** * Transaction for create a new user and profile, after a valid registration. * * @param array $data * @return User */ public function userRegisterTransaction(array $data) { DB::beginTransaction(); try { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); Profile::create([ 'user_id' => $user->id, 'country' => '', 'question' => '', 'answer' => '', ]); DB::commit(); return $user; } catch(\PDOException $e) { DB::rollBack(); Log::Debug('Transaction Error: '. print_r($e, true)); } } }メソッド名を
userRegisterTransaction
に変更していますが、ただ単にapp/Services/UserRegisterService.php
にお引越ししてきただけです。
これでRegisterController.php
のダイエットは完了です。UserRegisterService.phpさんにもダイエットしてもらおう
Repository層の導入
Service層はビジネスロジックを分離できるメリットがあるとお話しましたが、移植してきたものの内容を見てみるとデータの操作ぐらいしか行っていません。と言うか、ビジネスロジックに集中したいService層でデータアクセスを直接行っています。この書き方はもう少し変えていきたいと思います。
ビジネスロジックからデータ操作を切り離すことで、テスト容易性や、保守性、拡張性が保証されます。そこでドメイン駆動開発にも紹介されているRepository層を導入してみようと思います。
注意
実際問題、めったにデータソースが変わることがないサーバーにRepository層を装する必要は、あまりないかもしれません。レイヤー化、抽象化を目的とした設計なら他の方法があるかもしれませんね。
参考:“Repositoryによる抽象化の理想と現実/Ideal and reality of abstraction by Repository - Speaker Deck”
抽象クラス(RepositoryInterface)と具象クラス(Repository)の実装
私は今のところ、Repository層の最大の目的はデータの永続性だと理解しています。データソースが変更された時(MySQL→NoSQLなど)、移行の対応を極力抑えることが出来ます。各RepositoryInterfaceを継承した具象クラスを実装するだけなので、ビジネスロジックに手を加える必要がなくなります。
User
,Profile
それぞれの今回使うメソッドのみの実装を行っていきます。RepositoryInterfaceの実装
app/Repositories/Interfaces/UserRepositoryInterface<?php namespace App\Repositories\Interfaces; interface UserRepositoryInterface { public function register(array $data) }app/Repository/Interfaces/ProfileRepositoryInterface.php<?php namespace App\Repositories\Interfaces; interface ProfileRepositoryInterface { public function create(int $user_id); }インターフェイスはあくまで骨組みだけになります。各データソースごとに、このInterfaceを継承した具象クラスを作成することで変更の対応が容易になります。(後に述べるInterfaceとのバインディングを変更する必要があります)
今回のデータ操作はLaravelのORM、Eloquentを利用していきます。
クエリビルダ、SQLを使うことも出来ます。Repositoryの実装
app/Repositories/UserRepository.php<?php namespace App\Repositories; use App\Models\User; use Illuminate\Support\Facades\Hash; use App\Repositories\Interfaces\UserRepositoryInterface; class UserRepository implements UserRepositoryInterface { protected $user; public function __construct(User $user) { $this->user = $user; } /** * Create User * * @param $data * @return \App\Models\User */ public function register(array $data) { return $this->user->create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); } }app/Repository/Interfaces/ProfileRepository.php<?php namespace App\Repositories; use App\Models\Profile; use App\Repositories\Interfaces\ProfileRepositoryInterface; class ProfileRepository implements ProfileRepositoryInterface { protected $profile; public function __construct(Profile $profile) { $this->profile = $profile; } /** * Create Profile * * @param int $user_id * @return void */ public function create(int $user_id) { $this->profile->create([ 'user_id' => $user_id, 'country' => '', 'question' => '', 'answer' => '', ]); } }Repository層を実装した
UserRegisterService.php
は以下になります。
userRegisterTransaction
メソッドに依存注入されているのは具象クラスでなく抽象クラスであるInterface
であることに注意して下さい。app/Services/UserRegisterService.php<?php namespace App\Services; use App\Models\User; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use App\Repositories\Interfaces\UserRepositoryInterface; use App\Repositories\Interfaces\ProfileRepositoryInterface; class UserRegisterService { protected $user; protected $profile; public function __construct(UserRepositoryInterface $user, ProfileRepositoryInterface $profile) { $this->user = $user; $this->profile = $profile; } /** * Transaction for create a new user and profile, after a valid registration. * * @param array $data * @return \App\Models\User */ public function userRegisterTransaction(array $data) { DB::beginTransaction(); try { $newUser = $this->user->register($data); $this->profile->create($newUser->id); DB::commit(); return $newUser; } catch(\PDOException $e) { DB::rollBack(); Log::Debug('Transaction Error: '. print_r($e, true)); } } }RepositoryとInterfaceをバインディング
新しくRepository用のServiceProviderを作成するので、
config/app.php
に追加します。config/app.php〜〜 'providers' => [ /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\RepositoryServiceProvider::class, # 追加 ],
RepositoryServiceProvider.php
を作成し、抽象クラスと具象クラスをバインディングすれば実装完了です。データソースが違う具象クラスを利用する場合、こちらでInterfaceとバインディングされた具象クラスを変更します。app/Providers/RepositoryServiceProvider.php<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class RepositoryServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->bind( \App\Repositories\Interfaces\ProfileRepositoryInterface::class, \App\Repositories\ProfileRepository::class ); $this->app->bind( \App\Repositories\Interfaces\UserRepositoryInterface::class, \App\Repositories\UserRepository::class ); } /** * Bootstrap services. * * @return void */ public function boot() { // } }実装してみて
一年ぐらい前、エンジニアになるためにLaravelを勉強し始めたのですが、その時とコードの見た目が全く違うなあという印象です。コンストラクタにてDIを利用しクラスをセットしているので、依存クラスがひと目で分かりますね。実装時はディレクトリ構成とクラス名を考えるのに苦戦しました。実際にクラスを増やしていって見ていかないと、よりベターなディレクトリ構成やクラス名を付けれそうにないですね...
しかし機能やレイヤーをきっちり分けると、どこに何を書くべきか
が判断しやすくなりました。面白かったので引き続き勉強していきたいと思います。
参考にした記事
- Laravelで実践クリーンアーキテクチャ
- Laravelでクリーンアーキテクチャ参考にした書籍
- PHPフレームワーク Laravel Webアプリケーション
- 投稿日:2019-12-13T12:04:16+09:00
LaraDockを使って10分でLaravel+Nginx+MySql+Redisのローカル環境を構築してみる。
はじめに
こちらは TECOTEC Advent Calendar 2019 の13日目の記事です。
折り返しとなります。Qiitaの記事や他所のブログでも散々書かれていることかと思いますが、復習がてらLaraDockを使ってLaravelのローカル環境を構築してみようと思います。
目指せ10分でローカル環境構築!※当方macで作業しているため、macでの手順になります。Windowsの方は適時Windows版に読み換えてお試しください。
やること
- Dockerのインストール
- LaraDockのインストール
- Laravelプロジェクトの作成
- LaraDockの設定変更
- Laravelの設定変更
以上となります。
ではさっそくやってみましょう。
実演
Dockerのインストール
以下のサイトよりDocker for Macをダウンロードします。
https://docs.docker.com/docker-for-mac/install/
ダウンロードにはdocker hubへの登録が必要なので案内に従って登録してください。
Docker.dmg
をダウンロードできたら起動してインストールします。
インストール後はdocker.app
を起動しておいてください。LaraDockのインストール
GitHubからLaraDockのファイルを取得します。
ターミナルを起動して作業ディレクトリへ移動してください。ターミナルで作業$ cd ~/workcloneします。
ターミナルで作業$ git clone https://github.com/LaraDock/laradock.git取得できました。
laradock
ディレクトリが作成されています。
ワークスペースのコンテナを起動するために.env
ファイルを作ります。ターミナルで作業$ cd laradock $ cp env-example .envLaravelプロジェクトの作成
※開発が進んでいる場合はこの作業はスキップしてください。
※チーム等で管理しているGitからソースを取得してください。ワークスペースのコンテナを起動してログインします。
ターミナルで作業$ pwd ~/work/laradock $ docker-compose up -d workspace ~~~省略 Creating laradock_workspace_1 ... done $ docker-compose exec --user=laradock workspace bash laradock@0c7610d35e08:/var/www$ログイン完了です。
Laravelプロジェクトを作成します。workspace内で作業$ composer create-project laravel/laravel server
server
プロジェクトが作成されました。LaraDockの設定変更
ワークスペースからログアウトします。
workspace内で作業$ exit
vim
で.env
ファイルを開きます。(エディタならなんでもいいです)ターミナルで作業$ pwd ~/work/laradock $ vim .envプロジェクトのパスを変更します。
元の記述をコメントアウトして追記するか、直接編集してください。.env# APP_CODE_PATH_HOST=../ APP_CODE_PATH_HOST=../server/MySqlのデータを永続化させないために作業ディレクトリへ変更します。
※プロジェクトごとにデータを管理する想定です。共通で問題ない場合はこの作業はスキップしてください。。.env# DATA_PATH_HOST=~/.laradock/data DATA_PATH_HOST=.laradock/data/ポートを変更します。
※こちらは任意です。必要なければこの作業はスキップしてください。.env# NGINX_HOST_HTTP_PORT=80 NGINX_HOST_HTTP_PORT=8880MySqlのバージョンと接続情報を変更します。
最新の8.xは色々あれなので5.7を指定します。
※最新で問題ない場合はこの作業はスキップしてください。.env# MYSQL_VERSION=latest # MYSQL_DATABASE=default # MYSQL_USER=default MYSQL_VERSION=5.7 MYSQL_DATABASE=laradock MYSQL_USER=laradockMySqlとRedisのホスト名を設定しておきます。
ファイル内の一番下に追記してください。.envDB_HOST=mysql REDIS_HOST=redis設定を反映させるためコンテナを再起動します。
一緒にNginx・MySql・Redisを起動します。ターミナルで作業$ pwd ~/work/laradock $ docker-compose stop $ docker-compose up -d workspace nginx mysql redisLaravelの設定変更
ワークスペースのコンテナにログインして作業します。
ターミナルで作業$ pwd ~/work/laradock $ docker-compose exec --user=laradock workspace bash laradock@0c7610d35e08:/var/www$
.env
を編集します。workspace内で作業$ vim .envDBとRedis接続設定を書き換えます。
.envDB_HOST=mysql DB_DATABASE=laradock DB_USERNAME=laradock DB_PASSWORD=secret ~~ REDIS_HOST=redisパーミッションを変更します。
workspace内で作業$ chmod -R a+w storage $ chmod -R a+w bootstrap/cache依存ライブラリをインストールします。
workspace内で作業$ composer install接続確認!
http://localhost:8880/ にアクセスします。
Laravelのトップページが表示されれば完了です。
非常に簡単にLaravelの環境が構築できました。
ホストやDBの情報などは適時自分の使いやすいように変更してください。
NginxやMySqlの細かい設定はまた別の機会にかければ書きたいと思いいます。
というかそのままでローカルで使う分には全く問題ないのであまりいじったことがないです。おまけ
Laravelには便利な機能が沢山あります。
認証機能の実装
以下のコマンドを実行するだけで登録・ログイン・パスワードリセットなどが使用できるようになります。
workspace内で作業(ver5.xの場合)$ php artisan make:authworkspace内で作業(ver6.xの場合)$ composer require laravel/ui $ php artisan ui vue --authLaravelのバージョンで若干コマンドが異なるので注意が必要です。
表示を整えるためにnpm installとnpmを実行してcssとjsをコンパイルします。
workspace内で作業$ npm install && npm run devマイグレーションを実行します。
workspace内で作業$ php artisan migrate管理画面の実装
ついでに管理画面も作ってみます。
workspace内で作業$ composer require encore/laravel-admin以下のコマンドを実行します。
workspace内で作業$ php artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider" $ php artisan admin:installhttp://localhost:8880/admin/ にアクセスします。
初期のIDパスワードは、ID:admin
パスワード:admin
となっています。そのままで使えることは少ないですが、上記で認証機能と管理画面が作れてしまいます。
起動シェルを作ってみる
PC起動後など起動コマンドが長かったりするのでシェルを作っておくと捗ります。
起動
up.sh#!/bin/bash cd ./laradock; docker-compose up -d workspace nginx mysql redisワークスペースへログイン
exec_workspace.sh#!/bin/bash cd ./laradock; docker-compose exec --user=laradock workspace bash停止
down.sh#!/bin/bash cd ./laradock; docker-compose downまとめ
LaraDockはローカル環境構築が非常に簡単です。
PHPでのメインフレームワークはしばらくLaravelが多くなりそうなので、LaraDockは扱えるようにしておくと色々便利そうです。
簡単なwebページ作るならあっという間です。今後も色々試して使いこなしていきたいと思います。
よいエンジニアライフを!
- 投稿日:2019-12-13T10:37:34+09:00
LaravelとSpringBootでDIコンテナを利用してみる
はじめに
これはユアマイスターAdventCalendar2019の13日目の記事です。
(社会人になってから学んだことをアウトプットする記事になります。)今回の経緯
社会人になってからDI(依存性の注入)という概念を知りました。
WEBサービスの開発を行う際に、フレームワークを利用する場面は多々ありましたが、主に利用していたCake PHP
ではDIという概念は出ていなかったと記憶しています。
(記憶違いだったらすみません。)DIは、インスタンスをnewで作成して利用するのではなく、DIコンテナやサービスコンテナ呼ばれるもの(SpringBootではDIコンテナ、Laravelではサービスコンテナと呼ばれます)を利用して、あらかじめ登録されたインスタンスを利用します。
今回は、業務で利用しているSpringBootでのDIコンテナの利用、最近独学で学んでいるLaravelでのサービスコンテナを利用してみるというテーマで記事を書いてみたいと思います。
SpringBootの場合
コンストラクターインジェクションを利用する
ユアマイスターアドベントカレンダー2019 の7日目の記事で書いたコードを用いて書いていきます。
SpringBootで動的にDBを切り替えてみる
https://github.com/Masaki-Ogawa/datasourceDemo
- PersonRepository.java
PersonRepository.javapackage com.example.dataSourceDemo.domain.repositories; import com.example.dataSourceDemo.domain.models.Person; import org.springframework.data.jpa.repository.JpaRepository; @Repository public interface PersonRepository extends JpaRepository<Person, Integer> { }JpaRepositoryを継承したRepositoryクラス
- PersonServiceImpl.java
PersonServiceImpl.javapackage com.example.dataSourceDemo.domain.services; import com.example.dataSourceDemo.annotations.DataSource; import com.example.dataSourceDemo.annotations.DataSource.DataSourceType; import com.example.dataSourceDemo.domain.models.Person; import com.example.dataSourceDemo.domain.repositories.PersonRepository; import java.util.List; import org.springframework.stereotype.Service; @Service public class PersonServiceImpl implements PersonService { private final PersonRepository personRepository; public PersonServiceImpl( PersonRepository personRepository) { this.personRepository = personRepository; } /** * stgのDBからPersonテーブルのレコードを取得するメソッド * @return Personテーブルのレコード */ @Override public List<Person> findAllPersonInStg() { return personRepository.findAll(); } /** * stgのDBからPersonテーブルのレコードを取得するメソッド * @return Personテーブルのレコード */ @DataSource(value = DataSourceType.PROD) @Override public List<Person> findAllPersonInProd() { return personRepository.findAll(); } }ここでDIを行っています。
具体的には、
private final PersonRepository personRepository; public PersonServiceImpl( PersonRepository personRepository) { this.personRepository = personRepository; }の部分でコンストラクターインジェクションによるDIを行っています。
@Service
や@Repository
というアノテーションを利用することにより、DIコンテナに登録されます。
利用するには上記のように、コンストラクターインジェクション等を利用して、インスタンスを作成します。参考
Spring Framework 要点まとめ ~ DIについてLaravelでのDI
サービスプロバイダーを利用する
作成したサービスをサービスコンテナ登録するためのサービスプロバイダーを作成します。
今回はDemoServiceというサービスクラスを作成しました。AppServiceProvider.phpclass AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->bind('App\Services\DemoService'); } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }上記のようにサービスを登録します。
DemoService.phpclass DemoService { public function show() { echo "何かを表示します"; } }サービスクラスは今回は適当ですが、何か文字を出力するメソッドのみ実装します。
DemoController.phpclass MessageController extends Controller { protected $demoService; public function __construct(DemoService $demoService) { $this->demoService = $demoService; } public function index(Request $request) { return $this->demoService->show(); } }このようにこちらもコンストラクターインジェクションを利用して、こちらもDIを行います。
終わりに
個人的には、
@Service
や@Controller
等のアノテーションで、DIコンテナに登録できるSpringBootの方が利用しやすいと感じています。
Java、SpringBootを利用してやはり、アノテーションの強力さに気づかされる場面が多々あります。また、7日目のアドベントカレンダーで書きましたが、それぞれのフレームワークに良さや悪さがあり、多数のフレームワークに触るという経験は、今後何かものを作るときに、「どんなものを採用すれば、そのプロダクトにとって一番良いのか?」という判断材料になると思います。
今後も、業務、業務外を含めて触れていきたいと思います。
- 投稿日:2019-12-13T09:38:04+09:00
カルネージハートとは関係ないLaravelのnow()の小ネタ
カルネージハート Advent Calendar 2019 11日目の記事です。
今回はカルネージハートとは全く関係ないLaravelのnow()の小ネタの話です。
はじめに
公式ドキュメントのLaravel 6.x ヘルパ見てますか?
現在時刻が欲しいだけなら
use Carbon\Carbon;
は不要そのまんまです。
now();
よくuse Carbon\Carbon;宣言したり、Laravelでdate関数使ってごにょごにょしているの見て保守するの辛いんだなこれがで現在時刻取れます。
最後に
事実上の最新作EXAが2010年に発売以降続編の情報が皆無ですが、一部の熱狂的ファンは大会を開催してゲームを続けています。ゲームを盛り上げることで続編も出るかもしれません。カルネジスト、ネジらーの皆様のご協力をお願いします!
カルネージハートファンのプログラミング知識を共有しましょう!
- 投稿日:2019-12-13T09:13:16+09:00
【Laravel】初心者から実務をこなしていくまでの6ヶ月にやったこと
CODEBASE2期生、新卒1年目の @avocadoneko です。
仕事では主に Laravel / Vue.js を使って、開発をしています。この記事について
どんな人に読んでほしいか
- プログラミングを学び始めたばかりの人
- これから PHP, Laravel を学びたいと思っている人
- Laravel で開発している会社に入社する予定の人
書いてあること
- 初心者が PHP と Laravel をどうやって勉強してきたか
- 仕事で学んだ Laravel のこと
書いてないこと
- PHP、Laravel 以外のこと(Git とか Web のこととか他の言語とか)
注意
- いくつか紹介している記事や動画では、Laravel のバージョンが古いことがあります。
やったこと
まずはPHPを触ってみる
■ Progate
ゲーム感覚でプログラミングを学べる教材。
有料版は月額980円。
超初心者が PHP はどんな言語なのか知るのの導入に役立つと思う。実際、プログラミングが何もわからない状態でもサクサクと進めることができた。
但し、Progate だけやっていても仕方がないので、基礎を学んだ後はやらなくてもいいと思う。■ ドットインストール
月額1,080円(無料でも多くの動画を視聴できる)で手軽に学べる動画教材。
ローカル環境構築の方法も動画になっているので、動画を見るだけではなく、自分のPCで再現して学ぶのがおすすめ。
手を動かして学ぶのが好きな人には向いていると思う。
Laravel の動画に入る前に、PHPで小さいアプリケーションを作る動画をやるのが良さそう。以下、良いなと思った動画↓
書籍
■ 詳細! PHP 7+MySQL 入門ノート
この本は最初から読んでいって、知らなかったことだけ自分のPCで実行してみると良い。
また、 一番読んでほしいところは Chapter7 の「オブジェクト指向プログラミング」の章。
クラスの定義方法、継承、コンストラクタなど知っておくべき基本的なことについてについて、実際のコードと共に説明されている。■ PHPフレームワーク Laravel入門
Laravel 入門書として一番おすすめの本がこちら。
レビューにもある通り、かなり回りくどく書かれているからこそ、入門書として人気が高い。
この本の使い方は、とりあえず写経。
書いてあることは全部必要な知識だから、隅から隅まで読むべき。
写経は GitHub でリポジトリを作って動く単位でコミットしていき、コミットメッセージに気づいたことを書くといいと思う。
写経のやり方については下記の記事にわかりやすく書いてある。
技術書の写経を始めたのでやり方を書いておく完全に理解しようとして行き詰まるより、とにかく読み切ることを目標にして、70~80%くらいの理解で最後のページまで写経するといいかもしれない。
また、丸々一冊読み切った本は、後に振り返るのにも便利。記事
■ 【PHP超入門】クラス~例外処理~PDOの基礎
$this
とか スコープ定義演算子とか、とにかく PHP の基礎が全くわかってなかった頃にお世話になった記事。
初心者がつまずきやすいところがピンポイントで書かれていて、何度も助けられた。チュートリアル
■ Laravel 5.5 入門として「基本のタスクリスト」を作成する
検索したら大量に出てくるチュートリアルの中でも、これは特に分かりやすかったなという印象。コントローラを使わずに
web.php
に処理を書いてタスクリストを実装しているので、使用するファイルが少なく、混乱せずにできた。
(実際開発するときはありえないけど、初心者には向いているチュートリアルだと思う)
さらなるステップとして、ここに機能を追加していくのもいい。私の場合は編集機能を追加した。■ Laravelで飲食店検索LINE Botを作ろう!
このチュートリアルは私はやったことがないが、もっと前からあったらやってみたかった。
有料だが、Docker 開発環境の準備から丁寧に説明しているチュートリアルは少ない上、LINEbot を作るまでできたらそれなりに達成感がありそう。Laravelでオリジナルのアプリを作る
DB 設計から機能、UI まで、一つのアプリを作ってみるのがおすすめ。
先に完成物の機能を具体的に決めてから実装したほうが良い。
自分が作りたいものを作るのが一番良い。
私の場合は下記のような機能を実装した。
- 新規登録 / ログイン機能
- イベントの作成 / 編集 / 削除
- イベントごとに写真を追加 / 削除
テーブル数は3-4つくらいが丁度よかった。
余裕があったら、API叩いて、画面遷移なしでイベントや写真を作成・削除できるようにしてもいいかも。
自分でアプリを作ることで、自分がわからない部分が明確になるし、完成させたら自信になる。業務に入ってから
仕事では独学と違って、0から何かを作ることは少ない。
初めて会社のプロダクトコードをみたときは、ファイルとコード量が多すぎてびっくりした。
業務ではチームで開発するし、人の入れ替わりがあるので保守性を意識する必要があるため、「動く」だけでなく「読める」コードを書く意識をしなければいけない。書籍
■ PHPフレームワーク Laravel実践開発
前に紹介した書籍「PHPフレームワーク Laravel入門」(以下、青本)の中級者向けバージョン。
青本を読み終わった人向け。
同じ著者なので、青本が読みやすくて気に入ったら、こちらも読んでみると良い。
レビューで内容が薄いとあるが、その分気軽に読めるので全体を流し読みするのには最適。足りない部分はググって補うといいかと思う。
また、これもレビューにある通り、誤字脱字は多いのでたまに自分のPCで動かしてみても動かないことがあるので注意。■ PHPフレームワーク Laravel Webアプリケーション開発
この本は、仕事でコードを書く上でのユースケースがたくさん書かれている。
どうやって実装したらいいか迷ってるときに、この本に手を伸ばすと、解決することがある。
全部読むというより、辞書代わりに手元に置いておくと良い。記事
■ Laravelで始める依存性の注入(DI)
DIってなんだ??となったときに読んで役に立った記事
■ Laravelで実践クリーンアーキテクチャ
Laravel をクリーンアーキテクチャに当てはめるなら、どういう感じになるのか?そもそもクリーンアーキテクチャって???となったときに読んで役立った記事。
わからなくなったら
■ Laravel 日本語公式ドキュメント
実装中にわからないことがあってググった時、まずは公式ドキュメントを読むのがいい。
私は最初、書いてある日本語が難しくて読めなかったから、ある程度 Laravel に慣れてきたらでもいいと思う。
バージョンの違いや正確性を考慮すると、公式ドキュメントを読むのがいちばん(当たり前のことだけど)。■ Laravel API
ドキュメントに書いてないメソッドなどもここで検索すれば出てくる。
最後に
いくつか宣伝をさせてください。
■ CODEBASE 沖縄 プログラミング教室
学生時代、エンジニアになるために通っていたスクールです。
3ヶ月間で、何もわからない状態からソフトウェアエンジニアとして新卒就職できるまで成長できました。■ 千株式会社
私が新卒で入社した会社です。
千株式会社 では幼稚園・保育園向けインターネット写真サービス「はいチーズ!」を提供しています。
新卒、中途共に絶賛採用中です!週1回のリモートワーク、フレックス制度(コアタイムが12:00-15:00)など、柔軟な働き方ができる会社です。(朝の6時に出社すれば、なんと15時に退勤できてしまいます...!)
モダン(Laravel + Docker + CircleCI + AWS)で自由な環境で働いてみたい方におすすめの会社です。
もっと詳しく知りたい方は こちら!アドベントカレンダー 千 Advent Calendar 2019もやっているのでぜひ覗いて行ってください〜
この記事は CODEBASE okinawa Advent Calendar 2019 13日目の記事です。
- 投稿日:2019-12-13T00:40:04+09:00
Laravel(5.8)のFormファザードサンプル
概要
見た目がスッキリするのでLaravelの拡張としてよく使うFormFacade、ただ書き方を忘れる事があるのでメモとして残しておく。
公式はこちら環境
- laravel:5.8.*
- laravelcollective/html: ^5.8
前提
- old()はLaravelのヘルパーであり、直前のフォームに入力した値を取得する働きをする。
- 第2引数は初期値
BootStrap4を使っているのでformで使うclassを指定している
セレクトボックス・ラジオボタン・チェックボックスで使う配列は下記のような形とする
$array = [ 1 => 'hoge', 2 => 'fuga', 3 => 'piyo', ];フォームの開始と終了
// 開始 {{ Form::open(['route' => ['user.update', 'user' => $user->id], 'method' => 'put']) }} // 終了 {{ Form::close() }}テキスト
一番使う基本的な形
emailとかpasswordとかはほぼ同じ形なので省略{{ Form::text('name', old('name', $user->name), ['class' => 'form-control']) }}セレクトボックス
第2引数に配列、第3引数に初期値を入力すればよい
{{ Form::select('sample_id', $array , old('sample_id', $user->sample_id) , ['class' => 'form-control']) }}ラジオボタン
第3引数についてはbooleanを設定する。
この場合は三項演算子の省略でtrueかfalseを返すようにしている。
注意点として配列のキーに0をもたせていると強制一致してしまう場合があるのでその場合は===を利用する。
ここで真偽値表を確認するとよい@foreach($array as $key => $val) {{ Form::radio('sample_radio', $key, ($key == old('sample_radio', $user->sample_radio)), ['id' => 'radio'.$key]) }} {{ Form::label('radio'.$key, $val) }} @endforeachチェックボックス
第3引数についてはbooleanを設定する。
この場合はin_arrayの戻り値を利用している。($keyが各配列に存在するかチェックしている)
in_arrayの比較が不安だという方はin_arrayの第3引数にtrueと書きましょう。@foreach($array as $key => $val) {{ Form::checkbox('sample_check[]', $key, in_array($key, old('sample_check', $user->sample_check)), ['id' => 'check'.$key]) }} {{ Form::label('check'.$key, $val) }} @endforeachテキストエリア
HTMLを出力したい場合があるのでサンプルでは「!!」でエスケープ処理を解除した例を書いておく
{!! Form::textarea('memo', old('memo', $user->memo), ['class' => 'form-control']) !!}最後に
自分用のメモですが、誰かのためになれば幸いです。