- 投稿日:2019-07-11T20:31:06+09:00
Laravel で API リソースを使って CSV ダウンロードする
はじめに
Laravel で CSV ダウンロードするときのアーキテクチャはいくつかある。
けど、コントローラからモデルをとって、整形して、CSV にするなら、
標準の JSON API リソースが JSON じゃなくて CSV になるだけでもよいのではないかと思った。
そして意外となかったので、書いてみます。CSV API リソースを実装する
標準の JSON API リソースの実装を見て、最低限な次の機能を実装しました。
- CSV だと複数のレコードを扱うように思うので、コレクションを扱う
ResourceCollection
のみ- 整形とダウンロードのレスポンス生成のみ(ほかにも機能があるっぽい)
ResourceCollection.php<?php namespace App\Http\Resources\Csv; use Illuminate\Contracts\Support\Responsable; abstract class ResourceCollection implements Responsable { protected $collection; public function __construct($resource) { $this->collection = $resource; } public function toResponse($request) { return response()->streamDownload(function () { $file = new \SplFileObject('php://output', 'wb'); $header = $this->attributes(); if (! empty($header)) { $file->fputcsv($this->convertCharset($header)); } foreach ($this->toArray() as $fields) { $file->fputcsv($this->convertCharset($fields)); } }, $this->fileName()); } private function convertCharset($fields) { return collect($fields) ->map(function ($item) { return mb_convert_encoding($item, 'SJIS-win', mb_internal_encoding()); }) ->all(); } /** * 見出しがあれば返します */ protected function attributes() { return []; } /** * 整形した配列を返します */ abstract protected function toArray(); /** * ダウンロードファイルの名前があれば返します */ protected function fileName() { return 'download.csv'; } }使ってみる
注文情報を CSV に書き出すことを考えてみましょう。
コントローラ
ほとんど JSON のときと同じです。
OrderController.php<?php namespace App\Http\Controllers; use App\Order; use App\Http\Resources\ReportResource; class OrderController extends Controller { public function report() { return new ReportResource( Order::query() ->where('is_canceled', false) ->get() ); } }API リソース
JSON のときと同じく、
toArray
をオーバライドして整形します。
加えて、attributes
で見出しを、fileName
でダウンロードファイル名を、オーバライドして指定することもできます。ReportResource.php<?php namespace App\Http\Resources; use App\Http\Resources\Csv\ResourceCollection; class ReportResource extends ResourceCollection { protected function toArray() { $this->collection->load(['user', 'products']); return $this->collection ->map(function ($order) { return [ $order->id, $order->user->id, $order->user->name, $order->products->sum('price'), ]; }) ->all(); } protected function attributes() { return [ '注文ID', '会員ID', '会員名', '注文金額合計', ]; } protected function fileName() { return '注文一覧.csv'; } }ダウンロード例
注文一覧.csv注文ID,会員ID,会員名,注文金額合計 1,1,ほげほげ,1000 2,1,ほげほげ,1500 3,2,ふがふが,1000 4,2,ふがふが,2000おわりに
JSON のときと同じ使い方ができるので、覚えることが少ないし、標準にも近いかもしれない。
すっきり!
- 投稿日:2019-07-11T17:59:43+09:00
Laravelでaxiosのリクエストを受け取る際、$request->ajax()がfalseになる時の対応
TL;DR
headerに
X-Requested-With: XMLHttpRequest
を付けよう背景
Laravel 5.4
@nuxtjs/axios 5.4.1axiosでGETリクエストをした際、サーバー側で以下のような処理があり、JSONを返してほしいのにHTMLが返ってきて?となった
if ($request->ajax()) { // JSONを返す処理 } else { // HTMLを返す処理 }よくよく調べてみたら、↓の記事にもあるようにヘッダーの
X-Requested-With
で判断しているらしい
https://qiita.com/harunbu/items/8b746e589b7689ce0eb7axios.get("http://hoge/getContents", { headers: { "X-Requested-With": "XMLHttpRequest" }, data: {} })で無事解決
- 投稿日:2019-07-11T17:47:53+09:00
Laravelで独自バリデーションでのエラーメッセージがおかしい時
環境
Laravel Framework 5.6.26
状況
まず、独自のValidatorとなる部分を作る
PersonValidator.javapublic function validateAgeSizeLimit($attribute,$value,$parameters){//ageSizeLimitという名前でルールを定義 return $value <= 100; }PersonRequest.javapublic function rules() { return [ 'age'=>'ageSizeLimit', //先ほど作った独自のルールを指定 ]; } public function messages() { return [ 'age.ageSizeLimit'=>'年齢は100歳までの値で入力してください', ]; }まあ上記のバリデーションならLaravelがあらかじめ用意してる『max:100』だけでも可能です。
この独自ルールを作る際、「validate」の後の最初の部分を小文字にしたものを使えるということは知っていた。
例) 「validateHello」なら、「hello」という名前でルールが使える。なので、上記の「PersonValidator.java」での「validateAgeSizeLimit」
という名前なら「ageSizeLimit」という名前で使えると思って使ってみたら、
バリデーションエラーメッセージに「validation.age_size_limit」
となり、本当は「100以下の数値で入力してね」と表示したかったのだが、、、解決方法
ん?関数名がスネークケースになったものが表示されているな?
もしかしてキャメルケースではなくスネークケースにしなきゃいけないってことなのか?と名推理が働きました。PersonRequest.javapublic function messages() { return [ 'age.age_size_limit'=>'年齢は100歳までの値で入力してください' ]; }直った!!
正しい書き方をさりげなく出力してくれていたんですね。。。結論
オリジナルバリデータを作る際、ルール名となる関数名を使う場合は、
rules関数ではキャメルケースでよくて、messages関数ではスネークケースにする。
- 投稿日:2019-07-11T15:58:46+09:00
超初心者がMacでDocker/Laradock環境構築 備忘録
作りたいアプリが決まったら、まずは何はともあれ環境構築。
これをやらなきゃ始まらない。私はプログラミング学習を始めたばかりのころWindowsを使用していたため
これまではcloud9にて学習していたのですが。cloud9は予期せぬ動きをすることがあるので
便利だけどあまりおすすめはしないと現役プログラマーさんにアドバイスをされ、
(ここら辺は完全に個人の好みだとは思いますが…実際私も使っていてハァ?!ってなった事はある)PCも学習途中で最新Macに買い替えたことだし、せっかくならばIDEじゃなくて
仮想サーバー構築してwebアプリ処女作品を作っていこうかなと、そう思ったわけです。まずはDockerのインストール
数あるものの中からなぜDockerを選んだのかというと、
単におすすめされた内のひとつだったのと、注目度が高いって聞いたから。
Laradockなんて便利なものもあるしね。まあ、詳しい説明は割愛。
【ここ】からダウンロードに進む。
メールアドレス、ID、PWを設定したらリンク先からDockerをダウンロードする。
そしてDockerAppを起動し先ほど登録したIDとPWを入力してログインしておく。
たったこれだけ。なんて簡単。もしくはターミナルで
$ brew install docker $ brew cask install dockerを実行するだけ。
(私はたぶん途中で失敗してて
ダウンロードしてターミナルでコマンド実行、どっちもやらないと上手くいかなかった。)次はLaradockのインストール
これはLaravel+Dockerを手軽に構築できるすごいやつ。ありがたい。
インストール&初期設定方法はLaradock公式にも載っているのだけれど、
英語に普段馴染みのない私にはちょっと分かりにくかったので、Qiitaの投稿を参考にしました。
(参考にさせていただいた記事はページ下部にまとめて記載)なんの準備もしていなかった私は、まずは勉強用フォルダを作成。
で、その中に今回作るアプリのフォルダを作成。$ mkdir study && cd study $ mkdir first_app仮名だけどこんな感じ。
そしたら次は
first_app
フォルダ内に、Laradockをクローンする。
(gitは事前に設定済み)$ git clone https://github.com/Laradock/laradock.gitクローンするのはちょっと時間がかかった気がする。
これが終わった時点で
study
フォルダ内には、クローンによって作られたlaradock
と
先ほど作ったfirst_app
の2つのフォルダが入っているはず。ここまでできたら
laradock
フォルダに移動して、env-example
をコピーして.env
の作成。$ cd laradock $ cp env-example .env
.env
ファイル一部編集。
(隠しファイルを表示する設定をしていなくて、コピーした.envがない!って焦ったのはひみつ)APP_CODE_PATH_HOST=../ // ここを変更 ↓ APP_CODE_PATH_HOST=../first_appここまでできたら、一度コンテナを作成して実行する。
$ docker-compose up -d nginx mysql redis実行したいものが他にあればこの後ろに追記していく。
正直ここら辺はよくわかってないけど、ひとまずはこれでいいかな。初回の実行にはかなり時間がかかるので、目の休憩も兼ねてまったり休憩タイム。
インストールが終わったら、http://localhost にアクセスしてみて404エラーのページに繋がったらOK!
うまくいってます!天才!続いてLaravelのインストール
インストール祭りですね。ここらへんから私は疲れてきた。
laradock
フォルダ内のworkspace
フォルダに移動。
そしてその中に、Laravelをインストールしていきます。
workspace
へはログインが必要なのですが
そのままroot権限ユーザーでLaravelをインストールしようとすると「Worning!」って注意されてインストールできない。なのでここではlaradockユーザーでログインする。
$ docker-compose exec --user=laradock workspace /bin/bash $ composer create-project --prefer-dist laravel/laravel first_appここも長い時間がかかるので、リラックスしながら待ちます。
無事にインストールが完了したら、セットアップはあと少し!今度は
first_app
内の.env
ファイルの編集です。/* 変更前 DB_HOST=127.0.0.1 DB_DATABASE=homestead DB_USERNAME=homestead */ DB_HOST=mysql DB_DATABASE=default DB_USERNAME=defaultそれが終わったら、workspaceからログアウトして、dockerに再起動をかけます。
laradock@~~~~~~~:/var/www$ exit $ docker-compose stop $ docker-compose up -d nginx mysql redis再度http://localhost にアクセスしてみて、今度はLaravelのTOPページが表示されていれば成功です!
難しくはないけど、初心者には長い道のりでした...。本当はMySQLの細かい設定も一気に行ったほうが後々楽なのでしょうが、ここで一区切りにしました。
だって疲れたんだもの。走らせたコンテナは、きちんとstopさせます。
$ docker-compose stopここまでをfirst commitしてgithubにpushで本日の作業は終了。ふ〜〜。
お疲れ様でした。
参考記事
- 投稿日:2019-07-11T15:20:06+09:00
[Laravel]単体テストでログインできない!
LoginControllerでオーバーライドした
authenticated()
のテストをしていました。Controllers/Auth/LoginController.php/** * @param \Illuminate\Http\Request $request * @param mixed $user * @return \Illuminate\Http\Response */ protected function authenticated(Request $request, $user) { return redirect('users/' . $user->id)->with('my_status', __('You logged in.')); }ログインに成功するとユーザーページにリダイレクトして
You logged in.というフラッシュメッセージを表示します。テストのコード
/tests/Unit/LoginControllerTest.php/** * A basic unit test example. * * @return void */ public function testAuthenticated() { $user = factory(User::class)->create([ 'password' => '1234' ]); $response = $this->post('login', [ '_token' => csrf_token(), 'email' => $user->email, 'password' => '1234', 'remember' => 'on' ]); $response->assertRedirect('users/' . $user->id); $this->assertAuthenticatedAs($user); }テストを実行
$ vendor/bin/phpunit --filter='LoginControllerTest' PHPUnit 7.5.13 by Sebastian Bergmann and contributors. F. 2 / 2 (100%) Time: 2.85 seconds, Memory: 26.00 MB There was 1 failure: 1) Tests\Unit\LoginControllerTest::testAuthenticated Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'http://localhost/users/1' +'http://localhost' /home/user/php/app/lara58/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:224 /home/user/php/app/lara58/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:169 /home/user/php/app/lara58/tests/Unit/LoginControllerTest.php:32 FAILURES! Tests: 2, Assertions: 4, Failures: 1.
http://localhost/users/1
にリダイレクトしていないのでログイン失敗です。解決方法
/tests/Unit/LoginControllerTest.php$user = factory(User::class)->create([ 'password' => bcrypt('1234') ]);$ vendor/bin/phpunit --filter='LoginControllerTest' PHPUnit 7.5.13 by Sebastian Bergmann and contributors. .. 2 / 2 (100%) Time: 2.68 seconds, Memory: 26.00 MB OK (2 tests, 7 assertions)成功しました。
他のテストで
actingAs()
メソッドを使った時はbcrypt()
を使わなくてもできたのですが、ログインする時はbcrypt()
が必要なようです。
- 投稿日:2019-07-11T14:50:02+09:00
【Laravel】updated_atカラムがないテーブルをEloquentモデルを用いて更新するときの注意点
備忘録として残します。
Eloquentモデルを用いて更新をかけるとき下記のような記述をすると思います。
HogeTable::find($id)->fill($input)->save();
このときLaravelはEloquentモデルで更新しようとしたとき、暗黙的にupdated_atというカラムも同時に更新しようとします。しかし、updated_atというカラムがない場合は、エラーをはくので、この暗黙的にタイムスタンプをする機能をオフにする方法が下記になります。
■やり方
namespace App\Models; use Illuminate\Database\Eloquent\Model; class HogeTable extends Model { public $timestamps = false; // この行をEloquentモデルに追記 protected $table = 'hoge'; }
- 投稿日:2019-07-11T11:29:49+09:00
LaravelでfullCalendar
LaravelでfullCalendarを表示させるのにハマったのでメモ
layout.blade.php<script src="{{ mix('js/app.js') }}"></script> があると表示されないjs/app.jsrequire('./bootstrap'); を削除すると表示されるおまけ
一文追記で日本語化も出来る
layout.blade.php<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.2.7/fullcalendar.min.css"/> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.2.7/fullcalendar.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.2.7/lang/ja.js"></script> //これ
- 投稿日:2019-07-11T11:21:50+09:00
Laravel --resourceコマンド
何が言いたい
Laravelの
--resource
コマンドがうれしい。
自分のような初心者が一からCRUD機能付きアプリを実装したいとき、何をしたらいいかを示してくれる。
=>はじめの一歩でコケにくい。--resourceコマンド
Laravelでは
php artisan make:controller FooController
とコマンドを実行すると、FooControllerのひな型が作成されます。FooController.php<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; class FooController extends Controller { // }これだけでもありがたいのですが、中身がまっさらなので初心者的にはこれからどうすればいいかわからない...
そこで、上記のコマンドを実行する代わりに、末尾に
--resource
を追加して、
php artisan make:controller FooController --resource
とコマンドを実行すると...FooController.php<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; class FooController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } // 中略 // // create, store, show, edit, updateメソッドが含まれます // /** * Remove the specified resource from storage. * * @param \App\Foo $foo * @return \Illuminate\Http\Response */ public function destroy(Foo $foo) { // } }このように、登録・更新・参照・削除に必要なメソッドを簡単に用意してくれます?
次にこれらのメソッドを使用するために、ルーティングを設定しましょう。
と言っても...web.phpRoute::resource('foo', 'FooController');これをweb.phpファイルに追加して終了です。
php artisan route:list
コマンドを実行すると、それぞれのルーティングが設定できているのが確認できると思います。あとはそれぞれのメソッドの中に処理を書いていけばいいので、作業の見通しがつきますね!
おわりに
間違いがある場合、もしくはより良い方法がある場合には、忌憚なくご指摘いただけると嬉しいです。
- 投稿日:2019-07-11T10:52:49+09:00
[Laravel] Fakerを使ってテストデータを作成してみる
FakerとFactoryを使ってより現実に近いテストデータを用意してみます。
Fakerとは
より現実に近いテストデータを簡単に作成できるライブラリ。
作成できるダミーデータは主に次の通り。
項目名 出力データ name 氏名 メールアドレス safeEmail メールアドレス password パスワード address 住所 phoneNumber 電話番号 company 企業名 realText テキスト 使い方。
// 初期化 $faker = Faker\Factory::create('ja_JP'); // データの取得 $faker->name $faker->emailFakerの生成するデータのロケールを変更したい場合、設定ファイルでも変更できます。
config/app.php に以下を追加します。config/app.php<? return [ ... 'faker_locale' => 'ja_JP', ... ];Factoryとは
大量のデータベースレコードを作成するのに便利。
モデルクラスを作成します。app配下にHoge.phpが作成される。
$ php artisan make:model Hogeファクトリークラスを作成します。database/factoriesフォルダにHogeFactory.phpが作成される。
$ php artisan make:factory HogeFactoryHogeFactory.phpを編集します。
database/factories/HogeFactory.php<?php use Faker\Generator as Faker; $factory->define(App\Hoge::class, function (Faker $faker) { $now = \Carbon\Carbon::now(); return [ // 'name' => $faker->name, 'mail' => $faker->email, 'comment' => $faker->realText, 'created_at' => $now, 'updated_at' => $now, ]; });HogesTableSeeder.phpを編集します。
database/seeds/HogesTableSeeder.php<?php use Illuminate\Database\Seeder; class HogesTableSeeder extends Seeder { public function run () { // 50レコード作成する factory (\App\Hoge::class, 50)->create(); } }DatabaseSeeder.phpを編集します。
database/seeds/DatabaseSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run () { $this->call(HogesTableSeeder::class); } }実行します。
$ php artisan db:seedtestapp_db=# select * from hoges LIMIT 1; id | name | mail | comment | type | created_at | updated_at ----+-------------+----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+---------------------+--------------------- 51 | 青田 裕美子 | sayuri.yamaguchi@gmail.com | になっていたジョバンニに言いい ましたら、自分で一ぺんにぶっきりんころにはたしました。ジョバンニのうしてますと、いつはなんのようにそう言いいなんに牛乳ぎゅうを忘わすとみをたてず、どんなほんとながらすうりをはじめました。「そう、そのマジェランプが、くるところはゆるしてそれを見ながら、もうな用ようにあてていましたがね、こんばんをのぞいて外をなおにこには明るく下に青い天の川のなから四方を見ました標札ひょ。 | C | 2019-07-11 01:50:07 | 2019-07-11 01:50:07 (1 row)参考
- 竹澤勇貴・栗生和明・新原雅司・大村創太郎(2018)『PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5LTS対応』ソシム
- Laravel Document