20190711のlaravelに関する記事は9件です。

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 のときと同じ使い方ができるので、覚えることが少ないし、標準にも近いかもしれない。

すっきり!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでaxiosのリクエストを受け取る際、$request->ajax()がfalseになる時の対応

TL;DR

headerにX-Requested-With: XMLHttpRequestを付けよう

背景

Laravel 5.4
@nuxtjs/axios 5.4.1

axiosでGETリクエストをした際、サーバー側で以下のような処理があり、JSONを返してほしいのにHTMLが返ってきて?となった

if ($request->ajax()) {
    // JSONを返す処理
} else {
    // HTMLを返す処理
}

よくよく調べてみたら、↓の記事にもあるようにヘッダーのX-Requested-Withで判断しているらしい
https://qiita.com/harunbu/items/8b746e589b7689ce0eb7

axios.get("http://hoge/getContents", {
    headers: { "X-Requested-With": "XMLHttpRequest" },
    data: {}
})

で無事解決

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで独自バリデーションでのエラーメッセージがおかしい時

環境

Laravel Framework 5.6.26

状況

まず、独自のValidatorとなる部分を作る

PersonValidator.java
    public function validateAgeSizeLimit($attribute,$value,$parameters){//ageSizeLimitという名前でルールを定義
        return $value <= 100;
    }
PersonRequest.java
    public 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以下の数値で入力してね」と表示したかったのだが、、、

スクリーンショット 2019-07-11 17.44.44.png

解決方法

ん?関数名がスネークケースになったものが表示されているな?
もしかしてキャメルケースではなくスネークケースにしなきゃいけないってことなのか?と名推理が働きました。

PersonRequest.java
    public function messages()
    {
        return [
        'age.age_size_limit'=>'年齢は100歳までの値で入力してください'
        ];
    }

スクリーンショット 2019-07-11 17.45.19.png

直った!!
正しい書き方をさりげなく出力してくれていたんですね。。。

結論

オリジナルバリデータを作る際、ルール名となる関数名を使う場合は、
rules関数ではキャメルケースでよくて、messages関数ではスネークケースにする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者が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で本日の作業は終了。ふ〜〜。

お疲れ様でした。

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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()が必要なようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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';
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでfullCalendar

LaravelでfullCalendarを表示させるのにハマったのでメモ

layout.blade.php
<script src="{{ mix('js/app.js') }}"></script>
があると表示されない
js/app.js
require('./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> //これ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.php
Route::resource('foo', 'FooController');

これをweb.phpファイルに追加して終了です。
php artisan route:listコマンドを実行すると、それぞれのルーティングが設定できているのが確認できると思います。

あとはそれぞれのメソッドの中に処理を書いていけばいいので、作業の見通しがつきますね!

おわりに

間違いがある場合、もしくはより良い方法がある場合には、忌憚なくご指摘いただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Laravel] Fakerを使ってテストデータを作成してみる

FakerとFactoryを使ってより現実に近いテストデータを用意してみます。

Fakerとは

より現実に近いテストデータを簡単に作成できるライブラリ。
作成できるダミーデータは主に次の通り。

項目名 出力データ
name 氏名
email メールアドレス
safeEmail メールアドレス
password パスワード
address 住所
phoneNumber 電話番号
company 企業名
realText テキスト

使い方。

// 初期化
$faker = Faker\Factory::create('ja_JP');
// データの取得
$faker->name
$faker->email

Fakerの生成するデータのロケールを変更したい場合、設定ファイルでも変更できます。
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 HogeFactory

HogeFactory.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:seed
testapp_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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む