20191223のlaravelに関する記事は8件です。

【Laravel】Requestによるバリデーションは効かせつつ、他は共通エラー

Requestによるバリデーションチェックによるエラー表示は機能させつつ、
他のエラー発生時は共通エラー画面に遷移させる方法です。

環境

  • PHP:バージョン7.3.7
  • Laravel:バージョン5.8
  • OS:Windows10

はじめにやったこと

エラーが発生したら、とりあえずエラー画面に遷移させればいいんだよね、
というわけで、デフォルトの記述をコメントアウトし、以下のように変更。
※リダイレクト先は、\routes\web.php で制御しています。

\app\Exceptions\Handler.php
    public function render($request, Exception $e)
    {
        return redirect('/');
        /* 
        以下はデフォルトの記述
        return parent::render($request, $e);
        */

    }

問題発生

ところが、問題発生。
Requestを使ったバリデーションチェックで、エラー発生時にも共通エラー画面に遷移してしまった。
この場合はエラー内容を自画面に表示させたいので、これでは困る・・・。

解決方法

結論から言うと、\app\Exceptions\Handler.phpで、以下のように記述してあげればOK。

\app\Exceptions\Handler.php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
// 以下を追記
use Illuminate\Validation\ValidationException; 

class Handler extends ExceptionHandler
{

##
## 省略
##

    public function render($request, Exception $e)
    {
        if($e instanceof ValidationException) {
            // バリデーションエラー発生時
            return $this->convertValidationExceptionToResponse($e, $request);
        }else{
            // それ以外は共通エラー画面へ(リダイレクト先は\routes\web.phpで指定)
            return redirect('/');
        }
    }
}

use Illuminate\Validation\ValidationException;
して、バリデーションエラーの場合のみ、
return $this->convertValidationExceptionToResponse($e, $request);
で、バリデーションエラーが出るようにする。
それ以外は共通エラー画面へ。

もう少し詳しく見てみる

\app\Exceptions\Handler.phpで読み込まれていた、
\vendor\laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php
を見てみましょう。

\vendor\laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php
##
## 省略
##
use Illuminate\Validation\ValidationException;
##
## 省略
##

class Handler implements ExceptionHandlerContract
{
##
## 省略
##

    // \app\Exceptions\Handler.phpのrenderファンクションの親に当たるメソッド
    public function render($request, Exception $e)
    {
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {
            return $e->toResponse($request);
        }

        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {   // この部分を拝借
            return $this->convertValidationExceptionToResponse($e, $request);
        }

        return $request->expectsJson()
                        ? $this->prepareJsonResponse($request, $e)
                        : $this->prepareResponse($request, $e);
    }

##
## 省略
##

上のように、バリデーションエラーに関する記述があったので、その部分を拝借しました。
※「ValidationException」で検索してみてください。

感想

うまく条件分岐させれば、他のエラー発生時にも、応用効きそう。
そのうちやってみようと思います。

参考

以下を参考にさせていただきました。
ありがとうございました!

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

Laravel Mixで出力されたJavaScriptファイルのパッケージ構成比をsource-map-explorerで調べる

Laravel Mixで出力されたJavaScriptファイルのファイルサイズが大きくて困っているとき、
まずはどのパッケージがどれぐらいのサイズを占めているか調べると思います。
そこでどうやって構成比を調べるかを「webpack バンドル サイズ」などで検索したところ、source-map-explorerというパッケージを見つけました。

https://github.com/danvk/source-map-explorer

Getting started

サクッと使ってみましょう。
まず用意するのはLaravelアプリで、最新の6を落としてきてnpm installまで済ませたところから始めます。
次に今回の肝であるsource-map-explorerをインストールします。

$ npm install --save-dev source-map-explorer

package.jsonanalyzeコマンドを追加しましょう。

{
    ...
    "scripts": {
        ...

        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "analyze": "source-map-explorer 'public/js/*.js'"
    },
    ...
}

そしてJavaScriptファイルを出力した後にanalyzeを実行します。

$ npm run dev
$ npm run analyze

> @ analyze
> source-map-explorer 'public/js/*.js'

public/js/app.js
  Unable to find a source map.
  See https://github.com/danvk/source-map-explorer/blob/master/README.md#generating-source-maps

ソースマップがないと怒られました:cry:

webpack.mix.jsを修正します。

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps();

npm run productionでないとソースマップが出力されません。

$ npm run production
$ npm run analyze

スクリーンショット 2019-12-23 18.21.11.png

実行するとブラウザで上記のような画面が表示されます。
要素をクリックするとさらに詳しく調べられたりします。
シンプルでわかりやすいですね:beer:

参考URL

https://qiita.com/qrusadorz/items/cd05ffe3ad08754b9e65
https://create-react-app.dev/docs/analyzing-the-bundle-size
https://github.com/danvk/source-map-explorer

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

TechTrain開発がワクワクする5つの理由

株式会社TechBowl が運営している TechTrain の開発ディレクションを担当しています手島(@tejitak)です。【Mentor Ver.】TechTrain Advent Calendar 2019 23日目の記事です。

今回の記事では、TechTrain自体の開発の内側について少し書きます。普段TechTrainを使っていただいているユーザーの皆様/ご利用を検討中の方々/メンターの方々向けに、サービスの開発チームが普段どのようにサービス開発を進めているか、少し知っていただければ幸いです。そして、あわよくばTechBowlの開発チームに興味を持っていただいたらジョインも...是非!

2019年後半開発チームが行ってきたこと

2019年7月にチーム体制が出来上がり本格的に開発が始まりました。それから現在に至るまでの半年間ほど主に以下のようなタスクに従事していました。

  • AWS + Laravel + Nuxt + TypeScriptによる技術スタックの変更
  • ミッション機能の開発
  • 新機能の開発
  • 管理画面/Slack連携など運用チーム向けの開発
  • Redashを用いたデータ可視化ツールの構築
  • ユーザー・メンターヒアリングに基づく継続的改善活動

今現在の開発メンバーはフルタイム1名 + パートタイム3名(私含む)。フロント、サーバー、フルスタックと、それぞれ強みがうまく分かれている開発メンバーが自律的に動き、改善活動と新機能開発が活発、Trainならぬ機関車のごとくワクワクする開発現場となっています。

そんなTechTrainの開発がなぜワクワクするか?その理由を5つ記載します!

ワクワクする理由1 - 技術スタック「AWS + Laravel + Nuxt + TypeScript」が良い

今現在はLaravel + Nuxt + TypeScriptの技術スタックで快適に開発を進められています。この環境になるまで長い道のりがありました。

TechTrain開発秘話

私が開発に参画した頃には2019年春にβ版として既にサービスは稼働していました。
その時点ではAPIはPythonベース、ユーザー向け画面はNuxt、管理画面はReactで作られており、完成度は中途半端な状況でした。実はβ版の時点で多くの問題が発覚しており、βの開発をパートタイムで担当されていた方も忙しく、それを私が引き継ぐ形になりました。

例えば、既に発覚していた致命的な問題…

  • Firebase 認証に関連し一部のユーザーが永遠にログインできない
  • ユーザー登録時の一部のフォーム情報が全くDBに保存されていない
  • 管理画面が一部の情報の閲覧しかできず、更新機能がない

その他開発者・ユーザー共に悩ませる問題が多数あることが発覚…

「この開発案件は…もしかして…やばい?」

誰がなぜそんなことをしてしまったのか。
なってしまったものは、なってしまったもの。
責任追求はせずに、改善をすることに決めた。

以下技術スタックの変遷についてまとめます

APIはPython Flask → PHP Laravelへ

開発チーム体制を作り、まず取り組んだことはLaravelの導入でした。
PHP/LaravelはWebサーバーサイドを実装するにあたって、エンジニア間での共通認識度が高いフレームワークであり、情報も多く担当できるエンジニアの人数も多いです。元々β版のAPIはPython/Flaskで作られていましたが、今後のエンジニア採用も見据えると、β版から正式版にリリースするタイミングで書き換えてしまう方が良いという判断をし、2019年7月技術スタックの大きな変更に着手を始めました。

管理画面はReact → Laravel Adminへ

運営チームの使う管理画面はβの時点ではReactで作りかけのものがありましたが、実際に使って運用できるレベルのものではありませんでした。そこでAPIをLaravelに置き換えるのと同時に管理画面はLaravelの管理画面ジェネレーターを使う判断をしました。
管理画面ジェネレーターは多く存在しますが、これまでいくつかLaravelの管理画面ジェネレーターを使ってきた中でオススメのLaravel Adminを使うことにしました。簡単にDBと紐づいた一覧ページや編集フォームページを作ることができ、かつ、カスタマイズ性が高いものです。2019年8月には、めでたく管理画面とAPIの部分がLaravelに移行完了しました。

GCPからAWSへ

GCPによりデプロイを行なっていましたが、開発メンバーとしてRDS、S3、Elastickbeanstalk/ECSなどを利用したい気持ちが強く、また、今のところGCPの強みであるBigQueryやコンピューティング周りは使用しないサービスであるためAWSへのインフラの移行を行いました。

幸運にもAWSのスタートアップ支援のクレジットも獲得でき、元々β版をホスティングしていたGCPからAWSへ移行しました。

JavaScript → TypeScriptへ

TechTrainのユーザーの皆様が使っているフロント側の実装はβ時点でNuxtで作られていまして、そのソースコードを引き継いで開発をすることにしました。

そこでTechTrain開発に参画しているフロントエンドのエキスパート@y_temp4さんが以下をまるっと行なってくださいました。

  • TypeScript化 / Vuex-guardianの導入
  • Vueコンポーネントの疎結合化 / 複雑化していた実装のリファクタリング
  • デプロイ周りの対応
  • DevTools (Lint, Prettierなど)の導入
  • Jestによるフロントエンドテストの導入
  • などなど

TS化の恩恵で、型推論によりコードを実際にブラウザで実行する前に多くのコーディングミスの検知が可能になり、堅牢性も高く、生産性も高くなりました!

SSR(サーバーサイドレンダリング)の導入へ

よりSEO的に効果の高いレンダリング方式であるSSR形式でのデプロイもできるようになりました。
少々ハマりどころも多いですが、TechTrainのSEOやパフォーマンス向上を実現できました。常に新しい技術の取り組みも積極的に行なっています。

DX(Developer Experience)の向上

このような技術的変遷が数ヶ月で実現され、現在はメンバー自身が快適&自律的に気持ちよく働きたくなる環境が出来上がりました。
毎日githubのPRがバンバン作られ、レビューがされ今のTechTrainの改善活動・新機能開発が行われています。

ワクワクする理由2 - フレキシブルな働き方で進められる

プロフェッショナルが集まったリモート開発チーム

現在Techbowlの開発メンバーはフルタイム社員メンバー1名以外の3名はリモートで開発を行っています(今のところ)。
Slack上の本日宣言のルール(参考: 毎朝のSlack一言発言で劇的に生産性が上がる話 )は必須としていますが、稼働時の厳密なコアタイムは設けておらず、働く場所も自由です。

開発タスクはGithubのProjectとIssueで管理。
開発定例は2週間に一度のペースでzoomにて行い、各開発メンバーが以下のフォーマットでこれから着手するタスクの明確化・発言をしてもらっています。

  1. What I did (着手したこと)
  2. What I will do (これから着手すること)
  3. Blocking issues (何か困っているところ・課題があれば共有)

特に3つ目が重要で、各自困っているところがあれば随時共有して解決策をディスカッションしたりもしています。

現在の開発メンバーはスキルが高く、自律的に動けるプロフェッショナルなメンバーが揃っているため、リモートワークであってもフレキシブルな働き方を実現でき、圧倒的に高い生産性も実現できています。

参考: リモートワークがメインのエンジニアが普段意識していること (フロントエンド担当の@y_temp4さんの記事)

ワクワクする理由3 - 新機能立ち上げの「仮説→検証フェーズ」に携われる

TechTrainでは「ユーザーヒアリング→仮説を立てる→実装する→検証する」を、おざまささん率いるビジネスチームと一緒に開発を迅速に進めています。

TechTrainの開発ではサービスをグロースさせること・成功させることを第一に考えていますので、顧客の意見を反映させていく「仮説→検証フェーズ」は常に意識しなくてはなりません。

スタートアップのサービスに関わる開発者は、開発そのもの自体が目的になってしまわないように注意が必要です。TechTrainでは随時上がってくるビジネス要件やユーザーヒアリングの結果を開発チームがきちんと拾うことを心がけ、優先度を決めて開発タスクに落とし込んでいきます。

例えば2019年10月にリリースしたミッションという機能。ビジネスチームのヒアリングの結果、MVP(Minimum Viable Product)として実装されました。

機能の検証結果を早く知りたいということに注力してリリースを急ぎました。正直いうとリリース時は開発者としてはデザインや細かい点はまだまだ直せるなという点があると認識しつつのリリースです。

どこかのシリコンバレーの格言引用
自分がリリースしたものが恥ずかしくないなら、リリースが遅すぎるということを肝に銘じよう。

結果、ミッションの事業としての仮説は大きく間違っておらず(もちろん改善案は見つかものの)、現在は機能を利用するユーザーも企業側も増え、TechTrainのメイン機能として進めています。

仮に当初の仮説が大きく外れていたとしても、その機能は優先度を落とし別の優先度の高いタスクを強めていけば良い、という「仮説→検証」の考え方に基づいた繰り返しの早い開発を今後も意識的に行っていきます。

ワクワクする理由4 - サービスが成長しているのを体感できる

ミッションなどの新機能の追加後特に、口コミやビジネスチームの広報アクティビティのおかげもあり、日々ユーザー(ユーザーもメンターもクライアント様)が着実に増えています。

開発メンバーの半数は実はTechTrain上のメンターとしても登録されています。ミッション機能を公開した時期から、私への面談の数も増えて、自分の作っているサービスが使われているな、という実感が持てています。

開発者たるもの、自分が作ったものを誰かに使ってもらえることが楽しみですよね。

ワクワクする理由5 - メンターと定期的に出会える場が提供されている!

現在TechTrain上に登録されているメンターの数は80名近くいます。こちらからメンター一覧を見ることができます。

TechTrainに登録されている豪華メンター陣は著名な大企業/メガベンチャー/スタートアップに所属している方々がほとんどです。メンターこそがTechTrainの大きなアセットであり、メンター同士のシナジーも生み出したいと考えています。

普段開発担当をしている際にはヒアリングやお知らせなどを通じてオンライン上の付き合いがメインですが、実は定期的に、メンター同士がオフラインで会えるメンター向けmeetupが開催されています。そのほかにもHackbowlのメンターとしてメンター同士交流している方も多いです。

メンター同士の横のつながりが強くなる仕掛けも来年以降より増えていくと思います。

最後に

TechTrainの開発がどのように進んでいるかについて、現在開発のディレクションを行なっている立場から少しご紹介をしました。来年以降も引き続き機関車のごとく開発を進めていきますのでよろしくお願いします!

サービスに興味を持った方はぜひユーザー登録をしてみてください。

そして、、TechTrainの開発に興味を持っていただいたエンジニアの方はフルスタック・フロント・サーバー全方面で募集中ですので、ぜひお気軽に一声お願いします!

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

【laravel】sitemap.xmlの更新をしたくない気持ち(※未解決)

それはどんな気持ちか

  • ルートを変更する度にsitemapの更新したくないな
  • 特定のミドルウェアルートグループのgetルートが全てsitemap.xmlに勝手に登録されたらいいのにな

※ 未解決

全てのルートの取得方法の確認も合わせてやってみます。

やってみる

想定する流れ

  1. ルートを取得する
  2. sitemap.xmlに出力

ルートを取得する

参考

このコマンドでやっていることが参考になりそうなので、参考にしてみる。

$ php artisan route:list

Illuminate\Foundation\Console\RouteListCommand を読む。

RouteListCommand
// ※ use Illuminate\Routing\Router;


/**
 * Create a new route command instance.
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function __construct(Router $router)
{
    parent::__construct();

    $this->router = $router;
}

/**
 * Execute the console command.
 *
 * @return void
 */
public function handle()
{
    if (empty($this->router->getRoutes())) {
        return $this->error("Your application doesn't have any routes.");
    }

    if (empty($routes = $this->getRoutes())) {
        return $this->error("Your application doesn't have any routes matching the given criteria.");
    }

    $this->displayRoutes($routes);
}

$this->router->getRoutes() で全てのルートを取得し、ルート毎に属性(name,middlewareなど)をそれぞれ処理している。

今回はミドルウェアとメソッドが欲しいのでそれらを確認してみます。

  1. ミドルウェアの取得は

    RouteListCommand
    /**
     * Get before filters.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return string
     */
    protected function getMiddleware($route)
    {
        return collect($route->gatherMiddleware())->map(function ($middleware) {
            return $middleware instanceof Closure ? 'Closure' : $middleware;
        })->implode(',');
    }
    

    ルート毎にミドルウェアを取得(gatherMiddleware)してクロージャかの判断などしつつコレクション化しているようです。

  2. メソッドの取得は

    RouteListCommand
    /**
     * Get the route information for a given route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return array
     */
    protected function getRouteInformation(Route $route)
    {
        return $this->filterRoute([
            'domain' => $route->domain(),
            'method' => implode('|', $route->methods()),
            'uri'    => $route->uri(),
            'name'   => $route->getName(),
            'action' => ltrim($route->getActionName(), '\\'),
            'middleware' => $this->getMiddleware($route),
        ]);
    }
    

    $route->methods() で取れるようです。

取得してみる

とりあえずコントローラに直接書いてみます。

SiteMapController
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Router;

class SiteMapController extends Controller
{

    /**
     * @var Router
     */
    private $router;

    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    public function index()
    {
        $uris = [];
        foreach ($this->router->getRoutes() as $route) {

            if (!in_array('front', $route->gatherMiddleware()) || !in_array('GET', $route->methods()))
                continue;

            $uris[] = $route->uri;
        }
    }
}

取得出来る$urisはこんなかんじになる

適当な例
[
  0 => "/"
  1 => "article/{Article}"
  2 => "neko"
  3 => "inu"
]

問題

ルートパラメータの存在するルートを全て取得する方法を見つけられていない、のでここでは一度無視する?
モデル結合ルートに限れば、既に存在するURIは定義されているということになるはずなので、その辺でうまいこと取得出来ないだろうかと考えています。
もう少し検証しますが、いい方法があったら教えて欲しいです。

sitemap.xmlに出す

SiteMapController
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Response;

class SiteMapController extends Controller
{

    /**
     * @var Router
     */
    private $router;

    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    public function index()
    {
        $uris = [];
        foreach ($this->router->getRoutes() as $key => $route) {

            if (!in_array('front', $route->gatherMiddleware()) || !in_array('GET', $route->methods()))
                continue;

            $uris[] = $route->uri;
        }

        $view = view('site_map.index', compact('uris'));
        return Response::make($view, '200')->header('Content-Type', 'application/xml');

    }
}

site_map.index
<?php echo '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

    @foreach($uris as $uri)
        <url>
            <loc>{{ url($uri) }}</loc>
            <changefreq>daily</changefreq>
        </url>
    @endforeach

</urlset>

おわり

楽したいのでもっとうまい方法考えます。
ただ些細な更新ストレスを減らすための行為で制限を増やすと逆説的にストレスが増えちゃいそうなので、laravelが素直に出来る範囲ぐらいの程々で諦めるべきかもしれません。

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

レンタルサーバーでLaravelを動かす際の環境構築手順とTips

はじめに

この記事はLaravel Advent Calendar 201923日目の記事です
過去に培ったレンサバさくらサーババリューサーバでLaravelを動かすためのアレコレを書いていきます

.
.
.

という予定だったんですが、さくらサーバーでの環境構築手順メモしか手元に残ってなかったのでほぼさくらサーバーでの環境構築手順になります(主語でかで申し訳ないです)

AWSとかわからんからレンサバでLaravelを動かす

というのはまあ半分冗談で、大体がコストになるとか、先方指定とかで最終的にレンタルサーバーにしてくださいって言われるので、世間的にもそうなのかな?と思いつつ記事書いていきます。
このTipsが誰かの役に立てれば我幸です。

情報の鮮度的に19年9月ごろまでの話で今現在以下の通りでない可能性も無きにも非ずです
あとここで想定しているLaravelのバージョンは5.7~5.8系です。

さくらのレンタルサーバーで環境構築

※ローカルで作成してプロジェクトがGitホスティングサービス(GitLabとかGithub)上に上がってる前提です。

1,sshでログイン

おそらく借りた直後にssh接続すると以下のような画面になると思います。

FreeBSD 11.2-RELEASE-p5 (GENERIC) #0: Tue Nov 27 09:33:52 UTC 2018

Welcome to FreeBSD!

%
% ls -la
total 56
drwx------    9 testtest  users   512 Dec 13 19:23 .
drwxr-xr-x  160 root           wheel  3584 Jan 18 10:34 ..
-rw-r--r--    1 testtest  users   773 Nov 29 17:01 .cshrc
-rw-r--r--    1 testtest  users   258 Nov 29 17:01 .login
-rw-r--r--    1 testtest  users   167 Nov 29 17:01 .login_conf
-rw-r--r--    1 testtest  users     0 Nov 29 17:01 .php.module
-rw-r--r--    1 testtest  users   762 Nov 29 17:01 .profile
-rw-r--r--    1 testtest  users   980 Nov 29 17:01 .shrc
drwx------    2 testtest  users   512 Nov 18  2009 .spamassassin
drwx------    2 testtest  users   512 Nov 18  2009 .ssh
drwx------    3 testtest  users   512 Nov 18  2009 MailBox
drwx------    2 testtest  users   512 Nov 18  2009 db
drwxr-xr-x    2 testtest  users   512 Jan 28  2015 sakura_pocket
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 sblo_files
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 www

2,リポジトリ用のディレクトリを生成する

rootディレクトリで生成する。

以下、ディレクトリ名reposで作成した例

% mkdir /home/testtest/repos
% ls -la
total 60
drwx------   10 testtest  users   512 Jan 18 16:10 .
drwxr-xr-x  160 root           wheel  3584 Jan 18 10:34 ..
-rw-r--r--    1 testtest  users   773 Nov 29 17:01 .cshrc
-rw-r--r--    1 testtest  users   258 Nov 29 17:01 .login
-rw-r--r--    1 testtest  users   167 Nov 29 17:01 .login_conf
-rw-r--r--    1 testtest  users     0 Nov 29 17:01 .php.module
-rw-r--r--    1 testtest  users   762 Nov 29 17:01 .profile
-rw-r--r--    1 testtest  users   980 Nov 29 17:01 .shrc
drwx------    2 testtest  users   512 Nov 18  2009 .spamassassin
drwx------    2 testtest  users   512 Nov 18  2009 .ssh
drwx------    3 testtest  users   512 Nov 18  2009 MailBox
drwx------    2 testtest  users   512 Nov 18  2009 db
drwxr-xr-x    2 testtest  users   512 Jan 18 16:10 repos          // これ
drwxr-xr-x    2 testtest  users   512 Jan 28  2015 sakura_pocket
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 sblo_files
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 www

3,秘密鍵を生成

サーバーで作成した鍵を各々使っているGitホスティングサービス(GitlabやらGithubやら)からpullするのに秘密鍵を生成する。

sshディレクトリがある場所でssh-keygen -t rsa -b 4096 -C "メールアドレス"を実行

その後、.ssh/id_rsa.pubの中身を確認し、GitLabやGithubのプロジェクトへ登録する

% ssh-keygen -t rsa -b 4096 -C "hoge@example.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/testtest/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/testtest/.ssh/id_rsa.
Your public key has been saved in /home/testtest/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:************************************************** hoge@example.com
The key's randomart image is:
+---[RSA 4096]----+
|   B   .B..o++B=+|
|      .+o. o.=.+=|
|B    .o o.    . .|
|. o o .  B   .   |
|.  o o  .     o. |
|    . oo  .   oo.|
|. B .o.    . . +.|
|.  .     +  .  ++|
|  B   .   B   ..*|
+----[SHA256]-----+
%
% ls -la
total 60
drwx------   10 testtest  users   512 Jan 18 16:10 .
drwxr-xr-x  160 root           wheel  3584 Jan 18 10:34 ..
-rw-r--r--    1 testtest  users   773 Nov 29 17:01 .cshrc
-rw-r--r--    1 testtest  users   258 Nov 29 17:01 .login
-rw-r--r--    1 testtest  users   167 Nov 29 17:01 .login_conf
-rw-r--r--    1 testtest  users     0 Nov 29 17:01 .php.module
-rw-r--r--    1 testtest  users   762 Nov 29 17:01 .profile
-rw-r--r--    1 testtest  users   980 Nov 29 17:01 .shrc
drwx------    2 testtest  users   512 Nov 18  2009 .spamassassin
drwx------    2 testtest  users   512 Jan 18 16:13 .ssh
drwx------    3 testtest  users   512 Nov 18  2009 MailBox
drwx------    2 testtest  users   512 Nov 18  2009 db
drwxr-xr-x    2 testtest  users   512 Jan 18 16:10 repos
drwxr-xr-x    2 testtest  users   512 Jan 28  2015 sakura_pocket
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 sblo_files
drwxr-xr-x    2 testtest  users   512 Nov 18  2009 www

.sshへ移動して生成されたか確認

% cd .ssh/
% ls -la
total 16
drwx------   2 testtest  users   512 Jan 18 16:13 .
drwx------  10 testtest  users   512 Jan 18 16:10 ..
-rw-------   1 testtest  users  3247 Jan 18 16:13 id_rsa
-rw-r--r--   1 testtest  users   742 Jan 18 16:13 id_rsa.pub
% cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAAE/0H( 略 )fdg/tFm4WqQ== hoge@example.com

4,秘密鍵を各々のサービスのプロジェクトへ登録

各々使っているGitホスティングサービス(GitlabやらGithubやら)上へ登録する。

GitLabの場合、

プロジェクトを開き、メニューから「Settings」→「Repository」→「Deploy Keys」のタブを開き、「key」に.ssh/id_rsa.pubの中身をコピー&ペーストする。
「title」にはわかりやすい名前(サーバー名)を、「Write access allowed」のチェックボックスに関してはそのままで「Add Key」をクリックして完了

以下はGitHubの画面だがほぼ同じなので参考になる

5,git cloneする

ターミナルに戻り、先程作ったreposディレクトリへ移動し、git cloneコマンドを実行

% cd repos/
% ls -la
total 8
drwxr-xr-x   2 testtest  users  512 Jan 18 16:10 .
drwx------  10 testtest  users  512 Jan 18 16:10 ..

% git clone git@gitlab.com:*******/test-project.git
Cloning into 'test-project'...
The authenticity of host 'gitlab.com (35.231.145.151)' can't be established.
ECDSA key fingerprint is SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw.
No matching host key fingerprint found in DNS.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'gitlab.com' (ECDSA) to the list of known hosts.
remote: Enumerating objects: 1145, done.
remote: Counting objects: 100% (1145/1145), done.
remote: Compressing objects: 100% (416/416), done.
remote: Total 1145 (delta 756), reused 1078 (delta 695)
Receiving objects: 100% (1145/1145), 452.28 KiB | 640.00 KiB/s, done.
Resolving deltas: 100% (756/756), done.

% ls -la
total 12
drwxr-xr-x   3 testtest  users  512 Jan 18 16:46 .
drwx------  10 testtest  users  512 Jan 18 16:10 ..
drwxr-xr-x   4 testtest  users  512 Jan 18 16:46 test-project

ちなみにサーバーに二つの環境(ステージング環境・本番環境)作る場合は以下の例のように、
/reposディレクトリ以下にstagingproductionのディレクトリを作りそれぞれでgit cloneする。

[testtest@www4043 ~/repos]$ mkdir stg
[testtest@www4043 ~/repos]$ mkdir production
[testtest@www4043 ~/repos]$ ls -la
total 16
drwxr-xr-x   4 testtest  users  512 Jan 18 18:53 .
drwx------  12 testtest  users  512 Jan 18 18:03 ..
drwxr-xr-x   2 testtest  users  512 Jan 18 18:53 production
drwxr-xr-x   3 testtest  users  512 Jan 18 18:40 stg

ここからは主にproductionディレクトリ上での作業を例として説明していきます。

【番外】シェルをbashに変更する

個人的にbashに慣れているのでchshから変更する方法も載せておきます。

whichコマンドでbashの実行ファイルを確認。

chshコマンドでオプション-s(--shell)を指定し、ログインユーザーのシェルを変更する。

% which bash
/usr/local/bin/bash
% chsh -s /usr/local/bin/bash
Password: 
chsh: user information updated

今後もこのbashを使用するためのファイルを作る。

rootへ移動してコマンドを叩く

% vi .bash_profile

以下を記述する

PATH=$PATH:$HOME/bin:$HOME/usr/local/bin
export PATH

保存(!wq)してsshログアウト→再ログインする

[testtest@www4043 ~]$ cat .bash_profile
PATH=$PATH:$HOME/bin:$HOME/usr/local/bin
export PATH

これでお馴染みのbashが使えるようになる

6,composerをインストールする

さくらサーバーはComposer(PHPのパッケージ管理システム)が入ってない1

% composer -h
composer: Command not found.

ちなみにComposerとは

Composer は、PHP のプロジェクトが必要とするライブラリやパッケージを管理する「ライブラリ依存管理ツール」です。

rootへ移動してコマンドを叩く

// rootへ移動
$ cd ~

// ディレクトリ生成
//「-p」オプションには「ディレクトリが存在しない場合は、自動で作成される」かつ「作ろうとしているディレクトリが存在していてもエラーにしない」
$ mkdir -p usr/local/bin/

// curl コマンドでcomposerをダウンロード&インストール
// 「-sS」でエラーメッセージ表示
// 「--install-dir」で指定のディレクトリへインストールできる
$ curl -sS https://getcomposer.org/installer | php -- --install-dir=usr/local/bin/

// composer.pharを上階層のcomposerディレクトリへ移動
$ mv usr/local/bin/composer.phar usr/local/bin/composer

インストール後、入ってることを確認する(※.bash_profileにパスを通しておくこと)

[testtest@www4043 ~]$ composer -v
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.8.0 2018-12-03 10:31:16

7,プロジェクト内でcomposer installする

プロジェクトのディレクトリへ移動後、composer install --no-devコマンドを実行

[testtest@www4043 ~/repos/production/test-project/laravel]$ composer install --no-dev
Loading composer repositories with package information
Installing dependencies from lock file
Package operations: 41 installs, 0 updates, 0 removals
  - Installing doctrine/event-manager (v1.0.0): Loading from cache
  - Installing doctrine/cache (v1.8.0): Loading from cache

  // 省略

  - Installing psy/psysh (v0.9.9): Loading from cache
  - Installing laravel/tinker (v1.0.8): Loading from cache
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Package manifest generated successfully.

※レンタルサーバーによってはPHPのモジュールがインストールされてなかったりしてエラーが出る場合があります。
リリース前に十分な余裕をもって事前に確認しておきましょう。

8,環境設定ファイル(.env)生成

Laravelの標準に.env.exampleがあるのでそれをこの環境用に設置するためにコピーコマンドで複製する

[testtest@www4043 ~/repos/production/test-project/laravel]$ cp .env.example .env

.envの中身の例

APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

// 以下略

9,key:generateでアプリケーションのキーを発行する

.envファイルを作成後、同ディレクトリでphp artisan key:generateコマンドを実行してください。
Laravelが内部で暗号化に用いるキーになるため、環境ごとに実行するようにしてください

APP_NAME=Laravel
APP_ENV=local
APP_KEY=****************************************** // ここにランダムなパラメータが生成される
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

// 以下略

10,環境ごとの設定を.envに書き込む

アプリケーションのキーが生成されたことを確認後、viなどで必要箇所(DBの設定や環境等)を編集してください。
(黒い画面慣れてないならFTPでつないで編集しても問題ないです)
ここでDBなどの情報が間違っていると次のフェーズでうまく動きません

あと絶対に.envファイルをpublic以下(外部から参照できる場所)に配置しないようにしてください

意外と多いみたいです

そして、構築している環境によってAPP_ENVAPP_DEBUG適切に設定するようにしてください
(例:本番環境ならAPP_ENV=productionAPP_DEBUG=false

特にAPP_DEBUG本番環境なのにtrueに設定していて、バグを踏んだ際にWhoopsが表示されるWebアプリを今までに何個も発見しています
どれだけ.envファイルを参照できない場所へ設置していてもWhoopsが表示されて環境変数を見れる状態だとなんの意味もないです。

11,DBを作成(php artisan migrate

諸々と環境変数を設定後、artisanコマンドを実行してDBを構築

[testtest@www4043 ~/repos/production/test-project/laravel]$ php artisan migrate
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

// 省略

// php artisan migrate:statusでできてるか確認

[testtest@www4043 ~/repos/production/test-project/laravel]$ php artisan migrate:status
+------+-------------------------------------------------------------------------------------+
| Ran? | Migration                                                                           |
+------+-------------------------------------------------------------------------------------+
| Y    | 2014_10_12_000000_create_users_table                                                |
| Y    | 2014_10_12_100000_create_password_resets_table                                      |
| Y    | 2018_12_20_122201_create_categories_table                                           |
+------+-------------------------------------------------------------------------------------+

12,リポジトリとpublicとでシンボリックリンクを張る

rootへ戻ってwwwへ移動し、シンボリックリンクを貼ります。

[testtest@www4043 ~/www]$ ln -s /home/testtest/repos/production/test-project/laravel/public/ production
[testtest@www4043 ~/www]$ ls -la
total 8
drwxr-xr-x   2 testtest  users  512 Jan 18 19:23 .
drwx------  12 testtest  users  512 Jan 18 19:02 ..
lrwxr-xr-x   1 testtest  users   64 Jan 18 19:23 production -> /home/testtest/repos/production/test-project/laravel/public/

たまにうまくパスを指定していなくて失敗することがある(本人談)ので要注意

13、DBに初期値入れていく(※必要あるなら)

DBへマスタデータなど入れておく必要があるならSeederを実行しておく

[testtest@www4043 ~/repos/production/test-project/laravel]$ php artisan db:seed --class=DatabaseSeeder
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes

Seeding: BasesTableSeeder
Seeding: CategoriesTableSeeder
[testtest@www4043 ~/repos/production/test-project/laravel]$

これで一通りの準備ができたはずです。
URLへアクセスして正常に動いていることを確認してください。

(ちなみにここまでしなくても「hello, world」したいだけなら10の段階ででできるはずです)

バリューサーバーで動かす(おまけ)

本当はバリューサーバーでの環境構築手順も書く予定でしたが、自分用にまとめた資料がどっか逝ったので覚えている限りのTipsを書いておきます。

PHPのバージョンをコンパネから7.2以上へ変更しておく

おそらくPHPのバージョンがデフォルトで5.6になっている1ので、コントロールパネルから7.2以上のバージョンへ変更してください。
でないとLaravelがそもそも動かないです
(確か5.4系だとPHP5.6でも動いたはずですがおすすめしません)

phpのエイリアスを張っておく

デフォルトでコマンドをたたく際はコンパネから設定したPHPバージョン+実行したいコマンドを打つ必要があるので、環境構築の初めにphpのエイリアスを張っておきましょう。
でないと、以下のように

php56cli composer.phar -V

と、php[version]cliと書かないとコマンド実行できません。

xmlwriterがインストールされてない

PHPの拡張機能xmlwriterが入っていないため、PhpSpreadsheetなどのライブラリが動きませんでした。1
これに関してはレンタルサーバー側が対応してくれるまでどうしょうもないので、xmlwriter機能を使うライブラリを仕様する場合は注意が必要かなと

おわり

  • 何回も行っておきますが情報の鮮度的に19年9月ごろまでの話で今現在上記の通りでない可能性も無きにしです。
  • ちなみに自分が携わったプロジェクトに関して言えば8割くらい、業務システムはほぼ100%レンサバ指定でした
  • ちなみにちなみに明日は前職の後輩@namizatopが記事書くようですよ(お

参考


  1. 2019年9月頃の話ですので現在は異なるかもしれません 

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

モバイル環境でNetwork Errorでハマった。「EC2にDocker Caddy Laravel, S3にVuejsの構成」

指摘などウェルカムです。
クロスオリジン(CORS)に関連するエラーなんだと思うけどPCでは動くが、携帯 IOS Iphone7(古い)からのブラウザ(最新)ではsafariもchromeも動かないって現象で結構ハマった。

環境

大体の構成
route53から分岐
-> CloudFront+S3にvuejs
-> ALBからEC2(Ubuntu)にDocker Caddy Laravel

解決策

サーバー側のクロスオリジンの設定をちゃんと設定する。

Access-Control-Allow-Origin
Access-Control-Allow-Headers
をざっくり設定して * としていた。
これが原因だった。
ちゃんとフロントs3側のホスト名を指定したら動いた。
この記事で必須と書いてあるので、ん?と思って設定したら動いた。

:OK.php
<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
  public function handle($request, Closure $next)
  {
    return $next($request)
      //ちゃんとホスティングしてるs3のドメインを指定
      ->header(Access-Control-Allow-Origin, https://hoge.s3.com)
      ->header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS)
      ->header(Access-Control-Allow-Headers, X-Requested-With, Content-Type, X-Token-Auth, Authorization);
  }
}
駄目.php
<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
  public function handle($request, Closure $next)
  {
    return $next($request)
      ->header(Access-Control-Allow-Origin, *)
      ->header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS)
      ->header(Access-Control-Allow-Headers, X-Requested-With, Content-Type, X-Token-Auth, Authorization);
  }
}

https://whatsupguys.net/programming-learning-135/

調べた履歴

Laravelのルーティングについて結構大事だと思う。
axiosでget, post以外にoptionも使うのでルーティング注意。

Route::match(['options', 'patch']'/{test_id}', 'TestController@update');
Route::match(['options', 'delete'], '/{test_id}', 'TestController@destroy');

CORSを許可したLaravel製APIサーバーでput, patch, deleteが出来なくて泣いてたけど、ようやく解決出来た話

CSRFが問題か?
X-Requested-Withヘッダか?

Laravel/Vue SPAs: How to send AJAX requests and not run into CSRF token mismatch exceptions
https://medium.com/@serhii.matrunchyk/laravel-vue-spas-how-to-send-ajax-requests-and-not-run-into-csrf-tokens-mismatch-exceptions-da3b71b287ab
https://www.techalyst.com/posts/vuejs-axios-laravel-x-csrf-token-x-xsrf-token-csrf-protection

axiosが悪いのか?
https://qiita.com/terrierscript/items/ccb56b6fc05aa7821c42

crossのエラーぽいが。
https://stackoverflow.com/questions/50873764/cross-origin-read-blocking-corb
https://stackoverflow.com/questions/38749605/cors-access-control-allow-origin-on-laravel
https://stackoverflow.com/questions/20035101/why-does-my-javascript-code-get-a-no-access-control-allow-origin-header-is-pr
https://github.com/laravel/framework/issues/13643
https://medium.com/@petehouston/allow-cors-in-laravel-2b574c51d0c1

サーバの.htaccessが悪いのか?
https://forum.laragon.org/topic/1435/access-control-allow-origin-is-already-set/6

モバイルではそもそも動かないのか?

Axios doesn't work with Android (emulator) raising a Network Error
https://github.com/axios/axios/issues/973

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

Laravel6系 に Vuetify を入れてみる

こんにばんわ! @ktoshi です。
今回は私が困ったときに縋りつく Laravel についてお話します。
ちなみに私はインフラエンジニアです。メインは。

目的

Laravel6 に Vuetify を導入したい。
Laravel6 より Vue.js などが標準ライブラリから外れたため、Laravel5 以前の記事ではそのまま導入ができなくなりました。
毎度導入しているときに複数の記事を見ながら、導入しているので備忘録もかねて。

Vuetify とは

公式HP
Vue.js のコンポーネント集です。
ボタンやテーブルなどを描画する際のコンポーネントがあつまっているので、
モダンなデザインを容易に作ることができます。

環境

OS: Windows 10 Pro
PHP: 7.3.10

Composer インストール

Laravel で使用するパッケージ管理に利用します。
みなさんご存じですよね。多くは語りません。
Composerインストール手順(Windows) を参考されたし。

node.js インストール

node.js 自体をサーバとして利用も可能ですが、Laravel では主に Vue.js の
ビルドを目的に利用します。
みなさんご存じですよね。多くは語りません。
Node.js / npmをインストールする(for Windows) を参考されたし。
私の環境ではバージョンは下記でした。

node --version
v12.13.0

npm --version
6.12.0

Laravel インストール

みなさんご存じですよね。多くは語りません。

composer create-project laravel/laravel --prefer-dist <PROJECT NAME>

これでカレントディレクトリ以下に PROJECT_NAME のフォルダが作成され、
その配下に Laravel が導入された状態となっています。

Vue.js のインストール

先でも述べたように Laravel5 以前であれば、この状態で npm install をしてやると
Vue.js が導入できましたが、 Laravel6 以降ではひと手間必要です。
ひと手間が料理をおいしくすると同じくひと手間が Laravel をおいしくします。(知りません

# プロジェクトフォルダへ移動
cd <PROJECT NAME>

# 必要なライブラリをインストール
composer require laravel/ui

# 私は Vue を使うねん!と宣言
php artisan ui vue

# おら!
npm install

これで Vue.js の導入が完了です。
Laravel5 以前で npm install を叩いた状態と同じですね。

Vuetify インストール

Vuetify は npm(Node.js のパッケージ管理ツール)を用いてインストールします。
(いや、そのコマンドさっき使ってたのに今更説明かい。)

# Vuetify の最新パッケージのインストール
npm install vuetify

インストールが終われば、Laravel 側で Vuetify を利用するように設定します。

resoureces/js/app.js
/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

window.Vue = require('vue');
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';
Vue.use(Vuetify);

/**
 * The following block of code may be used to automatically register your
 * Vue components. It will recursively scan this directory for the Vue
 * components and automatically register them with their "basename".
 *
 * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
 */

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))

Vue.component('example-component', require('./components/ExampleComponent.vue').default);

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

const app = new Vue({
    el: '#app',
    vuetify: new Vuetify(),
});

これで Laravel にてVuetify を利用できる状態となりました。

実践

では、実際にVuetify を使ったページを作ってみましょう。
元々用意されているサンプルのファイルを書き換えてみます。

resources\js\components\ExampleComponent.vue
<template>
  <v-app>
    <v-container>
      <v-row>
        <v-col>
          <v-card class="mx-auto">
            <v-card-text>
              <p class="text--primary">
                Hello Vuetify World!!
              </p>
            </v-card-text>
            <v-divider></v-divider>
            <v-card-actions>
              <v-btn>やったね!!</v-btn>
            </v-card-actions>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </v-app>
</template>
resources\views\welcome.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <!-- CSRF Token -->
      <meta name="csrf-token" content="{{ csrf_token() }}">

      <title>{{ config('app.name', 'Laravel') }}</title>

      <!-- Styles -->
      <link rel="stylesheet" href="{{ mix('css/app.css') }}">
  </head>
  <body>
    <div id="app">
      <v-app>
        <!-- これがさっき修正したファイル -->
        <example-component></example-component>
      </v-app>
    </div>
    <!-- Scripts -->
    <script src="{{ mix('js/app.js') }}"></script>
  </body>
</html>

次に Vue.js のビルドと Laravel を起動させます。
なお、各コマンドともに実行状態が続くので二つのプロンプトで作業をしてください。

# Laravel の起動
php artisan serve
Laravel development server started: http://127.0.0.1:8000 # これが出ればOK
# ビルドコマンドを発行。watch にしておくと変更を検知して勝手にビルドしてくれるので楽。
npm run watch
DONE  Compiled successfully in XXXms # これが出ればOK 

それぞれが起動すれば http://localhost:8000/ へ接続してみましょう。
下記のようなページが表示されていればOKです。
sample.png

まとめ

Laravel6 から標準でなくなった Vue.js のインストールまで行ってしまえば、
後は Laravel5 と同様の方法で Vuetify を利用することが可能です。

私は今まで element-ui を主に使っていましたが、今回は Vuetify を利用する、という記事を書いてみました。
なんで変えたかって?飽きたからです。
ただ、実際に Vuetify を使ってみて思ったのはデザインなどは非常に好みでした。
テーブルに関しても element-ui より柔軟でよかったです。
ただ、timepicker のデザインが不満だったのと element-ui の datetimepicker がマジ神だなと感じました。

それでは皆様、よい Vuetify ライブをお送りください。

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

メール認証(アクティベーション)機能を自前で実装【Laravel】

この記事は、認証認可技術 Advent Calendar 2019 の23日目の記事です。

1. はじめに

Laravelって便利ですよね。

Laravel5.7からは、make:authコマンドやMustVerifyEmailインターフェースを使えばすぐにメール認証機能の実装もできるようになりました。(Laravel6.0からはmake:auth廃止されましたが)

ただ便利な反面、途中から機能追加したり、変更頻度が多いことを見越して自前で実装して柔軟に対応できるようにしておきたいことがあります。
こういったことがあって自前で実装したのでその時の備忘録です。
(APIサーバをつくるときの話で、LaravelはフルスタックフレームワークなのでAPIサーバのみの利用はあまり無かった)

少し前の実装なのでうる覚えな部分はありますがご容赦ください。

2. やりたいこと

タイトル通り、メール認証機能の実装です。

私が思っているメール認証機能の定義と流れは以下の通りです。

2.1. メール認証機能の定義

Webサービスでよくある新規登録時にほぼ間違いなくするメールアドレスの有効化のことです。
(メール認証==メール有効化==メールアクティベーションだと思っています。。。色んな言い方をしてすみません。)

この機能がないと偽りのメールアドレスを使って登録することができてしまうのでよく実装されています。(この他にもSMS認証とか)
要するにWebサービスを開発するときの必須機能の一つです。

2.2. メール認証機能の流れ

  1. ユーザがサーバにメールアドレスを入力して送信
  2. サーバがユーザに入力されたメールアドレスに認証メールを送信
  3. ユーザが認証メール内のURLにアクセス
  4. サーバがURLの正誤判定
  5. 正しい場合、サーバがデータベースにメールアドレスを登録

一般的な流れはこんな感じでしょうか。
誰でも一度はユーザの立場でやったことがある流れだと思います。

3. 実装方針

認証コードの作成・確認

認証コードの作成は、メールアドレスとサーバで定義しておいた秘密鍵を用いてハッシュ化をして行います。

確認は、ユーザがアクセスしたURL内の認証コード==サーバ側に仮保存している認証コードで行います。

認証コードとメールアドレスの紐付け

考える必要があるのは紐付け方法です。

様々な紐付け方法がありますが、今回はデータベースにメール認証用のテーブルを作って、メールアドレスと認証コードをセットで保存するという方法をとります。
メールアドレスの認証が確認できた時点で、このテーブルのレコードは消していきます。

おまけ: 様々な紐付け方法

  • アクセス先のページでメールアドレスを再入力
    • ユーザが手間(=UX悪い)なので却下
  • URLにクエリとしてメールアドレスを付与
    • ださいので却下、セキュリティ的にも心配(?)
  • メールアドレスのセッション保存
    • 他端末で認証できないので却下
  • 認証ファイルの作成
    • 個人的に実装が手間だったので迷った末に却下
    • データベース使わなくて済むが小規模なサービスの場合だと誤差

4. 開発環境

  • PHP7.3
  • Laravel6.0(5.8でも動作確認済)
  • MySQL8.0

5. 実装

5.1. テーブル作成(マイグレーション&モデル)

最初に、ユーザ用のテーブルとは別に、メール認証用のテーブルをつくっておきます。
MigrationとModelをつくります。

$ php artisan make:model MailVerification -m

Migration

mail_authenticationは認証コードを格納するカラムで、
mailはメールアドレスを格納するカラムです。

database/migrations/2019_12_23_000000_create_mail_verifications_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMailVerificationsTable extends Migration
{
    public function up()
    {
        Schema::create('mail_verifications', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('mail_authentication');
            $table->string('mail')->unique();
            $table->timestamps();
        });
    }
// 略
}

Model

app/Models/MailVerification.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class MailVerification extends Model
{
    protected $table = 'mail_verifications';

    protected $guarded = array('id');

    public $timestamps = true;

    protected $fillable =[
        'mail_authentication'
        , 'mail'
    ];
}

5.2. コントローラ作成

次に、コントローラをつくります。

Controller

storeメソッドで認証コードとメールアドレスを仮保存しておいて、ユーザに認証メールを送ります。
認証メールのURLにアクセスしたらverifyメソッドで確認して、仮保存しておいたレコードを削除し、認証完了メールを送ります。

createActivationCodeメソッドで、認証コードを作成しています。
ACTIVATE_SALTが秘密鍵です。

app/Http/Controllers/Api/MailVerificationsController.php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Models\MailVerification;
use App\Models\User;
use App\Mail\MailVerification as MailVerificationMail;
use App\Mail\MailVerificationConfimComplete as MailVerificationConfimCompleteMail;
use App\Http\Controllers\Controller;

define('ACTIVATE_SALT', '[秘密鍵]');

class MailVerificationsController extends Controller
{
    public function store($mail)
    {
        $mail_verification = new MailVerification();

        $mail_verification->mail_authentication = self::createActivationCode($mail);
        $mail_verification->mail = $mail;

        $mail_verification->save();

        self::sendMailVerification($mail_verification);
    }

    public function verify($active_code)
    {
        $mail_verification = self::getMailInfoFromActiveCode($active_code);
        $mail = $mail_verification[0]->mail;

        $user = new User();

        $user->isVerified($mail);

        self::destroy($mail_verification[0]->id);

        self::sendMailVerificationComplete($mail);
    }

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

    private function createActivationCode($mail)
    {
        return hash_hmac('sha256', $mail, ACTIVATE_SALT);
    }

    private function sendMailVerification($mail_verification)
    {
        Mail::to($mail_verification->mail)
            ->send(new MailVerificationMail($mail_verification->mail_authentication));
    }

    private function sendMailVerificationComplete($mail)
    {
        Mail::to($mail)
            ->send(new MailVerificationConfimCompleteMail());
    }

    private function getMailInfoFromActiveCode($active_code)
    {
        return MailVerification::where('mail_authentication', $active_code)->get(); 
    }
}

念のため、Userコントローラも。
storeメソッドでユーザ登録処理をしています。
app()->make()で別コントローラを呼ぶという力技・・・。

app/Http/Controllers/Api/UsersController.php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    public function store(Request $request)
    {
        $user = new User();

        $user->name = $request->input('name');
        $user->mail = $request->input('mail');
        $user->password = bcrypt($request->input('password'));

        $user->save();

        self::callMailVerificationStore($user);

        return response([], 201);
    }

    private function callMailVerificationStore($user)
    {
        $mail_varification = app()->make('App\Http\Controllers\Api\MailVerificationsController');
        $mail_varification->store($user->mail);
    }
}

5.3. ルーティング記述

この当たりで関係がある部分のルーティングを書いておきます。

routes/api.php
<?php

use Illuminate\Http\Request;

Route::group(["middleware" => "api"], function () {
        Route::post('/user', 'Api\UsersController@store');
        Route::get('/verify/{active_code}', 'Api\MailVerificationsController@verify');
});

5.4. メール

必要ないかもしれませんがメモ程度に。

Mail

app/Mail/MailVerification.php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

define('MAIL_VERIFICATION_SUBJECT', '[メールタイトル]');

class MailVerification extends Mailable
{
    use Queueable, SerializesModels;

    protected  $mail_verification;

    public function __construct($_mail_verification)
    {
        $this->mail_verification = $_mail_verification;
    }

    public function build()
    {
        $auth_url = config('const.BASE_URL') . '/verify/' . $this->mail_verification;

        return $this->from(config('mail.from.address'))
                    ->subject(MAIL_VERIFICATION_SUBJECT)
                    ->view('mails.verification', compact('auth_url'));
    }
}

簡単にですがBladeの方も。

resources/views/mails/verification.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">メールアドレス認証</div>

                <div class="card-body"> 
                    <a href='{{$auth_url}}'>こちらのリンク</a>をクリックしてメールを認証してください
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

6. おわりに

以上、Laravelでメール認証機能を自前で実装してみた備忘録でした。

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

質問やアドバイス等ありましたらコメントしてください。

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