20200805のlaravelに関する記事は11件です。

Laravel+Vue.jsで作成したSPAサイトでOGP対応

はじめに

個人開発でLaravel5.5とVue.jsを使用して作成したSPAサイトがあり、長らく放置していたのですが、最近勉強を兼ねて少しリファクタリングをしようかなと思いました。

ラブライブ専用掲示板

上記が作成したサイト(掲示板)になります。
治すべき部分はたくさんあるのですが、まず気になったのがTwitterカードの表示です。
特に、掲示板のスレッドURLをツイートした際に、デフォルトの情報が表示されてしまうのが見た目の部分で致命的でした。
image.png

まずやったこと

最初にapiでスレッドの情報を読み込んだ後のタイミングでquerySelectorを使用して動的にmetaタグを変更しました。

//APIでデータ取得後
document.title = this.thread_header[0].title + ' | LoveLiveBBS'; 
document.querySelector("meta[property='og:title']").setAttribute('content', this.thread_header[0].title + ' | LoveLiveBBS');
document.querySelector("meta[property='description']").setAttribute('content', this.thread_response[0]['writing']);
document.querySelector("meta[property='og:description']").setAttribute('content', this.thread_response[0]['writing']);

当たり前ですが、これでは書き換わる前のmetaが読み込まれてしまうので結果は変わりませんでした。。。

今回は/thread/[thread_id]ページのみの修正ということで、その為だけにわざわざプリレンダリングやSSRはしたくないなと思い、以下のような対応を行いました。

Laravelのbladeファイルでの対応

/thread/[thread_id]にアクセスされたときにLaravel側でデータを取得してmetaタグに設定する方法をとりました。

bladeファイルの作成

修正前は初回アクセス時にのみ使用するspa.blade.phpのみが存在していました。
スレッドページ用のthread.blade.phpを作成しました。

resources/views/threadPage.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    //コントローラーで作成したデータを表示
    <title>{{ $title }}</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="csrf-token" content="{!!csrf_token()!!}">
    <link href="https://fonts.googleapis.com/css?family=Kosugi+Maru&display=swap&subset=japanese" rel="stylesheet">
  //コントローラーで作成したデータを表示
    <meta name="description" content="{{ $description }}">
    <meta property="og:url" content="https://lovelivebbs.jp" />
  //コントローラーで作成したデータを表示
    <meta property="og:title" content="{{ $title }}" />
    <meta property="og:type" content="website">
  //コントローラーで作成したデータを表示
    <meta property="og:description" content="{{ $description }}" />
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:site" content="@lovelivebbs" />
  //コントローラーで作成したデータを表示
    <meta property="og:site_name" content="{{ $title }}" />
    <meta property="og:locale" content="ja_JP" />
</head>
<body>
<div id="app">
    <app></app>
</div>

<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

コントローラーにスレッドページ用のメソッドを追加

spa.blade.phpを返す役割だけのコントローラーにthreadPageメソッドを追加しました。

app/Http/Controllers/SpaController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Response;
use App\Thread;
use Exception;
use Illuminate\Support\Facades\Log;

class SpaController extends Controller
{
    public function index()
    {
        return view('spa');
    }

    /**
     * スレッドページのみmetaタグをbladeファイルで設定
     * @param $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function threadPage($id) {
        $meta = [
            'title' => 'LoveLive!BBS!',
            'description' => '当サイトはラブライブシリーズ専用掲示板です。'
        ];
        try {
            $threadDetail = Thread::where('id', $id)->first();
            if (is_null($threadDetail)) {
                return view('threadPage', $meta);
            } else {
                //取得したデータをmetaタグに設定
                $response1 = Response::where('thread_id', $id)->first();
                $meta['title'] = $threadDetail->title . ' | LoveLive!BBS!';
                $meta['description'] = $response1->writing;
                return view('threadPage', $meta);
            }
        } catch (Exception $exception) {
            Log::error('thread page exception');
            return view('spa');
        }
    }
}

DBから取得した値をまとめてbladeファイルに渡しています。

ルーティングの追加

routes/web.php
+ Route::get('/thread/{id}', 'SpaController@threadPage');

Route::get('/{any}', 'SpaController@index')->where('any', '.*');

結果

上記の変更を加えた後に、Twitterにスレッドのリンクを張ってみました。
image.png

無事、スレッドタイトルと内容が反映されました!???
こうなると次はOGPの画像生成をしたくなりますね。

あまり良いやり方ではないかもしれませんが、なんとかなりました。
もっといい方法があったら教えてください。

後このサイトですが、残念ながら全然使われてないのでラブライブが好きな方はぜひ書き込みだけでもしてみてください。。。(切実)
ラブライブ専用掲示板
ラブライブ専用掲示板:ABOUTページ

よろしくお願いします。?

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

都内の行きつけの居酒屋新規開拓サービスをリリースしたらダダ滑りしたので反省会・考察します【Laravel】

行きつけの居酒屋を新規開拓できるWEBサービスを作ったので振り返ります

初投稿です。
酔いどれ(@yoido_re)です。

この記事は先月7月末にプレリリースした個人開発のサービス、東京歩くマップについて、リリースから約1週間が経ったのでリリースまでの道のりから現状について皆さんに宣伝も兼ねてお話します。

東京歩くマップ - TokyoAlc.Map 行きつけの居酒屋を新規開拓しよう!
https://alcmap.tokyo/

スクリーンショット 2020-08-04 20.41.09.png

リリースした東京歩くマップとは

タイトルにも掲げている通り、東京歩くマップとは、東京都内に限定した行きつけの居酒屋を新規開拓するために、ユーザ同士が行きつけの居酒屋を共有し合うユーザ投稿型のサービスです。

背景

なぜ作ったの?

東京歩くマップのトップにも掲げています。

東京歩くマップ(東京Alc.マップ)とは
東京にある星の数ほどの居酒屋から自分好みの居酒屋を見つけるのは至難の技
一度気に入った居酒屋を見つけるとその居酒屋ばかりに固執してしまい、バリエーションが増やせない
レビューサイトで人気のお店には並んでまでして行こうとは思わない
そんな面倒くさがりな酒呑み達のためだけの行きつけの居酒屋共有サービス

食べ○グや他の口コミサイトとの差別化

以下2点です。
1点目。
★1の居酒屋を掲載したところで価値がないと考え、そもそも行きつけでない居酒屋はわざわざ掲載しない。本当に行きつけの居酒屋のみを掲載します。

2点目。
食べログのように、★星の数(評価)が高い=行きつけとは限らない。ウマいからと言って行きつけの居酒屋になるとは限らず、行きつけとなった背景には店主・店員さんとの素っ気ない会話、お店の入りやすさ等、別のパラメータが存在するのでは?と考察しました。

スクリーンショット 2020-08-04 20.40.36.png

最終的に行き着いたのは、その居酒屋へ行くリピート頻度(画像右上)が高いほど行きつけの居酒屋という定義をしました。
食べログや他の口コミサイトにはない、新たな指標値を見つけサービスの差別化ができたぞ!!!とサービス開発に火が付きました!

サービス思想

行きつけの居酒屋を探す方法を考察

行きつけの居酒屋を探す方法として、簡単に思いつくものに以下の手段が挙げられます。

  • WEBで探す
  • 街をブラブラして偶然入った店を気に入る
  • テレビの紹介
  • アプリ(食べログ)で評価の良い店を探す
  • 友人に聞いて勧めてもらう

上記についてツイッターで主要3つのものについてアンケートを取ると意外な結果になりました。
(全部でアンケート取ればよかったと反省)

スクリーンショット 2020-08-04 21.02.30.png

Twitterの結果から、美味しいお店をアプリで探している人と違い、酒呑み達にとってはその場でサクッと調べたお店に行きたがる傾向にあるのでは?と考察しました。
そこでアプリは断念。WEBサービスとして、酒呑みが集まるプラットフォームを構築しようと発案しました。
加えてTwitterアカウントを用いて投稿者を非匿名にすることで、友人に聞いたかの如く信憑性の高い行きつけの居酒屋を知ることができるのでは??とTwitterアカウントとの連携を採用。

ターゲティング(ペルソナ)

個人サービスにおいては、ブルーオーシャンでターゲットが狭ければ狭いほどウケると信じてます。そこで自分も該当する以下の3つをターゲットにします。

  • 都内在住
  • Twitterやってる人
  • 行きつけのお店がない人・行きつけのお店にハマりすぎて新規開拓できない人

SEOキーワードは以下を想定
行きつけ + 居酒屋 +(駅名 | 都内 | 新規開拓) 

サービス開発

システム構築

バックエンド

  • Amazon Lightsail
  • Laravel 7.2.x
  • PHP 7.3.x
  • MySQL
  • (Amazon S3はサービスが普及したら利用予定)
  • GoogleMapAPI
  • TwitterAPI
  • 最寄り駅API

フロントエンド

  • BootStrap
  • Semantic UI

スマホユーザが圧倒的に多いと想定し、スマホサイズに重きを置いたレスポンシブデザインにしました。

実装機能

限りなくスモールスタートです。
機能性≠ユーザの流入数、機能性=再帰率です。どんなに素晴らしい機能を実装してもユーザの目に触れなければ意味がないので、まずはコンセプトに沿った必要最低限の機能を実装しました。

実装機能 概要
Twitter認証 できるだけユーザ登録型のサービスは作りたくないのですが、非匿名での紹介が行きつけの居酒屋への信憑性につながると考えました
居酒屋の投稿機能 Twitter認証でユーザ登録したユーザのみ、居酒屋を登録可能
居酒屋の照会機能 誰でも照会可能
コメント機能&写真投稿機能 活気が出れば良いなと思い、簡単だったので設けました
未実装機能 理由
投稿の編集・削除 必須ではないと判断、ユーザが増えたら実装
マイページ ユーザが増えないうちに実装しても意味がない

投稿画面

スクリーンショット 2020-08-04 21.33.36.png

GoogleMapAPIを採用し、居酒屋の名前を正規化します。更に住所情報等のメタデータも同時に取得し、最寄駅を算出。
これにより、ユーザの入力を最小限に収めました。

照会画面

スクリーンショット 2020-08-04 21.36.03.png
スクリーンショット 2020-08-04 21.36.36.png
スクリーンショット 2020-08-04 21.37.13.png

反省

ユーザが増えない

リリースしてまだまだ浅いので焦るには早いかも知れませんが、以下2点を原因と考察。(≠真の原因)

SEOが弱い

検索流入が0。検索にすらヒットしていない・・・
東京メトロが運営しているトーキョーウォーキングマップとサービス名が類似しているのも反省。自ら検索妨害されに行ってしまいました・・・

そもそも”行きつけ”のキーワードで検索する人ってどのくらいいるんだろう・・?

マーケティングできていない

現状、身内・Twitterのフォロワーにしか宣伝できていません。投稿が増えたら広告も考えているのですが、投稿の少ないうちに広告を打っても閲覧者には響かないです。

投稿が増えない

恐らく、作ったサービスの質は悪くない。実際に回りの人に使ってもらうとそこそこの評価は貰えています。
それはあくまでも閲覧者側の目線。

投稿者側にメリットがないじゃん!!って意見も多々ありますが、そもそもログインされた形跡がなく、投稿画面にすら飛んでいない!!WHY!!!

投稿したくなるようなサービスでない?

投稿者側に現在の機能では何のメリットもない。投稿者-閲覧者の関係がWin-Winでないとサービスは成り立ちません。そこを全く考えていませんでした。
投稿に対していいね機能を設けるのも一つの手ですが、ユーザが少ないうちにその手は有効なのでしょうか・・・・

良かった点

  • 採用した技術はほとんど初めての利用です。初めてのPHPフレームワーク(Laravel)、初めてのLightsail。十分に使いこなせたと実感しています。

  • 5月から着手し、普段は仕事をしつつ、空いた時間のみで3ヶ月という期間サボりなしでリリースできたのは良かったと思います。
    (実稼働は1人月ほど?)

  • 開発着手前に、最低限必要な機能と2次開発機能を振り分けていた点、リリース時期やマイルストーンを決め打ちしていたことから順調にリリースまで迎えられました。

  • もともとWEBデザイン大好き人間なので満足行くデザインに仕上がりました。

  • 結果にはつながっていない話ですが、エンジニアは技術に没頭しがちでビジネス思考を忘れがちになります。今回は技術よりも如何にユーザ獲得に繋げられるか、ユーザビリティ向上するにはどうするか、常に意識していました。意識だけしていました。
    もちろん結果には(略

まとめ

今の所順調に失敗していますが、考案したサービスを無事リリースできたという点で非常に達成感を感じていると同時に自信につなげることができています。

また、実際にユーザが限りなく少ないわけではなく再帰率も高いことから、如何に継続して投稿を続けられるかがサービス継続の決め手になると思っています。

最後に再度、宣伝になりますが、東京歩くマップ、非常に有益な居酒屋情報が投稿されていますので、新規開拓したい方、サービス応援してくださる方は、ぜひ閲覧・投稿よろしくお願いいたします。

東京歩くマップ - TokyoAlc.Map 行きつけの居酒屋を新規開拓しよう!
https://alcmap.tokyo/

長々とご視聴ありがとうございました。


普段はLaravelや日常のブログも書いてます。(走り出したたこ焼きの開発
Twitterもぜひ。(@yoido_re

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

[Laravel]環境変数、サーバーから見るか?ファイルから見るか?

Laravelをお使いのみなさんは.envで環境変数を設定しているかと思いますが、もちろんサーバーの環境変数や実行時にコマンドで環境変数を指定することも可能です。

さて、.envとサーバー環境変数で同じキーを指定した場合にLaravelはどちらの値を優先するか、という記事です。

Laravelでの環境変数

Laravelではphpdotenvというライブラリを利用して環境変数を取得しています。
https://github.com/vlucas/phpdotenv

以下で各種Adapterが登録されます。
※Laravel v7系
※phpdotenv v4系

Illuminate\Support\Env
public static function getRepository()
{
    if (static::$repository === null) {
        $adapters = array_merge(
            [new EnvConstAdapter, new ServerConstAdapter],
            static::$putenv ? [new PutenvAdapter] : []
        );

        static::$repository = RepositoryBuilder::create()
            ->withReaders($adapters)
            ->withWriters($adapters)
            ->immutable()
            ->make();
    }

    return static::$repository;
}

ここでEnvConstAdapter,ServerConstAdapter,PutenvAdapterの順に登録しています。

Adapter

先程登録されていた各種Adapterは以下のような仕組みになっています。

EnvConstAdapter

PHPのスーパーグローバル変数$_ENVから値の取得・登録をします。
$_ENVは現在のスクリプトに渡された環境変数を取得します。

ServerConstAdapter

PHPのスーパーグローバル変数$_SERVERから値の取得・登録をします。
$_SERVERはサーバーに定義された環境変数を取得します。

PutenvAdapter

PHPの組み込み関数getenvputenvを利用して値の取得・登録をします。

環境変数の取得

env('string')が呼び出される先は以下のように先程登録したAdapterを順に参照して、定義されている場合に値を返します。
定義がなければnullが返されます。

Dotenv\Repository\AdapterRepository
protected function getInternal($name)
{
    foreach ($this->readers as $reader) {
        $result = $reader->get($name);
        if ($result->isDefined()) {
            return $result->get();
        }
    }

    return null;
}

結局優先順位はどうなってるの?

.envファイルが登録される際の挙動を見るとわかります。

Dotenv\Repository\AbstractRepository
public function set($name, $value = null)
{
    if (!is_string($name)) {
        throw new InvalidArgumentException('Expected name to be a string.');
    }

    // Don't overwrite existing environment variables if we're immutable
    // Ruby's dotenv does this with `ENV[key] ||= value`.
    if ($this->immutable && $this->get($name) !== null && $this->loaded->get($name)->isEmpty()) {
        return;
    }

    $this->setInternal($name, $value);
    $this->loaded->set($name, '');
}

ここのガート節で

if ($this->immutable && $this->get($name) !== null && $this->loaded->get($name)->isEmpty()) {
    return;
}

とありますね。
つまり、EnvConstAdapter,ServerConstAdapter,PutenvAdapterのどれらにも未定義の場合は値を新たに追加しています。

なので優先される値は

  1. 実行時の環境変数
  2. サーバー環境変数
  3. .envファイルの変数

になるかと思います。(違ったらご指摘ください)

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

[Laravel]Composerエラー 「Do not run Composer as root/super user!〜」

Composerを使ってライブラリを使ってインストールをしたとことろ以下のエラーが出ました。

Do not run Composer as root/super user! See https://getcomposer.org/root for details

composerを使ったインストールをrootユーザーでしようとしたため、エラーが出たみたいです。
なぜrootユーザーでインストールは好ましくないかについては以下を参照してください。
https://getcomposer.org/doc/faqs/how-to-install-untrusted-packages-safely.md

root以外のユーザーを作成します。Linuxのコマンドのuseraddでアカウントを設定します。

// rootユーザーになる
$ sudo su -

// ユーザーを追加
$ useradd -m 名前

// 追加したユーザーに移動
$ su 名前

名前$ composer コマンド

これでroot以外のユーザーになったため、composerでインストールができるようになりました

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

プロフィールの更新履歴を保存する仕組みを作るにはどのようにしたら良いのか。手順をまとめてみた。(自分用)

はじめに

※ Profile画面を作っていますので、profile(自分用)として色々記載しています。

実際のアプリケーション開発はある機能が1つのテーブルを使うだけで完結することは、ほとんどありません。

複数のテーブルを使ってシステムを実現します。
リレーショナルデータベースにはこのようなテーブル同士の関連を扱う機能が備わっており、
Eloquentで扱うことができます。
編集履歴は「Profileをいつ変更したか」「日付と時刻を記録し、参照することができる」機能のことです。

編集画面でデータを更新するタイミングで histories というテーブルにデータを登録し、
編集画面でその一覧を見られるように実装します。

では、流れに沿って記述していきます。

1.編集履歴テーブルの作成と関連付け

Migrationファイルの雛形を作成します。

$ php artisan make:migration create_histories_table

Migrationファイルを次のように編集いたします。

<?php

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

class CreateHistoriesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('histories', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('profile_id');
            $table->string('edited_at');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('histories');
    }
}
?>

Migrationを実行します。

$ php artisan migrate

Modelの雛形を作成します。

$ php artisan make:model History

app/History.php で History Modelを下記のように実装します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class History extends Model
{
    protected $guarded = array('id');

    public static $rules = array(
        'profile_id' => 'required',
        'edited_at' => 'required',
    );
}
?>

Profile Modelとの関連を定義するために、app/Profile.php へ以下の内容を追記します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Profile extends Model
{
    protected $guarded = array('id');

    public static $rules = array(
        'name' => 'required',
        'gender' => 'required',
        'hobby' => 'required',
        'introduction' => 'required',
    );

    // Profileモデルに関連付けを行う
    public function histories()
    {
    return $this->hasMany('App\History');
    }
}
?>

Profileモデルに関連付けを定義することで、

Profileモデルから $profile->histories() のような記述で簡単にアクセスすることができます。

2.編集履歴の記録と参照

ProfileController の update Actionで、Profile Model を保存するタイミングで、
同時にHistory Model にも編集履歴を追加するように実装します。

update Action を次のように変更。

<?php
public function update (Request $request)
{
    // validationをかける
    $this->validate($request, Profile::$rules);
    // Profile Model から データを取得する
    $profile = Profile::find($request->id);
    // 送信されてきたフォームデータを格納する
    $profile_form = $request->all();
    unset($profile_form['_token']);
    // 該当するデータを上書きして保存する
    $profile->fill($profile_form)->save();

    retern redirect('admin/profile');
}
?>

3.Viewを実装する

resources/views/admin/profile/edit.blade.php に以下を記述します。

{{-- layouts/profile.blade.phpを読み込む --}}
@extends('layouts.profile')


{{-- profile.blade.phpの@yield('title')にProfileの編集'を埋め込む --}}
@section('title', 'Profileの編集')

{{-- profile.blade.phpの@yield('content')に以下のタグを埋め込む --}}
@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 mx-auto">
            <h2>MyProfile編集画面</h2>
            <form action="{{ action('Admin\ProfileController@update') }}" method="post" enctype="multipart/form-data">
                @if (count($errors) > 0)
                <!-- errors内に個数があるならば配列中の個数を返す -->
                <ul>
                    @foreach($errors->all() as $e)
                    <!-- $errors内をループし、その中身を$eで表示する -->
                    <li>{{ $e }}</li>
                    @endforeach
                </ul>
                @endif
                <div class="form-group row">
                    <label class="col-md-2">氏名</label>
                    <div class="col-md-10">
                        <textarea class="form-control" name="name" rows="1">{{ old('body') }}</textarea>
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2">性別</label>
                    <div class="col-md-10">
                        <input type="radio" name="gender" value="male">男性
                        <input type="radio" name="gender" value="male">女性
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2">趣味</label>
                    <div class="col-md-10">
                        <textarea class="form-control" name="hobby" rows="10">{{ old('body') }}</textarea>
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2">自己紹介欄</label>
                    <div class="col-md-10">
                        <textarea class="form-control" name="introduction" rows="12">{{ old('body') }}</textarea>
                    </div>
                </div>
                <input type="hidden" name="id" value="{{ $profile_form->id }}">
                {{ csrf_field() }}
                <input type="submit" class="btn btn-primary" value="更新">
            </form>
        </div>
    </div>
</div>
@endsection

次に Routing を実装する
routes/web.php に追加するRouting設定です。

<?php
Route::get('profile/edit', 'Admin\ProfileController@edit');
Route::post('profile/edit', 'Admin\ProfileController@update');
?>

編集画面に実際にブラウザでアクセスしてみましょう。
一覧画面にある表の1番右側に、編集リンクがあります。
このリンクから編集画面に遷移し、実際にデータが編集できるか試してみましょう。

4.データの削除

データの削除は、データの更新と同じくモデルを取り出してから削除を指示します。

5.Controller を実装する

ProfileController に delete Action を追加してください。

<?php

public function delete(Request $requsest)
{
    // 該当する Profile Modelを取得
    $profile = Profile::find($request->id);
    // 削除する
    $profile->delete();
    return redirect('admin/profile');
}

?>

次はデータの削除について考えていきます。
一般的に削除に対応するアクション名はdeleteになります。

データをセーブするときは、$profile->save(); で save メソッドを利用しましたが、
データの場合はdelete()メソッドを使います。

6.Routing を実装する

routes/web.php に追加する Routing 設定です。

Route::get('profile/delete', 'Admin\ProfileController@delete');

削除機能は画面を持たず、id で指定されたモデルをすぐに削除します。
コントローラの最後の画面で一覧画面にリダイレクトしているため、
削除機能を持っていません。

そのため、ビューテンプレートは不要となる。
今回は以上です!
完全自分用のノートになってしましたすいません。
それでは
明日は今日以上のパフォーマンスを٩( ᐛ )و

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

MAMPでLaravelのルートの設定をどこにするか

Laravelのルートを最初はLaravelで作成したアプリ名のディレクトリにしていました。一番上のディレクトリ。

そこに設定していると、LaravelmのRouteでURLを設定してもNOT FOUNDになってパニック。

実はpublicにドキュメントルートを設定しないといけないよう。

Desktop/blog/publicという感じ。

The requested URL /blog/1 was not found on this server.
のようなエラーが出てきたら確認してみよう!

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

【Laravel】ルート定義で使う「get」「post」「put」「putch」「delete」を使い分けるためにすること

まずは、ルート定義の「HTTP動詞」についておさらい。

Laravelでは、複数のHTTP動詞に対応したルートを登録することができます。例えば、routes/web.php

routes/web.php
Route::get('/', 'SampleController@get');
Route::post('/', 'SampleController@post');
Route::put('/', 'SampleController@put');
Route::patch('/', 'SampleController@patch');
Route::delete('/', 'SampleController@delete');

と、このような感じで、同じURIでもget,post,put,patch,deleteの複数のHTTP動詞に振り分けてルートを定義することが可能になります。

上記の例では、いずれのルートも/がマッチするURIとなっていますが、Route::の次にくるHTTP動詞によって、適応されるルートが異なってきます。

GET動詞が付くルートにアクセスするには

GET動詞が付くルートにアクセスするには、普通にブラウザからURLを入力すると、GETリクエストでのアクセスとなりますので、GET動詞の付くルートが適応されます。フォームを使用しなければ、だいたいGETリクエストのアクセスなので想像しやすいと思います。普通にaタグのリンクで遷移するのもGETリクエストです。

POST動詞が付くルートにアクセスするには

POST動詞が付くルートにアクセスするには、HTMLフォームのmethod要素にPOSTを入力して送信してください。

<form action="/foo/bar" method="POST">
    <input type="text" name="hitokoto">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>

上記のようにフォームタグのmethodの値がPOSTになっています。これによってPOST動詞が定義されているルートが適応されます。

GET&POST動詞以外のHTTP動詞が付くルートにアクセスするには

HTMLフォームはmethod要素に、put,patch,deleteをサポートしていません。つまり、フォームタグのmethodput,patch,deleteを入力することができません。なので、フォームに_method隠しフィールドを追加して、疑似的にput,patch,deleteのリクエストを表現します。

<form action="/foo/bar" method="POST">
    <input type="text" name="hitokoto">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>

ちなみに、上記コードをさらにシンプルにすることもできます。_methodフィールドを、@method Bladeディレクティブに置き換えても良いでしょう。

<form action="/foo/bar" method="POST">
    <input type="text" name="hitokoto">
    @method('PUT')
    @csrf
</form>

少しすっきりしたコードになりました(=゚ω゚)ノ

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

ラズパイで部屋の見える化してみる、その2

はじめに

前回から大分時間が空いてしまいましたが、社内新卒LTで発表したラズパイとLaravelを使ったセンサーデータの可視化の続きを書きます。
前回のラズパイで部屋の見える化してみる、その1ではセンサーからデータの取得とcsvへの保存を行いました。
今回はLravelでAPI作ってデータベースにデータを保存までを書きます。
LaravelやMVCモデルについて詳しく解説というより、やったことを整理して残しておくためのメモ書きのつもりです。

手順

前回の記事を参照すると

  1. センサーからデータの取得&処理
  2. 液晶に表示
  3. csvに保存
  4. クラウドにデータを投げる
  5. DBに保存
  6. webページ上で表示

となっているので今回は4~5を実装していきます。

環境構築

研修でLaravelというものを初めて触ったのでLaravelでバックエンドを作っていきます。
基本的にVSCode の Remote - SSH機能を使ってConoha VPS上でLaravelの開発環境を用意する の説明どおりでLaravelの環境をセットアップし、マイグレーションまでできると思います。

作成したインスタンスのIPアドレスに接続するとLaravelのwelcomeページが表示されます。

スクリーンショット 2020-08-04 10.14.42.png

データベースの設定

マイグレーション

温湿度センサーのデータを格納するためのDBを用意します。直接DBを叩くのではなくLaravelのmigration機能を使ってテーブルを作成します。
まず、次のコマンドでマイグレーションファイルを作成します。

php artisan make:migration create_sensors_table --create=sensors

database/migrations/にマイグレーションファイルが生成されるのでpublic function up()の中に以下のように追加します。

Schema::create('sensors', function (Blueprint $table) {
    $table->increments('id');
    $table->float('temperature');
    $table->float('pressure');
    $table->float('humidity');
    $table->integer('sensor_id');
    $table->timestamps();
});

php artisan migrateコマンドでマイグレーションが実行されます。
MySQLにログインして確認してみます。

MariaDB [(none)]> use laravel;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [laravel]> show tables;
+-------------------+
| Tables_in_laravel |
+-------------------+
| failed_jobs       |
| migrations        |
| sensors           |
| users             |
+-------------------+
4 rows in set (0.00 sec)

MariaDB [laravel]> desc sensors;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| temperature | double(8,2)      | NO   |     | NULL    |                |
| pressure    | double(8,2)      | NO   |     | NULL    |                |
| humidity    | double(8,2)      | NO   |     | NULL    |                |
| sensor_id   | int(11)          | NO   |     | NULL    |                |
| created_at  | timestamp        | YES  |     | NULL    |                |
| updated_at  | timestamp        | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)

Sensorsテーブルとカラムが追加されていることが確認できます。

モデルの作成

テーブルが作成されたのでLaravelからDBへアクセスできるようModelクラスを作成します。
以下のコマンドでモデルを作成します。

php artisan make:Sensor

appディレクトリにSensor.phpが作られるので、ファイルの中身をSensorsテーブルに合わせて次のように編集します。

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Sensor extends Model
{
    protected $fillable = [
        'temperature',
        'pressure',
        'humidity',
        'sensor_id',
    ];
}

これでDBと連携してデータを保存できるようになりました。
LaravelではModelクラスを呼び出すことでDBへデータを挿入したり取り出したりすることができます。
初めて触ったときはこの辺の動きがよく分からず、とりあえずコピペして動かしたら動いた状態でした。

データを受け取るためのAPIを作る

今回はラズパイで収集したセンサーデータを受け取ってDBへ格納するだけなので、POSTで値を受け取る実装にします。

コントローラの作成

実際にデータを受け取ってモデルを操作するコントローラを作成します。

php artisan make:controller SensorController

app/Http/Controllers/SensorController.phpが作成されます。
このファイルにリクエストの処理を書きます。

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Sensor;

class SensorController extends Controller
{
    public function store(Request $request)
    {
        $sensor = Sensor::create([
            'temperature' => $request->input('temperature') ,
            'pressure' => $request->input('pressure'),
            'humidity' => $request->input('humidity'),
            'sensor_id' => $request->input('sensor_id'),
            ]);
            return response()->json($sensor, 201);
    }
}

先程作ったModelクラスをuse App\Sensor;で呼び出しています。
Sensor::createでDBへ指定した値を保存しています。
理解してしまえばシンプルで扱いやすいなと思いました。

ルーティングを設定

APIのルーティングを設定します。
LaravelではAPIのルーティングはroutes/api.phpに書きます。

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('api')->group(function () {
    Route::post('sensors', 'SensorController@store');
});

ルーティングではリクエストのメソッド、URI、呼び出すコントローラーを指定します。
ここではメソッドがpost、URIはsensors、コントローラーはSensorController@storeを指定しています。
実際にAPIを叩くときはIPアドレス/api/sensorsにPOSTすることでapp/Http/Controllers/SensorController.phpのstoreメソッドが実行されます。

php artisan route:listコマンドでルーティングを確認することができます。

php artisan route:list
+--------+----------+-------------+------+---------------------------------------------+------------+
| Domain | Method   | URI         | Name | Action                                      | Middleware |
+--------+----------+-------------+------+---------------------------------------------+------------+
|        | GET|HEAD | /           |      | Closure                                     | web        |
|        | POST     | api/sensors |      | App\Http\Controllers\SensorController@store | api        |
+--------+----------+-------------+------+---------------------------------------------+------------+

POSTしてみる

ここまででAPIはできたのでラズパイからPOSTできるか確認してみます。
確認のために簡単なプログラムを書きました。

import time
import random
import json
import urllib.request
url = `http://YourIPAddress/api/sensors`

def post_data(temp, press, hum):

    data = {
        'temperature' : temp,
        'pressure' : press,
        'humidity' : hum,
        'sensor_id' : 6,
    }
    headers = {
      'Content-Type': 'application/json',
    }
    req = urllib.request.Request(url, json.dumps(data).encode(), headers)
    try: urllib.request.urlopen(req)
    except urllib.error.URLError as e:
        print(e.reason)

while True:
    temp = random.uniform(10, 30)
    press = random.uniform(980, 1010)
    hum = random.uniform(50, 80)
    post_data(temp, press, hum)
    print("temp={0}, press={1}, hum={2}".format(temp, press, hum))
    time.sleep(1)

POSTリクエストはurllibを使って投げるようにします。
ここではPythonのrandom関数を使ってセンサーの値を擬似的に生成して1秒間隔で送信するようにしました。
MySQLにログインして実際にPOSTできたことを確認します。

スクリーンショット 2020-08-05 12.37.11.png

ここまで実装できると、前回の記事のセンサーデータの取得と組み合わせたらいい感じに表示できそうな気がしてきますね!

終わりに

今回はCONOHA上にインスタンスを立ててLaravelでAPIを作成からデータをPOSTしてDBへ保存するところまでを実装しました。
本当はデータの表示まで書きたかったのですが思ったより長くなったので次回に書こうと思います。
フロントエンド?知らない子ですね。。。

参考にした記事

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

xampp使用時にTNTSearchをインストールしようとしてエラー

状況

xamppを使用してLaravel7でアプリを作成し、Scout全文検索に使うTNTSearchをコンポーザーからインストールしようとしたら、エラーが出た。
XAMPP for Windowsのバージョンは 7.3.10

コマンドプロンプト
PC >> composer require teamtnt/tntsearch
Using version ^2.3 for teamtnt/tntsearch
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for teamtnt/tntsearch ^2.3 -> satisfiable by teamtnt/tntsearch[v2.3.0].
    - teamtnt/tntsearch v2.3.0 requires ext-sqlite3 * -> the requested PHP extension sqlite3 is missing from your system.

  To enable extensions, verify that they are enabled in your .ini files:
    - C:\xampp\php\php.ini
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.

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

解決法

C:\xampp\php\php.iniの

  • extension=pdo_sqlite (927行目)
  • extension=sqlite3 (938行目)

のコメントアウトを外すと、いけた。

php.ini自体は重要な設定ファイルなので、編集前にphp.iniのバックアップファイルの作成を行うことをお勧めします。

参考

Canvas: Failed Install Via Composer

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

SkyWayによるビデオ・音声通話の技術概要

この記事は「マイスター・ギルド:暑中見舞!夏のアドベントカレンダー2020」3日目の記事です。

初めに...

コロナの流行が始まったとき、「Stay Home」対策で自宅に閉じ込められたとき、ITワーカーの私たちは特に自宅からリモートで仕事をすることが可能でラッキーでした。
残念ながら、私たちのほとんどはリモートでの作業に慣れておらず、最初は上司、同僚、お客さん等とリモート通信が特に困難でした。
特殊なツールを使用しても改善されましたが、同じオフィスで作業するほど自然ではありません。
弊社のMeister Guildでもその新しい作業環境に答えるツールを探して色々なツールを使ってみた:

  • Zoom:ヴァーチャル背景を使える
  • Discord:完全に無料
  • Remo:ヴァーチャルルームに入れる、共有ホワイトボードもある
  • Spatial Chat:距離によると声の高さが変わる

ビデオ会議ができるツールはほかにもあります:

各ツールが得点と弱点を持つけど「これ!」ってなるツールがなかったので「私たちの理想なビデオ会議のツール作れるかな?」と思って調査することになりました。

ビデオ会議のツール作りの調査

そのようなツールを一から開発するのはとても大変な仕事になるので、開発をスピードアップするWebRTCフレームワークを探しました。
日本製で無料プランあり、NTTコミュニケーションズが作成したWebRTCフレームワークを見つけました:image.png
ユーザー認証をテストするために、認証付きのLaravelアプリケーションを作成し、ユーザーのメールをbase64でエンコーディングしてPeerIDとして使用しました。

SkyWayとは

ホームページによるとSkyWayは:

ビデオ・音声通話の機能をアプリケーションに簡単に実装できる、
マルチプラットフォームなSDK & フルマネージドなAPIサービスです。

無料プランで下記のSDKを使える:
- Javascript SDK
- iOS SDK
- Android SDK
- WebRTC Gateway
- APIキー認証

有料プランで録音SDK管理APIも使えるんですが例えば録音も録画も普通のMedia Capture and Streams API (Media Streams)でできる。

Javascriptサンプル:

ビデオ会議

SkyWay Room example

P2Pビデオ通話

SkyWay P2P Media example

P2Pテキスト通話

SkyWay P2P Data example

録音と録画

WebRTC samples MediaRecorder

P2Pビデオ通話

通話の相手は一人です。

基本

CDNからSDKをインポートする:

headタグ内
  <script src="https://cdn.webrtc.ecl.ntt.com/skyway-latest.js"></script>

カメラ映像を表示するvideo要素を追加する:

bodyタグ内
  <video id="my-video" width="400px" autoplay muted playsinline></video>

カメラ映像・マイク音声を取得する:

bodyタグ下部のscriptタグ内
  let localStream;

  // カメラ映像取得
  navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then( stream => {
    // 成功時にvideo要素にカメラ映像をセットし、再生
    const videoElm = document.getElementById('my-video')
    videoElm.srcObject = stream;
    videoElm.play();
    // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく
    localStream = stream;
  }).catch( error => {
    // 失敗時にはエラーログを出力
    console.error('mediaDevice.getUserMedia() error:', error);
    return;
  });

通話の相手のは「peer」と呼ばれてる。
通話出来るように自分のPeerオブジェクトを作成して相手のPeerオブジェクトと繋がる。

Peerオブジェクトの作成

Peerオブジェクトを作成するときに引数のIDを渡さない場合はランダムなIDが生成される:

scriptタグ内
        const peer = new Peer({
            key: '<SkyWayのAPIキー>',
            debug: 3
        });

PeerオブジェクトのIDはpeer.idで取得できる。

またはメールアドレスなどからIDを生成できる。
例えばLaravelのコントローラーでbase64にエンコード:

app/Http/Controllers/Controller.php
    public function index()
    {
        $user = Auth::user();
        return view('videochat',['user'=>['email'=>rtrim(base64_encode($user->email),"=")]]);
    }

ページでPeerオブジェクトに渡す:

scriptタグ内
            const peer = new Peer('{{$user['email']}}',{
                key: '<SkyWayのAPIキー>',
                debug: 3
            });

発信

相手のカメラ映像を表示するvideo要素を追加する:

bodyタグ内
  <video id="their-video" width="400px" autoplay muted playsinline></video>

相手へ発信してリスナーで接続することを待つ:

scriptタグ内
// 発信処理
const mediaConnection = peer.call('<相手のPeerID>', localStream);
setEventListener(mediaConnection);

接続ができたときにビデオ要素を設定する:

scriptタグ内
let remoteStream;
// イベントリスナを設置する関数
const setEventListener = mediaConnection => {
  mediaConnection.on('stream', stream => {
    // video要素にカメラ映像をセットして再生
    const videoElm = document.getElementById('their-video')
    videoElm.srcObject = stream;
    remoteStream = stream;
    videoElm.play();
  });
}

着信

相手側はPeerオブジェクトのcallイベントを待って着信の時ビデオ要素を設定する:

scriptタグ内
//着信処理
peer.on('call', mediaConnection => {
  mediaConnection.answer(localStream);
  setEventListener(mediaConnection);
});

映像・音声はオン・オフ等

マイク音声オフ

ミュートする:

scriptタグ内
    localStream.getAudioTracks().forEach(track => track.enabled = false);
音声オフ

相手の音声を削音する:

scriptタグ内
    remoteStream.getAudioTracks().forEach(track => track.enabled = false);

※ 音全部消したいときマイク音声もオフしなければならない。

カメラ映像オフ

カメラの映像を消す:

scriptタグ内
    localStream.getVideoTracks().forEach(track => track.enabled = false);
反響キャンセリング

反響を消す:

scriptタグ内
    localStream.getAudioTracks().forEach(track => {
        let constraints = track.getConstraints();
        constraints.echoCancellation = true;
        track.applyConstraints(constraints);
    });

ビデオ会議

ビデオ会議はビデオ通話との違いは2つ:
- 相手の数は一人以上になる
- roomオブジェクトで他のユーザーの存在(presence)が確認できる

基本

CDNからSDKをインポートする:

headタグ内
  <script src="https://cdn.webrtc.ecl.ntt.com/skyway-latest.js"></script>

カメラ映像を表示するvideo要素を追加する:

bodyタグ内
  <video id="js-local-stream"></video>

カメラ映像・マイク音声を取得する:

bodyタグ下部のscriptタグ内
  // カメラ映像取得
const localStream = await navigator.mediaDevices
    .getUserMedia({
      audio: true,
      video: true,
    })
    .catch(console.error);

  // Render local stream
  const localVideo = document.getElementById('js-local-stream');
  localVideo.muted = true;
  localVideo.srcObject = localStream;
  localVideo.playsInline = true;
  await localVideo.play().catch(console.error);

相手達のカメラ映像を表示する要素を追加する:

bodyタグ内
    <div class="remote-streams" id="js-remote-streams"></div>

Peerオブジェクトの作成

PeerオブジェクトのIDを生成する。
例えばLaravelのコントローラーでbase64にエンコード:

app/Http/Controllers/Controller.php
    public function index()
    {
        $user = Auth::user();
        return view('videochat',['user'=>['email'=>rtrim(base64_encode($user->email),"=")]]);
    }

ページでPeerオブジェクトに渡す:

scriptタグ内
            const peer = new Peer('{{$user['email']}}',{
                key: '<SkyWayのAPIキー>',
                debug: 3
            });

roomオブジェクト

Peerオブジェクトが生成された後でroomに参加する:

scriptタグ内
  peer.on('open', () => {
    const room = peer.joinRoom('test', {
      mode: 'sfu',
      stream: localStream,
    });
  });

※ roomは2つのタイプがある:'sfu'(通信がサーバーを通す)と'mesh'(通信が直接にPeerへ発信する)。

roomのイベント

open

roomに入ったとき:

scriptタグ内
    room.once('open', () => {
      ...
    });
close

roomを出たとき:

scriptタグ内
    room.once('close', () => {
      // テキスト通信を止める
      sendTrigger.removeEventListener('click', onClickSend);
      // 相手達のビデオストリームを止める
      Array.from(remoteVideos.children).forEach(remoteVideo => {
        remoteVideo.srcObject.getTracks().forEach(track => track.stop());
        remoteVideo.srcObject = null;
        remoteVideo.remove();
      });
    });
peerJoin

一人がroomに入ったとき:

scriptタグ内
    room.on('peerJoin', peerId => {
      ...
    });
peerLeave

一人がroomを出たとき:

scriptタグ内
    room.on('peerLeave', peerId => {
      // ストリームを閉じてvideo要素を消す
      const remoteVideo = remoteVideos.querySelector(
        `[data-peer-id=${peerId}]`
      );
      remoteVideo.srcObject.getTracks().forEach(track => track.stop());
      remoteVideo.srcObject = null;
      remoteVideo.remove();
    });
stream

roomに入った一人のストリームを表示:

scriptタグ内
    room.on('stream', async stream => {
      // video要素を生成
      const newVideo = document.createElement('video');
      newVideo.srcObject = stream;
      newVideo.playsInline = true;
      // peerLeaveイベントのときにストリームを見つけるためにpeerIdを付ける
      newVideo.setAttribute('data-peer-id', stream.peerId);
      remoteVideos.append(newVideo);
      await newVideo.play().catch(console.error);
    });

data

メッセージを着信したとき:

scriptタグ内
    room.on('data', ({ data, src }) => {
      // メッセージと発信者を表示
      messages.textContent += `${src}: ${data}\n`;
    });

テキスト発信

メッセージを発信するとき:

scriptタグ内
    sendTrigger.addEventListener('click', onClickSend);

    function onClickSend() {
      // websocketでroomの皆さんにメッセージを起こる
      room.send(localText.value);
      // メッセージと発信者を表示
      messages.textContent += `${peer.id}: ${localText.value}\n`;
      // インプットを消す
      localText.value = '';
    }
  });

映像・音声はオン・オフ等

マイク音声オフ

ミュートする:

scriptタグ内
    localStream.getAudioTracks().forEach(track => track.enabled = !audioInStatus);
音声オフ

相手の音声を削音する:

scriptタグ内
    remoteVideos.forEach(video => {
      video.srcObject.getAudioTracks().forEach(track => track.enabled = !audioOutStatus);
    });

※ 音全部消したいときマイク音声もオフしなければならない。

カメラ映像オフ

カメラの映像を消す:

scriptタグ内
    localStream.getVideoTracks().forEach(track => track.enabled = false);
反響キャンセリング

反響を消す:

scriptタグ内
    localStream.getAudioTracks().forEach(track => {
        let constraints = track.getConstraints();
        constraints.echoCancellation = true;
        track.applyConstraints(constraints);
    });

音声通話

navigator.mediaDevices.getUserMedia()の引数でメディアのタイプ(画像・音声・両方)等を選択できる:

scriptタグ内
  // カメラ映像取得
const localStream = await navigator.mediaDevices
    .getUserMedia({
      audio: true,
      video: false,
    })

画面共有

自分の画面をMediaStreamとして取得できる

scriptタグ内
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });

このメディアストリームを使って画面共有機能が実現できる。

終わりに...

WebRTCを使用できるのでウェブアプリケーションの元に使用できるフレームワークと思いました。

その調査をして私の理想なリモートワークのツールについていっぱいなアイデアが生まれて社長が本気で開発始めようのは本気になって欲しいです。

参考

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

Laravel初心者に知って欲しいルーティングの見やすい書き方とあれこれ

ルーティング綺麗に書いた方が見やすくない?

Laravelでいうroutes/web.phpのこと
初学者のコード見てるともう少しすっきり書けるのになー
そもそも使い方があんまり分からないのかな?と思い記事にする事にしました

そもそもどういう仕組み

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_293438_2c0af10e-1ee8-79e3-1b8c-53069c92e6ae.png

HTTP動詞

postだったりgetだったりresourceとかその時のHTTP動詞を指定

URLのパス

定義した文字にアクセスした時に適用されるルート

コントローラー名

app/Http/Controllers/の中で定義したいコントローラーを指定

メソッド名

上記で設定したコントローラー内のメソッドを指定

早速見やすい書き方講座

一般的な例としてユーザーが一般ユーザーと管理者で分かれており、それぞれにTOPページとCRUDページ(投稿・一覧・更新・削除・あと詳細)があるという構成です

良くある例

web.php
// 一般ユーザー
Route::group(['middleware' => 'auth:user'], function() {
    Route::get('home', 'User/HomeController@index')->name('user.home.index');
    Route::get('post', 'User/PostController@index')->name('user.post.index');
    Route::get('post/show/{id}', 'User/PostController@show')->name('user.post.show');
    Route::get('post/create', 'User/PostController@create')->name('user.post.create');
    Route::post('post/store', 'User/PostController@store')->name('user.post.store');
    Route::get('post/edit/{id}', 'User/PostController@edit')->name('user.post.edit');
    Route::post('post/update/{id}', 'User/PostController@update')->name('user.post.update');
    Route::post('post/destroy/{id}', 'User/PostController@destroy')->name('user.post.destroy');
});

// 管理者ユーザー
Route::group(['middleware' => 'auth:admin'], function() {
    Route::get('admin/home', 'Admin/HomeController@index')->name('admin.home.index');
    Route::get('admin/post', 'Admin/PostController@index')->name('admin.post.index');
    Route::get('admin/post/show/{id}', 'Admin/PostController@show')->name('admin.post.show');
    Route::get('admin/post/create', 'Admin/PostController@create')->name('admin.post.create');
    Route::post('admin/post/store', 'Admin/PostController@store')->name('admin.post.store');
    Route::get('admin/post/edit/{id}', 'Admin/PostController@edit')->name('admin.post.edit');
    Route::post('admin/post/update/{id}', 'Admin/PostController@update')->name('admin.post.update');
    Route::post('admin/post/destroy/{id}', 'Admin/PostController@destroy')->name('admin.post.destroy');
});

何だか全体的に記述が多いですよね...
これをもう少し見やすくしていきましょう。

今回提唱したい例

web.php
// 一般ユーザー
Route::middleware('auth:user')->namespace('User')->name('user.')->group(function() {
    Route::resource('home', 'HomeController', ['only' => 'index']);
    Route::resource('post', 'PostController');
});

// 管理者ユーザー
Route::middleware('auth:admin')->namespace('Admin')->prefix('admin')->name('admin.')->group(function() {
    Route::resource('home', 'HomeController', ['only' => 'index']);
    Route::resource('post', 'PostController');
});

かなりすっきりしました!
仕組みを解説していきます。

ルートグループのメソッドチェーン

良く見られる配列で指定するルートグループの書き方は実はLaravel5.3までの書き方で、
それ以降は->メソッドチェーンで定義するのが主流です!

古い例

web.php
Route::group(['prefix' => 'admin', 'middleware' => 'auth:admin'], function () {
    //
});

新しい例

web.php
Route::prefix('admin')->middreware('auth:admin')->group(function () {
    //
});

名前空間

ルートグループのnamespaceを使用する事でそのグループ内に定義したルート全てに名前空間が適用されます。
そのためわざわざルート毎にAdmin/と付ける必要がなくなります。

古い例

web.php
Route::group(['middleware' => 'auth:admin'], function() {
    Route::get('admin/home', 'Admin/HomeController@index')->name('admin.home.index');
});

新しい例

web.php
Route::middleware('auth:admin')->namespace('Admin')->group(function() {
    Route::get('admin/home', 'HomeController@index')->name('admin.home.index');
});

ルートプレフィックス

ルートグループのprefixを使用する事でそのグループ内に定義したルート全てにURIを指定することができます。
そのためわざわざルート毎にURIのパスをadmin/と付ける必要がなくなります。

古い例

web.php
Route::group(['middleware' => 'auth:admin'], function() {
    Route::get('admin/home', 'Admin/HomeController@index')->name('admin.home.index');
});

新しい例

web.php
Route::middleware('auth:admin')->namespace('Admin')->prefix('admin')->group(function() {
    Route::get('home', 'HomeController@index')->name('admin.home.index');
});

ルート名

ルート名使ってますか??
意外と便利なので使うようにしましょう!

説明はかずへいさんの記事に任せます!
Laravel使うならrouteには名前をつけたほうが良い

ルートグループのnameを使用する事でそのグループ内に定義したルート全てにルート名を指定することができます。
そのためわざわざルート毎にnameをadmin.と付ける必要がなくなります。

古い例

web.php
Route::group(['middleware' => 'auth:admin'], function() {
    Route::get('admin/home', 'Admin/HomeController@index')->name('admin.home.index');
});

新しい例

web.php
Route::middleware('auth:admin')->namespace('Admin')->prefix('admin')->name('admin.')->group(function() {
    Route::get('home', 'HomeController@index')->name('home.index');
});

resource

多分初学者の方は一番ここに興味持たれたのではないでしょうか。
resourceはリソースコントローラーと呼ばれ、一般的なCRUD(投稿・一覧・更新・削除・あと詳細)操作を予めLaravel側が用意してくれるというものです。

web.phpではHTTP動詞をRoute::resourceとする事で以下のメソッドを定義した扱いとなります。

  • index(一覧)
  • show(詳細)
  • create(作成画面)
  • store(作成処理)
  • edit(編集画面)
  • update(編集処理)
  • destroy(削除処理)

そのためこの様に行を短縮出来たんですね!

古い例

web.php
    Route::get('admin/post', 'Admin/PostController@index')->name('admin.post.index');
    Route::get('admin/post/show/{id}', 'Admin/PostController@show')->name('admin.post.show');
    Route::get('admin/post/create', 'Admin/PostController@create')->name('admin.post.create');
    Route::post('admin/post/store', 'Admin/PostController@store')->name('admin.post.store');
    Route::get('admin/post/edit/{id}', 'Admin/PostController@edit')->name('admin.post.edit');
    Route::post('admin/post/update/{id}', 'Admin/PostController@update')->name('admin.post.update');
    Route::post('admin/post/destroy/{id}', 'Admin/PostController@destroy')->name('admin.post.destroy');

新しい例

web.php
Route::resource('post', 'PostController');

またresourceはname()も勝手に付与してくれるため、その煩わしさもなくなります!
とっても便利ですね!

最後に

今回は短く書くことで興味を引く作戦でしたが、短ければ良いコードというわけではない。
読みやすく、改修が容易に済むコードを心掛けましょう()

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