「PHPフレームワーク Laravel Webアプリケーション開発」のご紹介

こんにちは、 id:nunulk です。

いま話題の「PHPフレームワーク Laravel Webアプリケーション開発」が SCOUTER 社に置いてあったので、ざっと読ませていただきました。

f:id:nunulk:20181014111718j:plain

主に「Chapter 3 アプリケーションアーキテクチャ」や「Chapter 7 処理の分離」の章について読んだ感想を書こうと思います。

全体の感想

まず、全体の感想ですが、 Laravel 自体の解説については、公式ドキュメントが充実しているのと、日本語訳も追従が早いので、個人的には書籍の必要性はあまり感じてはいませんでした。

ただ、本書は、JWT を使った認証だったり、Elasticsearch を使った全文検索の例だったりが載っていて、Laravel だけにとどまらず、Laravel を使ってウェブアプリケーションをつくる上で参考になる情報がたくさん掲載されているので、公式ドキュメントだけでは得られないものが得られるんじゃないかと思います。

アプリケーションアーキテクチャについて

ウェブサービスや業務システムでは、チュートリアルや公式ドキュメントに載っているような単純なサンプルコードでは単純すぎて参考にならなくなってきますが、本書ではアプリケーションアーキテクチャについての記述が多くあり、実際に業務で書くような複雑なアプリケーションにも適用可能なアイデアが手に入るんじゃないかと思います。

本書に載っているアーキテクチャ関連のセクションについて、いくつか紹介します。

ADR

ADR がなにか、というのは本書を読んでいただくとして、以下の文を引用しておきます。

ADRパターンを適用すると、実際にはMVCパターンと大きな相違はなく、MVCパターンよりも、より処理の内容が具体化したように感じられる。 (略) ここで重要なことはパターン名ではなく、どのような考え方でクラスや処理グループを分割し、責務を与えることであり、「どうしたらよりよいアプリケーション設計が行えるか」です。

アプリケーションが複雑になり、依存関係が増えてくると、どんどんクラスが大きくなってきて、見通しが悪くなってきますので、こうした分割手法は知っておいて損はないと思います。

レイヤードアーキテクチャ

Laravel は素晴らしいフレームワークですが、アプリケーション独自のクラスや処理(ドメインモデル、ドメインロジック)を書く際の指針は、自分で、あるいはチームで決めないといけないので、試行錯誤が必要かと思います。

それでも、先人たちの知恵を参考に、そうした複雑さを分解して、あるいは特定のクラスに閉じ込めることで、コントロールしやすくなるので、ドメイン駆動設計を理解している著者による、本書のサンプルコードは参考になるはずです。

この章以外にも、「5-5 リポジトリパターン」のところに詳しく載っているリファクタリングのところとか、既存のアプリケーションにリポジトリパターンを適用するようなケースで参考になるんじゃないでしょうか。

実用的な非同期処理の例

Laravel には、キューを使った非同期処理を行う仕組みがありますが、実用的な例、というのはインターネットではなかなか見かけないので、実際の使い方に不安を持っている方も多いんじゃないかと思っているんですが、本書では、supervisor の設定や、ElasticsearchClient の使い方も載っていて、実用的と思います。

本書でアプリケーションアーキテクチャについておおまかに理解したら、Chapter 3 の章末に載っている参考文献を片っ端から読んでみるのもいいかもしれません。

おわりに

全体で 500 ページを超える本ですが、Chapter 2 以降は一度目を通すのをおすすめします。どの章にも、公式ドキュメントを読んだだけでは得られない、へーこんな使い方もできるのかーという発見があるんじゃないかと思います。

SCOUTER では、不定期に「ワイワイLaravel語る会」というのを開催していますので、本書を読んだ感想とか、本書の記載を元にした議論とか、Laraveller のみなさんと語り合えたらうれしいです。

詳しくは Connpass のグループを見てみてください。

techscouter.connpass.com

laravelにnuxtを追加してみて出来たこと出来なかったこと

こんにちはhirokinishizawaです!

今回laravelにnuxtを追加したのですが、この三連休で自分が最終的に出来たこと出来なかったことを書いていこうかと思います。

nuxtを入れたことによって変更したファイル

viewファイル

resources
 └── views
      ├── auth
      │   ├── passwords
      │   │   ├── email.blade.php
      │   │   └── resets.blade.php
      │   └── login.blade.php
      │   └── register.blade.php
      ├── layouts
      │   └── app.blade.php
      └── posts
      │    ├── create.blade.php
      │    ├── delete.blade.php
      │    └── edit.blade.php
      ├── home.blade.php
      └── welcome.blade.php

こちらはcontrollerファイルからviewは呼び出さないので全て削除しました!

routes/web.php : routes/api.php

全てapi経由で使用するのでweb.phpにあったものは全てapi.phpに移行しました!

controllerファイル

controllerファイルはファイルごとに中身は少し変更していますが後ほど話そうかと思います!

出来たこと

nuxt導入

laravelにnuxt導入の仕方は以前 id:kotamatさんが書いてくれた「LaravelとNuxt.jsを同一レポジトリで管理するときの構成」を読んでもらえるとわかると思います!

techblog.scouter.co.jp

記事を読んでもらえるとわかると思いますが、今回サーバーはlocalhost:8000フロントはlocalhost:3000で話していきたいと思います。

フロントでapi繋ぎ込み

以前までのlaravelだけでviewを表示しいていた/homeのcontrollerファイルはこのようになっていました。

<?php

public function index(Request $request)
    {
        $user = $request->user();
        $posts = $user->load('posts');
        return view('home', ['posts'=>$posts->posts]);
    }

こちらでviewファイルであるbladeファイルをreturnしていましたが、今回api呼び出しをするのでこのようにしました

<?php

public function index(Request $request)
    {
        $user = $request->user();
        $posts = $user->load('posts');
        return $posts->posts;
    }

なおvueファイルでapiを叩く際はこのようになっています!

<template>
<div class="card" v-for="(fishingData, key) in data" :key="key">
      <div class="card-header">
           日付:{{fishingData.year}}年{{fishingData.month}}月{{fishingData.day}}日
      </div>
      <div class="card-body">
          <div class="column">
              <div class="fish_name">魚種:{{fishingData.fish_name}}</div>
          </div>
          <div class="column">
              <div class="place">場所:{{fishingData.prefecture}}{{fishingData.place}}</div>
          </div>
      </div>
</div
</template>

<script>
export default {
        async asyncData({app}){
            const data = await app.$axios.$get('http://localhost:8000/home')
            return {data};
        },
}
<script>

見た目はこんな感じです!

f:id:hiroki-nishizawa:20180925011343p:plain

他にそれぞれページの表示やページ遷移は<nuxt-link>を使ったりで出来たのですが、axios-moduleを使用して記録を作成したり編集したりすることは出来ませんでした。

なにに詰まったか

user認証

まず最初にlaravelでやっていたユーザー認証をnuxtではどのようにするのかと疑問を持ったので調べて参考にした記事がこちらになります

そこでcomposer require laravel/passportを実行すると

Your requirements could not be resolved to an installable set of packages.                                                                                                                                                                    
                                                                                                                                                                                                                                              
  Problem 1                                                                                                                                                                                                                                   
    - Conclusion: don't install laravel/passport v4.0.3                                                                                                                                                                                       
    - Conclusion: don't install laravel/passport v4.0.2                                                                                                                                                                                       
    - Conclusion: don't install laravel/passport v4.0.1                                                                                                                                                                                       
    - Conclusion: remove paragonie/random_compat v9.99.99                                                                                                                                                                                     
    - Installation request for laravel/passport ~4.0 -> satisfiable by laravel/passport[v4.0.0, v4.0.1, v4.0.2, v4.0.3].                                                                                                                      
    - Conclusion: don't install paragonie/random_compat v9.99.99                                                                                                                                                                              
    - laravel/passport v4.0.0 requires league/oauth2-server ^6.0 -> satisfiable by league/oauth2-server[6.0.0, 6.0.1, 6.0.2, 6.1.0, 6.1.1].                                                                                                   
    - league/oauth2-server 6.0.0 requires paragonie/random_compat ^2.0 -> satisfiable by paragonie/random_compat[v2.0.0, v2.0.1, v2.0.10, v2.0.11, v2.0.12, v2.0.13, v2.0.14, v2.0.15, v2.0.16, v2.0.17, v2.0.2, v2.0.3, v2.0.4, v2.0.5, v2.0.
6, v2.0.7, v2.0.8, v2.0.9].                                                                                                                                                                                                                   
    - league/oauth2-server 6.0.1 requires paragonie/random_compat ^2.0 -> satisfiable by paragonie/random_compat[v2.0.0, v2.0.1, v2.0.10, v2.0.11, v2.0.12, v2.0.13, v2.0.14, v2.0.15, v2.0.16, v2.0.17, v2.0.2, v2.0.3, v2.0.4, v2.0.5, v2.0.
6, v2.0.7, v2.0.8, v2.0.9].                                                                                                                                                                                                                   
    - league/oauth2-server 6.0.2 requires paragonie/random_compat ^2.0 -> satisfiable by paragonie/random_compat[v2.0.0, v2.0.1, v2.0.10, v2.0.11, v2.0.12, v2.0.13, v2.0.14, v2.0.15, v2.0.16, v2.0.17, v2.0.2, v2.0.3, v2.0.4, v2.0.5, v2.0.
6, v2.0.7, v2.0.8, v2.0.9].                                                                                                                                                                                                                   
    - league/oauth2-server 6.1.0 requires paragonie/random_compat ^2.0 -> satisfiable by paragonie/random_compat[v2.0.0, v2.0.1, v2.0.10, v2.0.11, v2.0.12, v2.0.13, v2.0.14, v2.0.15, v2.0.16, v2.0.17, v2.0.2, v2.0.3, v2.0.4, v2.0.5, v2.0.
6, v2.0.7, v2.0.8, v2.0.9].                                                                                                                                                                                                                   
    - league/oauth2-server 6.1.1 requires paragonie/random_compat ^2.0 -> satisfiable by paragonie/random_compat[v2.0.0, v2.0.1, v2.0.10, v2.0.11, v2.0.12, v2.0.13, v2.0.14, v2.0.15, v2.0.16, v2.0.17, v2.0.2, v2.0.3, v2.0.4, v2.0.5, v2.0.
6, v2.0.7, v2.0.8, v2.0.9].                                                                                                                                                                                                                   
    - Can only install one of: paragonie/random_compat[v2.0.0, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.1, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.10, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.11, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.12, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.13, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.14, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.15, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.16, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.17, v9.99.99].                                                                                                                                                                    
    - Can only install one of: paragonie/random_compat[v2.0.2, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.3, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.4, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.5, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.6, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.7, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.8, v9.99.99].                                                                                                                                                                     
    - Can only install one of: paragonie/random_compat[v2.0.9, v9.99.99].
    - Installation request for paragonie/random_compat (locked at v9.99.99) -> satisfiable by paragonie/random_compat[v9.99.99].


Installation failed, reverting ./composer.json to its original content.

このようなエラーがでました。

その中のrandom_compatがv9.99.99になっていたので変だと思ったので2.0.0にしたところエラーが解消されました!

postするときのエラー

postするときaxios.post('http://localhost:8006/api/posts', this.foge)とやって作成ボタンを押すとNo 'Access-Control-Allow-Origin'のエラーが出てきました。 調べたところCORS対応が必要みたいです。今回は説明を省くのでこちらを参考にしてください。

最後に

laravelとnuxtでの記事が少なく参考を探すのが大変でした。次回はしっかり解決して解決策をかければと思います!

Laravel ユーザー毎に記録を作成、表示できるように

こんにちは!Laravel勉強中のhirokinishizawaです!

このlaravel勉強ブログでは、はじめてversion minorをしてみたので前回から見て頂けると幸いです!(一気に.5上がってるのは内緒の方向でお願いします)

techblog.scouter.co.jp

はじめに

今回は前回のブログで言っていたユーザー毎に釣りの記録を管理できるようにしました!(あと何もやってなかったスタイルも少しだけ笑)

f:id:hiroki-nishizawa:20180917060008j:plain

f:id:hiroki-nishizawa:20180917060014j:plain

f:id:hiroki-nishizawa:20180917060017j:plain

ディレクトリについてしばらく書いていなかったので軽く書こうかと思います!

Model

User.php
Post.php

Controller

HomeController.php
PostsController.php

Views

resources
 └── views
      ├── auth
      │   ├── passwords
      │   │   ├── email.blade.php
      │   │   └── resets.blade.php
      │   └── login.blade.php
      │   └── register.blade.php
      ├── layouts
      │   └── app.blade.php
      └── posts
      │    ├── create.blade.php
      │    ├── delete.blade.php
      │    └── edit.blade.php
      ├── home.blade.php
      └── welcome.blade.php

この中で今回のユーザー毎に表示や作成出来るようにするためにModelとControllerファイルを変更しました!

モデル

UserとPostsの関係は1対多と呼ばれる関係です!

ユーザーは複数の記録を保存できるので、Userは複数のPostを保存する 1つの記録の情報は1人しか存在しないのでPostは1つのUserしか持ちません! 上記の関係に基づき、各モデルにはこのように関数を追記しました!!(リレーション)

User.php

<?php
public function posts()
{
    return $this->hasMany('App\Post');
}

Post.php

<?php
public function user()
{
    return $this->belongsTo('App\User');
}

コントローラー

HomeController.php

<?php
public function index(Request $request)
{
    $user = $request->user();
    $posts = $user->load('posts');
    return view('home', ['posts'=>$posts->posts]);
}

PostsController.php

<?php
public function create()
    {
        return view('posts.create');
    }

    public function edit(Post $post)
    {
        return view('posts.edit')->with('post', $post);
    }

    public function post(PostRequest $request)
    {
        $post = new Post();
        $post->fish_name = $request->fish_name;
        $post->year = $request->year;
        $post->month = $request->month;
        $post->day = $request->day;
        $post->prefecture = $request->prefecture;
        $post->place = $request->place;
        $post->user_id = $request->user()->id;
        $post->save();
        return redirect('/home');
    }

    public function update(PostRequest $request, Post $post)
    {
        $post->fish_name = $request->fish_name;
        $post->year = $request->year;
        $post->month = $request->month;
        $post->day = $request->day;
        $post->prefecture = $request->prefecture;
        $post->place = $request->place;
        $post->save();
        return redirect('/home');
    }

    public function delete(Request $request)
    {
        $post = Post::find($request->id);
        return view('posts.delete')->with('post',$post);
    }

    public function remove(Request $request)
    {
        $post = Post::find($request->id);
        $post->delete();
        return redirect('/home');
    }

実際に変更点があったのはpostだけになります。 user_idを取得するために$post->user_id = $request->user()->id;になっています!

躓いた点

リレーションと関係あるかはわからないですが、postをユーザー管理を出来るようにuser_idを作ってから送信しようとしたらUnknown column 'updated_at'というエラーが出てくるようになりました。 調べてみたところpublic $timestamps = false;でupdate_atを更新するのを回避できると書いてあったので試しにやってみたら次はcreated_atがないよーと怒られました。。。 postなのに作成日時はないとだめなのかな??

migrationファイルを見てみると作成した時からあるcreatedとmodifiedがありましたが、update_atはありませんでした。なのでcreatedとmodified($table->datetime('created');、$table->datetime('modified');)を削除してtimestamps($table->timestamps();)を入れることにより対応できました!

最後に

だんだんlaravelの内容が難しくなってきて、実装と文章にするのが難しくなってきましたーーー泣 分かりづらかったら申し訳ございません。

今は全てapp直下のmodelとcontrollerファイルだけを使って書いていますが何かほかのrepositoryファイルとかも使って作成していこうかと思います!

先週nuxtmeetup#5のイベントを公開したので興味あれば是非参加してください!! nuxt-meetup.connpass.com

ありがとうございました!

Laravel: アカウント登録出来るようにする

こんにちは!scouterでフロントエンジニアをしてるhirokinishizawaです!

はじめに

今回Laravel勉強version5.0.0では前回まで作っていた釣りの記録をユーザー管理するための準備でアカウント登録できるようにしました!

前回投稿した記事

techblog.scouter.co.jp

アカウント登録できるようにするにあたって

最初に言っておきますLaravelでアカウント登録をできるようにするのはとても簡単です!

アカウントを登録出来るようにするにはターミナルでLaravelをインストールしたプロジェクトに移動して

php artisan make:auth

こちらを実行すると次のようにアカウント登録するたに必要なファイルやルーティングが生成されアカウント登録、ログインなどが出来るようになります!

生成されるファイルはこちらになっています!

Created View: /resources/views/auth/login.blade.php
Created View: /resources/views/auth/register.blade.php
Created View: /resources/views/auth/passwords/email.blade.php
Created View: /resources/views/auth/passwords/reset.blade.php
Created View: /resources/views/auth/emails/password.blade.php
Created View: /resources/views/layouts/app.blade.php
Created View: /resources/views/home.blade.php
Created View: /resources/views/welcome.blade.php
Installed HomeController.
Updated Routes File.
Authentication scaffolding generated successfully!

ルーティングには

<?php

Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');

がweb.phpに追加されています!

Route::get('/home', 'HomeController@index')->name('home');こちらは作成されたHemeController.phpの中のindexメソッドを使用しているのはわかりますが、Auth::routes();の方はちょっとよく分かりませんでしたが今は置いておいてとりあえず出来ることが何なのかブラウザ上で確認してみようかと思います!Auth::routes();のルーティングの中身は後ほど説明したいと思います!

make:authをした後の挙動

最初の画面(topページ)

サーバーを立ち上げた後localhost:8000を開くと以前は

f:id:hiroki-nishizawa:20180910010206p:plain

このような画面でしたが上書きされて

f:id:hiroki-nishizawa:20180910010143p:plain

このような画面になっています!

アカウント登録,ログイン画面

topページのヘッダーにあるloginとregisterを押すとログイン画面、アカウント登録画面にいきます。

f:id:hiroki-nishizawa:20180910010146p:plain

f:id:hiroki-nishizawa:20180910010151p:plain

home画面

login完了するとhome画面に行きます

f:id:hiroki-nishizawa:20180910010155p:plain

初期画面はこのようになっているのですが、自分は前回まで作っていた釣りのhome画面があるので/homeにアクセスすると

f:id:hiroki-nishizawa:20180910010159p:plain

このような感じになっています!今はまだユーザー毎に記録したものを表示出来るようにしていないので、どのユーザーでアクセスしても同じ内容が表示されます!!

Forgot Your Password?

topページにあるForgot Your Password? を押すとパスワード再発行をする画面に行きます!

f:id:hiroki-nishizawa:20180910010202p:plain

追加されたAuth::routes()の中身

冒頭でmake:authをしたときルーティングに追加されたAuth::routes()の中身について書いていこうかと思います!

ルーティングの中身

先程わからないと言っていたAuth::routes()を調べてみたら中はvender\laravel\framework\src\Illuminate\Routing\Route.phpに記載されていることがわかりました!

中のauthメソッドを見てみるとこのようになっていました!

<?php
 public function auth()
    {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');

        // Registration Routes...
        $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
        $this->post('register', 'Auth\RegisterController@register');

        // Password Reset Routes...
        $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
        $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
        $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
        $this->post('password/reset', 'Auth\ResetPasswordController@reset');
    }

login画面を例として話していきます。login画面はルーティングを見る感じLoginControllerのshowLoginFormメソッドに書かれているのがわかると思います!ですがLoginControllerを見てみるとshoLoginFormというメソッドは見つかりませんでした。他のルーティングも見る限りApp\Http\Controllersの中のControllerファイルにはありませんでした。

結局分からず一度調べることに。。。

認証系のメソッドはほぼトレイトとして実装されており、その実体は、Illuminate\Foundation\Auth\以下に存在します。

らしいです。。。。。

こちらはmake:authを実行した際にAuth::routes();が追加されたことによって参照されるようになるみたいです!

すごい便利。。。笑

改めてLaravelの凄さを実感しました!笑

ではアカウント登録をするとき、ログインをしたときの動きをControllerファイルで見ていきましょう!

アカウント登録をするとき

ルーティング

// Registration Routes...
        $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
        $this->post('register', 'Auth\RegisterController@register');

RegisterController@showRegistrationForm'

<?php
public function showRegistrationForm()
    {
        return view('auth.register');
    }

こちらはただアカウント登録をするページを表示するだけのメソッドでした!

RegisterController@register

<?php
public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->guard()->login($user);

        return $this->registered($request, $user)
                        ?: redirect($this->redirectPath());
    }

登録したらその場でログインをし、AuthControllerで設定されたリダイレクト先へリダイレクトするようになっています。というわけで/homeにリダイレクトされます!

ログインするとき

ルーティング

// Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');

LoginController@showLoginForm

<?php
public function showLoginForm()
    {
        return view('auth.login');
    }

こちらはただログインする画面を表示するだけのメソッドでした!

LoginController@login

<?php
public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

認証がOKならログイン処理が行われます。 その他にもスロットル処理が行われており、ここではログインを何度も失敗して一定の数を超えると、しばらくログインできないという処理が加わっています。

最後に

このようにLaravelでアカウント認証の処理をしたいと思ったらphp artisan make:authをするだけで簡単にできます!本当に便利だと思いました!

来週はアカウント認証系のスタイルを釣りの記録用に変更をしたり釣りの記録をユーザー別に管理をしてみようかなーと思います!

このブログと同時に本日12:00にNuxtMeetUp #5を公開しました!10/18日です!ぜひご参加ください!

nuxt-meetup.connpass.com

LaravelのEditとDelete

こんにちはscouterでフロントエンジニアをしているhirokinishizawaです! laravel勉強ブログもVersion4.0.0になりました!

前回投稿した記事 techblog.scouter.co.jp

前回は釣りの記録を作成する際にデータベースにレコードを追加できるようにしました。なので今回は編集と削除を出来るようにしました!

はじめに

作成したものの動き

トップページの各記録に編集する削除するを追加しました

gyazo.com

あとレイアウトとして追加したのが

編集ページと

gyazo.com

削除する時の確認ページです

gyazo.com

やったこと

1.記録を編集できるようにした

2.Requestファイルを使って作成時と編集時のvalidateを一つにまとめた

3.記録を削除できるようにした

記録を編集

web.php

<?php

Route::get('/posts/{post}/edit', 'PostsController@edit');
Route::patch('/posts/{post}', 'PostsController@update');

PostsController.php

<?php

  public function edit(Post $post)
    {
        return view('posts.edit')->with('post', $post);
    }

 public function update(PostRequest $request, Post $post)
    {
        $this->validate($request, [
            'fish_name' => 'required',
            'year' => 'required|integer|between:1990,2018',
            'month' => 'required|integer|between:1,12',
            'day' => 'required|integer|between:1,31',
            'prefecture' => 'required',
            'place' => 'required'
        ]);
        $post->fish_name = $request->fish_name;
        $post->year = $request->year;
        $post->month = $request->month;
        $post->day = $request->day;
        $post->prefecture = $request->prefecture;
        $post->place = $request->place;
        $post->save();
        return redirect('/');
    }

前回の@postのときとほぼ同じです。違うのは引数でpostを受け取るので$post = new Post();このように新しくPostを作らなくていいところです。

edit.blade.php作成

コード自体は作成ページと編集ページのレイアウト見てもらうとわかると思いますがほぼかわっていません。変わったのはpostではくupdateするためにメソッドにpatchをを使用しました。

編集ページ

@section('content')
    <h1>記録編集ページ<a href="{{ url('/') }}" class="header-menu">戻る</a></h1>

    <form method="post" action="{{ url('/posts', $post->id) }}">
        {{ csrf_field() }}
        {{ method_field('patch') }}
        <div class="flex-box">
            <div class="column">
                魚の名前
            </div>
            <div>
                <input type="text" name="fish_name" placeholder="ブラックバス" value="{{old('fish_name', $post->fish_name)}}">
                @if($errors->has('fish_name'))
                    <span class="error">{{ $errors->first('fish_name') }}</span>
                @endif
            </div>
        </div>...

新規作成ページ

@section('content')
    <h1>記録を作成する<a href="{{ url('/') }}" class="header-menu">戻る</a></h1>

    <form method="post" action="{{ url('/posts') }}">
        {{ csrf_field() }}
        <div class="flex-box">
            <div class="column">
                魚の名前
            </div>
            <div>
                <input type="text" name="fish_name" placeholder="ブラックバス" value="{{old('fish_name')}}">
                @if($errors->has('fish_name'))
                    <span class="error">{{ $errors->first('fish_name') }}</span>
                @endif
            </div>
        </div>...

見比べると編集ページの方には{{ method_field('patch')}}がありますね。これをformタグの中に書くことによりhttpメソッドのpatchが使えるようになって{{ url('/posts') }}にidを渡すことにより上書きできるようになります。

(後で出てくるdeleteでも似たようなことをしています)

Requestファイル使用

前回postする際に書いたvalidateですがeditでも同じ条件でvalidateを掛けたいのですが下のように重複するのはあまり好ましくないのでRequestファイルに共通部分を書きました。

<?php

public function post(PostRequest $request)
    {
        $this->validate($request, [
            'fish_name' => 'required',
            'year' => 'required|integer|between:1990,2018',
            'month' => 'required|integer|between:1,12',
            'day' => 'required|integer|between:1,31',
            'prefecture' => 'required',
            'place' => 'required'
        ]);
        
        $post = new Post();
        $post->fish_name = $request->fish_name;
        $post->year = $request->year;
        $post->month = $request->month;
        $post->day = $request->day;
        $post->prefecture = $request->prefecture;
        $post->place = $request->place;
        $post->save();
        return redirect('/');
    }

    public function update(PostRequest $request, Post $post)
    {
        $this->validate($request, [
            'fish_name' => 'required',
            'year' => 'required|integer|between:1990,2018',
            'month' => 'required|integer|between:1,12',
            'day' => 'required|integer|between:1,31',
            'prefecture' => 'required',
            'place' => 'required'
        ]);
        
        $post->fish_name = $request->fish_name;
        $post->year = $request->year;
        $post->month = $request->month;
        $post->day = $request->day;
        $post->prefecture = $request->prefecture;
        $post->place = $request->place;
        $post->save();
        return redirect('/');
    }
php artisan make:request PostRequest

こちらのコマンドでApp\Http\Requestsの下にRequestファイルを作成することが出来ます。

<?php
    public function rules()
    {
        return [
            'fish_name' => 'required',
            'year' => 'required|integer|between:1990,2018',
            'month' => 'required|integer|between:1,12',
            'day' => 'required|integer|between:1,31',
            'prefecture' => 'required',
            'place' => 'required'
        ];
    }

    public function messages()
    {
        return [
            'fish_name.required' => '魚の名前を入れてください',
            'year.required' => '1990年から2018年の間で記入してください',
            'month.required' => '1年の中で1月から12月までしかありませんよ',
            'day.required' => '1ヶ月の中で1日から31日までしかありませんよ。',
            'prefecture.required' => '情報提供してください',
            'place.required' => '情報提供してください',
        ];
    }

rules()は名前の通りルールを書いていきます。

下のmessages()はそのルールを破った時にでてくる文章を作ることが出来ます!

前回は文字の変更のやり方が分からなかったのでとても分かりやすく便利だなと思ったRequestファイルでした!

記録を削除

web.php

<?php
Route::get('/posts/delete', 'PostsController@delete')->name('post.delete');
Route::delete('/posts/remove', 'PostsController@remove')->name('post.remove');

削除の方ではnameを使ってみました。nameについては後で説明します。

PostsController.php

<?php

    public function delete(Request $request)
    {
        $post = Post::find($request->id);
       return view('posts.delete')->with('post',$post);
    }

    public function remove(Request $request)
    {
        $post = Post::find($request->id);
        $post->delete();
        return redirect('/');
    }

delete.blade.php作成

コードはこのようになっています

@extends('layouts.app')

@section('content')
    <h1>本当に削除しますか?<a href="{{ url('/') }}" class="header-menu">戻る</a></h1>

    <div class="fishing-record">
        <div class="flex-box">
            <div class="column">魚種</div>
            <div class="text">{{ $post->fish_name }}</div>
        </div>
        <div class="flex-box">
            <div class="column">釣った日付</div>
            <div class="text">{{ $post->year }}年{{ $post->month }}月{{ $post->day }}日</div>
        </div>
        <div class="flex-box">
            <div class="column">釣った場所</div>
            <div class="text">{{ $post->prefecture }}: {{ $post->place }}</div>
        </div>
    </div>
    <form action='{{ route('post.remove') }}' method='post'>
        {{ csrf_field() }}
        {{ method_field('delete') }}
        <input type='hidden' name='id' value='{{ $post->id }}'><br>
        <input type='submit' value='削除'>
    </form>
@endsection
  • {{ route('post.remove') }} 先程のweb.phpで使用したnameはこのようにrouteとして使用することが出来ます

  • {{ method_field('delete') }} 編集機能のpatchと同じです。httpメソッドを使用できるようにしています。

躓いた点

今回editやdeleteをするのにid毎にそれぞれページ遷移させるのでidを渡さなければいけませんでした。以前作成ページを作った時は作成するボタン(遷移ボタン)を押したら作成ページに飛ぶだけでルーティングはRoute::get('/posts/create'だったので遷移ボタンのコードを書く時はhref="{{ url('/posts/create') }}"とこのような書き方をしました。ですが今回idを渡すのでルーティングはRoute::get('/posts/{post}/edit'。遷移先のコードはhref="{{ url('/posts/edit', $post->id) }}"とかいたのですが、遷移した先は/posts/edit/1になってしまいページがないよと言われました。なので別のページ遷移のやり方を調べhref="{{ action('PostsController@edit', $post) }}"という書き方で無事'/posts/1/edit'となりidごとのeditページやdeleteページに遷移することができました! href="{{ action('PostsController@edit', $post) }}"と同様にhref="{{route('post.edit', $post) }}"とnameを決めてrouteで書いても出来ました!というよりこっちの方が一般的みたいです!

終わりに

前回のデータベースを使って記録の更新に続きちょっとだけ応用したeditとdeleteですが今回調べるにあたりいろいろな人のブログを見たら大体一緒にやってることが多かったので次回は脱初心者に向けてなにかいいネタないか考えたいと思います(勉強するにあたって何をすればいいかわからないのでネタ提供求めますww)

来週もよろしくおねがいしますー!

Babel7 OptionalChainingでLaravelのバリデーションをハンドリングする

f:id:kotamat:20180829111209j:plain# Babel7 OptionalChainingでLaravelのバリデーションをハンドリングする

こんにちは、@kotamatです。

昨日Babel7がリリースされましたね🎉

https://babeljs.io/blog/2018/08/27/7.0.0

Babel6から3年かかってのリリースです。コントリビューターの方々お疲れ様でした。 パッケージ名からの破壊的変更が入っていますが、babelのupgrade用スクリプトが用意されているので、手軽にupgradeすることができます(執筆時点だと、beta版までしかあげられませんが‥)

Babel7のリリースに伴い、TS39の新しいシンタクスがサポートされ、その中でもStage1のOptional Chainingが、Laravelのエラーハンドリングにとても相性がいいので紹介させていただきます。

Laravelのバリデーションエラー

Laravel側でFormRequestを用いて下記のようなバリデーションルールを実装した場合、

class StoreRelationRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'address.prefecture'    =>['required', 'string'],
            'address.zipcode'    =>['required', 'integer'],
            'staff.*.id'    =>['required', 'integer'],
            'staff.*.name'  =>['required', 'string'], 
        ];
    }
}

staffの0, 1がバリデーションに引っかかった場合、下記のようなバリデーションメッセージが帰ってきます

{
    "address.prefecture": ["The prefecture field is required."],
    "address.zipcode": ["The zipcode field is required."],
    "staff.0.id": ["The id field is required."],
    "staff.0.name": ["The name field is required."],
    "staff.1.id": ["The id field is required."],
    "staff.1.name": ["The name field is required."]
}

このままでは、フロント側では扱いづらいので、フロント側で扱いやすい形に整形します

reno-shelter/convert-laravel-validationを使うと、上記メッセージは下記のように修正されます

{
    address: {
      prefecture: ['The prefecture field is required.'],
      zipcode: ['The zipcode field is required.'],
    },
    staff: {
      0: {
        name: ['The name field is required.'],
        id: ['The id field is required.'],
      },
      1: {
        name: ['The name field is required.'],
        id: ['The id field is required.'],
      },
    },
  }

Optional Chainingでメッセージを取り扱う

上記オブジェクトでは、例えば、address.*系のエラーがない場合は、当然ながら参照できない形になってしまうため、メッセージ表示部分で存在可否のチェックを行わないとundefined indexのFatal Errorが発生してしまいます。

そこで便利なのがOptionalChainingです。

Optional chainingの仕組み

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

const baz = obj?.foo?.bar?.baz; // 42

const safe = obj?.qux?.baz; // undefined

// Optional chaining and normal chaining can be intermixed
obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if
                   // `bar` exists

このように、存在しないパラメータがチェーンの途中にあるばあいは、そこでundefinedを返し、ランタイムでのエラーを防ぐ事ができます。

<template>
    <div>
        <input v-model="address.prefecture" />
        <error v-for="(msg, key) in error?.address?.prefecture || []" :key="key"  :msg="msg"/>
    </div>
</template>

上記のようにテンプレート内でerror?.address?.prefectureとすることによって、もしerror, error.address, error.address.prefectureがそれぞれ存在しなかったとしても問題なく実行されるため、無駄にif文で分岐して処理を複雑にする必要はなくなります。

インストール方法

babel7が入っている前提で話をすすめます。babel6以前からのアップデートはbabelupdateを参考にしてください。

まずはyarn (or npm)でプラグインをインストールし、

yarn add @babel/plugin-proposal-optional-chaining

.babelrcでpluginの設定をするだけです、

{
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}

Nuxt.jsであればnuxt.config.jsに下記のように記載します

export default {
  build: {
    babel: {
      plugins: ['@babel/plugin-proposal-optional-chaining']
    }
  }
}

まとめ

Babel7で新たに入ったOptionalChainingの紹介をさせていただきました。まだProposalの段階なので変更が入る可能性はありますが、より実装にフォーカスしたコーディングができるようになるので、是非導入してみてください!

Laravel:: Databaseを使用してデータの保管をしてみる

こんにちは最近laravelの勉強をしているSCOUTERでフロントエンドエンジニアをしているhirokinishizawaです!

前回はbladeを使って簡単なレイアウトを作成しました!

techblog.scouter.co.jp

今回はDatabaseを使ってデータの保管の練習をしてみたので最後まで見て頂けると嬉しいです!!

はじめに

今回何をどうゆう順序で作ったか箇条書き!

  1. テスト用のプロジェクト作成
  2. databaseを作成
  3. migration作成
  4. databaseにデータを送る
  5. databaseのデータを表示する

ざっと言うとこんな感じに作成していきました!

作成したもの

gyazo.com

これはまだデータベースに何も入っていない状態です!右上の作成するを押して作成画面に飛びます。

gyazo.com

これに記入していきアップロードするとデータが送られます。

gyazo.com

このように釣れた日付の古い順に表示するようにしました!そのやり方などは後ほど説明していきたいと思います!

新しいプロジェクトを作成した言い訳

前回のプロジェクトをそのまま使用してdatabaseを活用しようとしていたのですが、実際にやろうと思ったら頭の中でごちゃごちゃになってしまったので新しいプロジェクトを作成してアップロードして表示するまでの流れを理解するために新しいプロジェクトを作成しました!

データベース関連

データベース管理システムはMySQLを使用していて管理ソフトはSequel Proを使用しています。

やったこと

databaseの作成方法はSequel Proでクエリを開きcreate database DB_NAMEで作成できます!作成した後show databasesで作成出来ているか確認ができると思います!

databaseを作成したら次はmodelとmigrationを作成していきます!

php artisan make:model Post --migration

これを実行するとappディレクトリの直下にPost.phpというmodelと、database/migrationsの下にmigrationファイルが作成されます。

ではmigrationを書いていきましょうー

gyazo.com

このような感じに(魚の名前、釣った[年、月、日]、釣れた場所[都道府県、場所])を作成しました!

作成したら下のコマンドを実行することでmigrationがかかります

php artisan migrate
php artisan migrate:status

これを実行するといまmigrationがどのような常態かを見ることが出来ます!

次にdatabaseにデータを送ったり、取得してきて表示したりします!

まずはweb.phpとPostsControllerのなかを見ていこうかと思います!

web.php(ルーティング)

gyazo.com

今回はこちらの3つを使いました。使用用途をそれぞれ説明していきたいと思います!

  • '/'
    • これは釣りのデータを取得して表示する画面になります
  • '/posts/create'
    • これは送るためのデータを書き込む画面になります
  • '/posts'
    • これはdatabaseにpost送信して登録を行うurlになります。actionとしての内容はPostsController@postとなっているので次のControllerで説明します!

Controller

urlごとの役割をざっとweb.phpの方で書いたので、Controllerでどんな処理を行っているか説明をしていきたいと思います!

gyazo.com

index

  • orderBy('year', 'asc')
    • これは年が古い順にしてもらう処理です。続けて'month','day'を書くことで月日まで古い順に取得しています!下の写真が例です!
  • view('posts.index')->with('posts', $posts);
    • これは$postsという変数をindex.blade.phpでも使用できるようにしています。違う書き方だとview('posts.index', ['posts' => $posts]);でも同じように動きます!

gyazo.com

create

これはただcreate.blade.phpを表示しているだけですね笑

bladeは前回同様、表示のページと作成ページの共通部分をviews/layoutsのapp.blade.phpに書いてcontentに@yieldをセットしています!

gyazo.com

createのcontent部分はこのようになっています!

gyazo.com

formタグで囲いmethodをpost、actionを/postsにしました。

もしアップロードを押したときにエラーが発生した場合、直前に記入してあったものが消えないようにするためinputタグのvalue属性に{{ old('column名') }}を書いています。

error文に関してはController@postの説明でしたいと思います

post

gyazo.com

最初はこのようにvalidateを書かずにただデータベースに送ることだけを考えて作成。 return redirectは処理が終わった後にどこに遷移するかのurlです!

次にvalidateの設定をしていきます。validateの種類はこちらを確認してください!

gyazo.com

条件に当てはまらなかった時データベースには送られずエラーが発生し表示されます。

gyazo.com

躓いたポイント

maxやmin,betweenなどの数字の最大最小を活用したい時はその確認を実行する前に'integer'(整数値)をいれないと上手く最大最小値が確認できず送信できませんでした。

最後に

画像をアップロードしたりするにはどこかの保管場所が必要みたいなのでその辺の知識など増やして次は写真付きをチャレンジしてみようかと思います!

あと最近はtwitterで筋肉エンジニアの方々が活発に活動しているので、負けじと筋トレをしようかと思います!

hirokinishizawaでしたーーー!ありがとうございました!