20201114のlaravelに関する記事は30件です。

Laravel-引数ありコントローラーで、引数がない場合の動作も許可するAPIを作る

例えば投稿機能postコントローラーがあり、特定のuser_idの引数に渡す。
このページにアクセスした時に、
引数としてユーザーID(テーブル上では 'user_id')があれば、そのユーザーIDのpostだけをAPIとして表示し、
引数がなければ全postをAPIで表示したいときなんかの動き。

route側の設定

api.php

Route::get('postlist/{id?}','PostController@postlist');

こんな感じで引数に「?」を入れると、引数ありでもなしでもいけるよって言うルーティングを許可します。

コントローラー側の設定

idのある場合とない場合の動作もコントローラーに記述が必要です。

PostController.php

public function postlist($id = null)
    {   
        if($id == null){
            //user_idが入ってない場合は全ポストを取得
            $posts = Post::All();
        }else{
            //user_idが入っている場合は全ポストを取得
            $posts = Post::where('user_id', $id)->get();
        }
        return $posts;
    }

ユーザーIDが入っていない場合、user_idにかかわらず、全ユーザーのpostを返してくれる。
スクリーンショット 2020-11-14 23.31.22.png

ユーザーIDが入っている場合、そのユーザーIDの情報だけをAPIで返してくれる。
スクリーンショット 2020-11-14 23.29.38.png

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

Laravel|同じ処理を複数のControllerで使う時に便利な共通関数を脳死で作成する方法

こんにちは、くりぱんです。

この記事で実現できること

  • 共通関数の作成&実装
  • コントローラーの簡略化
  • コントローラーのメンテナンス性向上

説明

今回実装するのは、Hello world!と出力する処理を複数のコントローラーで実装する時に、それぞれのコントローラーで記述するのではなく、共通関数を作成し同じ処理を何回も書かないようにする方法です。

私の場合、チャット機能実装の際に、チャットルームを作成するという処理を複数のコントローラーで記述しなければならなかったので、チャットルーム作成という機能を共通関数化し、それぞれのコントローラーで共通関数を使い回す手法を行いました。

その時の備忘録を今回簡単に残していこうと思います。

開発環境

  • OS: MacOS Catalina
  • PHP: 7.3.11
  • Laravel: 6

実装の流れ

  1. Laravelプロジェクト作成
  2. composer.jsonへ登録
  3. composer dump-autoload実行
  4. config/app.phpにエイリアスとして追加
  5. 共通関数作成
  6. 処理を利用したいコントローラでuseする
  7. routeの設定
  8. viewの設定

実装

Laravelプロジェクト作成

今回はMAMPのhtdocs内にLaravelのプロジェクトを作成していきます。
ターミナルで以下のコマンドを実行してください。

$ cd /Applications/MAMP/htdocs
$ composer create-project --prefer-dist laravel/laravel commonHelloWorld "6.*"

これでhtdocs配下にcommonHelloWorldというLaravelのプロジェクトが作成されました。
記事の中では、/Applications/MAMP/htdocs/commonHelloworldフォルダのことをプロジェクトフォルダと呼んでいきます。

composer.jsonへ登録

今回はcommonHelloworld/appCommonフォルダを作成し、そこに共通関数を格納していきます。
そのため、プロジェクトフォルダ配下にcomposer.jsonがあるので以下の通りに編集してください。

composer.json
{
    ー省略ー

    "autoload": {
        "psr-4": {
            "App\\": "app/"
        },
        "classmap": [
            "database/seeds",
            "database/factories", // 最後にコンマを追加
            "app/Common" // ここを追加
        ]
    },

    ー省略ー
}

composer dump-autoload実行

まずは、commonHelloworld/app配下にCommonフォルダを作成してから以下のコマンドを実行してください。

$ composer dump-autoload

下記のような感じになれば成功です。

Package manifest generated successfully.
Generated optimized autoload files containing 4255 classes

もし下記のようなエラーが出た時は、commonHelloWorld/appCommonフォルダを作成してくださいね!

  [RuntimeException]                                                                              
  Could not scan for classes inside "app/Common" which does not appear to be a file nor a folder

config/app.phpにエイリアスとして追加

commonHelloWorld/config/app.phpのaliasesに共通関数の登録を行っていきます。
今回は、クラス名をsayHelloにするので、下記のように記述してください。
※クラス名は自由です。
※共通関数と同じクラス名にしてください。

app.php
<?php

return [

    ー省略ー
    'aliases' => [

        ー省略ー

        'URL' => Illuminate\Support\Facades\URL::class,
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,
        'sayHelloClass' => app\Common\sayHelloClas::class, //ここを追加
    ],

共通関数作成

今回は、単純にHello World!を出力するだけなので、commonHelloWorld/app/Common配下にsayHelloClass.phpを作成し、以下のように記述してください。

sayHelloClass.php
<?php

namespace app\Common;

class sayHelloClass
{
    public static function sayHello()
    {
        echo "Hello World!";
    }
}

処理を利用したいコントローラでuseする

まずは処理を利用したいコントローラーを作ります。
以下のコマンドをプロジェクトフォルダ配下で実行してください。

$ php artisan make:controller topContorller

これで、app/Http/Controllers配下にtopController.phpというコントローラーが作成できました。

では、以下のように編集してください。

topController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Common\sayHelloClass;

class topController extends Controller
{

    public function show()
    {
        sayHelloClass::sayHello();

        return view(
            'topShow'
        );
    }
}

これでtopShowというviewファイルにHello World!が出力されるようになります!
なお、topShowはこれから作成します。

routeの設定

次に、routeの設定をしていきます。
commonHelloWorld/routes/web.phpを下記のように編集してください。

web.php
<?php
Route::get('/', 'topController@show')->name('top.show');

これでrouteの設定は終わりました。

viewの設定

続いて、Viewファイルの作成です。
commonHelloWorld/resources/viewstopShow.blade.phpを作成し、下記のように編集してください。

topShow.blade.php
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  これはトップだよ
</body>

</html>

これで、あとは下記のコマンドでサーバーを立ち上げて、画面を確認してみましょう!

$ php artisan serve

画面を表示して、Hello World! これはトップだよ! と出てくれば成功です!

最後に

こんな簡単にコントローラーから機能を切り分けられるんですね!
さらに、これを使い回せば同じ処理を何回も書かなくていいですし、メンテナンス性も向上するしで、一石二鳥ですね!
他にもファットコントローラーをスリムにするやり方があるので、まとめていきたいな!

何か間違ったことがあったり、質問・意見等あればコメントへお願いいたします!

それでは、当記事を最後まで見ていただき、ありがとうございました!

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

LaravelでサクッとAPIサーバーを立てる

初めに

最近、React/Redux開発をするようになり、勉強のためにAPIサーバーを立てたいと思い、Laravelでさくっと、簡単なAPIサーバー立てようというのが、本記事の主旨となります。(※Reactから、API叩く際のCORSの設定などは追って、記事として残そうと思います)

対象者

ReactやVueを勉強していて、自分でAPI作成して学習したい方
取り敢えず、LaravelでAPIをさくっと作る流れが知りたい方

Laravel 新規プロジェクトを作成する

まず、新規でLaravelプロジェクトを作成しましょう。

laravelをインストール
composer create-project "laravel/laravel=6.*" laravelapi

ModelとMigrationファイルを作成

ここでは、良くあるArticleモデルのケースで作成していきます。

因みにここでは、Modelsディレクトリを切って作成しています。
同じように進めたい場合は、以下記事を参考にしてみて下さい。
[Laravel 6] Modelsディレクトリを作る

(因みに8系からModelsディレクトリはデフォルトで作成されます。)

titledescriptionカラムを追加します。

ArticleModelを作成する
php artisan make:model Models/Article -m
public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('title');
        $table->text('description');
        $table->timestamps();
    });
}

以下、コマンドでDBに反映しましょう。

php artisan migrate

ArticleControllerを作成する

php artisan make:controller Api/ArticlesController -r

アクションを定義します。

ArticlesController.php
public function index()
{
    $articles = Article::all();
    return $articles;
}

public function store(Request $request)
{
    $article = new Article;
    $article->title = $request->body;
    $article->body = $request->body;
    $article->save();
}

public function show($id)
{
    $article = Article::find($id);
    return $article;
}

public function update(Request $request, $id)
{
    $article = Article::find($id);
    $article->title = $request->title;
    $article->body = $request->description;
    $article->save();
}

public function destroy($id)
{
    $article = Article::find($id);
    $article->delete();
}

ルーティングを設定する

APIのルーティングは、api.phpに記述します。

api.php
Route::group(['middleware' => ['api']], function(){
    Route::resource('articles', 'Api\ArticlesController', ['except' => ['create', 'edit']]);
});

テストデータを作成

最後にテストデータを追加しましょう。

php artisan make:seeder ArticlesTableSeeder
ArticlesTableSeeder.php
public function run()
{
    DB::table('articles') -> insert([
        [
            'title' => 'React',
            'description' => 'hoge...'
        ],
        [
            'title' => 'Redux',
            'description' => 'fuga...'
        ],
    ]);
}
DatabaseSeeder.php
public function run()
{
     $this->call(ArticlesTableSeeder::class);
}
php artisan db:seed

以上でAPI実装の一通りの作業は完了です。

終わりに

簡素なものですが、この流れで、APIを作ることができます。
動作確認はPostmanなどで確認してみましょう。

次はこれを利用して、ReactからこのAPIを叩いてみます。

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

Laravelで日付を操作しよう

実務でLaravelを使って日付検索をしたことがあるので、ここで共有+アウトプットをしておく。

リクエストで文字列で渡ってきた日付を操作する必要があったのですが、簡単に調べてみるとdate関数とstrtotime関数を使えば簡単にできそう

date関数とstrtotime関数とは

公式より引用date関数
公式より引用strtotime関数

  • date関数・・・ ローカルの日付/時刻を書式化する。 指定された引数 timestamp を、与えられた フォーマット文字列によりフォーマットし、日付文字列を返します。 タイムスタンプが与えられない場合は、現在の時刻が使われます。
date ( string $format [, int $timestamp = time() ] ) : string

つまり第一引数のフォーマットにしたがって、第2引数のtimestampが日付文字列で変換される模様。

  • strtotime関数・・・英文形式の日付を Unix タイムスタンプに変換する この関数は英語の書式での日付を含む文字列が指定されることを期待しており、 now で与えられたその形式から Unix タイムスタンプ (1970 年 1 月 1 日 00:00:00 UTC からの経過秒数) への変換を試みます。 now が指定されていない場合は現在日時に変換します。
strtotime ( string $datetime [, int $now = time() ] ) : int

こちらは文字列型の日付をint型の時間で返す模様。

ここの第1引数で様々な操作ができます。
月、週、日付、時間など
ここではある日にちを第一引数としたものを1日追加してみましょう

$day = "2020-11-12";
echo strtotime($d . "+1 day"); //1605225600

という具合です。

なのでstrtotimeで文字列型の日付を一度操作して、date関数の第二引数に当てはめます。

$day = "2020-11-12";
echo date( "Y-m-d", strtotime($d . "+1 day")); //2020-11-13

という感じでリクエストで渡ってきた日付の文字列を操作できました。

使うこともあるかと思うので、是非参考にしてみてください。
ここでは紹介しませんがCarbonライブラリというのもあるみたいなので、そちらも調べてみるといいかもです。

最後まで読んでいただきありがとうございました。

参考文献

strtotime関数
date関数
qiita記事

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

未経験者がLaravelでポートフォリオを作成した話【学習開始〜AWSデプロイまで】

簡単に自己紹介

・東京で土木の構造設計をやっている30代前半
・働きつつ、これから転職活動するところ
・健康好きで趣味はアンチエイジング

どんなアプリ? 何ができる?

ひとことで言うと
「パレオダイエッター(という健康法をやってる人)向けのコミュニティ+計算機能ツール」
です。
画面キャプチャ

基本的なユースケースとしては、
 ・「ユーザー登録/ログイン/プロフィール編集」
 ・「文字や画像を投稿・編集/いいね/コメントで交流」
 ・「筋肉をつける・腹筋を割るための目標カロリー算出→マイページ登録」
という感じになります。

もう少し機能を細分化すると、
 ・ユーザー登録・ログイン機能・ゲストログイン機能
 ・プロフィール編集機能(自己紹介文とアバター画像)
 ・投稿作成機能(モーダル画面,文字数カウント,画像投稿)
 ・コメント機能
 ・投稿とコメントの編集・削除機能
 ・いいね機能(Ajax利用,リレーション数取得)
 ・効率よく筋肉をつけるための目標摂取カロリーおよび三大栄養素の計算機能(Ajax)
 ・最速で腹筋を割るための目標摂取カロリーおよび三大栄養素の計算機能(Ajax)
 ・投稿の検索機能とページネーション
といった具合です。

使用技術と環境は?

・言語:PHP7, JavaScript(jQuery), Bootstrap, HTML, CSS(Sass)
・フレームワーク:Laravel7
・DBMS:MySQL
・インフラ:AWS(EC2,RDS,ELB,S3,Route53,ACM)
・Webサーバー:Amazon Linux OS 2, Apache
・開発環境:ローカル環境(MacOS), VSCode
 (本当はDockerなども導入したかったですが..)

作った背景と目的

ポートフォリオを作った理由は、
 ①転職時に必要不可欠と考えるため
 ②アプリ開発を経験することで成長したいため
 ③必要な機能を実装して価値を提供したいため
の3つです。

①転職時に必要不可欠と考えるため

職務経歴や経験で充分にスキルを示せる場合は別ですが、そうでない場合は何かモノを作っていないとスキルを定量的に示すことができないかなぁと思います。
これは、もし自分が評価する側だったら「勉強してますが何も作ったことはありません!」だと、その人がこれから活躍していく姿をうまくイメージするのは難しいと考えるためです。

②ゼロからアプリ開発を経験することで成長したいため

Progateなどの教材はとても分かりやすいですが、環境構築や設計、エラー解消といった場面に遭遇することはありません。
また書籍や動画の教材もそうですが何か問題に遭遇してもそこに答えのコードがあるので、実際にロジックを考えることはほぼ無いと言えます。(書いてあるコードを再現すれば動く)

やはり実際に「こんなものを作りたい」「そのために必要なことは?」「問題が発生」「試行錯誤」「解決!」といったプロセスを経験することで得られる学びは計り知れないほど大きいと思います。

③必要な機能を実装して価値を提供したいため

人に貢献することが好きなので、アプリ制作をするなら誰かに価値を提供できるものにしたいと思いました。

しかし実際に人に利用してもらえるサービスを提供するとなるとマーケティングや運用保守の要素がかなりに必要になりますし、初学の段階で「誰かに使ってもらうこと」に期待しすぎても学習の挫折要因になりかねないとも考えます。

そのため「自分でこんなのがあったら良いな」を少しずつ実現することを目的のひとつとしました。
具体的には「仲間と情報交換したいなぁ」「カロリー計算とかエクセルでやるの面倒だな」という課題の解消ですね。

前提条件(学習期間)と開発期間は?

プログラミング学習を開始したのが2020年5月末。
仕事をしつつ6月は主にProgateで勉強し、10種類以上のコースを各1周。
HTML&CSS,Python,Java,PHP,SQL,Git,Sass,Rubyあたり。JavaScripとRailsも途中まで。

7月に小さなプログラミングスクールに入校。(コロナの影響でほぼオンライン)
内容は
・PHP/Laravelの3ヶ月コース。
・テキストベースのカリキュラムでHTML、PHP、Laravelを学びつつ課題をこなしていく形。
・開発環境はAWSのCloud9。
・週に1回オンラインで1時間のメンタリング。
という感じ。

8月上旬にはカリキュラムを終了し、
オリジナルアプリの制作に向けて企画/設計やER図作成を開始。
8月中旬から実際の開発作業に着手。
9月末でスクールは退会。
10月にAWSの学習とデプロイ作業。
という流れです。

開発期間としては、働きながら&勉強しながらとはいえ3ヶ月程度費やしていることになりますね。
時間をすべて記録していたわけではないので何とも言えませんが、
まず可処分時間は平均しておおよそ3時間/日程度を確保。
テレビはコンセントを抜いてゲームやYoutube等は撤廃、食事や大好きな読書も最小限にしました。

ITパスや基本情報技術者、AWSや各種技術書などの勉強も行っていたので、その半分の1.5時間をアプリ開発に充てたと考えると3ヶ月でだいたい150時間は費やしていることになります。
(6月からの学習時間や資格勉強などを全てトータルすると450時間ほどのプレイ時間になりますね..)

そしてやはり感じたのは、調べ物やエラーと戦っている時間がほとんどで、
実際にコードを書いてる時間は少ないということですね。。
よく言われることかもしれませんが。

企画/設計はどんな感じで行った?

・アイディア出し、ペルソナ設定
・リーンキャンバスの作成
・エレベータピッチ、ユーザーストーリーマッピング、MVPの設定
・ワイヤーフレームの作成(手書き)
・ER図の作成(手書き)
という手順で行いました。

収益化が主目的ではないのでリーンキャンバスは埋められる範囲で、という感じですね。(KPIなど省略)
今思うとこのフェーズでもう少しアプリケーションのロジックの部分の設計ができていればなぁとも思いますが、「走りながら考える」というのもまた正解でもあるので、何とも言えないですね。

データベース設計(ER図)はどうなってる?

最初に手書きで簡単に作りつつも、開発しながら随時テーブルは更新/拡張していき、
結局はこのような形になりました。
ER図
実装してみるとやはりテーブルの関連付けの部分が難しく、キー制約や正規化などDBの奥の深さや面白さを感じました。
そしてどうしてもやっているうちに開発前の構想とは変わっていくので、Migrationファイルがやや散らかってしまいました。。

インフラ構成はどうなってる??

すべてAWSで構成。主に使用したのはEC2、RDS、ELB、S3、Route53、ACMです。
インフラ構成図
本来はWebサーバーやDBサーバーは冗長化させて、S3もCloud Frontで..という想いはあるのですが、あくまで学習用。無料枠の中でということで断念しました。
ELBはロードバランサーというよりAmazon Certificate Managerで無料でhttps化(証明書発行)する目的で使用しています。(EC2インスタンスは1つで、ダミーのAZを設定)

またAWSを利用する上でルートユーザーはMFA(多要素認証)でログイン、IAMユーザーに最小限の権限を持たせて作業、アクセスキーは厳重に管理、といったセキュリティ面は留意しました。(少し痛い目にあったので..)

アプリを制作する上で意識したことは?

ひとりチーム開発

まず、実務を想定した「ひとりチーム開発」を意識しました。
スクール等でチームを組んで共同開発..というのがひとつの理想ではありますが、個人開発なので「どうやったら実務でスムーズにキャッチアップできるか?」ということを考え、
 ・Git/GitHubによるソース管理
 ・作業ごとにissueを作成し、featureブランチを切って作業
 ・こまめにコミットし、プルリクエスト。
といった形で開発を進めました。

開発用にGitHubの新規アカウントを作成し、contributionsはこのようになりました。
いわゆるというやつですね。これからも更新していきます。
githubの草

もちろん実務ではDockerでの開発環境、CI/CDパイプライン等いろいろと必要な事項があると思いますが「まずはアプリ自体を完成させなければ..」という想いから、このあたりの技術は転職活動〜入社の期間で少しずつ習得していこうと考えています。

わかりやすいコードにする

「後から見た時に分かりやすいコード」を意識しました。
現職(構造設計)でもドキュメントやメモを残すようにしていて、プログラミングにおいても意識してコメントは書いていたのですが、開発途中で書籍「リーダブルコード(O'RELLY社)」を読んで、リファクタリングを意識的に行うようになりました。
書籍の中では「そもそも最小限のコメントで分かるような変数名やロジックにする」というのが一番感銘を受けたポイントですね。。
変数やファイルの命名センスは大事。ロジックもコメントもセンスが大事。
(美しいコードとは?みたいなテーマで飲みながら語らえるようになりたい)

インフラにAWSを使う

少し話が脱線しましたが..
他には「インフラにAWSを使う」というのも実務を意識した点です。
なぜAWSか? については、
 ・教材でherokuにデプロイは経験していた(あとは読み込み遅いとかドメイン名とか)
 ・インフラ設計や構築について実際に手を動かして学びたかったから(分かってくるとインフラは楽しい)
 ・業務で最も使われているから(AzureやGCPも気になりますが)
です。

苦労した点は?

これはもう、とにかくたくさんあるのですが、、
・エラーや不具合にハマったとき(ほぼ毎日)
・投稿した画像を自動で加工する機能のあれこれ(InterventionImageを使いましたが色々問題発生)
・Ajaxでの計算機能,いいね機能の実装と理解(ControllerとViewとJSの処理のロジックが難しかった..)
・Gitでコンフリクトが生じた時(コミット打ち消しを行ったことなどに起因..)
・AWSの概念理解とデプロイ作業(https化も意外と大変だった..)
・環境構築の問題いろいろ(Composerメモリ足りない→php.iniとswapで対処等)
などなど、多岐にわたります。

開発期間はほとんど独習だったので、
「調べつつメンター契約して即時解決」というほうが良かったかも、、
ただ、苦労はしたけど"勉強が辛い"という感じはしなかったですね。(たぶん学習レベルだからですが)

全体的な感想

まずWebアプリを作ることは楽しいということ。
もちろん大変なことや覚えなければならないことは山ほどありますが、
 ・ロジックを考えてそれが実装できたとき
 ・エラーやデプロイの問題が解決できたとき
はガッツポーズしてしまうほどの達成感です。
また日々自分が成長していくことにもやりがいを感じますし、
自分と同じ問題で困っている人に教えてあげることにも喜びを感じました。
実際にサービスを提供して誰かの課題を解決できたら最高かもしれないですね。


それと蛇足ですが..
自分の場合プログラミングスクールは通う必要はなかったかなぁと後から思いました。

もちろん「大金を払っているので後戻りできない」とか「カリキュラムが系統的になっている」とか「質問できる環境」というメリットはありますが、、
今はもっと安くて分かりやすい教材や動画もありますし、だいたい何でも調べられるので「独学で充分できるかも..」と後から感じています。

当時にもう少し情報の収集力があれば良かった..けど後からなら何でも言えますね。
googleで検索してもアフィリエイト記事にしかたどり着けないですし。

今後の課題など

・Docker,CI/CDなど、開発環境,テスト,自動デプロイあたりについて学ぶ
・ユーザー登録時にメール認証させたりセキュリティ面もきちんと考える
・PHPを生の言語としてもっと深く学ぶ
・インフラ、ネットワーク、Linuxの基本をしっかり学ぶ
・UI/UXをもう少し意識する

まぁ実際にサービスを運用するとなるとやるべきことはもっとあると思いますが、少しずつ着実に力をつけていきたいなと考えております。


以上、最後までご覧いただきありがとうございました。
「おつかれ」「もっと頑張れよ」と思った方、LGTM!していただけたら今後の励みになります^^;

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

Laravel + Vue + TypeScript + Eslint

この記事ではLaravelのフロントエンドをVue+TypeScriptで実装します。
TypeScriptの整形にはESlintを使用します。

使うもの
- Laravel Laravel_Mix
- TypeScript ESlint
- Vue Vue-Router

使わないもの
- TSlint Pretter

環境

  • Laravel v7.5
  • Node v13
  • Vue v2.6

準備

Laravelのプロジェクトを作成
bash
composer create-project --prefer-dist laravel/laravel app

Vueをインストールする
bash
npm i
npm i -D vue vue-router vue-template-compiler
npm i -D typescript ts-loader
npm i -D eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser

追加・修正するファイル一覧

├---resources
|   ├---ts(追加)
|   |   ├---App.vue
|   |   ├---app.ts
|   |   ├---pages
|   |   |   └---index.vue
|   |   ├---route.ts
|   |   └---vue-shim.d.ts
|   └---views
|       └---index.blade.php(追加)
├---routes
|   └---web.php(修正)
├---.eslintrc.json(追加)
├---tsconfig.json(追加)
└---webpack.mix.js(修正)

ルートとテンプレートを追加修正

routes/web.php
php
Route::get('/{any?}', function () {
return view('index');
})->where('any', '.+');

resources/views/index.blade.php
```html
<!doctype html>


{{ config('app.name') }}

<!-- Scripts -->

<!-- Fonts -->

<!-- Styles -->





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

Elastic BeanstalkでLaravelとNuxt.jsをデプロイした時のメモ

概要

  • 構成  undefined.jpg
  • Elastic BeanstalkでEC2とRDSを立てる
  • ドメインと証明書を取得して設定する
  • ロードバランサーとセキュリティグループを設定する
  • EB Cliを使用する

Elastic BeanstalkでNuxt.jsをデプロイ

AWSコンソール
- Elastic BeanstalkでNode.jsプラットフォームの環境を作成
- [設定] - [ソフトウェア] - [変更]でノードコマンドにnpm run eb-startを入力
- [設定] - [ソフトウェア] - [変更]で環境変数を設定

ローカル環境
- eb initを実行
- /package.json内のscripts"eb-start": "npm run build && npm run start",を追加
- npm installで失敗する場合
- /.npmrcファイルを作成してunsafe-perm=trueと入力
- 下記の内容で/.ebextensions/chown.configを作成

commands:
chown:
command: chown -R nodejs:nodejs /tmp/.config
test: cd /tmp/.config

- /Procfileがある場合は/.ebignoreファイルを作成してProcfileをデプロイしないようにする

eb deployでデプロイ

Elastic BeanstalkでLaravelをデプロイ

AWSコンソール
- Elastic BeanstalkでPHPプラットフォームの環境を作成
- [設定] - [ソフトウェア] - [変更]で環境設定でドキュメントルートに/publicを設定
- [設定] - [ソフトウェア] - [変更]で環境変数を設定
- [設定] - [データベース] - [変更] からRDSインスタンスを作成

ローカル環境
- eb init
- 下記の内容で/.ebextensions/permission.configを作成

files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/99_change_permissions.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
sudo chmod -R 777 /var/app/current/storage

eb deployでデプロイ

Route53でドメインを設定

下のページを参考にドメインと証明書を取得
https://qiita.com/sk565/items/2da1fc0c5fc676f54994
現在は東京リージョンでも証明書の発行ができるようだ
https://aws.amazon.com/jp/about-aws/global-infrastructure/regional-product-services/

セキュリティグループを修正

VPCのメニューからElastic Beanstalkで作成されたセキュリティグループを修正
- Nuxt.jsのセキュリティグループのインバウンドルールにHTTPSを追加
- LaravelのセキュリティグループのインバウンドルールにHTTPSとRDSのドライバを追加

ロードバランサーを作成

EC2のメニューからターゲットグループとロードバランサーを作成
- Nuxt.jsのインスタンスに80番ポートで接続するターゲットグループを作成
- Laravelのインスタンスに80番ポートで接続するターゲットグループを作成
- Nuxt.js用のロードバランサーを作成
- Nuxt.jsのセキュリティグループを紐づける
- リスナーの転送先にNuxt.jsのターゲットグループを設定
- Laravel用のロードバランサーを作成
- Laravelのセキュリティグループを紐づける
- リスナーの転送先にLaravelのターゲットグループを設定

ホストゾーンを作成

Route 53のでホストゾーンを作成
- ホストゾーンを作成
- レコードを作成
- ドメインとロードバランサーを設定

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

Laravel7.0-Eloquent-

(2020-04-27 Laravel7.0対応)

Eloquentの準備

artisan make:model User
artisan make:model User -m # マイグレーション付き
class Flight extends Model
{
    protected $table = 'my_flights'; // 対応するテーブル
    public $timestamps = false; // データ操作時のタイムスタンプ更新
    protected $dateFormat = 'U'; // タイムスタンプのフォーマット
    const CREATED_AT = 'creation_date'; // 作成日時カラム名
    const UPDATED_AT = 'last_update'; // 更新日時カラム名
    protected $connection = 'connection-name'; // コネクション名
    protected $fillable = ['name']; // 複数代入する属性
    protected $guarded = ['price']; // 複数代入しない属性

    use SoftDeletes; // ソフトデリート

    protected static function boot() // グローバルスコープ
    {
        parent::boot();
        static::addGlobalScope('age', function (Builder $builder) {
            $builder->where('age', '>', 200);
        });
    }

    public function scopePopular($query, $type) // ローカルスコープ
    {
        return $query->where('votes', '>', $type);
    }
}

Flight::all(); // 全件取得
Flight::find(1); // 主キーで指定したモデル取得
Flight::where('active', 1)->first(); // 最初の一件を取得
Flight::findOrFail(1); // 見つからない場合ModelNotFoundExceptionを投げる
Flight::where('active', 1)->count(); // 件数
Flight::where('active', 1)->max('price'); // 最大値

$flight->save(); // モデルの保存
Flight::create(['name' => 'Flight 10']); // 新規
Flight::where('active', 1)->update(['delayed' => 1]); // 更新
Flight::firstOrCreate(
    ['name' => 'Flight 10'], ['delayed' => 1]
);
Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99]
);
$flight->delete();
Flight::destroy([1, 2, 3]);
$flight->trashed() // ソフトデリートされている場合true
Flight::withTrashed()
    ->where('account_id', 1)
    ->get(); // ソフトデリートされたデータも検索する
$flight->restore(); // ソフトデリートの解除
$flight->forceDelete(); // 強制削除

イベント

リレーション

class User extends Model
{
    protected $touches = ['post']; // userが更新された場合に関連するpostのtimestampを更新

    public function phone() // 1対1
    {
        return $this->hasOne('App\Phone');
    }

    public function comments() // 1対多
    {
        return $this->hasMany('App\Comment');
    }

    public function house() // 逆
    {
        return $this->belongsTo('App\House')->withDefault();
    }

    public function roles() // 多対多
    {
        return $this->belongsToMany('App\Role');
    }

    public function posts() // 仲介モデルを経由しての多対多
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

$user->roles->first()->pivot->created_at; // 中間テーブル取得
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2'); // 中間テーブルの値を取得
return $this->belongsToMany('App\Role')->withTimestamps(); // 中間テーブルのタイムスタンプを更新
return $this->belongsToMany('App\Role')->wherePivot('approved', 1); // 中間テーブルでフィルタリング

// 挿入・更新
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);

$post->comments()->save($comment); // ポストに関連するコメントを挿入

$post = Post::find(1);
$comment = $post->comments()->create([
    'message' => 'A new comment.',
]); // ポストに関連するコメントを挿入

$account = Account::find(10);
$user->account()->associate($account);
$user->save(); // ユーザーにアカウントを所属させる

$user->account()->dissociate();
$user->save(); // ユーザーに所属するアカウントのリレーションを削除

$user = User::find(1);
$user->roles()->attach($roleId); // ユーザーに役割を紐づける
$user->roles()->detach($roleId); // ユーザーの役割のリレーションを削除
$user->roles()->sync([1, 2, 3]); // ユーザーに役割を紐づけてそれ以外のリレーションを削除

ポリモーフィック

Collection

$collection = collect([1, 2, 3]); // コレクション生成

$collection->all() // 中身を配列で返す
$collection->get(key, default) // 指定されたキーのアイテムを返す
$collection->random(number) // コレクションからランダムにアイテムを返す
$collection->put(key, value) // 指定したキーと値をコレクションにセット
$collection->prepend(value, key) // アイテムをコレクションの最初に追加
$collection->push(value) // コレクションの最後にアイテムを追加
$collection->concat(items) // 指定した複数アイテムをコレクションの最後に追加
$collection->union(items) // 指定した複数アイテムをコレクションへ追加
$collection->pull(key, default) // キーのアイテムを取得して削除
$collection->splice(offset, length, replacement) // 指定したインデックス以降のアイテムを削除し、削除したアイテムたちを返す
$collection->shift() // 最初のアイテムを取得して削除
$collection->pop() // 最後のアイテムを取得して削除
$collection->forget(keys) // 指定したキーのアイテムを削除
$collection->dump() // ダンプ
$collection->reverse() // コレクションのアイテムを逆順にする
$collection->search(value, strict) // 指定した値のキーを返す、見つからない場合はfalseを返す
$collection->shuffle(seed) // コレクションをシャッフル
$collection->slice(offset, length) // 指定したインデックスからコレクションを切り分ける
$collection->sort(calback) // ソートする
$collection->sortBy(callback, options, descending) // 指定したキーでコレクションをソート
$collection->sortByDesc(callback, options) // 指定したキーでコレクションを逆順でソート
// 値
$collection->count() // コレクションのアイテム数を返す
$collection->max(callback) // 指定したキーの最大値を返す
$collection->min(callback) // 指定したキーの最小値を返す
$collection->average(callback) // 平均値
$collection->median(key) // 指定したキーの中央値を返す
$collection->mode(key) // 指定したキーの最頻値を返す
$collection->sum(callable) // 全アイテムの合計を返す
// 判定
$collection->contains(key, operator, value) // 指定した条件に合うアイテムを持つ場合true
$collection->every(key, operator, value) // 繰り返し処理を行い全アイテムがtrueならtrueを返す
$collection->has(key) // 指定したキーがコレクションに存在する場合true
$collection->isEmpty() // コレクションが空の場合true
$collection->isNotEmpty() // コレクションが空でない場合true
// 変換
$collection->map(callback) // 繰り返しで処理し、コールバックの処理を行った値をコレクションで返す
$collection->mapInto(class) // コレクションを繰り返し処理し、指定したクラスの新しいインスタンスを生成し、コンストラクタへ値を渡す
$collection->unique(key, strict) // 指定したキーが一意なコレクションを返す
$collection->toArray() // 配列に変換
$collection->toJson() // シリアライズ済みのJSON文字を返す
// その他
$collection->chunk(size) // 指定したサイズの小さなコレクションに分割 ビューで横3件づつ表示する場合に便利
$collection->forPage(pageNo, perPage) // perPageごとのpageNoのコレクションを返す
$collection->groupBy(groupBy, preserveKeys) // 指定したキーによりグループ化
$collection->implode(value, glue) // 指定したキーの値を指定した文字列で結合する
$collection->keyBy(keyBy) // 指定したキーをコレクションのキーにする
$collection->keys() // コレクションの全キーを返す
$collection->make(items) // 新しいコレクションインスタンスの生成
$collection->merge(items) // コレクションをマージ 引数の値が優先
$collection->only(keys) // コレクション中の指定したアイテムのみを返す
$collection->pluck(value, key) // 指定したキーの全コレクションの値を返す
$collection->when(value, callback, default) // valueがtrueの場合にcallbackを実行する
$collection->unless(value, callback, default) // valueがfalseの場合にcallbackを実行する
// 繰り返し
$collection->each(callback) // コレクションのアイテムを繰り返し処理
$collection->eachSpread(callback) // コレクションのアイテムを繰り返し処理
$collection->pipe(callback) // コレクションを指定したコールバックに渡し、結果を返す
$collection->reduce(callback, initital) // 繰り返しの結果を次の繰り返しに渡しながら単一値へ変換
$collection->tap(callback) // コレクションに影響を与えることなくコールバックを実行
$collection->transform(callback) // コールバックから返る値のコレクションを返す
// フィルタリング
$collection->first(callback, default) // 指定されたテストをパスしたコレクションの最初のアイテムを返す
$collection->firstWhere(key, operator, value) // 指定した条件の最初のアイテムを返す
$collection->last(callback, default) // 指定したテストをパスしたコレクションの最後の値を返す
$collection->filter(callback) // コールバックでフィルタリングを行いtrueのアイテムのみコレクションで返す
$collection->reject(callback) // コールバックでフィルタリングを行いfalseのアイテムのみコレクションで返す
$collection->partition(key, operator, value) // コールバックの可否で二つのコレクションに分割して返す
$collection->where(key, operator, value) // キー・値を含むコレクションを返す
$collection->whereIn(key, values, strict) // キー・値を含むコレクションを返す
$collection->whereNotIn(key, values, strict) // キー・値を含まないコレクションを返す
$collection->diff(items) // itemsに存在しないアイテムをコレクションで返す
$collection->diffAssoc(items) // itemsのキーは一致するが値が異なるアイテムをコレクションで返す
$collection->diffKeys(items) // itemsに含まれないキーのアイテムをコレクションで返す
$collection->only(keys) // コレクション中の指定したアイテムのみを返す
$collection->except(keys) // 指定したキー以外のアイテムをコレクションで返す

コレクションの拡張(サービスプロバイダ内でマクロを使用してクラスメソッドを追加することが可能)

ミューテタ

APIリソース

APIリソース JSONレスポンスを返すときにJSONのdata要素を整形するクラス
コレクションリソース JSONレスポンスを返すときにJSON自体を整形するクラス
bash
artisan make:resource UserResource
artisan make:resource Users --collection
artisan make:resource UserCollection

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

return new UserResource(User::find(1)); // 単体
return UserResource::collection(User::all()); // 複数

シリアライズ

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

Laravel7.0-データベース-

(2020-04-24 Laravel7.0対応)

利用開始

DB::connection('foo') // 接続先を指定して接続
DB::connection()->getPdo(); // PDOを返す

DB::select('select * from users where id = :id', ['id' => 1]); // モデル
DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']); // 
DB::update('update users set votes = 100 where name = ?', ['John']); // レコード数を返す
DB::delete('delete from users'); // レコード数を返す
DB::statement('drop table users');

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
}); // トランザクション

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
}, 5); // デットロック発生時に5回試す

DB::beginTransaction(); // トランザクション開始
DB::rollBack(); // ロールバック
DB::commit(); // コミット

クエリイベントのリッスン
php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
DB::listen(function ($query) {
// $query->sql
// $query->bindings
// $query->time
});
}

クエリビルダ

DB::table('users')->get(); // 全件取得 Collection<StdClass>
DB::table('users')->where('name', 'John')->first(); // 1件取得
DB::table('users')->where('name', 'John')->value('email'); // カラム指定
DB::table('users')->select('name', 'email as user_email')->get(); // カラム指定
DB::table('roles')->pluck('title'); // カラムを指定してリストで取得
DB::table('users')->count(); // 件数
DB::table('orders')->max('price');
DB::table('orders')->min('price');
DB::table('orders')->avg('price');
DB::table('orders')->sum('price');
DB::table('users')->distinct()->get(); // 重複行をまとめる
DB::table('users')->whereBetween('votes', [1, 100])->get();
DB::table('users')->whereNotBetween('votes', [1, 100])->get();
DB::table('users')->whereIn('id', [1, 2, 3])->get();
DB::table('users')->whereNotIn('id', [1, 2, 3])
->get();
DB::table('users')->whereDate('created_at', '2016-12-31')->get();
DB::table('users')->whereMonth('created_at', '12')->get();
DB::table('users')->whereDay('created_at', '31')->get();
DB::table('users')->whereYear('created_at', '2016')->get();
DB::table('users')->whereTime('created_at', '=', '11:20')->get();
DB::table('users')->whereColumn('first_name', 'last_name')->get();
DB::table('users')->orderBy('name', 'desc')->get();
DB::table('users')->latest()->first(); // created_atで逆順にソート
DB::table('users')->inRandomOrder()->first(); // ランダム順にする
DB::table('users')->groupBy('account_id')->having('account_id', '>', 100)->get();
DB::table('users')->skip(10)->take(5)->get();
DB::table('users')->offset(10)->limit(5)
->get();

DB::table('users')
    ->where('name', '=', 'John')
    ->orWhere(function ($query) {
        $query->where('votes', '>', 100)
              ->where('title', '<>', 'Admin');
    })->get();

DB::table('users')
    ->whereExists(function ($query) {
        $query->select(DB::raw(1))
            ->from('orders')
            ->whereRaw('orders.user_id = users.id');
    })->get();

DB::table('users')
    ->when($role, function ($query) use ($role) {
        return $query->where('role_id', $role);
    })->get(); // $roleがtrueの場合のみクエリに追加する

DB::table('users')
    ->join('contacts', 'users.id', '=', 'contacts.user_id')
    ->select('users.*', 'contacts.phone', 'orders.price')
    ->get(); // INNER JOIN

DB::table('users')
    ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
    ->get(); // LEFT JOIN

DB::table('sizes')
    ->crossJoin('colours')
    ->get(); // CROSS JOIN

DB::table('users')
    ->join('contacts', function ($join) {
        $join->on('users.id', '=', 'contacts.user_id')
             ->where('contacts.user_id', '>', 5);
    })->get(); // 複雑なJOIN

DB::table('users')
    ->select(DB::raw('count(*) as user_count, status'))
    ->selectRaw('price * ? as price_with_tax', [1.0825])
    ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
    ->havingRaw('SUM(price) > 2500')
    ->orderByRaw('updated_at - created_at DESC')
    ->get(); // SQLを直接使用

DB::table('users')->orderBy('id')->chunk(100, function ($users) {
    foreach ($users as $user) {
        //
    }
}); // 結果の分割

DB::table('users')->insert(
    ['email' => 'john@example.com', 'votes' => 0]
);

DB::table('users')->insertOrIgnore([
    ['id' => 1, 'email' => 'taylor@example.com'],
    ['id' => 2, 'email' => 'dayle@example.com']
]); // 重複レコードエラーを無視

$id = DB::table('users')->insertGetId(
    ['email' => 'john@example.com', 'votes' => 0]
); // IDを返す

DB::table('users')
    ->where('id', 1)
    ->update(['votes' => 1]);

DB::table('users')->increment('votes'); // 1増やす
DB::table('users')->increment('votes', 5); // 5増やす
DB::table('users')->decrement('votes'); // 1減らす
DB::table('users')->decrement('votes', 5); // 5減らす

DB::table('users')->where('votes', '>', 100)->delete();
DB::table('users')->truncate();

DB::table('users')->where('votes', '>', 100)->sharedLock()->get(); // 共有ロック
DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get(); // 占有ロック

ページネーション

DB::table('users')->paginate(15);
DB::table('users')->simplePaginate(15); // 前へ次へのみ
$users->withPath('custom/url'); // URI指定

{{ $users->links() }}
{{ $users->appends(['sort' => 'votes'])->links() }}

{{ $results->count() }} // 現在のページのアイテム数
{{ $results->currentPage() }} // 現在のページ数
{{ $results->firstItem() }} // 現在ページの最初のアイテムが何番目か
{{ $results->getOptions() }} // ページネータオプション取得
{{ $results->getUrlRange($start, $end) }} // 一定範囲のページネーションURLを取得
{{ $results->hasPages() }} // 複数ページに分割できるだけのアイテムがあるか判定
{{ $results->hasMorePages() }} // データストレージにまだアイテムが存在しているか判定
{{ $results->items() }} // 現在ページのアイテムを取得
{{ $results->lastItem() }} // 現在ページの最後のアイテムが何番目か
{{ $results->lastPage() }} // 利用可能な最終ページ数取得
{{ $results->nextPageUrl() }} // 次ページのURL取得
{{ $results->onFirstPage()  }} // ペジネータが最初のページを扱っているか判定
{{ $results->perPage() }} // ページごとに表示するアイテム数
{{ $results->previousPageUrl() }} // 前ページのURL取得
{{ $results->total() }} // 総数
{{ $results->url($page) }} // 指定したページのURL取得
{{ $results->getPageName() }} // ページを保存するために使用するクエリ文字変数を取得
{{ $results->setPageName($name) }} // ページを保存するために使用するクエリ文字変数を指定

マイグレーション

artisan make:migration create_users_table --create=users
artisan make:migration add_votes_to_users_table --table=users

artisan migrate
artisan migrate --force // 実働環境での強制マイグレーション
artisan migrate:rollback
artisan migrate:rollback --step=5
artisan migrate:reset
artisan migrate:refresh
artisan migrate:fresh
artisan migrate:fresh --seed
Schema::create('flights', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->timestamps();
});

Schema::rename($from, $to); // テーブル名変更
Schema::drop('flights'); // テーブル削除

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
});
// カラムタイプ
$table->id(); // Alias of $table->bigIncrements('id').
$table->tinyInteger('votes'); // TINYINTカラム
$table->integer('votes'); // INTEGERカラム
$table->bigInteger('votes'); // BIGINTカラム
$table->increments('id'); // 符号なしINTを使用した自動増分ID(主キー)
$table->float('amount', 8, 2); // 有効(全体桁数)/小数点以下桁数指定のFLOATカラム
$table->double('amount', 8, 2); // 有効(全体桁数)/小数点以下桁数指定のDOUBLEカラム

$table->string('name', 100); // オプションの文字長を指定したVARCHARカラム
$table->text('description'); // TEXTカラム
$table->binary('data'); // BLOBカラム
$table->enum('level', ['easy', 'hard']); // ENUMカラム

$table->timestamp('added_on'); // TIMESTAMPカラム
$table->year('birth_year'); // YEARカラム
$table->date('created_at'); // DATEカラム
$table->time('sunrise'); // TIMEカラム
$table->timestampTz('added_on'); // タイムゾーン付きTIMESTAMPカラム

$table->boolean('confirmed'); // BOOLEANカラム
$table->json('options'); // JSONフィールド
$table->jsonb('options'); // JSONBフィールド
$table->ipAddress('visitor'); // IPアドレスカラム
$table->macAddress('device'); // MACアドレスカラム

$table->rememberToken(); // VARCHAR(100)でNULL値可能なremember_tokenを追加
$table->timestamps(0); // NULL値可能なcreated_atとupdated_atカラム追加
$table->softDeletes('deleted_at', 0); // ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加
$table->timestampsTz(0); // タイムゾーン付きのNULL値可能なcreated_atとupdated_atカラム追加
$table->softDeletesTz('deleted_at', 0); // ソフトデリートのためにNULL値可能なdeleted_atタイムゾーン付きTIMESTAMPカラム追加

$table->dropRememberToken(); // remember_tokenカラムのドロップ
$table->dropSoftDeletes(); // deleted_atカラムのドロップ
$table->dropSoftDeletesTz(); // dropSoftDeletes()メソッドの別名
$table->dropTimestamps(); // created_atとupdated_atカラムのドロップ
$table->dropTimestampsTz(); // dropTimestamps()メソッドの別名
// カラム修飾子
->autoIncrement() // 整数カラムを自動増分ID(主キー)へ設定
->default($value) // カラムのデフォルト(default)値設定
->nullable($value = true) // (デフォルトで)NULL値をカラムに挿入する
->useCurrent() // TIMESTAMPカラムのデフォルト値をCURRENT_TIMESTAMPに指定
// インデックス
$table->primary(['id', 'parent_id']);
$table->unique('email');
$table->index(['account_id', 'created_at']);
$table->dropPrimary('users_id_primary');
$table->dropUnique('users_email_unique');
$table->dropIndex('geo_state_index');
// 外部キー制約
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->dropForeign('posts_user_id_foreign'); // テーブル名_カラム名_foreign
Schema::enableForeignKeyConstraints();
Schema::disableForeignKeyConstraints();

シーディング

artisan make:seeder UserSeeder // 作成後 要composer dump-autoload
artisan db:seed
artisan db:seed --class=UserSeeder

Redis

composer require predis/predis
Redis::get('user:profile:'.$id);
Redis::set('name', 'Taylor');
Redis::lrange('names', 5, 10);

Redis::pipeline(function ($pipe) {
    for ($i = 0; $i < 1000; $i++) {
        $pipe->set("key:$i", $i);
    }
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7.0-より深く知る-

(2020-04-27 Laravel7.0対応)

Artisanコンソール

Available commands:
  clear-compiled       Remove the compiled class file
  down                 Put the application into maintenance mode
  env                  Display the current framework environment
  inspire              Display an inspiring quote
  migrate              Run the database migrations
  optimize             Cache the framework bootstrap files
  serve                Serve the application on the PHP development server
  test                 Run the application tests
  tinker               Interact with your application
  ui                   Swap the front-end scaffolding for the application
  up                   Bring the application out of maintenance mode
 auth
  auth:clear-resets    Flush expired password reset tokens
 cache
  cache:clear          Flush the application cache
  cache:forget         Remove an item from the cache
  cache:table          Create a migration for the cache database table
 config
  config:cache         Create a cache file for faster configuration loading
  config:clear         Remove the configuration cache file
 db
  db:seed              Seed the database with records
  db:wipe              Drop all tables, views, and types
 key
  key:generate         Set the application key
 make
  make:channel         Create a new channel class
  make:command         Create a new Artisan command
  make:component       Create a new view component class
  make:controller      Create a new controller class
  make:event           Create a new event class
  make:exception       Create a new custom exception class
  make:factory         Create a new model factory
  make:job             Create a new job class
  make:listener        Create a new event listener class
  make:mail            Create a new email class
  make:middleware      Create a new middleware class
  make:migration       Create a new migration file
  make:model           Create a new Eloquent model class
  make:notification    Create a new notification class
  make:observer        Create a new observer class
  make:policy          Create a new policy class
  make:provider        Create a new service provider class
  make:request         Create a new form request class
  make:resource        Create a new resource
  make:rule            Create a new validation rule
  make:seeder          Create a new seeder class
  make:test            Create a new test class
 migrate
  migrate:fresh        Drop all tables and re-run all migrations
  migrate:install      Create the migration repository
  migrate:refresh      Reset and re-run all migrations
  migrate:reset        Rollback all database migrations
  migrate:rollback     Rollback the last database migration
  migrate:status       Show the status of each migration
 notifications
  notifications:table  Create a migration for the notifications table
 optimize
  optimize:clear       Remove the cached bootstrap files
 package
  package:discover     Rebuild the cached package manifest
 route
  route:cache          Create a route cache file for faster route registration
  route:clear          Remove the route cache file
  route:list           List all registered routes
 session
  session:table        Create a migration for the session database table
 storage
  storage:link         Create the symbolic links configured for the application
 view
  view:cache           Compile all of the application's Blade templates
  view:clear           Clear all compiled view files

ブロードキャスト

キャッシュ

cache('key', 'default') // キャッシュ取得 デフォルト値
cache(['key' => 'value'], 5) // 5分間有効なキャッシュを追加
cache(['key' => 'value'], now()->addSeconds(10)) // 10秒後まで有効なキャッシュを追加

Cache::get('key', 'default') // キャッシュ取得 デフォルト値
Cache::get('key', function () {
    return DB::table(...)->get();
})
Cache::pull('key') // キャッシュを取得後削除
Cache::store('file')->get('foo') // 保存先を指定してキャッシュ取得
Cache::has('key') // キャッシュがある場合true
Cache::increment('key'); // キャッシュの整数アイテムの値を1増やす
Cache::increment('key', $amount); // キャッシュの整数アイテムの値を$amountだけ増やす
Cache::decrement('key');
$value = Cache::remember('users', $minutes, function () {
    return DB::table('users')->get();
}); // キャッシュに存在しない場合クロージャが実行され、結果がキャッシュに保存される。
$value = Cache::remember('users', $minutes, function () {
    return DB::table('users')->get();
}); // クロージャの結果が永久に保存される。
Cache::put('key', 'value', $minutes); // キャッシュへアイテム保存
Cache::add('key', 'value', $minutes); // キャッシュに保存されていない場合のみ保存。追加された場合true
Cache::forever('key', 'value'); // アイテムを永遠に保存
Cache::forget('key'); // キャッシュからのアイテム削除
Cache::flush(); // キャッシュ全体を削除

Collection

イベント

ファイルストレージ

  • local 非公開 storage/app配下へ保存
  • public 一般公開 storage/app/public配下へ保存 bash artisan storage:link # public/storageからstorage/app/publicへシンボリックリンクを張る
  • ftp
  • sftp
  • s3 要league/flysystem-aws-s3-v3パッケージ
  • rackspace 要league/flysystem-rackspaceパッケージ

Rackspaceドライバ設定

'rackspace' => [
    'driver'    => 'rackspace',
    'username'  => 'your-username',
    'key'       => 'your-key',
    'container' => 'your-container',
    'endpoint'  => 'https://identity.api.rackspacecloud.com/v2.0/',
    'region'    => 'IAD',
    'url_type'  => 'publicURL',
],
echo asset('storage/file.txt'); <!-- storage/app/public/file.txt -->
Storage::disk('public')->put('file.txt', 'Contents'); // storage/app/public/file.txtへファイルを保存
Storage::disk('local')->put('file.txt', 'Contents'); // storage/app/file.txtへファイルを保存

Storage::get('file.jpg'); // ファイルを取得
Storage::exists('file.jpg'); // ファイルが存在すればtrue
Storage::url('file1.jpg'); // URL
Storage::temporaryUrl('file1.jpg', now()->addMinutes(5)
); // 一時的なURL
Storage::size('file1.jpg'); // ファイルサイズ
Storage::lastModified('file1.jpg'); // 更新日時

Storage::put('file.jpg', $contents); // ファイルを保存
Storage::putFile('photos', new File('/path/to/photo')); // 一意のIDでファイルを保存
Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg'); // ファイル名を指定して保存
Storage::putFile('photos', new File('/path/to/photo'), 'public'); // アクセス権限を指定
Storage::prepend('file.log', 'Prepended Text'); // ファイルの先頭にテキストを保存
Storage::append('file.log', 'Appended Text'); // ファイルの末尾にテキストを保存
Storage::copy('old/file1.jpg', 'new/file1.jpg'); // コピー
Storage::move('old/file1.jpg', 'new/file1.jpg'); // 移動

$request->file('avatar')->store('avatars'); // アップロードファイルをavatarフォルダに一意のファイル名で保存
Storage::putFile('avatars', $request->file('avatar'));
$request->file('avatar')->storeAs('avatars', $request->user()->id
); // ファイル名を指定
Storage::putFileAs('avatars', $request->file('avatar'), $request->user()->id
);
$request->file('avatar')->store('avatars/'.$request->user()->id, 's3'
); // ストレージを指定
Storage::disk('s3')->putFileAs('avatars', $request->file('avatar'), $request->user()->id);
Storage::delete('file.jpg'); // 削除

Storage::put('file.jpg', $contents, 'public'); // public属性で保存
Storage::getVisibility('file.jpg'); // 視認性の取得
Storage::setVisibility('file.jpg', 'public') // 視認性の設定

Storage::files($directory); // ディレクトリの全ファイルを取得
Storage::allFiles($directory); // サブディレクトリのファイルも取得

Storage::directories($directory); // 全ディレクトリを取得
Storage::allDirectories($directory); // サブディレクトリのディレクトリも取得

Storage::makeDirectory($directory); // ディレクトリの作成
Storage::deleteDirectory($directory); // ディレクトリの削除

ヘルパ
php
// 配列とオブジェクト
Arr::add(array, key, value) // 指定されたキーが存在しない場合、配列に追加
Arr::divide(array) // キーの配列と値の配列を返す
Arr::dot(array, prepend) // 多次元配列をドット記法に変換
Arr::get(array, key, default) // ドット記法で指定された値を取得
Arr::pull(array, key, default) // 指定された要素を取得して削除
Arr::set(array, key, value) // 値をセット
Arr::forget(array, keys) // ドット記法で指定されたキーを配列から取り除きます
Arr::has(array, keys) // ドット記法で指定されたアイテムが配列に存在する場合true
Arr::first(array, callback, default) // テストにパスした最初の要素を返す
Arr::last(array, callback, default) // テストをパスした最後の配列要素を返す
Arr::only(array, keys) // 指定されたキーのアイテムを返す
Arr::except(array, keys) // 指定されたキーを配列から削除
Arr::pluck(array, value, key) // 指定キーに対する値を全て取得
Arr::prepend(array, value, key) // 先頭に要素を追加
Arr::random(array, number) // ランダムに取得
Arr::sort(array, callback) // ソート
Arr::sortRecursive(array) // 再帰的にソート
Arr::where(array, callback) // フィルタリング
head(array) // 配列の最初の要素を返す
last(array) // 配列の最後の要素を返す
// パス
app_path(path) // appディレクトリへの完全パスを取得
base_path(path) // プロジェクトルートの完全パスを取得
config_path(path) //configディレクトリの完全パスを取得
database_path(path) // databaseディレクトリの完全パスを取得
mix(path) // バージョンつけしたMixファイルのパスを取得
public_path(path) // publicディレクトリの完全パスを取得
resource_path(path) // resourcesディレクトリの完全パスを取得
storage_path(path) // storageディレクトリの完全パスを取得
// 文字列
__(key, replace, locale) // ローカリゼーションファイルを使用し、翻訳
class_basename(class) // 名前空間を除いたクラス名だけを取得
// URL
asset(path, secure) // 現在のリクエストスキーマを使い、アセットへのURLを生成
secure_asset(path) // HTTPSを使い、アセットへのURLを生成
route(name, parameters, absolute) // 名前付きルートへのURLを生成
secure_url(path, parameters) // 指定したパスへの完全なHTTPSURLを生成
url(path, parameters, secure) // 指定したパスへの完全なURLを生成
// レスポンス
response(content, status, headers) // responseインスタンスを返す
view(view, data, mergeData) // Viewインスタンスを返す
abort(code, message, headers) // HTTP例外を投げる
redirect(to, status, headers, secure) // リダイレクトインスタンスを返す
back(status, headers, fallback) // 直前のローケーションへのリダイレクトレスポンスを生成
abort_if(boolean, code, message, headers) // 指定された論理値がtrueならHTTP例外を投げる
abort_unless(boolean, code, message, headers) // 指定された論理値がfalseならHTTP例外を投げる
// インスタンス
app(abstract, parameters) // サービスコンテナのインスタンスを返す
resolve(name) // サービスコンテナを使い、インスタンス自身を依存解決する
auth(guard) // 認証インスタンスを返す
collect(value) // コレクションインスタンスを生成
cookie(name, value, minutes, path, domain, secure, httpOnly, raw, sameSite) // クッキーインスタンスを生成
factory(class, name, amount) // 指定したクラスのモデルビルダを生成する
policy(class) // ポリシーインスタンスを取得
validator(data, rules, messages, customAttributes) // バリデータインスタンスを生成
// フォーム
csrf_field() // CSRFトークン値を持つHTML入力フィールドを生成
csrf_token() // CSRFトークン値を取得
method_field(method) // patchやdeleteの入力フィールドを生成
old(key, default) // セッションにフラッシュデータとして保存されている直前の入力値を取得
// イベント・キュー
broadcast(event) // 指定したイベントをリスナへブロードキャストする
dispatch(job) // 指定したジョブをLaravelのジョブキューへ投下する
dispatch_now(job, handler) // 指定したジョブを即時実行し、handleメソッドからの値を返す
event(event, payload, halt) // イベントをリスナに発行
// ログ
info(message, context) // ログ出力
logger(message, context) // ログ出力
// 例外
throw_if(condition, exception, paramaters) // 論理式がtrueの場合例外を投げる
throw_unless(condition, exception, paramaters) // 論理式がfalseの場合例外を投げる
rescue(callback, rescure) // 指定されたクロージャを実行し、例外をキャッチする。キャッチはレポートさ
// クロージャ
transform(value, callback, default) // 指定値がblankでない場合にクロージャを実行し結果を返す
retry(times, callback, sleep) // 指定回数コールバックを実行する
tap(value, callback) // valueに対してcallbackを実行してvalueを返す
value(value) // 値を返す
with(value, callback) // クロージャの結果を返す
// 暗号化
bcrypt(value, options) // 値をハッシュ化
encrypt(value, serialize) // 値を暗号化する
decrypt(value, unserialize) // 値を復号する
// 日時
now(tz) // 日時のCarbonインスタンスを返す
today(tz) // 日付のCarbonインスタンスを返す
// その他
cache(key, default) // キャッシュから値を取得
session(key, default) // セッションへ値を設定、取得
env(key, default) // 環境変数を取得、config:cacheを実行後はnullを返すので注意
config(key, default) // 設定変数の値を取得
dd() // 指定された変数の内容を表示し、スクリプトの実行を停止
dump(var) // 指定した変数をダンプ
report(exception) // 例外をレポートする
optional(value) // オブジェクトのプロパティやメソッドを呼び出す。オブジェクトがnullの場合はエラーではなくnullを返す
request(key, default) // リクエストインスタンスを返すか、入力アイテムを取得

メール

  • SMTP
  • Mailgun
  • Postmark
  • Amazon SES
  • sendmail
composer require guzzlehttp/guzzle # APIドライバの利用に必要

artisan make:mail OrderShipped // Mailableクラス作成
artisan make:mail OrderShipped --markdown=emails.orders.shipped // Markdownテンプレート指定
return $this->from('example@example.com')
    ->view('emails.orders.shipped')
    ->text('emails.orders.shipped_plai')
    ->attach('/path/to/file', [
        'as' => 'name.pdf',
        'mime' => 'application/pdf',
    ])
    ->with([
        'orderName' => $this->order->name,
        'orderPrice' => $this->order->price,
    ]); 
{{ $orderPrice }} <!-- 変数の値を出力 -->
{{ $message->embed($pathToFile) }} <!-- ファイル埋め込み -->
{{ $message->embed($data, $name) }} <!-- ファイル埋め込み -->

bladeコンポーネント
```php
@component('mail::button', ['url' => $url, 'color' => 'green'])
注文の確認
@endcomponent

@component('mail::panel')
ここはパネルの内容です。
@endcomponent

@component('mail::table')
| Laravel | テーブル | 例 |
| ------------- |:-------------:| --------:|
| Col 2 is | 中央寄せ | $10 |
| Col 3 is | 右寄せ | $20 |
@endcomponent

メール送信
php
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));

Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue(new OrderShipped($order));
```

通知

artisan make:notification InvoicePaid # 通知作成
$user->notify(new InvoicePaid($invoice));
Notification::send($users, new InvoicePaid($invoice));

メール通知
Markdownメール通知
データベース通知
ブロードキャスト通知
SMS通知
Slack通知

パッケージ開発

キュー

タスクスケジュール

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

Laravel7.0-セキュリティ-

(2020-04-24 Laravel7.0対応)

認証

認証後のリダイレクト先や処理をカスタマイズできる。
```php
$user = Auth::user(); // 現在認証されているユーザーの取得
$id = Auth::id(); // 現在認証されているユーザーのID取得
$request->user() // 認証済みユーザーのインスタンスを返す
Auth::check() // 現在のユーザーが認証されている場合true
Auth::attempt(['email' => $email, 'password' => $password]) // 認証を行い、成功した場合true

Route::get(...)->middleware('auth') // ルート定義で使用
$this->middleware('auth') // コントローラのコンストラクタで使用
middleware('auth:api') // ガードを指定

return redirect()->intended('dashboard'); // 認証フィルターにかかる前にアクセスしようとしていたURLへリダイレクトさせる。リダイレクトが不可能な場合は指定されたURIへ

Auth::logout(); // ログアウト

Auth::attempt(['email' => $email, 'password' => $password], $remember) // 継続ログイン

Auth::viaRemember() // remembermeクッキーを使用して認証されている場合true

Auth::login($user) // Userインスタンスによる認証 要Authenticatable契約
Auth::login($user, true) // Userインスタンスによる認証 remembber

Auth::loginUsingId(1)
Auth::loginUsingId(1, true)

Auth::once($credentials)
```

API認証

インストール
bash
composer require laravel/airlock
artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider"
artisan migrate

トークンの発行
```php
class User extends Authenticatable
{
use HasApiTokens;
...

$token = $user->createToken('token-name'); // トークンを発行
return $token->plainTextToken; // 平文を返す

$user->tokens()->delete(); // 全トークンの破棄
$user->tokens()->where('id', $id)->delete(); // 特定トークンの破棄
...
}
```

ルート保護
php
Route::middleware('auth:airlock')->get('/user', function (Request $request) {
return $request->user();
});

php
$response = $client->request('GET', '/api/user', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
],
]);

SPA認証

認可

ゲート
```php
// App\Providers\AuthServiceProvider boot関数内
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});

// コントローラ内でのアクションの認可
Gate::allows('update-post', $post) // 現在のユーザーはこのポストを更新できる場合true
Gate::denies('update-post', $post) // 現在のユーザーはこのポストを更新できない場合true
Gate::forUser($user)->allows('update-post', $post) // 渡されたユーザーはこのポストを更新できる場合true
Gate::forUser($user)->denies('update-post', $post) // 渡されたユーザーはこのポストを更新できない場合true
```

ポリシー作成
bash
artisan make:policy PostPolicy
artisan make:policy PostPolicy --model=Post

ポリシー記述
```php
class PostPolicy
{
public function create(User $user)
{
return $user->role === 'admin';
}

public function update(User $user, Post $post)
{
    return $user->id === $post->user_id;
}

}
```
AuthServiceProvider内でモデルにポリシーを登録

ポリシーフィルター(特定ユーザーには全アクションを許可する)
ポリシー
php
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}

アクションの認可
```php
$user->can('update', $post) // 現在のユーザーがポストを更新できる場合true
$user->can('create', Post::class) // モデルを必要としない場合

Route::put('/post/{post}', ...)->middleware('can:update,post'); // ミドルウェアによる認可
Route::put(...)->middleware('can:update,App\Post'); // モデルを必要としないアクション

$this->authorize('update', $post); // コントローラヘルパでの認可
$this->authorize('create', Post::class); // モデルを必要としない場合

Bladeテンプレートでの認可
html
@can('update', $post)
<!-- 現在のユーザーはポストを更新できる -->
@elsecan('create', App\Post::class)
<!-- 現在のユーザーはポストを作成できる -->
@endcan

@cannot('update', $post)
<!-- 現在のユーザーはポストを更新できない -->
@elsecannot('create', App\Post::class)
<!-- 現在のユーザーはポストを作成できない -->
@endcannot

@can('create', App\Post::class)
<!-- 現在のユーザーはポストを更新できる -->
@endcan

@cannot('create', App\Post::class)
<!-- 現在のユーザーはポストを更新できない -->
@endcannot
```

暗号化

artisan key:generate // キー生成
encrypt('secret') // OpenSSLとAES-2560CBCで暗号化
decrypt($encryptedValue) // 復号化
Crypt::encryptString('Hello world.') // シリアライズしないで暗号化
Crypt::decryptString($encrypted) // シリアライズしないで復号化

ハッシュ

Hash::make('password') // ハッシュ化した値を返す
Hash:check('password', $hashedPassword) // 一致したらtrue
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7.0-フロントエンド-

(2020-04-24 Laravel7.0対応)

Bladeテンプレート

<html>
    <head>
        <title>アプリ名 - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            ここがメインのサイドバー
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>
@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
    @parent

    <p>ここはメインのサイドバーに追加される</p>
@endsection

@section('content')
    <p>ここが本文のコンテンツ</p>
@endsection

コンポーネントとスロット
bash
artisan make:component Alert

```php
class Alert extends Component
{
public $type;
public $message;

public function __construct($type, $message)
{
    $this->type = $type;
    $this->message = $message;
}

public function render()
{
    return view('components.alert');
}

}

html


{{ $message }}
{{ $slot }}


html



Woops!Something went wrong.


データ表示
html
Hello, {{ $name }}.
Hello, {!! $name !!}. <!-- エスケープしない -->
The current UNIX timestamp is {{ time() }}. <!-- PHP関数 -->
var app = @json($array); <!-- JSONを表示 -->
Hello, @{{ name }}. <!-- JSフレームワークで処理する -->
@verbatim <!-- 囲まれた範囲の波括弧をJSフレームワークで処理する -->

Hello, {{ name }}.

@endverbatim

制御構文
php
@if (count($records) === 1)
@elseif (count($records) > 1)
@else
@endif

@unless (Auth::check())
@endunless

@isset($records)
@endisset

@empty($records)

@endempty

@auth
@endauth

@guest

@endguest

@auth('admin')
@endauth

@guest('admin')
@endguest

@switch($i)
@case(1)
@break
@case(2)
@break
@default
@endswitch

@for ($i = 0; $i < 10; $i++)
現在の値は: {{ $i }}
@endfor

@foreach ($users as $user)

これは {{ $user->id }} ユーザーです。


@endforeach

@while (true)

無限ループ中


@endwhile

@foreach ($users as $user)
@if ($loop->first)
// これは最初の繰り返し
@endif

@if ($loop->last)
    // これは最後の繰り返し
@endif
<p>これは {{ $user->id }} ユーザーです。</p>

@endforeach

@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
// これは親のループの最初の繰り返しだ
@endif
@endforeach
@endforeach

セクションディレクティブ
php
@hasSection('navigation')


@yield('navigation')
<div class="clearfix"></div>

@endif
```

プロパティ 説明
$loop->index 現在のループのインデックス(初期値0)
$loop->iteration 現在の繰り返し数(初期値1)
$loop->remaining 繰り返しの残り数
$loop->count 繰り返し中の配列の総アイテム数
$loop->first ループの最初の繰り返しか
$loop->last ループの最後の繰り返しか
$loop->even 偶数回目の繰り返しか
$loop->odd 奇数回目の繰り返しか
$loop->depth 現在のループのネストレベル
$loop->parent ループがネストしている場合、親のループ変数

コメント
php
{{-- このコメントはレンダー後のHTMLには現れない --}}

PHP
php
@php
//
@endphp

サブビュー
php
@include('shared.errors', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeWhen($boolean, 'view.name', ['some' => 'data'])
@includeUnless($boolean, 'view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])

コレクションのレンダービュー
php
@each('view.name', $jobs, 'job', 'view.empty')

スタック
php
@push('scripts')
<script src="/example.js"></script>
@endpush

サービス注入
```php
@inject('metrics', 'App\Services\MetricsService')

Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
# スカフォールド
```bash
composer require laravel/ui
artisan ui bootstrap
artisan ui vue
artisan ui react

artisan ui bootstrap --auth
artisan ui vue --auth
artisan ui react -- auth

アセットのコンパイル(Laravel Mix)

npm install
npm run dev
npm run production
npm run watch
npm run watch-poll
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps()
    .version()

mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');
mix.copyDirectory('resources/img', 'public/img');
<script src="{{ mix('/js/app.js') }}"></script>
<link rel="stylesheet" href="{{ mix('/css/app.css') }}">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7.0-基礎-

(2020-04-24 Laravel7.0対応)

ルーティング

Route::get('user/{user}', 'UserController@index')->name('user'); // メソッド パラメータ コントローラー 名前
Route::get('user/{user?}', 'UserController@index')->name('user');  // 任意パラメータ
Route::redirect('here', 'there', 301); // リダイレクト ステータスコード指定
Route::view('welcome', 'welcome', ['name' => 'Taylor']); // ビュー 名前

Route::resource('photos', 'PhotoController')->only('index'); // リソースルート
Route::resource('photos', 'PhotoController')->except('index'); // リソースルート
Route::apiResource('photo', 'PhotoController'); // APIリソースルート index store show update destroy

// ルートグループ
Route::namespace('Admin')->group(function () {
    // "App\Http\Controllers\Admin"名前空間下のコントローラ
});
Route::domain('{account}.myapp.com')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        // URL {account}.myapp.com/user/{id}
    });
});
Route::prefix('admin')->group(function () {
    // URL /admin/*
});

Route::current(); // ルートクラス
Route::currentRouteName(); // ルート名 未定義ならnull
Route::currentRouteAction(); // アクション名 未定義ならnull

ミドルウェア

artisan make:middleware CheckAge
Route::get('/', 'StaticController@index')->middleware('first', 'second');
Route::middleware(['first', 'second'])->group(function () {
    // firstとsecondミドルウェアを使用
}); 

CSRF保護

<form method="POST" action="/profile">
  @csrf <!--<input type="hidden" name="_token" value="7753xoabfc5SK4hv1mJo3NGwC47DLZ2ZbmGoJihX">-->
</form>

<meta name="csrf-token" content="{{ csrf_token() }}">
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

コントローラ

artisan make:controller PhotoController --resource // リソースコントローラ
artisan make:controller PhotoController --resource --model=Photo // モデル指定
return view('user.profile', ['user' => User::findOrFail($id)]);

public function __construct()
{
    $this->middleware('log')->only('index');
    $this->middleware('subscribed')->except('store');

    $this->middleware(function ($request, $next) {
        return $next($request);
    });
}
@method('PUT') <!-- <input type="hidden" name="_method" value="PUT"> -->

HTTPリクエスト

$request->path() // パス ドメイン以降
$request->is('admin/*') // パスと一致するかどうかを返す
$request->url() // クエリ文字列なし
$request->fullUrl() // クエリ文字列付き
$request->method() // メソッド
$request->isMethod('post') // メソッドと一致するかどうか

$request->all() // 全入力を配列として受け取る
$request->input() // queryとformの全入力の配列
$request->input('user.name', 'default') // queryとformから デフォルト値
$request->query() // queryの全入力の配列
$request->query('name', 'Helen') // queryから デフォルト値
$request->name // 動的プロバティ
$request->only(['username', 'password']) // queryとformから 一部取得
$request->except(['credit_card']) // queryとformから 一部取得
$request->has(['name', 'email']) // 指定値がすべて存在する場合 true
$request->filled('name') // 指定値が存在し、かつ空ではない場合 true

$request->flash() // 現在の入力を一時的にセッションに保存
$request->flashOnly(['username', 'email']) // 一部入力をフラッシュデータとしてセッションに保存
$request->flashExcept('password') // 一部入力をフラッシュデータとしてセッションに保存
$request->old('username') // フラッシュデータを取得
old('username') // フラッシュデータを取得

$request->cookie('name') // クッキーを取得

$request->file('photo') // ファイルの取得
$request->hasFile('photo') // ファイルが存在する場合にtrue
$request->file('photo')->isValid() // アップロードに成功している場合にtrue
$request->photo->path() // ファイルパス
$request->photo->extension() // 拡張子
$request->photo->store('images', 's3') // ファイルをストレージに保存
$request->photo->storeAs('images', 'filename.jpg', 's3') // ファイル名を指定してストレージに保存

HTTPレスポンス

return 'Hello World'; // 文字列をHTTPレスポンスに変換
return [1,2,3]; // 配列をJSONレスポンスに変換
return response('Hello World', 200)->header('Content-Type', 'text/plain'); // ヘッダーを付与

return response('Hello World', 200)->cookie($name, $value, $minutes, $path, $domain, $source, $httpOnly); // クッキーを付与
Cookie::queue('name', 'value', $minutes); // クッキーを付与

return redirect('home/dashboard'); // URLへのリダイレクト
return back()->withInput(); // 直前のページへのリダイレクト
return redirect()->route('profile', ['id' => 1]); // 名前付きルートへのリダイレクト パラメータ指定
return redirect()->route('profile', [$user]); // 名前付きルートへのリダイレクト パラメータ指定
return redirect()->action('HomeController@index'); // コントローラアクションへのリダイレクト
return redirect()->away('https://www.google.com'); // 外部サイトへのリダイレクト
return redirect('dashboard')->with('status', 'Profile updated!'); // フラッシュデータを保存してリダイレクト
return redirect()->intended('dashboard'); // 認証フィルターにかかる前にアクセスしようとしていたURLへリダイレクトする。
                       // リダイレクトが不可能な場合は指定されたURLへ

return response()->view('hello', $data, 200); // ビューレスポンス

return response()->json([
    'name' => 'Abigail',
    'state' => 'CA'
]); // JSONレスポンス

ビュー

View::exists('emails.customer') // ビューが存在する場合true
return view()->first(['custom.admin', 'admin'], $data); // 利用可能な最初のViewを返す

全ビュー間のデータ共有
ビューコンポーザ

URL生成

url("/posts/{$post->id}") // http://example.com/posts/1
url()->current(); // クエリ文字列を含んだ現在のURL
url()->full(); // クエリ文字列を含んだ現在のURL
url()->previous(); // 直前のリクエストの完全なURL

route('post.show', ['post' => 1]) // 名前付きルートのURL
route('post.show', ['post' => $post]) // 名前付きルートのURL

action('UserController@profile', ['id' => 1]) // コントローラアクションのURL

URL::defaults(['locale' => $request->user()->locale]); // routeのデフォルト値をミドルウェアで設定

HTTPセッション

config/session.phpでセッションの保存場所を設定可能
- file storage/framewrk/sessionsに保存
- cookie
- database
bash
artisan session:table
artisan migrate

- memcached/redis
要predis/predisパッケージ
- array
リクエスト間で継続しない
通常テストに使用

session('key', 'default') // セッションから値を取得
session(['key' => ''value]) // セッションに値を保存

$request->session()->get('key', 'default') // セッションから値を取得
$request->session()->all() // セッション内の全データを取得
$request->session()->exists('users') // セッション内の指定値があればtrue
$request->session()->has('users') // セッション内の指定値がnullでなければtrue
$request->session()->put('key', 'value') // セッションに値を保存
$request->session()->push('user.teams', 'developers') // セッション内の配列に値を追加
$request->session()->pull('key', 'default') // セッションの値を取得後削除
$request->session()->flash('status', 'Task was successful!') // フラッシュデータを保存
$request->session()->reflash() // フラッシュデータを次のリクエストまで持続させる
$request->session()->keep(['username', 'email']) // 特定のフラッシュデータだけ次のリクエストまで持続させる
$request->session()->forget('key') // セッションからデータを削除
$request->session()->flush() //セッションから全データを削除

$request->session()->regenerate() // セッションIDの再発行

カスタムセッションドライバの追加

バリデーション

コントローラ内でのバリデーション
php
$validatedData = $request->validate([
'title' => 'required|unique:posts',
'body' => 'required',
]);

viewでのエラー取得
※$errors変数はShareErrorsFromSessionミドルウェアによりビューに結合される
html
$errors->any() // エラーがある場合true
$errors->all() // エラーを取得

フォームリクエストバリデーションクラス作成
bash
artisan make:request StoreBlogPost

php
function rules(){...} // バリデーションルールの定義
function withValidator(){...} // フォールリクエスト実行後の処理を追加
function authorize(){...} // フォームリクエストの認可
function messages(){...} // エラーメッセージの定義

名前付きエラー
php
redirect('register')->withErrors($validator, 'login');

html
{{ $errors->login->first('email') }}

バリデーションインスタンス
```php
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts',
'body' => 'required',
]);

$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong!');
}
});

if ($validator->fails()) {
//
}

$validator->errors()->first('email') // 最初のエラーメッセージ取得
$validator->errors()->get('email') // 指定したフィールドの全エラーメッセージを取得
$validator->errors()->all() // 全フィールドの全エラーメッセージを取得
```
バリデーションルール

ルール 説明
accepted yes,on,1,true
active_url dns_get_record()により、有効なURLである
after:日付 指定された日付より後日付の代わりに他のフィールドを指定することも可能
alpha 全部アルファベット文字
alphas_dash 全部アルファベット文字と数字、ダッシュ(-)、下線(_)
alpha_num 全部アルファベット文字と数字
array 配列であること
before:日付 指定された日付より前日付の代わりに他のフィールドを指定することも可能
between:最大値,最小値 最小値から最大値の間文字数、数値、配列サイズ、ファイルサイズが評価される
boolean true,false,1,0,"1","0"
confirmed フィールド名+_confirmationのフィールドと同じ
date 日付
date_equals:日付 指定した日付を同じ
date_format:フォーマット フォーマット定義と一致
different:フィールド 指定されたフィールドと異なる
digits:値 数値で値の桁数
digits_between:最小値,最大値 整数で、桁数が最小値から最大値の間
distinct 配列の場合、フィールドに重複した値がない
email メールアドレス
exists:DB名.テーブル,カラム 指定されたデータベーステーブルに存在
file アップロードに成功したファイル
filled 存在する場合に空ではないこと
image 画像(jpg,png,bmp,gif,svg)
in:foo,bar 指定したリストの中の値に含まれている
in_array:フィールド 指定したフィールドの値のどれかであること
integer 整数値であること
json JSON
max:値 指定された値以下文字数、数値、配列サイズ、ファイルサイズを評価
min:値 指定した値以上文字数、数値、配列サイズ、ファイルサイズを評価
not_in:foo,bar,... 指定された値のリストに含まれない
nullable nullを許容
numeric 数値
password 認証中のユーザーのパスワードと一致
regex:正規表現 正規表現にマッチ
required フィールドが存在し、空でないこと
same:フィールド 指定されたフィールドを同じ値であること
size:値 指定された値と同じサイズであること文字数、数値、配列サイズ、ファイルサイズ
string 文字列
unique:テーブル,カラム,除外ID,IDカラム 指定されたデータベーステーブルで一意であること
Rule::exists('staff')->where(function ($query) {
    $query->where('account_id', 1);
}),

カスタムバリデーションルール

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

Laravel7.0-構成の概念-

(2020/04/24 Laravel 7.0に対応)

ライフサイクル

public/index.php
bootstrap/app.php // インスタンスを取得
app/Http/Kernel.php // bootstrappersの配列を定義
           // HTTPミドルウェアのリスト定義
           // サービスプロバイダ(コンポーネントを提供)の読み込み
ルーター
( app/HTTP/Middleware/*.php )
routes/*.php
app/Http/Controllers/*.php
( app/HTTP/Middleware/*.php )

サービスコンテナ

結合
依存関係
コンテナイベント

サービスプロバイダ

ファサード

ファサード クラス サービスコンテナ結合
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection db.connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Response (Instance) Illuminate\Http\Response
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 7.0-準備-

(2020-04-24 Laravel7.0に対応)

設定

artisan config:cache // 設定をキャッシュ env関数が使えなくなる
artisan down --message="Upgrading Database" --retry=60 // メンテナンスモード
artisan up // メンテナンスモード解除
App::environment() // 現在の環境を返す
App::environment('local') // 現在の環境がlocalならtrueを返す
App::environment(['local', 'staging']) // 現在の環境がlocalかstagingならtrueを返す

config('app.timezone', 'default value') // 設定の取得 デフォルト値を設定可能

デプロイ

composer install --optimize-autoloader --no-dev // オートローダー最適化
artisan config:cache // 設定をキャッシュ(env関数が使えなくなる)
artisan route:cache // ルートロードの最適化
artisan view:cache // ビューロードの最適化
artisan route:clear
artisan migrate // DB

Laravel Forge

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

Docker+Laravel+Codeception

LaravelでCodeceptionを使用した時の記録

Codeceptionの準備

下のコマンドでインストール

composer require codeception/codeception --dev

下のコマンドでテストに必要なファイルを作成

composer exec codecept bootstrap

seleniumサーバー用意

Acceptionテストに使用するseleniumサーバーを用意する
docker-compose.ymlに下の4行を追加する
yml
selenium:
...
depends_on:
- nginx
links:
- nginx:localhost

サーバー起動

docker-compose up -d selenium

その他の準備

envファイルのコピー

cp .env .env.testing

/tests/functional.suite.yml
yml
actor: FunctionalTester
modules:
enabled:
- Laravel5:
environment_file: .env.testing
- \Helper\Functional

/tests/unit.suite.yml
yml
actor: UnitTester
modules:
enabled:
- Asserts
- Laravel5:
environment_file: .env.testing
- \Helper\Unit

/tests/acceptance.suite.yml
yml
actor: AcceptanceTester
modules:
enabled:
- WebDriver:
url: http://localhost/
browser: chrome
host: selenium
- \Helper\Acceptance

参考

https://codeception.com/for/laravel

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

Laravel関連のおすすめVSCode拡張機能

VSCodeでLaravelのコードを書く際に便利な拡張機能をメモ
2020-06-22 内容を一部修正

Laravel

Laravel Extension Pack 下の9つの拡張機能をインストールする
            インストールしたくない拡張機能がある場合は個別にインストール
- Laravel Blade Snippets Bladeのコードスニペット
- Laravel Snippets Laravelのコードスニペット
- Laravel Artisan コマンドパレットでArtisanコマンドを実行
- Laravel goto view プログラムからテンプレートにジャンプ
- DotENV .envファイルのシンタックスハイライト

PHP

php cs fixer PHPコードの整形
PHP DocBlocker Docブロックの入力をサポート

Git

GitLens git操作

その他

Japanese Language Pack VSCodeの日本語化
VSNotes メモ機能 MarkDown対応

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

Laravelのテストメソッド抜粋

環境

Laravel5.8

前処理・後処理

    protected function setUp()
    {
        parent::setUp();
        // 前処理
    }

    public function tearDown()
    {
        parent::tearDown();
        // 後処理
    }

データベース

Artisan::call('migrate:refresh');
$this->seedseed($class = 'DatabaseSeeder');
$this->assertDatabaseHas($table, $data, $connection = null);
$this->assertDatabaseMissing($table, $data, $connection = null);
$this->assertSoftDeleted($table, $data, $connection = null);

ファクトリー

// インスタンスの作成
$user = factory(User::class)->make();
$user = factory(User::class)->make(["name" => "Abigail"]);
// インスタンスの保存
$user = factory(App\User::class)->create();

認証

$this->actingAs($user, $driver = null);
$this->assertAuthenticated($guard = null);
$this->assertGuest($guard = null);
$this->assertAuthenticatedAs($user, $guard = null);
$this->assertCredentials($credentials, $guard = null);
$this->assertInvalidCredentials($credentials, $guard = null);

Httpリクエスト

$response = $this->followRedirects($response);
$response = $this->get($uri, $headers = []);
$response = $this->post($uri, $data = [], $headers = []);
$response = $this->put($uri, $data = [], $headers = []);
$response = $this->patch($uri, $data = [], $headers = []);
$response = $this->delete($uri, $data = [], $headers = []);

レスポンス

$response->assertStatus($code);

$response->assertCookie($cookieName, $value = null);
$response->assertCookieMissing($cookieName);
$response->assertSessionHas($key, $value = null);
$response->assertSessionMissing($key);

$response->assertViewIs($value); //レスポンスが指定したビューかどうか
$response->assertViewHas($key, $value = null); //レスポンスのビューが指定したデータを持っている
$response->assertViewMissing($key);
$response->assertLocation($uri);
$response->assertRedirect($uri);

$response->assertSee($value);
$response->assertDontsee($value);
$response->assertSeeText($value);
$response->assertDontSeeText($value);

メール

Mail::fake();
Mail::assertNothingSent();

Mail::assertSent(OrderShipped::class, 2);
Mail::assertNotSent(AnotherMailable::class);
Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
    return $mail->order->id === $order->id &&
            $mail->hasTo($user->email) &&
            $mail->hasCc('...') &&
            $mail->hasBcc('...');
});

$failures = Mail::failures();

PHPUnit

$this->assertTrue($condition, $message = '');
$this->assertFalse($condition, $message = '');
$this->assertNull($variable, $message = '');
$this->assertSame($expected, $actual, $message = '');   // ===
$this->assertEquals($expected, $actual, $message = ''); // ==
$this->assertEmpty($actual, $message = '');
$this->assertNotEmpty($actual, $message = '');
$this->assertRegExp($pattern, $string, $message = '');
$this->assertContains($needle, $haystack, $message = '');
$this->assertCount($expectedCount, $haystack, $message = '');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel の artisan package:discover で database error した時のメモ

laravelを初めてherokuにデプロイする際に少しハマったのでメモ
PackageInstallの後のGenerating optimized autoload filesで下記のエラー

Illuminate\Foundation\ComposerScripts::postAutoloadDump
remote:        > @php artisan package:discover --ansi
remote:
remote:        In Connection.php line 664:
remote:
remote:          SQLSTATE[42P01]: Undefined table: 7 ERROR:  relation "******" does not exist

migrateしてないのでテーブルが見つからないのは当たり前なのだが。
調べたら ViewServiceProvicer の boot() で該当するテーブルの処理が問題らしいので一時的にコメントアウトしてデプロイー>migrateー>コメントを解除してデプロイで対応。

if(Schema::hasTable('users')) {}で対応したほうが良かったかもしれないが未検証。

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

Laravel関連 (個人的)命名規則

Laravelとその周辺の命名規則を個人的なものも含めてまとめたメモ
2020/06/18 Vueを追加

ソースコード

モデル名 EditUser (単数形、パスカルケース)
コントローラー名 EditUsersController (単数形か複数形、パスカルケース)
ビュー edit_users/create.blade.php (単数形か複数形、スネークケース)

  • コントローラーとビューには同じ規則を適用

データベース

テーブル名 edit_users (複数形、スネークケース)
カラム名 second_name (スネークケース)
外部キー edit_user_id (モデル名_id)
中間テーブル post_tag (モデルのアルファベット順、スネークケース)

edit_users (id int, name string, second_name string)
posts      (id int, body string, edit_user_id int)
tags       (id int, name string)
post_tag   (id int, post_id int, tag_id int)

PSR-2

クラス名 EditUsersController (パスカルケース)
メソッド名 createEditUser (キャメルケース)
変数 $edit_user (スネークケース)
定数 MAX_USER (大文字 + _)

  • 名前は長くても20文字程度

Javascript

オブジェクト・関数・インスタンス (キャメルケース)
js
var thisIsMyObject = {};
function thisIsMyFunction() {}
var user = new User({
name: 'Bob Parr'
});

クラス・コンストラクタ (パスカルケース)
```js
function User(options) {
this.name = options.name;
}

var good = new User({
name: 'yup'
});
```
定数 MAX_USER (大文字 + _)
プライベートメンバ _internalFunction (_ + キャメルケース)
イベントハンドラ onDialogAccept (on + パスカルケース)

Vue

ファイル名 ((Base, App, V + )パスカルケース) 複数単語にする
コンポーネント パスカルケース
propsを渡す側 ケバブケース
ptopsを受け取る側 キャメルケース
js
<UserListItem first-name="satou" />
...
export default {
props: {
firstName: String
}
}

HTML

class属性 CSSで使用するのでケバブケース
id属性 JavaScriptで使用するのでキャメルケース
data-*属性 JavaScriptで使用するのでキャメルケース

その他

ルート http://abc.de.jp/play-histories/{play_history} (ケバブケース)

  • 変数はスネークケース
  • 基本は小文字で統一

参考

https://laraweb.net/knowledge/942/
https://qiita.com/Takashi_INOUE/items/41d9fedaad1ff338cada
http://snowdream.github.io/javascript-style-guide/javascript-style-guide/jp/naming-conventions.html
https://www.asobou.co.jp/blog/web/url-optimisation
https://qiita.com/itagakishintaro/items/168667d5ee4c56b30d52
https://qiita.com/ngron/items/ab2a17ae483c95a2f15e

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

Laravel で Ruby on Rails チュートリアル【14章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

14.1 Relationshipモデル

14.1.1 データモデルの問題(および解決策)

(/app/Relationship.php)
php
class Relationship extends Model
{
protected $guarded = ['id'];
}

(/database/migrations/[timestamp]createrelationships.php)
```php
class CreateRelationships extends Migration
{
public function up()
{
Schema::create('relationships', function (Blueprint $table) {
$table->increments('id');
$table->integer('follower
id');
$table->integer('followed_id');
$table->timestamps();

        $table->index('follower_id');
        $table->index('followed_id');
        $table->unique(['follower_id', 'followed_id']);

        $table->foreign('follower_id')->references('id')->on('users')->onDelete('cascade');
        $table->foreign('followed_id')->references('id')->on('users')->onDelete('cascade');
    });
}

public function down()
{
    Schema::dropIfExists('relationships');
}

}
```

14.1.2 User/Relationshipの関連付け

(/app/User.php)
php
public function activeRelationships()
{
return $this->hasMany("App\Relationship", "follower_id");
}

(/app/Relationship.php)
```php
public function follower()
{
return $this->belongsTo("App\User", "id", "follower_id");
}

public function followed()
{
    return $this->belongsTo("App\User", "id", "followed_id");
}
# 14.1.3 Relationshipのバリデーション

# 14.1.4 フォローしているユーザー

(/app/User.php)
```php
    public function following()
    {
        return $this->hasManyThrough("App\User", "App\Relationship", "follower_id", "id", "id", "followed_id");
    }

    public function isFollowing($user_id)
    {
        return (bool) Relationship::where("follower_id", $this->id)->where("followed_id", $user_id)->count();
    }

(/tests/Unit/UserTest.php)
php
public function testFollowAndUnfollow()
{
$user_1 = User::find(1);
$user_2 = User::find(2);
$this->assertFalse($user_1->isFollowing($user_2->id));
$user_1->follow($user_2->id);
$this->assertTrue($user_1->isFollowing($user_2->id));
$user_1->unfollow($user_2->id);
$this->assertFalse($user_1->isFollowing($user_2->id));
}

(/app/User.php)
```php
public function follow($user_id)
{
$this->activeRelationships()->create(["followed_id" => $user_id]);
}

public function unfollow($user_id)
{
    $this->activeRelationships()->where("followed_id", $user_id)->delete();
}

public function isFollowed($user_id)
{

    return (bool) Relationship::where("follower_id", $user_id)->where("followed_id", $this->id)->count();
}
# 14.1.5 フォロワー

(/app/User.php)
```php
    public function passiveRelationships()
    {
        return $this->hasMany("App\Relationship", "followed_id");
    }

    public function followers()
    {
        return $this->hasManyThrough("App\User", "App\Relationship", "followed_id", "id", "id", "follower_id");
    }

(/tests/Unit/UserTest.php)
php
$this->assertEquals(1, $user_1->following()->where("followed_id", $user_2->id)->count());

14.2 Follow のWebインターフェイス

14.2.1 フォローのサンプルデータ

(/database/seeds/UsersTableSeeder.php)
php
$user = User::first();
$following = User::where("id", ">=", 2)->where("id", "<=", 50);
$followers = User::where("id", ">=", 3)->where("id", "<=", 40);
$following->each(function ($following) use ($user) { $user->follow($following->id); });
$followers->each(function ($follower) use ($user) { $follower->follow($user->id); });

14.2.2 統計と Follow フォーム

(/routes/web.php)
php
Route::prefix('users')->group(function () {
Route::get('{user}/following', "UsersController@following")->name("following");
Route::get('{user}/followers', "UsersController@followers")->name("followers");
});

(/resources/views/shared/stats.blade.php)
html
@php empty($user) && $user = Auth::user() @endphp
<div class="stats">
<a href="{{ route("following", $user->id) }}">
<strong id="following" class="stat">
{{ $user->following()->count() }}
</strong>
following
</a>
<a href="{{ route("followers", $user->id) }}">
<strong id="followers" class="stat">
{{ $user->followers()->count() }}
</strong>
followers
</a>
</div>

(/resources/views/static_pages/home.blade.php)
html
<section class="stats">
@include ("shared.stats")
</section>

(/routes/web.php)
php
Route::resource('relationships', "RelationshipsController", ["only" => ['store', 'destroy']]);

(/resources/views/users/follow_form.blade.php)
html
@unless ($user == Auth::user())
<div id="follow_form">
@if (Auth::user()->isFollowing($user->id))
@include("users.unfollow")
@else
@include("users.follow")
@endif
</div>
@endunless

(/resources/views/users/follow.blade.php)
html
{{ Form::open(["route" => "relationships.store"]) }}
{{ Form::hidden('followed_id', $user->id) }}
{{ Form::submit("Follow", ["class" => "btn btn-primary"]) }}
{{ Form::close() }}

(/resources/views/users/unfollow.blade.php)
html
{{ Form::open(["route" => ["relationships.destroy", $user->id], "method" => "delete"]) }}
{{ Form::submit("Unfollow", ["class" => "btn"]) }}
{{ Form::close() }}

(/resources/views/users/show.blade.php)
```html
@extends('layouts.application')

@section('title', $user->name)

@section('content')





{!! gravatar_for($user) !!}
{{ $user->name }}




@include("shared.stats")



@includeWhen (Auth::check(), "users.follow_form")
@if ($user->microposts())

Microposts ({{ $user->microposts()->count() }})



    @foreach ($microposts as $micropost)
    @include ("microposts.micropost")
    @endforeach

{{ $microposts->links() }}
@endif


@endsection
```

14.2.3 Following と Followers ページ

(/tests/Unit/UsersControllerTest.php)
```php
public function testRedirectFollowing()
{
$response = $this->get(route("following", 1));
$response->assertRedirect(route("login"));
}

public function testRedirectFollowers()
{
    $response = $this->get(route("followers", 1));
    $response->assertRedirect(route("login"));
}
(/app/Http/Controllers/UsersController.php)
```php
    public function __construct()
    {
        $this->middleware('guest')->only(["index", "edit", "update", "destroy", "following", "followers"]);
    }

    public function following($user)
    {
        $title = "Following";
        $user  = User::find($user);
        $users = $user->following()->paginate(30);
        return view("users.show_follow")->with(["title" => $title, "user" => $user, "users" => $users]);
    }

    public function followers($user)
    {
        $title = "Following";
        $user  = User::find($user);
        $users = $user->followers()->paginate(30);
        return view("users.show_follow")->with(["title" => $title, "user" => $user, "users" => $users]);
    }

(/resources/views/users/show_follow.blade.php)
```html
@extends('layouts.application')

@section('title', $title)

@section('content')




{!! gravatar_for($user) !!}

{{ $user->name }}


{{ Html::linkRoute("users.show", "view my profile", $user->id) }}
Microposts: {{ $user->microposts()->count() }}


@include("shared.stats")
@if ($users)

@endif



{{ $title }}


@if ($users)

{{ $users->links() }}
@endif


@endsection

(/database/seeds/test/TestSeeder.php)
php
Relationship::create(["follower_id" => 1, "followed_id" => 3]);
Relationship::create(["follower_id" => 1, "followed_id" => 4]);
Relationship::create(["follower_id" => 3, "followed_id" => 1]);
Relationship::create(["follower_id" => 2, "followed_id" => 1]);

(/tests/Feature/FollowingTest.php)
php
class FollowingTest extends TestCase
{
private $user;
protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:refresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
    $this->be($this->user);
}

public function testFollowingPage()
{
    $response = $this->get(route("following", $this->user->id));
    $this->assertNotEmpty($this->user->following());
    $response->assertSeeText((string) $this->user->following()->count());
    $dom = $this->dom($response->content());
    foreach ($this->user->following as $following) {
        $this->assertSame(route("users.show", $following->id), $dom->filter("a:contains(\"{$following->name}\")")->attr("href"));
    }
}

public function testFollowersPage()
{
    $response = $this->get(route("followers", $this->user->id));
    $this->assertNotEmpty($this->user->followers());
    $response->assertSeeText((string) $this->user->followers()->count());
    $dom = $this->dom($response->content());
    foreach ($this->user->followers as $follower) {
        $this->assertSame(route("users.show", $follower->id), $dom->filter("a:contains(\"{$follower->name}\")")->attr("href"));
    }
}

}
```

14.2.4 Follow ボタン (基本編)

(/tests/Unit/RelationshipsControllerTest.php)
```php
class RelationshipsControllerTest extends TestCase
{
public function testRedirectCreatePage()
{
$count = Relationship::all()->count();
$response = $this->post(route("relationships.create"));
$this->assertEquals($count, Relationship::all()->count());
$response->assertRedirect(route("login"));
}

public function testRedirectDestroyPage()
{
    $count = Relationship::all()->count();
    $response = $this->delete(route("relationships.destory", 1));
    $this->assertEquals($count, Relationship::all()->count());
    $response->assertRedirect(route("login"));
}

}

(/app/Http/Controllers/RelationshipsController.php)
php
class RelationshipsController extends Controller
{
public function __construct()
{
$this->middleware('authenticate');
}

public function store(Request $request)
{
    Auth::user()->follow($request->followed_id);
    return redirect()->route("users.show", Auth::id());
}

public function destroy($id)
{
    Auth::user()->unfollow($id);
    return redirect()->route("users.show", Auth::id());
}

}
```

14.2.5 Follow ボタン (Ajax編)

14.2.6 フォローをテストする

(/tests/Feature/FollowingTest.php)
```php
class FollowingTest extends TestCase
{
private $user;
private $other_user;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:refresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
    $this->other_user = User::find(2);
    $this->be($this->user);
}

public function testFollowUser()

{
    $count = Relationship::all()->count();
    $this->post(route("relationships.store"), ["followed_id" => $this->other_user->id]);
    $this->assertEquals($count + 1, Relationship::all()->count());
}

public function testUnfollowUser()
{
    $this->user->follow($this->other_user->id);
    $count = Relationship::all()->count();
    $this->delete(route("relationships.destroy", $this->other_user->id));
    $this->assertEquals($count - 1, Relationship::all()->count());
}

}
```

14.3 ステータスフィード

14.3.1 動機と計画

(/tests/Unit/UserTest.php)
php
public function testStatusFeed()
{
$user_1 = User::find(1);
$user_2 = User::find(2);
$user_3 = User::find(3);
foreach ($user_3->microposts as $micropost) {
$this->assertTrue($user_1->feed()->get()->contains($micropost));
}
foreach ($user_1->microposts as $micropost) {
$this->assertTrue($user_1->feed()->get()->contains($micropost));
}
foreach ($user_2->microposts as $micropost) {
$this->assertFalse($user_1->feed()->get()->contains($micropost));
}
}

14.3.2 フィードを初めて実装する

(/app/User.php)
php
public function feed()
{
$relations = $this->activeRelationships()->get()->toArray();
$followed_ids = array_pluck($relations, "followed_id");
return Micropost::whereIn("user_id", $followed_ids)->orWhere("user_id", $this->id);
}

(/app/Http/Controllers/StaticPagesController.php)
php
public function home()
{
$feed_items = null;
if (Auth::check()) {
$feed_items = Auth::user()->feed()->paginate(30);
}
return view('static_pages/home')->with("feed_items", $feed_items);
}

14.3.3 サブセレクト

14.4 最後に

14.4.1 サンプルアプリケーションの機能を拡張する

14.4.2 読み物ガイド

14.4.3 本省のまとめ

14.4.4 役者あとがき

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

Laravel で Ruby on Rails チュートリアル【13章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

13.1 Micropostモデル

13.1.1 基本的なモデル

(/app/Micropost.php)
php
class Micropost extends Model
{
public function user()
{
return $this->belongsTo("App\User");
}
}

(/database/migrations/[timestamp]create_microposts_table.php)
```php
class CreateMicropostsTable extends Migration
{
public function up()
{
Schema::create('microposts', function (Blueprint $table) {
$table->increments('id');
$table->text('content');
$table->integer('user
id');
$table->timestamps();

        $table->foreign('user_id')->references('id')->on('users');
    });
}

public function down()
{
    Schema::dropIfExists('microposts');
}

}
```

13.1.2 Micropostのバリデーション

(/app/Http/Controllers/MicropostsController.php)
php
public function store(Request $request)
{
$request->validate([
'content' => 'required|max:140'
]);
}

13.1.3 User/Micropostの関連付け

(/app/Microposts.php)
```php
class Micropost extends Model
{
protected $guarded = ['id'];

public function user()
{
    return $this->belongsTo("User");
}

}

(/app/User.php)
php
class User extends Authenticatable
{
public function microposts()
{
return $this->hasMany("App\Micropost");
}
}
```

13.1.4 マイクロポストを改良する

(/tests/Unit/MicropostTest.php)
php
public function testMicropostOrder()
{
$this->assertEquals(Micropost::orderBy("created_at", "desc")->first(), Micropost::first());
}

(/database/seeds/tests/TestSeeder.php)
php
class TestSeeder extends Seeder
{
public function run()
{
Micropost::create([
"content" => "I just ate an orange!",
"created_at" => Carbon::now()->subminutes(10)
]);
Micropost::create([
"content" => "Check out the @tauday site by @mhartl: http://tauday.com",
"created_at" => Carbon::now()->subYears(3)
]);
Micropost::create([
"content" => "Sad cats are sad: http://youtu.be/PKffm2uI4dk",
"created_at" => Carbon::now()->subHours(2)
]);
Micropost::create([
"content" => "Writing a short test",
"created_at" => Carbon::now()
]);
}
}

(/app/Micropost.php)
php
protected static function boot()
{
parent::boot();
static::addGlobalScope('created_at', function (Builder $builder) {
$builder->orderBy('created_at', 'desc');
});
}

(/database/migrations/[timestamp]_create_microposts_table.php)
php
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

13.2 マイクロポストを表示する

13.2.1 マイクロポストの描画

(/resources/views/microposts/micropost.blade.php)
html
<li id="micropost-{{ $micropost->id }}">
<a href="{{ route("users.show", $micropost->user_id) }}">{!! gravatar_for($micropost->user, ["size" => 50]) !!}</a>
<span class="user">{{ Html::linkRoute("users.show", $micropost->user->name, $micropost->user_id) }}</span>
<span class="content">{{ $micropost->content }}</span>
<span class="timestamp">
Posted {{ time_ago_in_words($micropost->created_at) }} ago.
</span>
</li>

(/app/Http/Controller/UsersController)
php
$microposts = $user->microposts()->paginate(30);
return view('users.show')->with(['user' => $user, 'microposts' => $microposts]);

(/resouces/Views/users/show.blade.php)
html
<div class="col-md-8">
@if ($user->microposts())
<h3>Microposts ({{ $user->microposts()->count() }})</h3>
<ol class="microposts">
@foreach ($microposts as $micropost)
@include("microposts.micropost")
@endforeach
</ol>
{{ $microposts->links() }}
@endif
</div>

(/app/helper.php)
php
if (! function_exists('time_ago_in_words')) {
function time_ago_in_words($date)
{
return \Carbon\Carbon::parse($date)->diffForHumans();
}
}

13.2.2 マイクロポストのサンプル

(/database/Factories/MicropostFactory.php)
php
$factory->define(Micropost::class, function (Faker $faker) {
return [
'content' => $faker->text,
'created_at' => $faker->dateTimeThisYear,
];
});

(/database/seeds/DatabaseSeeder.php)
php
User::take(6)->get()->each(function ($u) {
$u->microposts()->saveMany(Factory(Micropost::class, 50)->make(["user_id" => $u["id"]]));
});

13.2.3 プロフィール画面のマイクロポストをテスト

(/database/seeds/test/TestSeeder.php)
```php
Micropost::create([
"content" => "I just ate an orange!",
"created_at" => Carbon::now()->subminutes(10),
"user_id" => 1
]);
Micropost::create([
"content" => "Check out the @tauday site by @mhartl: http://tauday.com",
"created_at" => Carbon::now()->subYears(3),
"user_id" => 1
]);
Micropost::create([
"content" => "Sad cats are sad: http://youtu.be/PKffm2uI4dk",
"created_at" => Carbon::now()->subHours(2),
"user_id" => 1
]);
Micropost::create([
"content" => "Writing a short test",
"created_at" => Carbon::now(),
"user_id" => 1
]);

    factory(Micropost::class, 30)->create(["user_id" => 1, "created_at" => Carbon::now()->subDays(42)]);
(/tests/Feature/UsersProfileTest.php)
```php
class UsersProfileTest extends TestCase
{
    private $user;

    protected function setUp()
    {
        parent::setUp();
        Artisan::call('migrate:fresh');
        $this->seed('TestSeeder');
        $this->user = User::find(1);
    }

    public function testProfileDisplay()
    {
        $response = $this->get(route("users.show", $this->user->id));
        $response->assertViewIs("users.show");
        $dom = $this->dom($response->content());
        $this->assertSame(full_title($this->user->name), $dom->filter("title")->text());
        $this->assertRegExp("/".$this->user->name."/", $dom->filter('h1')->text());
        $this->assertSame(1, $dom->filter("h1>img.gravatar")->count());
        $response->assertSeeText((string) $this->user->microposts()->count());
        $this->assertSame(1, $dom->filter("ul.pagination")->count());
        foreach ($this->user->microposts()->paginate(30) as $micropost) {
            $response->assertSeeText($micropost->content);
        }
    }
}

13.3 マイクロポストを操作する

(/routes/web.php)
php
Route::resource('microposts', "MicropostsController", ["only"=> ['store', 'destroy']]);

13.3.1 マイクロポストのアクセス制御

(/tests/Unit/MicropostsControllerTest.php)
```php
class MicropostsControllerTest extends TestCase
{
private $micropost;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:fresh');
    $this->seed('TestSeeder');
    $this->micropost = Micropost::find(1);
}

public function testRedirectCreate()
{
    $count = Micropost::all()->count();
    $response = $this->post(route("microposts.store", ["content" => "Lorem ipsum"]));
    $this->assertEquals($count, Micropost::all()->count());
    $response->assertRedirect(route("login"));
}

public function testRedirectDestroy()
{
    $count = Micropost::all()->count();
    $response = $this->delete(route("microposts.destroy", $this->micropost->id));
    $this->assertEquals($count, Micropost::all()->count());
    $response->assertRedirect(route("login"));
}

}

(/app/Http/Controllers/MicropostsController.php)
php
class MicropostsController extends Controller
{
public function __construct()
{
$this->middleware('authenticate')->only(["store", "destroy"]);
}
}
```

13.3.2 マイクロポストを作成する

(/app/Http/Controllers/MicropostsController.php)
php
public function store(Request $request)
{
$request->validate([
'content' => 'required|max:140'
]);
$micropost = new Micropost(["content" => $request->content]);
Auth::user()->microposts()->save($micropost);
session()->flash('message', ['success' => 'Micropost created!']);
return redirect("/");
}

(/resources/views/static_pages/home.blade.php)
```html
@extends('layouts.application')

@section('content')
@if (Auth::check())




@include("shared.user_info")


@include("shared.micropost_form")



@else

Welcome to the Sample App

    <h2>
        This is the home page for the
        <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
        sample application.
    </h2>

    {{ Html::linkRoute('signup', "Sign up now!", [], ["class" => "btn btn-lg btn-primary"]) }}
</div>

<a href="http://rubyonrails.org/">{{ Html::image("img/rails.png", "Rails logo") }}</a>

@endif
@endsection

(/resources/views/shared/user_info.blade.php)
html
{!! gravatar_for(Auth::user(), ["size" => 50]) !!}

{{ Auth::user()->name }}


{{ Html::linkRoute("users.show", "view my profile", Auth::id()) }}
{{ Auth::user()->microposts()->count() . " " . str_plural('micropost', Auth::user()->microposts()->count()) }}

(/resources/views/shared/micropost_form.blade.php)
html
{{ Form::open(["route" => "microposts.store"]) }}
@include('shared.error_messages')

{{ Form::textarea('content', null, ["placeholder" => "Compose new micropost..."]) }}

{{ Form::submit("Post", ["class" => "btn btn-primary"]) }}
{{ Form::close() }}
```

13.3.3 フィードの原型

(/app/Http/Controllers/StaticPagesController.php)
php
public function home()
{
$feed_items = null;
if (Auth::check()) {
$feed_items = Auth::user()->microposts()->paginate(30);
}
return view('static_pages/home')->with("feed_items", $feed_items);
}

(/resources/views/shared/feed.blade.php)
html
<ol class="microposts">
@foreach ($feed_items as $micropost)
@include("microposts.micropost")
@endforeach
</ol>
{{ $feed_items->links() }}

(/resources/views/static_pages/home.blade.php)
```html
@extends('layouts.application')

@section('content')
@if (Auth::check())




@include("shared.user_info")


@include("shared.micropost_form")



Micropost Feed


@includeWhen($feed_items, "shared.feed")


@else

Welcome to the Sample App

    <h2>
        This is the home page for the
        <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
        sample application.
    </h2>

    {{ Html::linkRoute('signup', "Sign up now!", [], ["class" => "btn btn-lg btn-primary"]) }}
</div>

<a href="http://rubyonrails.org/">{{ Html::image("img/rails.png", "Rails logo") }}</a>

@endif
@endsection
```

13.3.4 マイクロポストを削除する

(/resources/views/microposts/micropost.blade.php)
```html

課題

(/tests/Feature/MicropostsInterfaceTest.php)
php
public function testSidebarCount()
{
$this->be($this->user);
$response = $this->get("/");
$response->assertSeeText("{$this->user->microposts()->count()} microposts");
$other_user = User::find(4);
$this->be($other_user);
$response = $this->get("/");
$response->assertSeeText("0 micropost");
$other_user->microposts()->create(["content" => "A micropost"]);
$response = $this->get("/");
$response->assertSeeText("A micropost");
}

13.4 マイクロポストの画像投稿

13.4.1 基本的な画像のアップロード

composer require intervention/image

(.env)

FILESYSTEM_DRIVER=public

(/database/migrations/[timestamp]_add_picture_to_microposts.php)
```php
class AddPictureToMicroposts extends Migration
{
public function up()
{
Schema::table('microposts', function (Blueprint $table) {
$table->string('picture')->nullable();
});
}

public function down()
{
    Schema::table('microposts', function (Blueprint $table) {
        $table->dropColumn('picture');
    });
}

}

(/app/Http/Controllers/MicropostsController.php)
php
public function store(Request $request)
{
$request->validate([
'content' => 'required|max:140'
]);
$micropost = new Micropost;
$micropost->content = $request->content;
if ($request->hasFile('picture')) {
$file = $request->picture;
$path = "micropost_proto/" . $file->hashName();
$encode_file = Image::make($file)->encode();
Storage::put($path, (string) $encode_file, "public");
$micropost->picture = $path;
}
Auth::user()->microposts()->save($micropost);
session()->flash('message', ['success' => 'Micropost created!']);
return redirect("/");
}

(/resources/views/shared/micropost_form.blade.php)
html
{{ Form::open(["route" => "microposts.store", 'files' => true]) }}
@include('shared.error_messages')


{{ Form::textarea('content', null, ["placeholder" => "Compose new micropost..."]) }}

{{ Form::submit("Post", ["class" => "btn btn-primary"]) }}

{{ Form::file("picture") }}

{{ Form::close() }}

(/resources/views/microposts/micropost.blade.php)
html

{{ $micropost->content }}
@if ($micropost->picture)
課題

(/tests/Feature/MicropostsInterfaceTest.php)
```php
public function testMicropostInterface()
{
Storage::fake('design');

    $this->be($this->user);
    $response = $this->get("/");
    $this->assertSame(1, $this->dom($response->content())->filter("ul.pagination")->count());
    $this->assertSame(1, $this->dom($response->content())->filter("input[type=file]")->count());
    $count = Micropost::all()->count();
    $response = $this->followingRedirects()->post(route("microposts.store"), ["content" => " "]);
    $this->assertEquals($count, Micropost::all()->count());
    $this->assertSame(1, $this->dom($response->content())->filter("div#error_explanation")->count());
    $content = "This micropost really ties the room together";
    $picture = UploadedFile::fake()->image('design.jpg');
    $count = Micropost::all()->count();
    $response = $this->followingRedirects()
                    ->post(route("microposts.store"), [
                        "content" => $content,
                        "picture" => $picture
                    ]);
    $this->assertEquals($count + 1, Micropost::all()->count());
    $response->assertViewIs("static_pages.home");
    $response->assertSeeText($content);
    $this->assertGreaterThan(0, $this->dom($response->content())->filter("a:contains(\"delete\")")->count());
    $first_micropost = $this->user->microposts->first();
    $count = Micropost::all()->count();
    $response = $this->followingRedirects()->delete(route("microposts.destroy", $first_micropost->id));
    $this->assertEquals($count - 1, Micropost::all()->count());
    $response = $this->get(route("users.show", 2));
    $this->assertSame(0, $this->dom($response->content())->filter("a:contains(\"delete\")")->count());
}
# 13.4.2 画像の検証

(/app/Http/Controllers/MicropostController.php)
```php
        $request->validate([
            'content' => 'required|max:140',
            'picture' => 'nullable|mimes:jpeg,gif,png|image|max:5120'
        ]);

(/resources/views/shared/micropost_form.blade.php)
html
<script type="text/javascript">
$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
</script>

13.4.3 画像のリサイズ

(/app/Http/Controllers/MicropostsController.php)
php
public function store(Request $request)
{
$request->validate([
'content' => 'required|max:140',
'picture' => 'nullable|mimes:jpeg,gif,png|image|max:5120'
]);
$micropost = new Micropost;
$micropost->content = $request->content;
if ($request->hasFile('picture')) {
$file = $request->picture;
$path = "micropost_proto/" . $file->hashName();
$encode_file = Image::make($file)->resize(400, 400, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->encode();
Storage::put($path, (string) $encode_file, "public");
$micropost->picture = $path;
}
Auth::user()->microposts()->save($micropost);
session()->flash('message', ['success' => 'Micropost created!']);
return redirect("/");
}

13.4.4 本番環境での画像アップロード

本番環境ではs3を使用する

composer require league/flysystem-aws-s3-v3

(本番環境の.env)
```
AWS_ACCESS_KEY_ID="作成したアクセスキー"
AWS_SECRET_ACCESS_KEY="作成したシークレットキー"
AWS_DEFAULT_REGION=ap-northeast-1(東京の場合)
AWS_BUCKET="作成したバケット"

FILESYSTEM_DRIVER=s3


heroku config:set AWS_ACCESS_KEY_ID="作成したアクセスキー"
heroku config:set AWS_SECRET_ACCESS_KEY="作成したシークレットキー"
heroku config:set AWS_DEFAULT_REGION=ap-northeast-1(東京の場合)
heroku config:set AWS_BUCKET="作成したバケット"

heroku config:set FILESYSTEM_DRIVER=s3
```
s3を用意する
https://qiita.com/tiwu_official/items/ecb115a92ebfebf6a92f

13.5 最後に

herokuにgdをインストールする
(composer.json)
json
"require": {
"ext-gd": "*",


composer update

herokuのnginxとphp.iniでファイルのアップロードサイズにかかっている制限を修正
(/Procfile)

web: vendor/bin/heroku-php-nginx -C heroku_nginx.conf public/

(/heroku_nginx.conf)

client_max_body_size 20M;

(/public/.user.ini)

post_max_size = 20M
upload_max_filesize = 5M

参考

https://qiita.com/Yorinton/items/0ca1f2802244581afc83
http://kayakuguri.github.io/blog/2017/06/16/larave-std-error/

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

Laravel で Ruby on Rails チュートリアル【12章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

12.1 PasswordResetsリソース

12.1.1 PasswordResetsコントローラー

ルート追加(/routes/web.php)
php
Route::get('password_resets/create', "PasswordResetsController@create")->name("resets.create");
Route::post('password_resets', "PasswordResetsController@store")->name("resets.store");
Route::get('password_resets/{token}/edit', "PasswordResetsController@edit")->name("resets.edit");
Route::patch('password_resets/{token}', "PasswordResetsController@update")->name("resets.update");

レイアウト(/resources/views/sessions/create.blade.php)
ppp
{{ Html::linkRoute("resets.create", "(forgot password)") }}

12.1.2 新しいパスワードの設定

ミグレーション(/dataase/migrations/[timestamp]add_reset_to_users.php)
```php
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('reset
digest')->nullable();
$table->dateTime('reset_sent_at')->nullable();
});
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('reset_digest');
        $table->dropColumn('reset_sent_at');
    });
}
(/resources/views/password_resets/create.blade.php)
```html
@extends('layouts.application')

@section('title', 'Forgot password')

@section('content')
<h1>Forgot password</h1>

<div class="row">
    <div class="col-md-6 col-md-offset-3">
        {{ Form::open(['route' => 'resets.store']) }}

        {{ Form::label('email') }}
        {{ Form::text('email', "", ["class" => "form-control"]) }}

        {{ Form::submit("Submit", ["class" => "btn btn-primary"]) }}
        {{ Form::close() }}
    </div>
</div>
@endsection

(/app/Http/Controllers/PasswordResetsController.php)
php
public function create()
{
return view("password_resets.create");
}

12.1.3 createアクションでパスワード再設定

(/app/Http/Conttollers/PasswordResetsController.php)
php
public function store(Request $request)
{
$user = User::where("email", $request->email)->first();
if (!$user) {
session()->flash('message', ['danger' => 'Email address not found']);
return redirect()->back();
}
$reset_token = str_random(22);
$user->reset_digest = bcrypt($reset_token);
$user->reset_sent_at = Carbon::now();
$user->save();
$user->sendPasswordResetMail($reset_token);
session()->flash('message', ['info' => 'Email sent with password reset instructions']);
return redirect("/");
}

(/app/User.php)
php
public function sendPasswordResetMail($token)
{
Mail::to($this)->send(new PasswordReset($this, $token));
}

12.2 パスワード再設定のメール送信

12.2.1 パスワード再設定のメールとテンプレート

(/app/Mail/PasswordReset.php)
```php
class PasswordReset extends Mailable
{
use Queueable, SerializesModels;

public $user;
public $reset_token;

public function __construct($user, $reset_token)
{
    $this->user = $user;
    $this->reset_token = $reset_token;
}

public function build()
{
    return $this->from("noreply@example.com")
                ->subject("Password reset")
                ->view('emails.password_reset');
}

}

(/resources/views/emails/password_reset.blade.php)
html

Password reset

To reset your password click the link below:

{{ Html::linkRoute("resets.edit", "Reset password", ["token" => $reset_token, "email" => $user->email]) }}

This link will expire in two hours.

If you did not request your password to be reset, please ignore this email and your password will stay as it is.

# 12.2.2 送信メールのテスト

(/tests/Unit/MailerTest.php)
```php
    public function testPasswordReset()
    {
        Mail::fake();

        $user = User::find(1);
        $reset_token = str_random(22);
        Mail::to($user)->send(new PasswordReset($user, $reset_token));
        Mail::assertSent(PasswordReset::class, function ($mail) use ($user, $reset_token) {
            $mail->build();
            $this->assertEquals("Password reset", $mail->subject);
            $this->assertTrue($mail->hasTo($user->email));
            $this->assertTrue($mail->hasFrom("noreply@example.com"));
            $this->assertEquals($user->name, $mail->user->name);
            $this->assertEquals($reset_token, $mail->reset_token);
            $this->assertEquals($user->email, $mail->user->email);
            return true;
        });
    }

12.3 パスワードを再設定する

12.3.1 editアクションで再設定

(/resources/views/password_resets/edit.blade.php)
```html
@extends('layouts.application')

@section('title', 'Reset password')

@section('content')

Reset password

{{ Form::model($user, ["route" => ["resets.update", $token], "method" => "patch"]) }} @include('shared.error_messages') {{ Form::hidden('email', $user->email) }} {{ Form::label('password') }} {{ Form::password('password', ["class" => "form-control"]) }} {{ Form::label('password_confirmation') }} {{ Form::password('password_confirmation', ["class" => "form-control"]) }} {{ Form::submit("Update password", ["class" => "btn btn-primary"]) }} {{ Form::close() }}

@endsection

(/app/Http/Controllers/PasswordResetController.php)
php
public function _construct()
{
$this->middleware(function ($request, $next) {
$user = User::where("email", $request->email)->first();
if (!$user || !$user->activated || !Hash::check($request->token, $user->reset_digest)) {
return redirect('/');
}
return $next($request);
})->only(["edit", "update"]);
}
...
public function edit(Request $request)
{
$user = User::where("email", $request->email)->first();
return view("password
resets.edit")->with(["user" => $user, "token" => $request->token]);
}
```

12.3.2 パスワードを更新する

(/app/Http/Controllers/PasswordResetsController.php)
```php
public function __construct()
{
$this->middleware(function ($request, $next) {
$user = User::where("email", $request->email)->first();
if ($user->checkExpiration()) {
session()->flash('message', ['danger' => 'Password reset has expired']);
return redirect()->back();
}
return $next($request);
})->only(["edit", "update"]);
}

public function update(Request $request)
{
    $request->validate([
        'password' => 'required|min:6|confirmed',
        'password_confirmation' => 'required|min:6',
    ]);

    $user = User::where("email", $request->email)->first();
    $user->password = bcrypt($request->password);
    $user->reset_digest = null;
    $user->save();
    Auth::login($user);
    session()->flash('message', ['success' => 'Password has been reset.']);
    return redirect()->route("users.show", $user->id);
}
(/app/User.php)
```php
    public function checkExpiration()
    {
        return $this->reset_sent_at < Carbon::now()->subHours(2);
    }

12.3.3 パスワードの再設定をテストする

(/tests/Feature/PasswordResetsTest)
```php
class PasswordResetsTest extends TestCase
{
private $user;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:fresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
}

public function testPasswordResets()
{
    Mail::fake();

    $response = $this->get(route("resets.create"));
    $response->assertViewIs("password_resets.create");
    $response = $this->post(route("resets.store"), ["email" => ""]);
    $response->assertSessionHas("message");
    $response->assertRedirect(route("resets.create"));
    $response = $this->post(route("resets.store"), ["email" => $this->user->email]);
    $this->assertNotEquals($this->user->reset_digest, User::find(1)->reset_digest);
    Mail::assertSent(PasswordReset::class, 1);
    $response->assertSessionHas("message");
    $response->assertRedirect("/");

    $reset_token = str_random(22);
    $this->user->update(["reset_digest" => bcrypt($reset_token)]);
    $response = $this->get(route("resets.edit", ["token" => $reset_token, "email" => ""]));
    $response->assertRedirect("/");
    $this->user->update(["activated" => false]);
    $response = $this->get(route("resets.edit", ["token" => $reset_token, "email" => $this->user->email]));
    $response->assertRedirect("/");
    $this->user->update(["activated" => true]);
    $response = $this->get(route("resets.edit", ["token" => "wrong token", "email" => $this->user->email]));
    $response->assertRedirect("/");
    $response = $this->followingRedirects()
                    ->get(route("resets.edit", ["token" => $reset_token, "email" => $this->user->email]));
    $response->assertViewIs("password_resets.edit");
    $dom = $this->dom($response->content());
    $this->assertSame(1, $dom->filter("input[name=email][type=hidden][value=\"{$this->user->email}\"]")->count());
    $response = $this->followingRedirects()
                    ->patch(route("resets.update", $reset_token), [
                        "email" => $this->user->email,
                        "password" => "foobaz",
                        "password_confirmation" => "barquux"
                    ]);
    $this->assertSame(1, $this->dom($response->content())->filter("div#error_explanation")->count());
    $response = $this->followingRedirects()
                    ->patch(route("resets.update", $reset_token), [
                        "email" => $this->user->email,
                        "password" => "",
                        "password_confirmation" => ""
                    ]);
    $this->assertSame(1, $this->dom($response->content())->filter("div#error_explanation")->count());
    $response = $this->followingRedirects()
                    ->patch(route("resets.update", $reset_token), [
                        "email" => $this->user->email,
                        "password" => "foobaz",
                        "password_confirmation" => "foobaz"
                    ]);
    $this->assertTrue(Auth::check());
    $response->assertSeeText("Password has been reset.");
    $response->assertViewIs("users.show");
}

}
```

12.4 本番環境でのメール送信(再掲)

12.5 最後に

12.6 照明期限切れの比較

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

Laravel で Ruby on Rails チュートリアル【11章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

11.1 AccountActivationsリソース

11.1.1 AccountActivationsコントローラ

ルート(/routes/web.php)
php
Route::get('account_activations/{token}/edit', "AccountActivationsController@edit")->name('activation');

11.1.2 AccountActivationのデータモデル

(/database/migrations/[timestamp]add_activation_to_users.php)
```php
class AddActivationToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('activation
digest')->nullable();
$table->boolean('activated')->default(false);
$table->dateTime('activated_at')->nullable();
});
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('activation_digest');
        $table->dropColumn('activated');
        $table->dropColumn('activated_at');
    });
}

}

コントローラー(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->email = strtolower($request->email);
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users",
"password" => "required|confirmed|min:6",
"password_confirmation" => "required|min:6"
]);
$user = new User;
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$activation_token = str_random(22);
$user->activation_digest = bcrypt($activation_token);
$user->save();
Auth::login($user);
session()->flash("message", ['success' => 'Welcome to the Sample App!']);
return redirect()->route("users.show", $user);
}

ファクトリー(/database/factories/UserFactory.php)
php
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => "example-{$faker->unique()->randomNumber}@railstutorial.org",
'password' => bcrypt("password"),
'activated' => true,
'activated_at' => Carbon::now(),
];
});

シーダー(/database/seeds/UserTableSeeder.php)
php
DB::table('users')->insert([
"name" => "Example User",
"email" => "example@railstutorial.org",
"password" => bcrypt("foobar"),
"admin" => true,
"activated" => true,
"activated_at" => Carbon::now()
]);

(/database/seeds/test/TestSeeder.php)
php
User::create(["name" => "Michael Example", "email" => "michael@example.com", "password" => bcrypt("password"), "admin" => true, "activated" => true, "activated_at" => Carbon::now()]);
User::create(["name" => "Sterling Archer", "email" => "duchess@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);
User::create(["name" => "Lana Kane", "email" => "hands@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);
User::create(["name" => "Malory Archer", "email" => "boss@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);
```

11.2 アカウント有効化のメール送信

11.2.1 送信メールのテンプレート

パッケージインストール

composer require guzzlehttp/guzzle

メールモデルの作成

artisan make:mail AccountActivation
artisan make:mail PasswordReset

(/resources/views/emails/account_activation.blade.php)
```html

Sample App

Hi {{ $user->name }},

Welcome to the Sample App! Click on the link below to activate your account:

{{ Html::link(route("activation",["token" => $activation_token, "email" => $user->email]), "Activate") }}

(/app/Mail/AccountActivation.php)
php
class AccountActivation extends Mailable
{
use Queueable, SerializesModels;

public $user;
public $activation_token;

public function __construct($user, $activation_token)
{
    $this->user = $user;
    $this->activation_token = $activation_token;
}

public function build()
{
    return $this->from("noreply@example.com")
                ->subject("Account activation")
                ->view('emails.account_activation');
}

}

メール送信(/app/Http/Controllers/UsersController.php)
php
Mail::to($user)->send(new AccountActivation($user, $activation_token));
```

11.2.2 送信メールのプレビュー

mailHogを使用する
https://windii.jp/backend/laravel/laravel-mailhog-docker-compose

11.2.3 送信メールのテスト

テスト(/tests/Unit/MailerTest.php)
```php
public function testAccountActivation()
{
Mail::fake();

    $user = User::find(1);
    $activation_token = str_random(22);
    Mail::to($user)->send(new AccountActivation($user, $activation_token));
    Mail::assertSent(AccountActivation::class, function ($mail) use ($user, $activation_token) {
        $mail->build();
        $this->assertEquals("Account activation", $mail->subject);
        $this->assertTrue($mail->hasTo($user->email));
        $this->assertTrue($mail->hasFrom("noreply@example.com"));
        $this->assertEquals($user->name, $mail->user->name);
        $this->assertEquals($activation_token, $mail->activation_token);
        $this->assertEquals($user->email, $mail->user->email);
        return true;
    });
}
# 11.2.4 ユーザーのcreateアクションを更新

(/app/Http/Controllers/UsersController.php)
```php
        $user->save();
        Mail::to($user)->send(new AccountActivation($user, $activation_token));
        session()->flash('message', ['info' => 'Please check your email to activate your account.']);
        return redirect("/");

(/tests/Feature/UsersSignupTest.php)
php
# $response->assertViewIs("users.show");
# $response->assertSeeText("laravel_tutorialにようこそ!");
# $this->assertTrue(Auth::check());

11.3 アカウントを有効化する

11.3.1 authenticated?メソッドの抽象化

11.3.2 editアクションで有効化

(/app/Http/Controllers/AccountActivationsController.php)
php
class AccountActivationsController extends Controller
{
public function edit(Request $request, $token)
{
$user = User::where('email', $request->email)->first();
if ($user && !$user->activated && Hash::check($token, $user->activation_digest)) {
$user->activated = true;
$user->activated_at = Carbon::now();
$user->save();
Auth::login($user);
session()->flash('message', ['success' => 'Account activated!']);
return redirect()->route("users.show", $user->id);
} else {
session()->flash('message', ['danger' => 'Invalid activation link']);
return redirect("/");
}
}
}

(/app/Http/Controllers/SessionController.php)
php
public function store(Request $request)
{
$user = User::where('email', $request->email)->first();
if ($user && Hash::check($request->password, $user->password)) {
if ($user->activated === true) {
Auth::login($user, $request->remember_me === "1");
return redirect()->intended("/users/" . Auth::id());
} else {
session()->flash('message', ['warning' => 'Account not activated. Check your email for the activation link.']);
return redirect("/");
}
} else {
session()->flash('message', ['danger' => 'Invalid email/password combination']);
return back()->withInput();
}
}

11.3.3 有効化のテストとリファクタリング

テスト(/tests/Feature/UsersSignupTest.php)
```php
class UsersSignupTest extends TestCase
{
use RefreshDatabase;

public function testInvalidSignup()
{
    $this->get("signup");
    $count = User::all()->count();
    $response = $this->followingRedirects()
                    ->post(route("signup"), [
                        "name" => "",
                        "email" => "user@invalid",
                        "password" => "foo",
                        "password_confirmation" => "bar"
                    ]);
    $this->assertSame($count, User::all()->count());
    $response->assertViewIs("users.create");
    $dom = $this->dom($response->content());
    $this->assertSame(1, $dom->filter('div#error_explanation')->count());
    $this->assertSame(1, $dom->filter('div.alert-danger')->count());
}

public function testValidSignupWithAccountActivation()
{
    Mail::fake();

    $this->get(route('signup'));
    $count = User::all()->count();
    $response = $this->followingRedirects()
                     ->post(route('signup'), [
                            'name' => "Example User",
                            'email' => "user@example.com",
                            'password' => "password",
                            'password_confirmation' => "password"
                     ]);
    $this->assertSame($count + 1, User::all()->count());
    Mail::assertSent(AccountActivation::class, 1);
    $user = User::where("email", "user@example.com")->first();
    $activation_token = str_random(22);
    $user->update(["activation_digest" => bscrypt($activation_token)]);
    $this->assertFalse($user->activated);

    $this->post("login", ["email" => $user->email, "password" => "password"]);
    $this->assertFalse(Auth::check());
    $this->get(route("activation", ["token" => "incalid token", "email" => $user->email]));
    $this->assertFalse(Auth::check());
    $this->get(route("activation", ["token" => $activation_token, "email" => "wrong"]));
    $this->assertFalse(Auth::check());
    $response = $this->get(route("activation", ["token" => $activation_token, "email" => $user->email]));
    $this->assertTrue(User::find($user->id)->activated);
    $response->assertRedirect(route("users.show", $user->id));
    $this->assertTrue(Auth::check());
}

}

(/app/Http/User.php)
php
public function activate()
{
$this->activated = true;
$this->activated_at = Carbon::now();
$this->save();
}

public function sendActivateEmail($token)

{
    Mail::to($this)->send(new AccountActivation($token, $this));
}
#### 課題

(/app/Http/Controllers/UsersController.php)
```php
    public function index()
    {
        $users = User::where("activated", true)->paginate(30);
        return view('users.index')->with('users', $users);
    }
...
    public function show($id)
    {
        $user = User::find($id);
        if ($user->activated) {
            return view('users.show')->with('user', $user);
        } else {
            return redirect("/");
        }
    }

11.4 本番環境でのメール送信

sendgridを使用

heroku addons:create sendgrid:starter

herokuのサイトからsendgridにとんでmailを認証
その後残りの設定

heroku config:set MAIL_HOST=smtp.sendgrid.net
heroku config:set MAIL_USERNAME={heroku config:get SENDGRID_USERNAME}
heroku config:set MAIL_PASSWORD={heroku config:get SENDGRID_PASSWORD}

11.5 最後に

https://windii.jp/backend/laravel/api-register
https://www.ritolab.com/entry/38
https://laraweb.net/tutorial/2665/
https://windii.jp/backend/laravel/laravel-mailhog-docker-compose
https://sendgrid.com/docs/for-developers/sending-email/laravel/

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

Laravel で Ruby on Rails チュートリアル【10章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

10.1 ユーザーを更新する

10.1.1 編集フォーム

コントローラー(/app/Http/Controllers/UsersController.php)
php
public function edit($id)
{
$user = User::find($id);
return view('users.edit')->with('user', $user);
}

レイアウト(/resources/views/users/edit.blade.php)
```html
@extends('layouts.application')

@section('title', 'Edit user')

@section('content')

Update your profile

{{ Form::model($user, ['route' => ['users.update', $user->id], "method" => "patch"]) }} @include('shared.error_messages') {{ Form::label('name') }} {{ Form::text('name', $user->name, ["class" => "form-control"]) }} {{ Form::label('email') }} {{ Form::email('email', $user->email, ["class" => "form-control"]) }} {{ Form::label('password') }} {{ Form::password('password', ["class" => "form-control"]) }} {{ Form::label('password_confirmation') }} {{ Form::password('password_confirmation', ["class" => "form-control"]) }} {{ Form::submit("Save changes", ["class" => "btn btn-primary"]) }} {{ Form::close() }}
{!! gravatar_for($user) !!} change

@endsection

編集画面へのリンク(/retources/views/layouts/header.blade.php)
html

{{ Html::linkRoute("users.edit", "Settings", [Auth::id()]) }}
```

課題

(/resources/views/users/form.blade.php)
```html
{{ Form::model($user, $options) }}
@include('shared.error_messages')
{{ Form::label('name') }}
{{ Form::text('name', $user->name, ["class" => "form-control"]) }}
{{ Form::label('email') }}
{{ Form::email('email', $user->email, ["class" => "form-control"]) }}
{{ Form::label('password') }}
{{ Form::password('password', ["class" => "form-control"]) }}
{{ Form::label('password_confirmation') }}
{{ Form::password('password_confirmation', ["class" => "form-control"]) }}

{{ Form::submit("Save changes", ["class" => "btn btn-primary"]) }}
{{ Form::close() }}

(/resources/views/users/edit.blade.php)
html
@extends('layouts.application')

@section('title', 'Edit user')

@section('content')

Update your profile

@include("users.form", ["options" => ['route' => ['users.update', $user->id], "method" => "patch"]])
{!! gravatar_for($user) !!} change

@endsection

(/resources/views/users/create.blade.php)
html
@extends('layouts.application')

@section('title', 'Sign up')

@section('content')

Sign up

@include("users.form", ["user"=> new App\User, "options" => ['route' => 'signup']])

@endsection
```

10.1.2 編集の失敗

ユーザーを更新するアクション(/app/Http/Controllers/UsersController.php)
php
public function update(Request $request, $id)
{
$request->email = strtolower($request->email);
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users|email",
"password" => "nullable|confirmed|min:6",
"password_confirmation" => "nullable|min:6"
]);
$user = User::find($id);
$user->name = $request->name;
$user->email = $request->email;
if (!empty($request->password)) {
$user->password = bcrypt($request->password);
}
$user->save();
return redirect()->route("users.show", $user->id);
}

10.1.3 編集失敗時のテスト

テスト(/tests/Feature/UsersEditTest.php)
```php
class UsersEditTest extends TestCase
{
private $user;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:fresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
}

public function testUnsuccessfulEdit()
{
    $response = $this->get(route("users.edit", $this->user->id));
    $response->assertViewIs("users.edit");
    $response = $this->followingRedirects()
                    ->patch(route("users.update", $this->user->id), [
                        "name" => " ",
                        "password" => "foo",
                        "password" => "bar"
                    ]);
    $response->assertViewIs("users.edit");
}

}
```

10.1.4 TDDで編集を成功させる

テスト(/tests/Feature/UsersEditTest.php)
php
public function testSuccessfulEdit()
{
$response = $this->get(route("users.edit", $this->user->id));
$response->assertViewIs("users.edit");
$name = "Foo Bar";
$email = "foo@bar.com";
$response = $this->followingRedirects()
->patch(route("users.update", $this->user->id), [
"name" => $name,
"email" => $email,
"password" => "",
"password_confirmation" =>""
]);
$response->assertSessionHas("message");
$response->assertViewIs("users.show");
$response = $this->get(route("users.show", $this->user->id));
$this->assertEquals($name, $response->original->user->name);
$this->assertEquals($email, $response->original->user->email);
}

メッセージ追加(/app/Http/Controllers/UsersController.php)
php
session()->flash("message", ['success' => 'Profile updated']);

emailが変わらないケースに対応(/app/Http/Requests/UserUpdatePost.php)
php
$request->validate([
"name" => "required|max:50",
"email" => ['required', 'max:255', 'email', Rule::unique('users')->ignore(Auth::id())],
"password" => "nullable|confirmed|min:6",
"password_confirmation" => "nullable|min:6"
]);

10.2 認可

10.2.1 ユーザーにログインを要求する

(/app/Http/Middleware/Authenticate.php)
```php
class Authenticate
{
public function handle($request, Closure $next)
{
if (!Auth::check()) {
session()->flash('message', ['danger' => 'Please login.']);
return redirect(route('login'));
}

    return $next($request);
}

}

(/app/Http/Kernel.php)
php
protected $routeMiddleware = [
'authenticate' => \App\Http\Middleware\Authenticate::class,
];

特定アクションにログインを強要(/app/Http/Controllers/UsersController.php)
php
public function __construct()
{
$this->middleware('authenticate')->only(["edit", "update"]);
}

(/tests/Feature/UsersEditTest.php)
php
$this->be($this->user);

テスト(/tests/Unit/UsersControllerTest.php)
php
public function testRedirectEditWithGuest()
{
$response = $this->get(route("users.edit", $this->user->id));
$response->assertSessionHas("message");
$response->assertRedirect(route("login"));
}

public function testRedirectUpdateWithGuest()
{
    $response = $this->patch(route("users.update", $this->user->id), [
                    "name" => $this->user->name,
                    "email" => $this->user->email
                ]);
    $response->assertSessionHas("message");
    $response->assertRedirect(route("login"));
}
# 10.2.2 正しいユーザーを要求する

二人目のユーザーを追加(/database/seeds/test/TestSeeder.php)
```php
    public function run()
    {
        DB::table('users')->insert([
            'name' => "Michael Example",
            'email' => strtolower("michael@example.com"),
            'password' => bcrypt('password'),
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
        ]);

        DB::table('users')->insert([
            'name' => "Sterling Archer",
            'email' => strtolower("duchess@example.gov"),
            'password' => bcrypt('password'),
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
        ]);
    }

テスト(/tests/Unit/UsersControllerTest.php)
```php
public function testRedirectEditWrongUser()
{
$this->be($this->other_user);
$response = $this->get(route("users.edit", $this->user->id));
$response->assertSessionMissing("message");
$response->assertRedirect("/");
}

public function testRedirectUpdateWrongUser()
{
    $this->be($this->other_user);
    $response = $this->patch(route("users.update", $this->user->id), [
        "name" => $this->user->name,
        "email" => $this->user->email
    ]);
    $response->assertSessionMissing("message");
    $response->assertRedirect("/");
}
コントローラー(/app/Http/Controllers/UserController.php)
```php
        $this->middleware(function ($request, $next) {
            if ($request->user != Auth::id()) {
                return redirect('/');
            }
            return $next($request);
        })->only(["edit", "update"]);

10.2.3 フレンドリーフォワーディング

テスト(/tests/Feature/UsersEditTest.php)
```php
public function testSuccessfulEdit()

{
    $this->get(route("users.edit", $this->user->id));
    $response = $this->followingRedirects()
                    ->post(route("login"), [
                        "email" => $this->user->email,
                        "password" => $this->user_pass
                    ]);
    $response->assertViewIs("users.edit");
    $name  = "Foo Bar";
    $email = "foo@bar.com";
    $response = $this->followingRedirects()
                    ->patch(route("users.update", $this->user->id), [
                        "name"    => $name,
                        "email"    => $email,
                        "password" => "",
                        "password_confirmation" =>""
                    ]);
    $response->assertSeeText("Profile updated");
    $response->assertViewIs("users.show");
    $response = $this->get(route("users.show", $this->user->id));
    $this->assertEquals($name, $response->original->user->name);
    $this->assertEquals($email, $response->original->user->email);
}
ミドルウェア(/app/Http/Middleware/Authenticate.php)
```php
        if (!Auth::check()) {

            session()->flash('message', ['danger' => 'Please login.']);
            session(["url.intended" => url()->current()]);
            return redirect('/login');
        }

コントローラー(/app/Http/Controllers/SessionController.php)
php
public function store(Request $request)
{
$user = User::where("email", strtolower($request->email))->first();
if ($user && Hash::check($request->password, $user->password)) {
Auth::login($user, $request->remember_me === "1");
return redirect()->intended(route("users.show", $user->id));
} else {
session()->flash('message', ['danger' => 'Invalid email/password combination']);
return back()->withInput();
}
}

10.3 すべてのユーザーを表示する

10.3.1 ユーザーの一覧のページ

テスト(/tests/Unit/UsersControllerTest.php)
php
public function testRedirectIndexGuest()
{
$response = $this->get(route("users.index"));
$response->assertRedirect(route("login"));
}

コントローラー(/app/Http/Controllers/UsersController.php)
php
$this->middleware('authenticate')->only(["index", "edit", "update"]);
...
public function index()
{
$users = User::all();
return view("users.index")->with("users", $users);
}

レイアウト(/resources/views/users/index.blade.php)
```html
@extends('layouts.application')

@section('title', "All users")

@section('content')

All users

    @foreach ($users as $user)
  • {!! gravatar_for($user, ["size" => 50]) !!} {{ Html::linkRoute("users.show", $user->name, [$user->id]) }}
  • @endforeach

@endsection

(/resources/views/layouts/header.blade.php)
html

{{ Html::linkRoute("users.index", "Users") }}
```

10.3.2 サンプルのユーザー

ファクトリー(/database/factories/UserFactory.php)
php
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => "example-{$faker->unique()->randomNumber}@railstutorial.org",
'password' => bcrypt("password")
];
});

シーディング(/database/seeds/DatabaseSeeder.php)
```php
class DatabaseSeeder extends Seeder
{
public function run()
{
User::create(["name" => "Example User", "email" => "example@railstutorial.org", "password" => bcrypt("foobar")]);

    factory(User::class, 99)->create();
}

}
```

10.3.3 ページネーション

コントローラー(/app/Http/Controllers/UsersController.php)
php
public function index()
{
$users = User::paginate(30);
return view('users.index')->with('users', $users);
}

レイアウト
```html
@extends('layouts.application')

@section('title', "All users")

@section('content')

All users

{{ $users->links() }}

    @foreach ($users as $user)
  • {!! gravatar_for($user, ["size" => 50]) !!} {{ Html::linkRoute("users.show", $user->name, [$user->id]) }}
  • @endforeach

{{ $users->links() }}

@endsection
```

10.3.4 ユーザー一覧のテスト

テスト用のシーダー(/database/seeds/test/TestSeeder.php)
```php
class TestSeeder extends Seeder
{
public function run()
{
User::create(["name" => "Michael Example", "email" => "michael@example.com", "password" => bcrypt("password")]);
User::create(["name" => "Sterling Archer", "email" => "duchess@example.gov", "password" => bcrypt("password")]);
User::create(["name" => "Lana Kane", "email" => "hands@example.gov", "password" => bcrypt("password")]);
User::create(["name" => "Malory Archer", "email" => "boss@example.gov", "password" => bcrypt("password")]);

    factory(User::class, 30)->create();
}

}

テスト(/tests/Unit/UsersIndexTest.php)
php
class UsersIndexTest extends TestCase
{
private $user;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:fresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
}

public function testIndexPagination()
{
    $this->be($this->user);
    $response = $this->get(route("users.index"));
    $response->assertViewIs("users.index");
    $dom = $this->dom($response->content());
    $this->assertSame(2, $dom->filter("ul.pagination")->count());
    foreach (User::paginate(30) as $user) {
        $this->assertSame(route("users.show", $user->id), $dom->filter("a:contains(\"{$user->name}\")")->attr("href"));
    }
}

}
```

10.3.5 パーシャルのリファクタリング

(/resoueces/views/users/index.blade.php)
html
<ul class="users">
@foreach ($users as $user)
@include('users.user')
@endforeach
</ul>

(/resources/views/users/user.blade.php)
html
<li>
{!! gravatar_for($user, ["size" => 50]) !!}
{{ Html::linkRoute("users.show", $user->name, [$user->id]) }}
</li>

10.4 ユーザーを削除する

10.4.1 管理ユーザー

(/database/migrations/[timestamp]_add_admin_to_users.php)
```php
class AddAdminToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('admin')->default(false);
});
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('admin');
    });
}

}

シーダー編集(/database/seeds/UsersTableSeeder.php)
php
User::create([
"name" => "Example User",
"email" => "example@railstutorial.org",
"password" => bcrypt("foobar"),
"admin" => true
]);

アドミンを更新出来ないようモデルを修正(/app/Http/User.php)
php
protected $guarded = ['id', 'admin'];
```

課題

(/tests/Unit/UsersController.test)
php
public function testEditAdminAttributeViaWeb()
{
$this->be($this->other_user);
$this->assertSame(false, $this->other_user->admin);
$this->patch(route("users.update", $this->other_user->id), [
"name" => $this->other_user->name,
"email" => $this->other_user->email,
"admin" => true
]);
$this->assertSame(false, User::find(2)->admin);
}

10.4.2 destroyアクション

(/resources/views/users/user.blade.php)
html
<li>
{!! gravatar_for($user, ["size" => 50]) !!}
{{ Html::linkRoute("users.show", $user->name, [$user->id]) }}
@if (Auth::user()->admin === true && Auth::user() != $user)
| <a href="javascript:if(window.confirm('Yes Sure?')){document.deleteform{{$user->id}}.submit()}">delete</a>
{{ Form::open(["route" => ["users.destroy", $user->id], "method" => "delete", "name" => "deleteform{$user->id}"]) }}
{{ Form::close() }}
@endif
</li>

(/app/Http/Controllers/UsersController.php)
php
$this->middleware('guest')->only(["index", "edit", "update", "destroy"]);
...
public function destroy($id)
{
User::destroy($id);
session()->flash("message", ["success" => "User deleted"]);
return redirect()->route("users.index");
}

(/app/Http/Controllers/UsersController.php)
php
$this->middleware(function ($request, $next) {
if (Auth::user()->admin === false) {
return redirect('/');
}
return $next($request);
})->only(["destroy"]);

10.4.3 ユーザー削除のテスト

シーダー(/database/seeds/test/TestSeeder.php)
php
User::create(["name" => "Michael Example", "email" => "michael@example.com", "password" => bcrypt("password"), "admin" => true]);

テスト(/tests/Unit/UsersControllerTest.php)
```php
public function testRedirectDestroyGuest()
{
$count = User::all()->count();
$response = $this->delete(route("users.destroy", $this->user->id));
$this->assertEquals($count, User::all()->count());
$response->assertRedirect(route("login"));
}

public function testRedirectDestroyNonadmin()
{
    $this->be($this->other_user);
    $count = User::all()->count();
    $response = $this->delete(route("users.destroy", $this->user->id));
    $this->assertEquals($count, User::all()->count());
    $response->assertRedirect("/");
}
テスト(/tests/Feature/UsersIndexTest.php)
```php
class UsersIndexTest extends TestCase
{
    private $admin;
    private $non_admin;

    protected function setUp()
    {
        parent::setUp();
        Artisan::call('migrate:fresh');
        $this->seed('TestSeeder');
        $this->admin = User::find(1);
        $this->non_admin = User::find(2);
    }

    public function testIndexAsAdmin()
    {
        $this->be($this->admin);
        $response = $this->get(route("users.index"));
        $response->assertViewIs("users.index");
        $dom = $this->dom($response->content());
        $this->assertSame(2, $dom->filter("ul.pagination")->count());
        foreach (User::paginate(30) as $user) {
            $this->assertSame(route("users.show", $user->id), $dom->filter("a:contains(\"{$user->name}\")")->attr("href"));
            if ($user != $this->admin) {
                $this->assertSame(route("users.destroy", $user->id), $dom->filter("form[name=deleteform{$user->id}]")->attr("action"));
            }
        }
        $count = User::all()->count();
        $this->delete(route("users.destroy", $this->non_admin->id));
        $this->assertSame($count - 1, User::all()->count());
    }

    public function testIndexAsNonadmin()
    {
        $this->be($this->non_admin);
        $response = $this->get(route("users.index"));
        $dom = $this->dom($response->content());
        $this->assertSame(0, $dom->filter('a:contains("delete")')->count());
    }
}

10.5 最後に

本番でもfakerを使う場合はインストールする

composer require fzaninotto/faker

参考

https://qiita.com/zaburo/items/56bc5d2f3377c3c50028
https://qiita.com/n-oota/items/e1890a6451fe33fb25f6

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

Laravel で Ruby on Rails チュートリアル【9章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

9.1 Remember me 機能

9.1.1 記憶トークンと暗号化

9.1.2 ログイン状態の保持

ログインしてユーザーを保持する(/app/Http/Controllers/SessionController.php)
php
public function store(Request $request)
{
$user = User::where("email", strtolower($request->email))->first();
if ($user && Hash::check($request->password, $user->password)) {
Auth::login($user, true);
return redirect()->route("users.show", $user);
} else {
session()->flash('message', ['danger' => 'Invalid email/password combination']);
return back()->withInput();
}
}

9.1.3 ユーザーを忘れる

9.1.4 2つの目立たないバグ

テスト追加(/tests/Feature/UsersLoginTest.php)
php
$response = $this->followingRedirects()->delete(route("logout"));

9.2 Remember me チェックボックス

チェックボックス追加(/resources/views/sessions/create.blade.php)
html
<label class="checkbox inline">
{{ Form::hidden("remember_me", "0") }}
{{ Form::checkbox("remember_me", "1", null, ["id" => "session_remember_me"]) }}
<span>Remember me on this computer</span>
</label>

処理を追加(/app/Http/Controllers/SessionController.php)
php
public function store(Request $request)
{
$user = User::where("email", strtolower($request->email))->first();
if ($user && Hash::check($request->password, $user->password)) {
Auth::login($user, $request->remember_me === "1");
return redirect()->route("users.show", $user);
} else {
session()->flash('message', ['danger' => 'Invalid email/password combination']);
return back()->withInput();
}
}

9.3 Remember me のテスト

9.3.1 Remember me ボックスをテストする

テスト(/tests/Feature/UsersLoginTest.php)
ブラウザでログアウトするとcookieが削除されるが、phpunitでログアウトしても削除されないのでとりあえずコメントアウト。
```php
public function testLoginRemember()
{
$response = $this->followingRedirects()
->post(route("login"), [
'email' => $this->user->email,
'password' => $this->user_pass,
'remember_me' => "1"
]);
$i = 0;
foreach ($response->headers->getCookies() as $cookie) {
if (preg_match('/^remember_web_\w+$/', $cookie->getName())) {
$i++;
}
}
$this->assertSame(1, $i);
}

public function testLoginNotRemember()
{
    /*$response = $this->followingRedirects()
                    ->post(route("login"), [
                        'email' => $this->user->email,
                        'password' => $this->user_pass,
                        'remember_me' => "1"
                    ]);*/
    $response = $this->followingRedirects()->delete('/logout');
    $response = $this->followingRedirects()
                    ->post(route("login"), [
                        'email' => $this->user->email,
                        'password' => $this->user_pass,
                        'remember_me' => "0"
                    ]);
    $i = 0;
    foreach ($response->headers->getCookies() as $cookie) {
        if (preg_match('/^remember_web_\w+$/', $cookie->getName())) {
            $i++;
        }
    }
    $this->assertSame(0, $i);
}
# 9.3.2 Remember me をテストする

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

Laravel で Ruby on Rails チュートリアル【8章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

8.1 セッション

8.1.1 Sessionsコントローラ

コントローラー作成(/app/Http/Controllers/SessionsController.php)
php
class SessionsController extends Controller
{
public function create()
{
return view("sessions.crate");
}
}

ルート追加(/routes/web.php)
php
Route::get('login', "SessionsController@create")->name('login');
Route::post('login', "SessionsController@store");
Route::delete('logout', "SessionsController@destroy")->name('logout');

テスト作成(/tests/Unit/SessionsControllerTest.php)
php
class SessionsControllerTest extends TestCase
{
public function testGetCreate()
{
$response = $this->get(route("login"));
$response->assertStatus(200);
}
}

8.1.2 ログインフォーム

レイアウト作成(/resources/views/sessions/create.blade.php)
```html
@extends('layouts.application')

@section('title', 'Log in')

@section('content')

Log in

{{ Form::model($session, ['route' => 'login']) }} {{ Form::label('email', 'Email') }} {{ Form::text('email', "", ["class" => "form-control"]) }} {{ Form::label('password', 'Password') }} {{ Form::password('password', ["class" => "form-control"]) }} {{ Form::submit("Log in", ["class" => "btn btn-primary"]) }} {{ Form::close() }}

New user? {{ Html::link(route("signup"), "Sign up now!") }}

@endsection
```

8.1.3 ユーザーの検索と認証

ログイン処理を実装(/app/Http/SessionsController.php)
php
public function store(Request $request)
{
$user = User::where("email", strtolower($request->email))->first();
if ($user && Hash::check($request->password, $user->password)) {
// ログイン後にユーザー情報のページにリダイレクトする
} else {
// エラーメッセージを作成する
return back()->withInput();
}
}

8.1.4 フラッシュメッセージを表示する

フラッシュ追加(/app/Http/Controllers/SessionsController.php)
php
session()->flash('message', ['danger' => 'Invalid email/password combination']);

8.1.5 フラッシュのテスト

テスト作成(/tests/Feature/UsersLoginTest.php)
php
public function testInvalidLogin()
{
$response = $this->get(route('login'));
$response->assertViewIs('sessions.create');
$response = $this->followingRedirects()
->post('/login', [
'email' => "",
'password' => "",
]);
$response->assertViewIs('sessions.create');
$response->assertSeeText('Invalid email/password combination');
$response = $this->get('/');
$response->assertDontSeeText('Invalid email/password combination');
}

8.2 ログイン

8.2.1 log_inメソッド

(/app/Http/Controllers/SessionsController.php)
php
public function store(Request $request)
{
$user = User::where("email", strtolower($request->email))->first();
if ($user && Hash::check($request->password, $user->password)) {
Auth::login($user);
return redirect()->route("users.show", $user);
} else {
session()->flash('message', ['danger' => 'Invalid email/password combination']);
return back()->withInput();
}
}

8.2.2 現在のユーザー

Auth::user();

8.2.3 レイアウトリンクを変更する

レイアウト修正(/resources/views/layouts/header.blade.php)
html
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
{{ Html::link("/", "sample app", ["id" => "logo"]) }}
<nav>
<ul class="nav navbar-nav navbar-right">
<li>{{ Html::link("/", "Home") }}</li>
<li>{{ Html::linkRoute("help", "Help") }}</li>
@if (Auth::check())
<li>{{ Html::link("#", "Users") }}</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>{{ Html::linkRoute("users.show", "Profile", [Auth::id()]) }}</li>
<li>{{ Html::link("#", "Settings") }}</li>
<li class="divider"></li>
<li>
<a href="javascript:document.logoutform.submit()">Log out</a>
{{ Form::open(["route" => "logout", "method" => "delete", "name" => "logoutform"]) }}
{{ Form::close() }}
</li>
</ul>
</li>
@else
<li>{{ Html::linkRoute("login", "Log in") }}</li>
@endif
</ul>
</nav>
</div>
</header>

ログアウト実装(/app/Http/Coontrollers/SessionsController.php)
php
public function destroy()
{
Auth::logout();
return redirect("/");
}

8.2.4 レイアウトの変更をテストする

テスト用のデータを作成(/database/seedes/test/TestSeeder.php)
php
class TestSeeder extends Seeder
{
public function run()
{
DB::table('users')->insert([
'name' => "Michael Example",
'email' => strtolower("michael@example.com"),
'password' => bcrypt('password'),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
}

テストで使用するDBを指定する(/phpunit.xml)
xml
<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"/>
<env name="DB_DATABASE" value="mydatabase_2_test"/>
</php>

テスト実装(/tests/Feature/UsersLoginTest.php)
```php
class UsersLoginTest extends TestCase
{
private $user;
private $user_pass;

protected function setUp()
{
    parent::setUp();
    Artisan::call('migrate:fresh');
    $this->seed('TestSeeder');
    $this->user = User::find(1);
    $this->user_pass = "password";
}

...
public function testValidLogin()
{
$this->get(route("login"));
$response = $this->followingRedirects()
->post(route("login"), [
'email' => $this->user->email,
'password' => $this->user_pass
]);
$response->assertViewIs('users.show');
$dom = $this->dom($response->content());
$this->assertSame(0, $dom->filter('a:contains("Log in")')->count());
$this->assertSame(route("logout"), $dom->filter('form[name="logoutform"]')->attr("action"));
$this->assertSame(route("users.show", $this->user), $dom->filter('a:contains("Profile")')->attr("href"));
}
}
```

8.2.5 ユーザー登録時にログイン

ログイン処理追加(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->email = strtolower($request->email);
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users|email",
"password" => "required|confirmed|min:6",
"password_confirmation" => "required|min:6"
]);
$user = new User;
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
Auth::login($user);
session()->flash("message", ['success' => 'Welcome to the Sample App!']);
return redirect()->route("users.show", $user);
}

ログイン状態を確認するテスト(/tests/Feature/UsersSignupTest.php)
php
$this->assertTrue(Auth::check());

8.3 ログアウト

ログアウト処理を追加(/app/Http/Controllers/SessionsController.php)
php
public function destroy()
{
Auth::logout();
return redirect("/");
}

ログアウトテスト(/tests/Feature/UsersLoginTest.php)
php
public function testValidLoginToLogout()
{
$this->get(route("login"));
$response = $this->followingRedirects()
->post(route("login"), [
'email' => $this->user->email,
'password' => $this->user_pass
]);
$this->assertTrue(Auth::check());
$response->assertViewIs('users.show');
$dom = $this->dom($response->content());
$this->assertSame(0, $dom->filter('a:contains("Log in")')->count());
$this->assertSame(route("logout"), $dom->filter('form[name="logoutform"]')->attr("action"));
$this->assertSame(route("users.show", $this->user), $dom->filter('a:contains("Profile")')->attr("href"));
$response = $this->followingRedirects()->delete(route("logout"));
$this->assertFalse(Auth::check());
$response->assertViewIs("static_pages.home");
$dom = $this->dom($response->content());
$this->assertSame(route("login"), $dom->filter('a:contains("Log in")')->attr("href"));
$this->assertSame(0, $dom->filter('form[name="logoutform"]')->count());
$this->assertSame(0, $dom->filter('a:contains("Profile")')->count());
}

8.4 最後に

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

laradock+VSCode+xdebugの設定でハマったところ

環境

laradock
Docker for Windows 2.0.0.3 (31259)
VSCode 1.33.1
xdebug 2.7.2
php 7.2

laradock側の設定

ワークスペースとPHPでxdebugをインストールするよう設定(.env)

...
WORKSPACE_INSTALL_XDEBUG=true
...
PHP_FPM_INSTALL_XDEBUG=true
...

2つのxdebug.iniを下のように修正
(/php-fmp/xdebug.ini)
(/workspace/xdebug.ini)
xdebug.remote_host windowsの場合はdockerhostで動いたがmacの場合はDockerのネットワーク構成が異なるので別の設定じゃいと動かないらしい
xdebug.remote_port 他のアプリで使用していないポートならデフォルトの9000でも何でも良い

xdebug.remote_host=dockerhost
xdebug.remote_connect_back=0
xdebug.remote_port=9002
...
xdebug.remote_autostart=1
xdebug.remote_enable=1
xdebug.cli_color=1

これでdocker-compose upすればlaradock側はok

VSCode側の設定

拡張機能のPHP Debugをインストール

Vscodeで開いているlaravelプロジェクトに下記のファイルを作成(/.vscode/launch.json)
portはxdebug.iniで指定した値
pathMappingsは {リモートのドキュメントルート} : {ホストのドキュメントルート}
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9002,
"stopOnEntry": true,
"pathMappings": {
"/var/www": "${workspaceRoot}"
}
}
]
}

Firewallの設定

VSCodeがDocker内のXdebugと通信出来るようにFirewallを設定する必要がある。
とりあえずVSCodeはプライベートネットワークでの通信出来るように設定した。
Dockerのworkspaceコンテナでxdebugを試みるが下のようなエラーが出て接続出来ない。
```
I: Connecting to configured address/port: dockerhost:9002.

    E: Time-out connecting to client. :-(
色々調べてみたらDockerが動いているネットワークが識別されていないネットワーク(パブリック)なためFirewallに通信を遮断されていた。
なのでVSCodeがパブリックネットワークでも通信出来るようにするか、識別されていないネットワークをプライベートとみなすかすることで接続出来るようになった。

# 参考
https://qiita.com/hiro-tarosuke/items/adcc382ca98dfb89401b
https://qiita.com/ditflame/items/bf4b5f412bf607c5c6d2#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%8C%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel で Ruby on Rails チュートリアル【7章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

7.1 ユーザーを表示する

7.1.1 デバッグとRails環境

デバッグ用のパッケージを追加

composer require barryvdh/laravel-debugbar --dev
composer require nunomaduro/collision --dev

7.1.2 Usersリソース

artisan tinkerでユーザー作成
php
User::create(["name" => "Michael Hartl", "email" => "mhartl@example.com", "password" => bcrypt("foobar")]);

リソースのルート設定(routes/web.php)
php
Route::resource('users', "UsersController", ["except" => ["create"]]);

showページのレイアウト(/resources/views/users/show.blade.php)
html
{{ $user->name }}, {{ $user->email }}

showアクション(/app/Http/Controllers/UsersController.php)
php
public function show($id)
{
$user = User::find($id);
return view('users.show')->with('user', $user);
}

7.1.3 debuggerメソッド

デバッグに便利なパッケージを追加でインストール

7.1.4 Gravatar画像とサイドバー

showレイアウトの修正(/resources/views/users/show.blade.php)
```php
@extends('layouts.application')

@section('title', $user->name)

@section('content')


{!! gravatar_for($user) !!}
{{ $user->name }}


@endsection

ヘルパ関数追加(/app/helpers.php)
php
if (! function_exists('gravatar_for')) {
function gravatar_for($user)
{
$gravatar_id = md5(strtolower($user->email));
$gravatar_url = "https://secure.gravatar.com/avatar/{$gravatar_id}";
return Html::image($gravatar_url, $user->name, ["class" => "gravatar"]);
}
}

テキストにそってデータ更新

$ artisan tinker

User::find(1)->update(["name" => "Example User", "email" => "example@railstutorial.org", "password" => bcrypt("foobar")]);
```

演習

if (! function_exists('gravatar_for')) {
    function gravatar_for($user, $options = ["size" => 80])
    {
        $gravatar_id = md5(strtolower($user->email));
        $gravatar_url = "https://secure.gravatar.com/avatar/{$gravatar_id}?s={$options["size"]}";
        return Html::image($gravatar_url, $user->name, ["class" => "gravatar"]);
    }
}

7.2 ユーザー登録フォーム

7.2.1 form_forを使用する

登録フォーム修正(/resources/views/users/create.blade.php)
```html
@extends('layouts.application')

@section('title', 'Sign up')

@section('content')

Sign up

{{ Form::model($user, ['url' => ['users']]) }} {{ Form::label('name', 'Name') }} {{ Form::text('name') }} {{ Form::label('email', 'Email') }} {{ Form::email('email') }} {{ Form::label('password', 'Password') }} {{ Form::password('password') }} {{ Form::label('password_confirmation', 'Password confirmation') }} {{ Form::password('password_confirmation') }} {{ Form::submit("Create my account", ["class" => "btn btn-primary"]) }} {{ Form::close() }}

@endsection
```

7.2.2 フォームHTML

7.3 ユーザー登録失敗

7.3.1 正しいフォーム

コントローラにユーザーを保存するアクションを追加(/app/Http/Controllerss/UsersController.php)
php
public function store(Request $request)
{
$request->email = strlower($request->email);
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users|email",
"password" => "required|confirmed|min:6",
"password_confirmation" => "required|min:6"
]);
$user = new User;
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
return redirect()->route("signup");
}

7.3.2 Strong Parameters

7.3.3 エラーメッセージ

エラーメッセージを表示するようレイアウト修正
(/resources/views/users/create.blade.php)
```html
@extends('layouts.application')

@section('title', 'Sign up')

@section('content')

Sign up

{{ Form::model($user, ['url' => ['users']]) }} @include('shared.error_messages') {{ Form::label('name', 'Name') }} {{ Form::text('name', "", ["class" => "form-control"]) }} {{ Form::label('email', 'Email') }} {{ Form::email('email', "", ["class" => "form-control"]) }} {{ Form::label('password', 'Password') }} {{ Form::password('password', ["class" => "form-control"]) }} {{ Form::label('password_confirmation', 'Password confirmation') }} {{ Form::password('password_confirmation', ["class" => "form-control"]) }} {{ Form::submit("Create my account", ["class" => "btn btn-primary"]) }} {{ Form::close() }}

@endsection

(/resources/views/shared/error_messages.blade.php)
html
@if ($errors->any())



The form contains {{ $errors->count() . str_plural('error', $errors->count()) }}.



@endif
```

7.3.4 失敗時のテスト

失敗時のテスト (/tests/Feature/UsersSignupTest.php)
```php
class UsersSignupTest extends TestCase
{
use RefreshDatabase;

public function testInvalidSignup()
{
    $this->get("signup");
    $count = User::all()->count();
    $response = $this->followingRedirects()
                    ->post("users", [
                        "name" => "",
                        "email" => "user@invalid",
                        "password" => "foo",
                        "password_confirmation" => "bar"
                    ]);
    $this->assertSame($count, User::all()->count());
    $response->assertViewIs("users.create");
}

}
```

課題

(/tests/Feature/UsersSignupTest.php)
php
$dom = $this->dom($response->content());
$this->assertSame(1, $dom->filter('div#error_explanation')->count());
$this->assertSame(1, $dom->filter('div.alert-danger')->count());

(/routes/web.php)
php
Route::post('signup', 'UsersController@store')->name('signup');
Route::resource('users', "UsersController", ["except" => ["create", "store"]]);

(/resources/views/users/create.blade.php)
html
{{ Form::model($user, ['route' => 'signup']) }}

(/tests/Feature/UsersSignupTest.php)
php
$response = $this->followingRedirects()
->post("signup", [

7.4 ユーザー登録成功

7.4.1 登録フォームの完成

リダイレクト先の修正(/app/Http/Conotrollers/UsersController.php)
php
public function store(Request $request)
{
$request->email = strtolower($request->email);
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users|email",
"password" => "required|confirmed|min:6",
"password_confirmation" => "required|min:6"
]);
$user = new User;
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
return redirect()->route("users.show", $user);
}

7.4.2 flash

フラッシュメッセージの追加(/app/Http/Coontrollers/UsersController.php)
php
public function store(UserAddPost $request)
{
$user = new User;
$user->name = $request->name;
$user->email = $request->email;
$user->password = $request->password;
$user->save();
session()->flash("message", ['success' => 'Welcome to the Sample App!']);
return redirect()->route("users.show", $user);
}

レイアウトにフラッシュを追加(/resources/views/layouts/application.blade.php)
```html

        @if(session('message'))
            @foreach (session('message') as $message_type => $message)
                <div class="alert alert-{{ $message_type }}">{{ $message }}</div>
            @endforeach
        @endif
        @yield('content')
        @include('layouts.footer')
    </div>
### 7.4.3 実際のユーザー登録

### 7.4.4 成功時のテスト

テスト追加(/tests/Feature/UsersSignupTest.php)
```php
    public function testValidSignup()
    {
        $this->get(route('signup'));
        $count = User::all()->count();
        $response = $this->followingRedirects()
                         ->post('/signup', [
                                'name' => "Example User",
                                'email' => "user@example.com",
                                'password' => "password",
                                'password_confirmation' => "password"
                         ]);
        $this->assertSame($count + 1, User::all()->count());
        $response->assertViewIs("users.show");
    }

課題

$response->assertSeeText("Welcome to the Sample App!");

7.5 プロのデプロイ

7.5.1 本番環境でのSSL

ミドルウェア追加

artisan make:middleware ForceHttps

修正(/app/Http/Middleware/ForceHttps.php)
```php
class ForceHttps
{
public function handle($request, Closure $next)
{
if (!$request->secure() && config('app.env') === 'production') {
return redirect()->secure($request->getRequestUri(), 301);
}

    return $next($request);
}

}
```

カーネルにミドルウェアを追加(/app/Http/Kernel.php)
php
protected $middlewareGroups = [
'web' => [
...
\App\Http\Middleware\ForceHttps::class,
...
],

「クラウド」ロードバランサプロバイダを使用している場合は下記を修正(/app/Http/Middleware/TrustProxies.php)
php
class TrustProxies extends Middleware
{
protected $proxies = '**';
...
}

7.5.2 本番環境用のWebサーバー

7.5.3 本番環境へのデプロイ

herokuにpostgresを追加してない場合は追加

7.6 最後に

参考

https://qiita.com/sympe/items/9297f41d5f7a9d91aa11
https://qiita.com/sutara79/items/9fd442a81001842aeba1
https://qiita.com/daisu_yamazaki/items/e44d4b744d9d5f9bc8b3
https://qiita.com/ohtsuka_shouta/items/b360c20a0eef8d8a44e0
https://qiita.com/tamappe/items/14a4e6890ecef4d49d1f
http://kayakuguri.github.io/blog/2017/10/27/laravel-https-loadbalancer/

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

Laravel で Ruby on Rails チュートリアル【6章】

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

6.1 Userモデル

6.1.1 データベースの移行

既存のUserモデル、create_users_tableマイグレーションを流用する

マイグレーション編集(/database/migrations/[timestamp]create_users_table.php)
```php
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email');

        $table->timestamps();
    });
}

public function down()
{
    Schema::dropIfExists('users');
}

}

マイグレーション適用

artisan migrate
```

6.1.2 modelファイル

(/app/User.php)
```php
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
use Notifiable;

protected $table = 'users';

protected $guarded = ["id"];

}
```

6.1.3 ユーザーオブジェクトを作成する

$user = new User;
$user->name = "Michael Hartl";
$user->email = "mhartl@example.com";
$user->save();

$foo = User::create(['name' => "A Nother", 'email' => "another@example.org"]);
$foo->delete();

6.1.4 ユーザーオブジェクトを検索する

User::find(1);
User::where('email', "mhartl@example.com");
User::first();
User::all();

6.1.5 ユーザーオブジェクトを更新する

$user = User::find(1);

$user->email = "mhartl@example.com";
$user->save();

$user->update(["name" => "The Dude", "email" => "dude@abides.org"]);

6.2 ユーザーを検証する

6.2.1 有効性を検証する

6.2.2 存在性を検証する

(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->validate([
"name" => "required"
,
"email" => "required"
]);
}

6.2.3 長さを検証する

(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255"
]);
}

6.2.4 フォーマットを検証する

(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email"
]);
}

6.2.5 一意性を検証する

(/app/Http/Controllers/UsersController.php)
php
public function store(Request $request)
{
$request->validate([
"name" => "required|max:50",
"email" => "required|max:255|email|unique:users|email"
]);
}

マイグレーション作成(database/migrations/[timestamp]_add_index_to_users_email.php)
```php
class AddIndexToUsersEmail extends Migration
{

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->unique('email');
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropUnique('users_email_unique');
    });
}

}
```

6.3 セキュアなパスワードを追加する

6.3.1 ハッシュ化されたパスワード

マイグレーション作成(/database/migrations/[timeatamp]_add_password_digest_to_users.php)
※laravelの処理の関係でremember_tokenもこのタイミングで追加
```php
class AddPasswordDigestToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('password');
$table->rememberToken();
});
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('password');
        $table->dropRememberToken();
    });
}

}
```

6.3.2 ユーザーがセキュアなパスワードを持っている

バリデーションルール修正(/app/Http/Controllers/UsersController.php)
```php
public function store(Request $request)

{
    $request->validate([
        "name" => "required|max:50",
        "email" => "required|max:255|email|unique:users|email",
        "password" => "required|confirmed"
    ]);
}
### 6.3.3 パスワードの最小文字数

バリデーションルールの修正(/app/User.php)
```php
    public function store(Request $request)
    {
        $request->validate([
            "name" => "required|max:50",
            "email" => "required|max:255|email|unique:users|email",
            "password" => "required|confirmed|min:6"
        ]);
    }

6.3.4 ユーザーの作成と認証

tinkerでユーザを作成
php
User.create(["name" => "Michael Hartl", "email" => "mhartl@example.com", "password" => bcrypt("foobar")]);

6.4 最後に

参考

https://qiita.com/kd9951/items/c06d107aa4018295aa25

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