20210125のlaravelに関する記事は13件です。

LaravelにDockerファイルでcronを導入して定期的にメール送信する方法

https://zenn.dev/kiyo_tech/articles/8e79e9873ff687
の転載です。

dockerでcronを導入する方法が日本語では見つからなかったので書きました。

dockerファイルの書き方が全くわからない人は難しいです。

以下の例ではdocker内部ではprojectフォルダにソースを入れています。

あとdocker-composeでマウントする場合はCOPY . /project/が不要、 最後のRUN 以降で、chmod 0644 /etc/cron.d/cronとtouch /var/log/cron.log以外は不要です。
通常のLaravelで立ち上げるdockerファイルとは分けましょう。

app/docker/cron/dockerfile
FROM php:7-fpm

ENV DEBIAN_FRONTEND noninteractive

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get -y install git cron libicu-dev libonig-dev libzip-dev unzip &&\
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* &&\
    docker-php-ext-install pdo_mysql &&\
    composer config -g process-timeout 3600 && \
    mkdir -p /project
COPY . /project/
COPY ./docker/cron/php.ini /usr/local/etc/php/php.ini
COPY ./docker/cron/root /etc/cron.d/cron

WORKDIR /project

RUN composer install &&\
    chmod -R a+w storage/ bootstrap/cache &&\
    chmod 0644 /etc/cron.d/cron &&\
    touch /var/log/cron.log &&\
    php artisan cache:clear &&\
    php artisan config:clear &&\
    php artisan route:clear

CMD cron -f

project/docker/cron/root
* * * * * root /usr/local/bin/php -q -f /project/artisan schedule:run --no-ansi >> /dev/null 2>&1

project/app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('email:send')->hourly()->onOneServer();
    }

コマンドは公式を参照しましょう。
https://readouble.com/laravel/7.x/ja/scheduling.html

php artisan make:command SendMailTest

作成したファイルでコマンドの内容をつくります。

project/app/Console/Commands/SendMailTest.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\User;
use App\Mail\SendMail;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;

class SendMailTest extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'email:send';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'メールを定期的に配信する';


    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
    // 対象者を絞ったり
        $user = User::find(対象者);

    // 内容をDBから取り出しておいたり、
    $contents = ~~~

    Mail::to($user)->send(new SendMail($contents));
    }
}

またコマンド

php artisan make:mail SendMail

以下内容です。

project/app/Mail/SendMail.php
<?php

namespace App\Mail;

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

class SendMail extends Mailable
{
    use Queueable, SerializesModels;

    // 引数で受け取る変数
    protected $user;
    protected $contents;

    // コンストラクタ設定
    public function __construct($user,$contents)
    {
        // 引数で受け取ったデータを変数にセット
       $this->user = $user;
       $this->contents = $contents;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
      return $this
    ->subject('メールのタイトル')
    ->view('emails.content')
    ->with([
        'user' => $this->user,
        'contents' => $this->contents,
    ]);
    }
}

resources/views/emailsディレクトリを作成し、content.blade.phpを作成します。

project/resources/views/emails/content.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
 <!-- 何かしらの内容 -->
</body>
</html>

内容面は適宜カスタマイズしてください。

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

共同開発講座を受講中にコツコツ書いた「詰まったメモ」をジャンル別に全て公開します【総まとめ集編】

はじめに

タイトルの通り、共同開発講座を受講中に起きたトラブルや詰まりポイントをまとめたメモ(約50記事)をジャンル別に全て公開します。
ジャンルは以下の通りなので、気になるものがあればぜひご覧ください。

【ジャンル一覧】

GitHub関連
Laravel関連
データベース(migration/seeding)関連
環境関連
総まとめ集

下記の点を、ご了承ください。
・ジャンルで統一しているため、内容に関しては統一性はありません。
・メモの難易度もバラバラです。
・初学者向けの内容となっております。
・自分用のメモを転用しておりますので、表現が稚拙な部分があるかと思います。
・あくまで僕のメモです!!

・マイグレーションとシーディングの説明と一連の流れ

マイグレーションとは

ザックリ言うとテーブルを作成するための作業
マイグレーションファイルをコマンドで作成し、作りたいテーブルのカラムを記載していく。
記載したファイルをコマンドでマイグレーションすると、データベースにテーブルが作成される。
また、マイグレーションファイルにはカラムに対してデータ型や修飾子、FKを記載しておくとマイグレーション実行時に反映される。
ただ、FKの関係でマイグレーションする順番が重要になってくるので、マイグレーションファイルを作成する順番に気を付ける。(FKで指定されているテーブルがないと、エラーが起こるので、先に親テーブルからマイグレーションされるように作成順番を考える。→子テーブルを後に作成する)
今回はphpmyadminをしようしているので、ブラウザ上で視覚的に確認することができる。
Phpmyadmin上で、使用するデータベースを作成しておかないと、テーブルをその中に作ることができないので注意

マイグレーションファイルの作成(カラム、FK、その他修飾子の作成)

php artisan make:migration ファイル名

でファイル作成。

ファイルの中身はこんな感じ

 public function up()
    {
        Schema::create('m_products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('product_name', 64);
            $table->integer('category_id')->unsigned();
            $table->integer('price')->unsigned();
            $table->string('description', 256);
            $table->integer('sale_status_id')->unsigned();
            $table->integer('product_status_id')->unsigned(); 
            $table->timestamp('resist_date');
            $table->integer('user_id')->unsigned();
            $table->char('delete_flag', 1);

            $table->foreign('sale_status_id')->references('id')->on('m_sales_statuses')->onDelete('cascade');
            $table->foreign('product_status_id')->references('id')->on('m_products_statuses')->onDelete('cascade');
        });
    }

データ型について

上記のファイル内の記載のうち、string integer timestamp などがデータ型にあたる。
そのデータはどういう形式のデータなのか?文字なのか?数字なのか?その時の時間なのか?
を指定することができる。詳しくはGoogle先生に。(他の記事にも記載あります)

php artisan migration の実行

このコマンドを実行すると、作成したマイグレーションファイルがテーブルとして作成される。
エラーになる場合は、他のエラーに関するメモを参照。

migrationエラーについて

多かったエラーは
①データ型があっていない場合
②migrationする順番が違い、外部キー制約がつかないために起こるエラー

シーディングとは

ザックリい言うと、テストデータを一気に作成することができる機能で、ファイルを作成してシーディングを行うことテーブル内にデータが挿入される。

テストデータの決定

シーディングを行うテーブルに適したデータを考える。
例えば、商品(肉とか魚)のテストデータを考える時は、それぞれのカラムに適応した具体的なデータ(商品名や値段)を考える。
何個でも一気に登録できるが、まぁ、テストなので5~10個程度あれば十分。

シーディングファイルの作成

php artisan make:seeder ファイル名

上記コマンドでシーディングファイルが作成される。
中身には前項で決定したテストデータを記載していく。

実際の内容としてはこんな感じ

public function run()
    {
        DB::table('m_products')->insert([
           'id' => 1,
           'product_name' => '黒毛和牛サーロイン',
           'category_id' => 1,
           'price' => 8000,
           'description' => 'なめらかでとろける食感と、甘くコクがある上品な香りが特徴です' ,
           'sale_status_id' => 1,
           'product_status_id' => 1,
           'resist_date' => date('Y-m-d H:i:s'),
           'user_id' => 1,
           'delete_flag' => ''
        ]);
        DB::table('m_products')->insert([
            'id' => 2,
            'product_name' => 'A5ランク松坂牛',
            'category_id' => 1,
            'price' => 12000,
            'description' => '松坂牛は濃厚で上品な甘みが絶品!' ,
            'sale_status_id' => 1,
            'product_status_id' => 1,
            'resist_date' => date('Y-m-d H:i:s'),
            'user_id' => 1,
            'delete_flag' => ''
         ]);

例として2つだけここに書いたが、実際は何個でも良い。

php artisan db seed  の実行

このコマンドを実行することで、作成したシーディングファイル(テストデータ込み)がシーディングされて、当該テーブルにデータが挿入される。
このコマンドの実行時にエラーとしてよく起こるのは
①子テーブルと親テーブルの関係
②外部キー関連
が多いが、詳しくは別のメモにまとめてあるものを参照。

・viewの実装手順(一応ここでは新規ユーザ登録画面)

実装の流れ

①作成していたモックアップ画面のhtml要素をコピーして、lara-d(プロジェクト)の当該ファイルに貼り付ける
②laravelcollectiveを導入する
③formタグ部分をlaravelcollectiveで書き換える。この時のformタグ内でのデータ型はデータベースのものとは違うので気を付ける(例:stringやintegerは使えない。ルーティングエラーになる。form内はtext型を使う)
④バリデーションをcontrollerファイル内で定義する。この時の指定はintegerやstringを使って行う。
⑤rink_to_routeの実装を行なって、挙動がしっかりしていることを確認する

ブラウザ上でviewファイル(ルーティング)を確認する方法

URLに何を打ち込めばviewファイルが表示されるのか??

URLに打ち込むのは…

192.168.33.11/signup

192.168.33.11は自分で決めたローカルホストのIPアドレスのこと。
と打ち込めばローカルホストに繋がり、「signup」というルーティングから行き着く先のviewファイルを表示することができる。この「signup」はweb.phpに記述したルーティングの内容のこと。

これを打ち込めば「signup」というルーティング名で設定した先のviewが表示される

・新規ユーザ登録機能の実装

実装手順

1.画面(新規ユーザ登録画面フォーム)を作る

・LaravelCollectiveを導入
・フォームに情報を持たせてLaravelCollectiveで実装していく
→Form open/close 送信するルーティング名を指定
→カラム名、old()関数、クラス(bootstrap)
password確認のフォーム実装は’confirmation’が必要
→型は基本的にtextで必要に応じてemail型やpassword型を記述する
・今回はcssが崩れていたのでcssも編集

2.ルーティングの実装

・「ユーザ新規登録画面を表示させる」ルーティングを書く(signup)
・「ユーザ新規登録させる」ルーティングを書く(signup.post)

Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');

RegisterControllerは元々存在し、Registerアクションも元々存在する!(トレイトに入っている)
→なので、コントローラを新しく生成する必要はない。

3.Userモデルを作成

・Userモデル自体は元々存在する
・$fillableに作成したいユーザの情報要素を書いていく
・どのテーブルにそのモデルが作成されるのかを指定する

protected $table = 'm_users';

protected $fillable = [
        'last_name',
        'first_name',
        'zipcode',
        'prefecture',
        'municipality',
        'address',
        'apartments',
        'email',
        'phone_number',
        'password',
        'company_name',
    ];

4.バリデーションの実装

・RegisterControllerにバリデーション記載していく
・Userモデルで定義したカラム(フォームから飛んでくる項目)に対して記載する
・integerよりもnumericを使用する

return Validator::make($data, [
        'last_name' => ['required', 'string', 'max:10'],
        'first_name' => ['required', 'string', 'max:10'],
        'zipcode' => ['required', 'numeric', 'digits:7'],
        'prefecture' => ['required', 'string', 'max:5'],
        'municipality' => ['required', 'string', 'max:10'],
        'address' => ['required', 'string', 'max:191'],
        'apartments' => ['required', 'string', 'max:191'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'phone_number' => ['required', 'numeric', 'max:15'],
        'password' => ['required', 'string', 'min:6', 'max:15', 'confirmed'],
    ]);

5.createアクションの中身の記載

・RegisterControllerのcreateアクションに作成したいデータを変数に入れていく
・Userモデルで定義したカラム(フォームから飛んでくる項目)に対して記載する

return User::create([
            'last_name' => $data['last_name'],
            'first_name' => $data['first_name'],
            'zipcode' => $data['zipcode'],
            'prefecture' => $data['prefecture'],
            'municipality' => $data['municipality'],
            'address' => $data['address'],
            'apartments' => $data['apartments'],
            'email' => $data['email'],
            'phone_number' => $data['phone_number'],
            'password' => bcrypt($data['password']),
        ]);

6.リダイレクト先の変更

・RegisterControllerの上の方にある$redirectTo=の部分を”/”に変える(新規登録後に飛ぶページにリンクするように)

protected $redirectTo = '/';

7.リンク先の指定(ページ内のボタン及びヘッダーからの画面遷移)

・ページ内のボタンのリンクをページ遷移に従って変更
・ヘッダーからの画面遷移に従ってリンク先を変更

その他(注意点)

user_classification_idに先にテストデータを流し込んでおくこと
ブラウザからフォームを送信してデータベースに登録する際にSQL1364エラーが起こることがある
→このエラーが一番引っかかった。。
これは今回で言うと、user_classification_idにデータが入っていないことで起こる。また、マイグレーションファイルにデフォルト値が指定されていないことが原因である。詳しくは別のエラーメモを参照(SQL1364エラー)

->nullable()と->default()について
なるべく  ->nullable()  よりも  ->default()  を用いるほうがいい。
また、default値を指定するときについて
今回の場合は、登録されるユーザはどんなタイプのユーザなのか?(user_classification_id)を意識してデフォルト値を設定すること。

・商品詳細画面の実装(セッションの保存まで)

web.php
Route::resource('cartitem', 'CartController', ['only' => ['index']]);
Route::group(["prefix" => 'iteminfo'], function() {
    Route::get('/{id}', 'CartController@show');
    Route::post('/add', 'CartController@addCart')->name('addcart');
});
CartController.php (showアクション)
public function show($id)
    {
        //変数の初期化
        $ProductInfo = array();
        $ProductCategory = array();
        $UserId = '';
        //urlパラメータから飛んできたユーザidを元にモデルからそれぞれ商品、カテゴリーを特定
        $ProductInfo = Product::findOrFail($id);
        $ProductCategory = Category::findOrFail($ProductInfo -> category_id);
        $UserId = Auth::user()->id;

        return view('iteminfo', 
        [
            'ProductInfo' => $ProductInfo,
            'ProductCategory' => $ProductCategory,
            'UserId' => $UserId,
        ]);
    }

〜iteminfo(商品詳細)画面の表示〜


{!! Form::open(['route' => 'addcart']) !!}
    {{ Form::hidden('user_id', $UserId) }}

    {{ Form::hidden('ProductId', $ProductInfo->id) }}

        <div class="form-group row justify-content-center">
            <label class="col-form-label">購入個数</label>
           <div class="">

           <input type="number" class="inputNumber form-control" value="0" name="Quantity" >
           </div>
            <lavel class="col-form-label"></lavel>
                <div class="col-sm-auto">
                 {!! Form::submit('カートへ', ['class' => 'btn btn-primary']) !!}
          </div>
        </div>
{!! Form::close() !!}

ここで

{{ Form::hidden('user_id', $UserId) }}

{{ Form::hidden('ProductId', $ProductInfo->id) }}

この3つのname属性で送信する

web.phpを介して

CartController(addCartアクション)へ

CartController(addCartアクション)
public function addCart(Request $request)
    {
        //セッションに保存したい変数を定義する(ここでは商品idと注文個数)
        //飛んできた$requestの中のname属性をそれぞれ指定
        $SessionProductId = $request->ProductId;
        $SessionProductQuantity = $request->Quantity;
        //配列の入れ物を作る(初期化)
        $SessionData = array();

        //作った配列に、compact関数を用いてidと個数の変数をまとめる(”” を使っているが変数の意味)
        $SessionData = compact("SessionProductId", "SessionProductQuantity");

        //session_dataというキーで、$SessionDataをセッションに登録
        $request->session()->push('session_data', $SessionData);

        return redirect('cartitem');

    }

このアクションでセッションにIDと個数を保存する。

$SessionProductId = $request->ProductId;
$SessionProductQuantity = $request->Quantity;

この二つの記述でIDと個数のそれぞれを変数と置く。

データをひとまとめにするために

$SessionData = array();

として、空の配列を定義する。

配列に二つの変数を統合する

$SessionData = compact("SessionProductId", "SessionProductQuantity");

この時ダブルクォーテーションがついているが"SessionProductId"は変数を意味している

そしてセッションにpushして配列に次々と値を加えていく形をとる

$request->session()->push('session_data', $SessionData);


web.phpのcartitemにリダイレクトする

CartControllerのindexアクションに繋がる

CartController(indexアクション)
public function index(Request $request)
    {
        //セッションに保存していた値を取得し、変数として定義
        $SessionData = $request->session()->get('session_data');
        //セッションデータのなかのそれぞれのデータを抽出
        $SessionProductId = array_column($SessionData, 'SessionProductId');
        $SessionProductQuantity = array_column($SessionData, 'SessionProductQuantity');
        dd($SessionData);


ここから下はまだ不明

セッションに保存していた値を取得し、変数として定義

$SessionData = $request->session()->get('session_data');

array_column関数を用いてIDと個数のデータをそれぞれ抽出

$SessionProductId = array_column($SessionData, 'SessionProductId');

あとはこのデータを用いてカート内商品一覧画面に表示できればそれで完了のはず。。

・バック商品修正画面の実装

ちらECサイトの中で、管理者(出品者)側の画面で、出品する商品を修正する画面の実装です

実装する内容

1.画面遷移時の商品情報のpre表示
2.商品修正機能
3.商品情報の削除機能

1.画面遷移時の商品情報のpre表示

BackProductController
public function edit($id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);

        //category関連の定義
        $categories = MCategory::getLists();
        $categoryName = MCategory::find($product->category_id)->category_name;
        $categoryId = $product->category_id;

        //sale_status関連の定義
        $saleStatuses = MSalesStatus::getLists();
        $saleStatusName = MSalesStatus::find($product->sale_status_id)->sale_status_name;
        $saleStatusId = $product->sale_status_id;

        //product_status関連の定義
        $productStatuses = MProductStatus::getLists();
        $productStatusName = MProductStatus::find($product->product_status_id)->product_status_name;
        $productStatusId = $product->product_status_id;

        return view('seller.back_product_edit',[
                'product' => $product,
                'categories' => $categories,
                'categoryName' => $categoryName,
                'categoryId' => $categoryId,
                'saleStatuses' => $saleStatuses,
                'saleStatusName' => $saleStatusName,
                'saleStatusId' => $saleStatusId,
                'productStatuses' => $productStatuses,
                'productStatusName' => $productStatusName,
                'productStatusId' => $productStatusId,
            ]
        );
    }

editアクションでその$idを持つ商品の修正画面に飛ばす。
MProductモデルから$idを持つ商品を探すときに、リレーション先のデータまで取得できるようにしなければならない。
MProductモデルに記載されているリレーションを貼った関数名をwith関数の引数に入力する。
その後、viewに返す。

例) 下記のような内容の関数名

MProduct.php(モデル)
public function saleStatus()
    {
        return $this->belongsTo(MSaleStatus::class);
    }
@extends('layouts.seller_app')
@section('content')
<main>

    <div class="page-header mt-5 text-center">
        <h4>商品情報修正</h4>
    </div>


    <div class="row mt-5 mb-5">
        <div class="col-sm-5 mx-auto">

            {!! Form::open(['route' => ['back_product_update', $product->id]]) !!}
            {{ method_field('PUT') }}


                <div class="form-group-sm">
                    {!! Form::label('productName', '商品名', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::text('productName', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productName'))
                            {{ $errors->first('productName') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('categoryName', '商品カテゴリ', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        <select class="form-control d-inline w-100" name="categoryId">
                        <option value={{ $categoryId }}>{{ $categoryName }}</option>
                            @foreach($categories as $id => $category_Name)
                                <option value={{ $id }}>
                                    {{ $category_Name }}
                                </option>  
                            @endforeach, 
                        </select>
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('categoryId'))
                            {{ $errors->first('categoryId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('price', '販売単価', ['class' => 'd-block mt-2 mb-0']) !!}
                    {!! Form::text('price', $product->price, ['class' => 'ml-3 mr-2 form-control col-sm-8 d-inline'])."円" !!}
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('price'))
                            {{ $errors->first('price') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('saleStatusName', '販売状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="saleStatusId">
                        <option value="{{ $saleStatusId }}">{{ $saleStatusName }}</option>
                            @foreach($saleStatuses as $id => $sale_StatusName)
                                <option value="{{ $id }}">
                                    {{ $sale_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('saleStatusId'))
                            {{ $errors->first('saleStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('productStatusName', '商品状態', ['class' => 'd-block mt-2 mb-0']) !!}
                    <select class="ml-3 form-control col-sm-8" name="productStatusId">
                        <option value="{{ $productStatusId }}">{{$productStatusName}}</option>
                            @foreach($productStatuses as $id => $product_StatusName)
                                <option value="{{ $id }}">
                                    {{ $product_StatusName }}
                                </option>  
                            @endforeach, 
                    </select>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('productStatusId'))
                            {{ $errors->first('productStatusId') }}
                        @endif
                    </div>
                </div>

                <div class="form-group-sm">
                    {!! Form::label('description', '商品説明', ['class' => 'mt-2 mb-0']) !!}
                    <div class="pl-3">
                        {!! Form::textarea('description', $product->description, ['class' => 'form-control d-inline w-100']) !!}
                    </div>
                    <div class="mt-1 text-right text-danger">
                        @if($errors->has('description'))
                            {{ $errors->first('description') }}
                        @endif
                    </div>
                </div>

                <div>
                    <div class="w-50 float-left">
                        <div class="text-center mt-5"> 
                            {!! Form::submit('修正', ['class' => 'button btn btn-primary mt-2']) !!}
                        </div>
                    </div>
                    {!! Form::close() !!}

                    {!! Form::open(['route' => ['back_product_delete', $product->id]]) !!}
                    {{ method_field('DELETE') }}
                    <div class="w-50 float-right">
                        <div class="text-center mt-5">
                            {!! Form::submit('削除', ['class' => 'button btn btn-danger mt-2']) !!}
                        </div>
                    </div>
                </div> 
            {!! Form::close() !!}
        </div>
    </div>
</main>
@endsection


------------申し訳ありません-----------------
メモ作成後にコードを編集したので以下、少し変わります。
ですが、知識としては使えますので参考までにお願いいたします。

--このView画面で$idの商品情報をpre表示させる--
例えばこれ

{!! Form::text('product_name', $product->product_name, ['class' => 'form-control d-inline w-100']) !!}

(重要ポイント!!)
text型のformで
●第一引数:HTMLフォームの name属性
ここにはデータベースのカラム名を入れておくと良い
●第二引数:HTMLフォームの value属性
ここには予め表示したい内容を入れておく
●第三引数:追加属性(連想配列形式)
ここにはクラス(CSS要素)を記入

この第二引数が重要!!

$product->product_name

これは特定した $product のproduct_nameというカラムに格納されているデータを呼び出しており、入力欄にはそれが表示される。

ではこれはどうか

 {!! Form::text('category_name', $product->category->category_name, ['class' => 'form-control d-inline w-100']) !!}

第二引数は

$product->category->category_name

となっているが
これは
特定した$productからリレーションを貼っている関数名categoryを呼び出し、そのリレーション先にあるcategory_nameを呼び出している
という形。
こう書くことで、リレーション先のデータを取得することができる。

上記を全てのフォームに記載することで商品情報のpre表示が完成する。


-------ここまで、viewと比べてコードが違いますm(__)m---------

2.商品修正機能

BackProductController
public function update(CreateProductRequest $request, $id)
    {
        $product = MProduct::with(['category', 'saleStatus', 'productStatus'])->find($id);

        $product->product_name = $request->productName;
        $product->category_id = $request->categoryId;
        $product->price = $request->price;
        $product->sale_status_id = $request->saleStatusId;
        $product->product_status_id = $request->productStatusId;
        $product->description = $request->description;
        $product->save();

        return redirect('seller/items');
    }

updateアクションで商品情報を修正する

大まかな流れ

商品修正画面(back_product_edit.blade.php)のフォームから書き換えられた内容が送信された後、このアクションにくる。

飛んできた$requestを頼りに、それぞれのカラムデータに格納していく。

格納した情報を保存する
という流れ。

実装の解説

まず

$product = MProduct::with(['category', 'saleStatus' ,'productStatus'])->find($id);

修正する商品である$productを特定する。このときリクエストから飛んできたidを元にモデルから探し出す。さらにリレーション先のテーブルも取得できるようにwith関数を使用。
特定ができたら

$product->product_name = $request->productName;

その商品が持つproduct_nameを新しく$request->productNameと定義し直す。
また
リレーション先のデータを書き換えたい場合は

$product->category_id = $request->categoryId;

のようにリレーション先に指定している関数名(MProductモデルに記載)をアロー関数でつなぐことで、リレーション先のテーブルにあるcategory_nameカラムを指定することができる。

2.削除機能

割愛!!!!!!

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

【Laravel6】Gate機能を使った権限機能を簡単に実装する

はじめに

Laravel6で権限機能を実装したのでその方法についてまとめます。

権限機能と言えば

  • 「一般ユーザー」と「管理者」でアクセスできるページや操作可能な機能を制限する
  • 「管理者」の中でグレード(種別)を設定して管理画面上で色々と制限をかける権限機能

が主にあるかなと思いますが、この記事では後者を例にします。
(とはいえ、前者でも基本的な考え方は同じだと思います)

環境

Laravel 6.8

設定する権限

記事用に仮想的な権限を簡易的に設定します。

権限名 内容
最強 全ての機能を使える
普通 一部機能を使える
最強 1つの機能しか使えない

こんな感じで、権限の強さによって使える機能を限定します。

権限機能の実装方法

手順はざっくりこんな感じです。
(順番を入れ替えても問題なく実装できます)

  1. 各ユーザーに権限を設定する
  2. 権限用データ作成
  3. Gate機能で権限を定義する
  4. ルーティングに権限情報を追加する
  5. (補足)5.ユーザーの権限によってViewの表示を変える

1.各ユーザーに権限を設定する

まずは各ユーザーに権限を設定する必要があるのでマイグレーションファイルを修正してDBを仕様を変えます。
ユーザーに権限を持たせる方法としては

  • Usersテーブル(テーブル名は任意)に直接権限情報を持たせる
  • 権限用のテーブルを作成してUsersテーブルに外部キーでリレーションさせる

の方法があると思いますが、僕は後者の方が今後管理しやすいので後者で説明します。

以下コマンドで権限用テーブルを作成します。

$ hp artisan make:migration create_permissions_table —-create=permissions

以下のように修正します。

database/migrations/****_**_**_******_create_permissions_table.php
<?php

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

class CreatePermissionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('permissions', function (Blueprint $table) {
            $table->increments('id')->comment('権限ID');
            $table->char('name', 10)->comment('権限名');
            $table->timestamps();
        });
    }

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

既存のUsersテーブルに権限用のカラムを追加するので以下コマンドを実行

$ php artisan make:migration add_permission_id_column_to_users_table —-table=users
database/migrations/****_**_**_******_add_permission_id_column_to_users_table.php
<?php

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

class AddPermissionIdColumnToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('permission_id')->unsigned()->comment('権限ID')->after('address3');
            $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropForeign('users_permission_id_foreign');
        });
    }
}

開発の始めから権限も盛り込んだ計画にしていれば、usersテーブル作成用のマイグレーションファイルに権限用のカラムを追記すればOK。

2.権限用データ作成

以下コマンドでシーダーファイルを作成。

$ php artisan db:seed --class=PermissionTableSeeder

シーダーファイルを修正。

database/seeds/PermissionTableSeeder.php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class PermissionTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('permissions')->insert([
            [
                // id = 1
                'name' => '最弱',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                // id = 2
                'name' => '普通',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                // id = 3
                'name' => '最強',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
        ]);
    }
}

これで権限の仕様としてはこんな感じになりました。

権限名 内容 権限ID
最強 全ての機能を使える 1
普通 一部機能を使える 2
最強 1つの機能しか使えない 3

あとはUsersテーブルに権限IDを持たせれば良いです。
というわけでUsersテーブル用のシーダーファイルにPermissionsテーブルの情報を持たせるので修正します。

database/seeds/UsersTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            [
                //略
                'permission_id' => 1,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                //略
                'permission_id' => 2,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                //略
                'permission_id' => 3,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
        ]);
    }
}

3.Gate機能で権限を定義する

Laravelで権限機能を実装する場合はGate機能を使います。

参考:ReaDouble:Laravel 6.x 認可

app/Providers/AuthServiceProvider.phpを修正します。

app/Providers/AuthServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    //略

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // 「最強」だけに適用
        Gate::define('saikyou_only', function ($user) {
            return ($user->permission_id == 1);
        });

        // 「最強」と「普通」に適用
        Gate::define('saikyou_and_futsuu', function ($user) {
            return ($user->permission_id <= 2);
        });

        // 「最強」と「普通」と「最弱」全てに適用
        Gate::define('all', function ($user) {
            return ($user->permission_id <= 3);
        });
    }
}

この記事では簡易的にsaikyou_onlyとかsaikyou_and_futsuuにしてますが実際に定義する時はちゃんとした英語の方が良いですw

4.ルーティングに権限情報を追加する

app/Providers/AuthServiceProvider.phpに定義した権限をルーティングに適用します。

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

//略

// 「最強」のユーザーしか使えない機能
Route::group(['middleware' => ['auth', 'can:saikyou_only']], function () {
    //処理
});

// 「最強」と「普通」のユーザーが使える機能
Route::group(['middleware' => ['auth', 'can:saikyou_and_futsuu']], function () {
    //処理
});

//全てのユーザーが使える機能
Route::group(['middleware' => ['auth', 'can:all']], function () {
    //処理
});

これで権限が与えられてないユーザーが該当のURLにアクセスすると403エラーになります。

(補足)5.ユーザーの権限によってViewの表示を変える

「ログインしているユーザーの権限によってこのボタンを非表示にしたい!」なんてこともあるかと。

そんな時はViewファイルをこんな風に書きます。

****.blade.php
@can('saikyou_only')
    <!-- (表示したい要素) -->            
@endcan

これで権限が「最強」(permission_id=1)のユーザーでログインした時のみ表示される要素にできます。

参考記事

さいごに

実装するまで「権限機能ってなんか複雑そうだな...」と思いましたが、(意外と)簡単に実装することができました。

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

【Laravel6】Gate機能を使って権限機能を簡単に実装する

はじめに

Laravel6で権限機能を実装したのでその方法についてまとめます。

権限機能と言えば

  • 「一般ユーザー」と「管理者」でアクセスできるページや操作可能な機能を制限する
  • 「管理者」の中でグレード(種別)を設定して管理画面上で色々と制限をかける権限機能

が主にあるかなと思いますが、この記事では後者を例にします。
(とはいえ、前者でも基本的な考え方は同じだと思います)

環境

Laravel 6.8

設定する権限

記事用に仮想的な権限を簡易的に設定します。

権限名 内容
最強 全ての機能を使える
普通 一部機能を使える
最弱 1つの機能しか使えない

こんな感じで、権限の強さによって使える機能を限定します。

権限機能の実装方法

手順はざっくりこんな感じです。
(順番を入れ替えても問題なく実装できます)

  1. 各ユーザーに権限を設定する
  2. 権限用データ作成
  3. Gate機能で権限を定義する
  4. ルーティングに権限情報を追加する
  5. (補足)ユーザーの権限によってViewの表示を変える

1.各ユーザーに権限を設定する

まずは各ユーザーに権限を設定する必要があるのでマイグレーションファイルを修正してDBを仕様を変えます。
ユーザーに権限を持たせる方法としては

  • Usersテーブル(テーブル名は任意)に直接権限情報を持たせる
  • 権限用のテーブルを作成してUsersテーブルに外部キーでリレーションさせる

の方法があると思いますが、僕は後者の方が今後管理しやすいので後者で説明します。

以下コマンドで権限用テーブルを作成します。

$ hp artisan make:migration create_permissions_table —-create=permissions

以下のように修正します。

database/migrations/****_**_**_******_create_permissions_table.php
<?php

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

class CreatePermissionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('permissions', function (Blueprint $table) {
            $table->increments('id')->comment('権限ID');
            $table->char('name', 10)->comment('権限名');
            $table->timestamps();
        });
    }

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

既存のUsersテーブルに権限用のカラムを追加するので以下コマンドを実行

$ php artisan make:migration add_permission_id_column_to_users_table —-table=users
database/migrations/****_**_**_******_add_permission_id_column_to_users_table.php
<?php

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

class AddPermissionIdColumnToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('permission_id')->unsigned()->comment('権限ID')->after('address3');
            $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropForeign('users_permission_id_foreign');
        });
    }
}

開発の始めから権限も盛り込んだ計画にしていれば、usersテーブル作成用のマイグレーションファイルに権限用のカラムを追記すればOK。

2.権限用データ作成

以下コマンドでシーダーファイルを作成。

$ php artisan db:seed --class=PermissionTableSeeder

シーダーファイルを修正。

database/seeds/PermissionTableSeeder.php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class PermissionTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('permissions')->insert([
            [
                // id = 1
                'name' => '最弱',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                // id = 2
                'name' => '普通',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                // id = 3
                'name' => '最強',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
        ]);
    }
}

これで権限の仕様としてはこんな感じになりました。

権限名 内容 権限ID
最強 全ての機能を使える 1
普通 一部機能を使える 2
最弱 1つの機能しか使えない 3

あとはUsersテーブルに権限IDを持たせれば良いです。
というわけでUsersテーブル用のシーダーファイルにPermissionsテーブルの情報を持たせるので修正します。

database/seeds/UsersTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            [
                //略
                'permission_id' => 1,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                //略
                'permission_id' => 2,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
            [
                //略
                'permission_id' => 3,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ],
        ]);
    }
}

3.Gate機能で権限を定義する

Laravelで権限機能を実装する場合はGate機能を使います。

参考:ReaDouble:Laravel 6.x 認可

app/Providers/AuthServiceProvider.phpを修正します。

app/Providers/AuthServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    //略

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // 「最強」だけに適用
        Gate::define('saikyou_only', function ($user) {
            return ($user->permission_id == 1);
        });

        // 「最強」と「普通」に適用
        Gate::define('saikyou_and_futsuu', function ($user) {
            return ($user->permission_id <= 2);
        });

        // 「最強」と「普通」と「最弱」全てに適用
        Gate::define('all', function ($user) {
            return ($user->permission_id <= 3);
        });
    }
}

この記事では簡易的にsaikyou_onlyとかsaikyou_and_futsuuにしてますが実際に定義する時はちゃんとした英語の方が良いですw

4.ルーティングに権限情報を追加する

app/Providers/AuthServiceProvider.phpに定義した権限をルーティングに適用します。

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

//略

// 「最強」のユーザーしか使えない機能
Route::group(['middleware' => ['auth', 'can:saikyou_only']], function () {
    //処理
});

// 「最強」と「普通」のユーザーが使える機能
Route::group(['middleware' => ['auth', 'can:saikyou_and_futsuu']], function () {
    //処理
});

//全てのユーザーが使える機能
Route::group(['middleware' => ['auth', 'can:all']], function () {
    //処理
});

これで権限が与えられてないユーザーが該当のURLにアクセスすると403エラーになります。

(補足)5.ユーザーの権限によってViewの表示を変える

「ログインしているユーザーの権限によってこのボタンを非表示にしたい!」なんてこともあるかと。

そんな時はViewファイルをこんな風に書きます。

****.blade.php
@can('saikyou_only')
    <!-- (表示したい要素) -->            
@endcan

これで権限が「最強」(permission_id=1)のユーザーでログインした時のみ表示される要素にできます。

参考記事

さいごに

実装するまで「権限機能ってなんか複雑そうだな...」と思いましたが、(意外と)簡単に実装することができました。

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

[Laravel7.x]ドキュメントを読みながらVuetifyを導入する

今回の題

VuetifyをLaravelに導入する手順を書き残します。
ドキュメントを読みながら出来るだけ忠実に導入していきます。

環境

  • Laravel 7.
  • Vue.js 2.5.17
  • Vuetify 2.4.3

手順

1 Laravelのインストール

readouble - Laravel 7.x インストールの通りにインストールします。

  • インストーラを使うのであれば
$ composer global require laravel/installer
$ laravel new プロジェクト名
  • Composerのcreate-projectコマンドを使うなら、
$ composer create-project --prefer-dist laravel/laravel プロジェクト名

その後、プロジェクトに移動して以下を実行。

$ php artisan serve

http://localhost:8000にアクセスしてLaravelの初期画面が確認できればOK。

2 Vue.jsのインストール

readouble - Laravel 7.x JavaScriptとCSSスカフォールド
を読みながらインストールします。

まずは、laravel/uiのインストール。

$ composer require laravel/ui

laravel/uiはLaravel6から用いられる、Bootstrap、React、Vueなどのフロントエンドパッケージをインストールする為のライブラリ。

laravel/uiのインストールを終えたら、以下でVue.jsのスカフォールドを作成。

$ php artisan ui vue

package.jsonはこんな感じ。Vue.jsの項目もちゃんとできてます。

package.json
"devDependencies": {
    "axios": "^0.19",
    "cross-env": "^7.0",
    "laravel-mix": "^5.0.1",
    "lodash": "^4.17.19",
    "resolve-url-loader": "^2.3.1",
    "sass": "^1.20.1",
    "sass-loader": "^8.0.0",
    "vue": "^2.5.17",
    "vue-template-compiler": "^2.6.10"
}

bootstrapは使用しないので消し、依存関係にあるjquery、poper.jsも消しました。

npm installを実行。

$ npm install

app.jsを編集。
bootstrapを読み込んでいる以下の箇所を消します。

resource/js/app.js
- require('./bootstrap');

Vue.jsはこれでOK。
試しにLaravelが用意してくれているコンポーネントを読み込んでみます。
resources/views/welcome.blade.phpを以下のように変更します。

resources/views/welcome.blade.php
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
  <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <meta name="csrf-token" content="{{ csrf_token() }}">
      <link rel="stylesheet" href="{{ mix('css/app.css') }}">
      <title>{{ config('app.name', 'Laravel') }}</title>
  </head>
  <body>
    <div id="app">
      <v-app>
        <example-component></example-component>
      </v-app>
    </div>
    <!-- js読み込み -->
    <script src="{{ mix('js/app.js') }}"></script>
  </body>
</html>

以下でコンパイルを行い、

$ npm run hot

http://localhost:8000にアクセスして確認。
bootstrapを消したのでスタイルが当てられていないと思いますが、後にVuetifyを導入してスタイルを当てるので問題ありません。

3 Vuetifyのインストール

Vuetifyの公式ドキュメントを読みながらインストールします。
vuetifyjs - Webpackでのインストール

Vuetifyのインストール。

$ npm install vuetify --save-dev

Vuetifyのドキュメントではsassとsass-loaderもインストールしていますが、Laravelにはデフォルトでpackage.jsonにそれらの記述があります。
なので、Vue.jsをインストールした時のnpm installで既にインストールされていますのでここで行う必要はありません。

ドキュメント通り、pluginsディレクトリを作成し、そこにvuetify.jsを作成します。

$ mkdir resources/js/plugins
$ cd resources/js/plugins
$ touch vuetify.js

vuetify.jsを以下のように編集。

resources/js/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'

Vue.use(Vuetify)

const opts = {}

export default new Vuetify(opts)

app.jsで読み込みます。

resource/js/app.js
import Vue from 'vue';
import Vuetify from './plugins/vuetify' // vuetify.jsを読み込み

import ExampleComponent from './components/ExampleComponent.vue'

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

これでVuetifyの導入は完了です。

4 マテリアルデザインのインストールとフォントの適応

一度ここまでの設定で、ドキュメントに掲載されているCardsの例を表示してみます。
↓こういうの。
スクリーンショット 2021-01-25 16.37.28.png

では、components/ExampleComponent.vueを以下のように編集(コピペ)。

resource/js/components/ExampleComponent.vue
<template>
  <v-card
    :loading="loading"
    class="mx-auto my-12"
    max-width="374"
  >
    <template slot="progress">
      <v-progress-linear
        color="deep-purple"
        height="10"
        indeterminate
      ></v-progress-linear>
    </template>

    <v-img
      height="250"
      src="https://cdn.vuetifyjs.com/images/cards/cooking.png"
    ></v-img>

    <v-card-title>Cafe Badilico</v-card-title>

    <v-card-text>
      <v-row
        align="center"
        class="mx-0"
      >
        <v-rating
          :value="4.5"
          color="amber"
          dense
          half-increments
          readonly
          size="14"
        ></v-rating>

        <div class="grey--text ml-4">
          4.5 (413)
        </div>
      </v-row>

      <div class="my-4 subtitle-1">
        $ • Italian, Cafe
      </div>

      <div>Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio seating.</div>
    </v-card-text>

    <v-divider class="mx-4"></v-divider>

    <v-card-title>Tonight's availability</v-card-title>

    <v-card-text>
      <v-chip-group
        v-model="selection"
        active-class="deep-purple accent-4 white--text"
        column
      >
        <v-chip>5:30PM</v-chip>

        <v-chip>7:30PM</v-chip>

        <v-chip>8:00PM</v-chip>

        <v-chip>9:00PM</v-chip>
      </v-chip-group>
    </v-card-text>

    <v-card-actions>
      <v-btn
        color="deep-purple lighten-2"
        text
        @click="reserve"
      >
        Reserve
      </v-btn>
    </v-card-actions>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      loading: false,
      selection: 1,
    }),
    methods: {
      reserve () {
        this.loading = true
        setTimeout(() => (this.loading = false), 2000)
      },
    },
  }
</script>

これをブラウザで表示してみると……
スクリーンショット 2021-01-25 19.18.22.png

赤枠で囲んだ部分に表示されるはずの星マーク(v-rating)が表示されていません。
理由はマテリアルデザインアイコンをインストールしていないから。
ドキュメントによると、

Vuetify uses Google’s Roboto font and Material Design Icons.

だそうなので、マテリアルデザインアイコンのインストールとグーグルフォントの適応を行います。

方法は二つ。

方法その1 - CDNを使う

最もシンプルな方法としてドキュメントで紹介されている方法です。
以下の2つをhead内に付け足すだけ。

<!-- グーグルフォント -->
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<!-- マテリアルデザインアイコン -->
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">

方法その2 - インストールする

まずはマテリアルデザインアイコンのインストール。

npm install --save  @mdi/font

plugins/vuetify.jsの編集。

resources/js/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import '@mdi/font/css/materialdesignicons.css' // 追記

Vue.use(Vuetify)

// opt内にiconfontを追記して適応させる
const opts = {
  icons: {
    iconfont: 'mdi', 
  },
}

export default new Vuetify(opts)

続いて、グーグルフォントを適応させる。

resources/sass/app.scss
// Fonts
// @import url('https://fonts.googleapis.com/css?family=Nunito');
// ↓ 以下に変更
@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900');

// Variables
@import 'variables';

これでOK。
コンパイルして再度ブラウザで確認してみると……。

スクリーンショット 2021-01-25 19.51.15.png

完璧?
わかりづらいですがフォントもしっかりと適応されています。

以上!!!

参考

Vuetify - ドキュメント
readouble - Laravel 7.x

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

【Laravel】ヘルパ関数の作り方と使い方。

Laravelでヘルパ関数を自分で定義し使う方法について。

viewに渡すデータをPHPで前処理する場合に、特定のページのみで使うデータであれば対象のコントローラ内で定義すればいい。

もし、そのPHP処理を他の場所でも使い回す場合は、自分でヘルパ関数を作成しておくと便利。(割と簡単に作れます)


1. 自作のヘルパ関数を記述するファイルを作成

app > helpers.php

image.png

composerでファイルパスを指定して読み込むため、場所はどこでもいい。

2. 関数を定義する

<?php

if (! function_exists('関数名')) {
    /**
     * 関数の説明文
     *
     * @param  string  $xxx
     */
    function 関数名(引数)
    {
        処理
    }
}

function_exists ( string $function_name )
組み込み関数に引数で指定した関数が存在すればtreuを返す。

!をつけることで、デフォルトの関数と被らない場合のみ関数を作成できるようにしている。


3. composerのautoloadに追記する

ルートディレクトリにあるcomposer.jsonautoloadに以下を追記する。

composer.json
    "autoload": {
        "files": [
            "app/helpers.php"
        ],
    }


4. autoloadの更新

$ composer dump-autoload

以上でLaravelのどこからでも呼び出せるようになる。


実例

1. 関数を定義する

<?php

if (! function_exists('greeting')) {
    /**
     * s3のurlをcloudfrontのurlに変換する
     *
     * @param  string  $name
     * @return string
     */
    function greeting(string $name)
    {
        return "hello ".$name."!!!";
    }
}

2. autoloadをリロードする

$ composer dump-autoload

Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
Generated optimized autoload files containing 4624 classes


3. コントローラ経由でビューに渡す

web.php(ルーティング)
Route::get('helper', 'HelperController@index');
HelperController.php(コントローラ)
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelperController extends Controller
{
    public function index(){
        $name = greeting("xxx");
        return view('helper', ['name'=>$name]);
    }
}
helper.blade.php(ビュー)
<div>{{ $name }}</div>

▼ブラウザの表示

image.png

自作のヘルパ関数を使ってビューにデータを表示することができた。


tinker(対話モード)でチェック

tinkerを使って自作のヘルパ関数をチェックすることもできる。

$ php artisan tinker
Psy Shell v0.10.6 (PHP 7.3.11 — cli) by Justin Hileman
>>> greeting('XXX')
=> "hello XXX!!!"

自作したヘルパ関数を呼び出すことができた。



以上。

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

Dockerで構築したLaravelにgRPCを導入してfirestoreにデータを書き込む方法

LaravelをAPIサーバーとして使い、Laravelからwebsocketなどではなく、firestoreでチャットやプッシュ通知などリアルタイム通信させたいときがあります。
公式( https://firebase.google.com/docs/firestore/quickstart?hl=ja )によれば、
PHPの場合、gRPCのインストールが必要です。
dockerでのインストールの情報が皆無なので書いておきます。

gRPCのインストール

dockerfileの修正(docker-compose で制御する場合の参考)

docker/php/Dockerfile
FROM php:7-fpm

ENV DEBIAN_FRONTEND noninteractive

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get -y install git autoconf zlib1g-dev libicu-dev libonig-dev libzip-dev unzip &&\
    apt-get clean && \
    pecl install grpc && \
    pecl install protobuf && \
    rm -rf /var/lib/apt/lists/* &&\
    docker-php-ext-install pdo_mysql &&\
    composer config -g process-timeout 3600 && \
    mkdir -p /app
COPY ./docker/php/php.ini /usr/local/etc/php/php.ini

WORKDIR /app

この中で、gRPCのインストールに関係する部分は、

docker/php/Dockerfile
autoconf zlib1g-dev

でpeclをインストールし、

docker/php/Dockerfile
pecl install grpc && \
pecl install protobuf && \

でgrpc本体をインストールという流れです。
protobufはgrpcのパフォーマンスを向上させるライブラリのようです。

php.iniの修正

dockerfile書くときにphp.iniも書いていると思いますので、
そこに以下を追記

docker/php/php.ini
extension=grpc.so
extension=protobuf.so

そして、composer.jsonファイルの
"require"に

composer.json
"grpc/grpc": "^1.27.0",

を追記してください。
バージョンは公式(https://cloud.google.com/php/grpc?hl=ja)で最新版を確認してください。

そのうえで、再ビルドすればgRPCの導入完了です。
(ただし、gRPCのインストールはめちゃくちゃ時間がかかります。。。)

laravelからfirestoreへのsetができるようにする

ライブラリの導入

コマンドでライブラリのインストール

command
composer require google/cloud-firestore
composer require kreait/laravel-firebase

config/app.phpに追記

config/app.php
return [
    // ...
    'providers' => [
        // ... これを追記
        Kreait\Laravel\Firebase\ServiceProvider::class
    ]
    // ...
];

次に、firebaseのコンソールで、歯車 > サービスアカウント > FIrebase Admin SDK > 新しい秘密鍵の生成
をクリックしてください。

2021-01-25_14h31_21.png

ダウンロードされたファイル名を
firebase_credentials.json
に変えて、プロジェクト直下に設置し、
秘匿情報なので、
gitignoreに
firebase_credentials.json
を追記しておきましょう。

もし本番環境がECS fargateの場合、本番環境ではsshログインできませんので、codebuildのときにs3からコピーするように処理を追記しておきましょう

.env
FIREBASE_CREDENTIALS=firebase_credentials.json

そして、コマンドでconfigファイルを作っておきましょう。

command
php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config

firestoreへのデータのセット等はいろんな箇所で使うことが多いと思うので、サービス化しておきましょう。

以下、サービス化(知っている方、必要ない方は読み飛ばしてください。)

app/Providers/FirebaseApiServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Foundation\Application;
use App\Services\FirestoreApiService;

class FirestoreApiServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('FirestoreApi', function(Application $app){
            return new FirestoreApiService();
        });
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}


app/Facades/FirestoreApiFacade.php
<?php

namespace App\Facades;
use Illuminate\Support\Facades\Facade;

class FirestoreApiFacade extends Facade
{
  protected static function getFacadeAccessor()
  {
    return 'FirestoreApi';
  }
}

config/app.php
// providersに追記
        App\Providers\FirestoreApiServiceProvider::class,

// aliasesに追記
        'FirestoreApi' => App\Facades\FirestoreApiFacade::class,

これでサービス化は完了です。

そしていよいよfirestoreへのデータset,update,deleteです。

app/Services/FirestoreApiService.php
<?php
namespace App\Services;

use Google\Cloud\Firestore\FieldValue;

class FirestoreApiService
{
    protected $db;

    public function __construct()
    {
        $this->db = app('firebase.firestore')->database();
    }

    /**
     * firestoreへのデータset
     * @param sender_id int
     * @param room_id int
     * @param comment string
     * @return void
     */
    public function storeComment($sender_id, $room_id, $comment_id ,$comment)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->set([
            'comment' => $comment,
            'sender_id' => $sender_id,
            'created_at' => FieldValue::serverTimestamp(),
            'updated_at' => FieldValue::serverTimestamp(),
        ]);
    }

    /**
     * firestoreのデータをupdate
     * @param sender_id int
     * @param room_id int
     * @param comment_id int
     * @param comment string
     * @return void
     */
    public function updateComment($sender_id, $room_id, $comment_id, $comment)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->update([
            [ 'path' => 'comment', 'value' => $comment],
            ['path' => 'updated_at', 'value' => FieldValue::serverTimestamp()]
        ]);
    }

    /**
     * firestoreのデータをdelete
     * @param room_id int
     * @param comment_id int
     * @return void
     */
    public function deleteComment($room_id, $comment_id)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->delete();
    }

}

これでuseなどを使わなくても、どこでも

\FirestoreApi::storeComment($sender_id, $room_id, $comment);

で呼び出して、Laravelからfirestoreにsetしたりできます。

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

【Laravel】Dockerコンテナ内でのユーザー認証導入

初めまして!
スクールではRuby on Railsを学習。
就活の関係でPHPの学習始めたばかりです。
初めて1からLaravelで作成するので記録を残します。

DockerでLaravelの開発環境を作成

これに関してはこちらの記事を参考にというかもうそのまま使わせて頂きました!
大変わかりやすかったです。
ありがとうございました!

ユーザー認証導入

ユーザー登録やログイン機能の実装です。
Railsだとdeviseで簡単にできるイメージでしたが結構つまりました。
Dockerというのが理解が追いついていないので難しかったです。

目標物
Laravelの標準のユーザー認証機能の導入
スクリーンショット 2021-01-25 13.56.50.png

まずコンテナ内に入ります

ターミナル
% docker-compose exec app bash

コンテナ内でユーザー認証導入のコマンドを入力します

コンテナ
composer require laravel/ui

ここで下記のエラーが発生しました。

Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes)

調べると「メモリ不足なのでインストールできないよ」とのこと。
なので

コンテナ
COMPOSER_MEMORY_LIMIT=-1 composer require laravel/ui

上記のコマンドは一時的に容量を無制限にするコマンドのようです。
こちらの記事を参考にさせていただきました!ありがとうございます。

vue.jsを元にしたテンプレートの認証機能を追加します

コンテナ
php artisan ui vue --auth

下記のように出れば成功です。

Vue scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
Authentication scaffolding generated successfully.

これでvue.jsを書く初期状態が完成。
app/Http/Controllers/Authにはバックエンドでの認証機能関係のファイルが追加されます。
resources/views/authにはフロントエンド関係のviewファイルが追加されます。

ここまでで一旦は認証機能の導入が終わりましたが、まだ表示が崩れたままです。

画面を整えるためNode.jsのパッケージ管理ツールをインストールします。

コンテナ
npm install && npm run dev

そうすると

npm: command not found

とエラー。
Node.jsのインストールしてなかったからみたいです(それはそうですね)
公式からインストールし再実行するも変わらず。
インストールの確認のためコンテナから出て
which nodeと入力すると/usr/local/bin/nodeと出力され
npm -vと入力すると6.14.10と出力されるがコンテナ内で行うと出てこないのでコンテナ内にNodeが存在しないことが原因と分かりました。

コンテナ内に反映させるため,php/DockerfileのWORKDIRの前に以下を追加します。

COPY --from=node:12.20 /usr/local/bin /usr/local/bin
COPY --from=node:12.20 /usr/local/lib /usr/local/lib

コンテナから抜けてビルドし直します。

docker-compose up -d --build

このあと再度コンテナ内に入りnpm install && npm run devで無事完了。
public/jspublic/cssに新しいファイルが生成されます。

リアルタイムでコンパイルさせる

phpのコンテナ内で

npm run watch-poll

を実行することでコードを書きながらでもリアルタイムでjsがコンパイルされ画面が反映されます。

完成

以上です!
ご指摘などございましたらよろしくお願い致します!

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

【PHP初心者】Laravelを2週間触ってみて理解したこと③【blade】【継承】

こんにちは、nakaです?
最近在宅が増えて早起きができなくなってしまいました・・・
通勤がないとぎりぎりまで寝れてしまう&お布団があったかい誘惑に負けてしまいそうになります。こういう場合私は朝に楽しみをもってきて早く起きる作戦を実行します。今日の夜はホームベーカーでも仕込もうかな。

さて前回はLaravelのルーティングについてお話しました。
前回の記事:【PHP初心者】Laravelを2週間触ってみて理解したこと②【ルーティング】

今回はLaravelのテンプレートエンジンである、bladeについてまとめようと思います!

bladeとは

初回の記事でも触れましたが、bladeはLaravelのビューの部分です。
・ビューに直接PHPが記述できる
・ファイル名は「~.blade.php」という拡張子
という特徴があります。

まず環境を確認

まずは環境を確認しましょう。
今使っているLaravelのバージョンを確認してください。
(バージョンでbladeの書き方が違ってくる可能性があるので確認が必要)

Laravelのバージョン確認コマンド

php artisan -v

今回はLaravel5.5の環境で動かしてみます。
もし違うバージョンの場合は動作確認してくださいね。

さてここからは公式ドキュメントにそってbladeについてまとめます。

テンプレートの継承

まずbladeのテンプレートの継承について。
ビューに親子関係があり、親のレイアウトを子が継承するかたちになります。
親を「resources/views/layouts/app.blade.php」
子を「resources/views/child.blade.php」とします。
※共通レイアウトを親にして、各ページ独自にもつものを子に書きます。

その前に、ディレクティブについて。ディレクティブとはblade内で使える命令のことで、@if @foreachなど様々なものがあります。
今回テンテンプレートの継承で使うでは以下の3つです。

@extends
@section
@yield

実際に親子のビューを見ていきましょう。
➀まず親のビューを作成し、子に継承する
➁子独自のパーツを作成
➂親側で呼び出す
親ビューをベースにして独自の子ビューを組み込みページを表示することができます!

child.blade.php
// ➀親を継承
@extends('layouts.app') 

// ➁子固有の箇所を@section ~ @endsectionで囲み、名前をつける(今回はcontent)
@section('content')
    <p>ここが本文のコンテンツ</p>
@endsection
app.blade.php
<html>
    <head>
        <title>サンプル</title>
    </head>
    <body>
        <div class="container">
            // ➂ 子で指定した「content」を呼び出す
            @yield('content')
        </div>
    </body>
</html>
結果
<html>
    <head>
        <title>サンプル</title>
    </head>
    <body>
        <div class="container">
            // 結果
            <p>ここが本文のコンテンツ</p>
        </div>
    </body>
</html>

ちょっと短いですが区切りが悪いので次回にします。
blade続けます、次回はbladeの「コンポーネントとスロット」

参考記事

Laravel 5.5 Bladeテンプレート
【Laravel入門】ビューとBladeと継承
【Laravel】Viewのbladeの書き方

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

【Laravel】バリデーションのrequiredとビューの必須マークを連動させたい

概要

こういう必須マーク、よく付け忘れや消し忘れが目立つ。
test.png
なのでバリデーションルールのrequiredに沿ってviewに表示されるような仕組みを考えてみました。

考え方

  • コントローラーでバリデーション配列を呼び出し、ビューにセットする
  • ビューの入力フォームで入力フォームの名前をセットし、入力フォームの該当のバリデーション配列内にrequiredがあれば必須マークを付ける

実装例

  • コントローラー
  • バリデーション
  • ビュー(入力フォーム部分)
  • ビュー(必須マーク部分)

を記載します。
やりたいことが何となく伝わればよいかなということで、バリデーションルールとかビューの入力項目1個1個の中身は適当です。

コントローラー

サンプルコード
UserController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\UserCreateRequest;

class UserController extends Controller
{
    public function create()
    {
        $validation = new UserCreateRequest();
        return view('user.create', [
            'rules' => $validation->rules(),
        ]);
    }
}

バリデーション

サンプルコード
UserCreateRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserCreateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:40',
            'name_kana' => 'required|max:40|regex:/^[ァ-ヶー]+$/u',
            'post_code' => 'nullable|numeric|digits:7',
            'pref_code' => 'nullable',
            'address1' => 'nullable|max:80',
            'address2' => 'nullable|max:80',
            'email' => 'required|email|confirmed|max:255',
            'login_id' => 'required|min:8|max:32|unique:users,login_id',
            'password' => 'required|confirmed|min:8|max:32',
        ];
    }
}

ビュー(入力フォーム部分)

サンプルコード
create.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>ユーザー登録</title>
    <meta name="robots" content="noindex">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.1/css/bootstrap.min.css"
          integrity="sha384-VCmXjywReHh4PwowAiWNagnWcLhlEJLA5buUprzK8rxFgeH0kww/aWY76TfkUoSX" crossorigin="anonymous">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
            integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
            integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
            crossorigin="anonymous"></script>
</head>
<body class="bg-light environment_image">
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
    <a class="navbar-brand" href="/">ユーザー管理画面</a>
</nav>
<div class="container mb-5">
    <div class="card">
        <h5 class="card-header">ユーザー登録</h5>
        <div class="card-body">
            <div class="mb-3 collapse show">
                @if (!empty(session('messages')))
                    <div class="alert alert-success" role="alert">
                        @foreach (session('messages') as $message)
                            {{ $message }}<br>
                        @endforeach
                    </div>
                @endif
                <form action="" method="POST" id="input_form">
                    @csrf

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="last_name">名前
                                @include('common.required',['rules' => $rules['name'] ?? ''])
                            </label>
                            <input type="text" name="name" id="name" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="name_kana">名前(フリガナ)
                                @include('common.required',['rules' => $rules['name_kana'] ?? ''])
                            </label>
                            <input type="text" name="name_kana" id="name_kana" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="post_code">郵便番号
                                @include('common.required',['rules' => $rules['post_code'] ?? ''])
                            </label>
                            <input type="text" name="post_code" id="post_code"
                                   class="form-control"
                                   placeholder="9999999"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="pref_code">都道府県
                                @include('common.required',['rules' => $rules['pref_code'] ?? ''])
                            </label>
                            <select id="pref_code" class="form-control" name="pref_code">
                                <option value="">選択してください</option>
                                <option value="13">東京都</option>
                            </select>
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address1">市区町村
                                @include('common.required',['rules' => $rules['address1'] ?? ''])
                            </label>
                            <input type="text" name="address1" id="address1"
                                   class="form-control" placeholder="〇〇市××町"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address2">番地、マンション名、部屋番号
                                @include('common.required',['rules' => $rules['address2'] ?? ''])
                            </label>
                            <input type="text" name="address2" id="address2"
                                   class="form-control" placeholder="1-2-3"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email">メールアドレス
                                @include('common.required',['rules' => $rules['email'] ?? ''])
                            </label>
                            <input type="text" name="email" id="email"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email_confirmation">メールアドレス(確認)
                                @include('common.required',['rules' => $rules['email_confirmation'] ?? ''])
                            </label>
                            <input type="text" name="email_confirmation" id="email_confirmation"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="login_id">ログインID
                                @include('common.required',['rules' => $rules['login_id'] ?? ''])
                            </label>
                            <input type="text" name="login_id" id="login_id"
                                   class="form-control"
                                   placeholder="英数字で入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password">パスワード
                                @include('common.required',['rules' => $rules['password'] ?? ''])
                            </label>
                            <input type="password" name="password" id="password"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password_confirmation">パスワード(確認)
                                @include('common.required',['rules' => $rules['password_confirmation'] ?? ''])
                            </label>
                            <input type="password" name="password_confirmation" id="password_confirmation"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <button type="button" class="btn btn-primary m-2 disabled_button">登録</button>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

ビュー(必須マーク部分)

サンプルコード
required.blade.php
@php
    // バリデーションルールが配列の形では無い場合、配列にする
    if(isset($rules) && is_array($rules) === false) {
        $rules = explode('|', $rules);
    }

    // 必須表示フラグを初期化
    $is_required = false;

    // バリデーションルール配列の中にrequiredがあれば必須マークを使用する
    if(isset($rules) && is_array($rules) && in_array('required', $rules, true)) {
        $is_required = true;
    }
@endphp

@if ($is_required)
    <span class="badge badge-danger">必須</span>
@endif

あとがき

includeが多くなったりin_arrayを使用してたりするので、フロント側の表示など速度を求められる部分には不向きかなと思います。
入力項目が多くなりがちな管理画面とかには良いかもです。

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

【Laravel】シーダーの複数あるテストデータをforeachでスマートに書く

はじめに

Laravelでシーディングのテストデータが複数ある場合、コードが冗長になってしまいます。
今回はforeachを使ってスマートにまとめる書き方をお伝えします。

今回の例ではCarbonを使っておりますが、Carbonについて詳しくは以下の記事が参考になります。
PHPで日付時刻処理を書くならCarbonを使うべき - Qiita

対象

LaravelでWebアプリを作っている初学者向けの内容となっております。
本記事はLaravel青本をベースに書いております。
PHPフレームワークLaravel入門 第2版 | 津耶乃, 掌田 |本 | 通販 | Amazon

環境

Laravel:6系
PHP:7.4.13


それではまず通常のシーダーファイルの書き方の一例です。

PostsTableSeeder.php
class PostsTableSeeder extends Seeder
{
    public function run()
    {
        $param = [
            'name' => '竈門 炭治郎',
            'body' => '水の呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('sizes')->insert($param);

        $param = [
            'name' => '我妻 善逸',
            'body' => '雷の呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('sizes')->insert($param);

        $param = [
            'name' => '嘴平 伊之助',
            'body' => 'ケダモノの呼吸',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now()
        ];
        DB::table('posts')->insert($param);        
    }
}

created_atupdated_atが同じなのに何度も書いているのはあまりスマートではありません。
foreachを使って以下のように書くことができます。

PostsTableSeeder.php
class PostsTableSeeder extends Seeder
{
    public function run()
    {
        $params = 
        [
            [
                'name' => '竈門 炭治郎',
                'body' => '水の呼吸'
            ],
            [
                'name' => '我妻 善逸',
                'body' => '雷の呼吸'
            ],
            [
                'name' => '嘴平 伊之助',
                'body' => 'ケダモノの呼吸'
            ]
        ];

        $now = Carbon::now();
        foreach ($params as $param) {
            $param['created_at'] = $now;
            $param['updated_at'] = $now;
            DB::table('posts')->insert($param);
        }
    }
}

内容が変わるテストデータ(今回は'name''body')を$paramsの配列に入れます。
そして共通項目('created_at''updated_at')をforeachの中で書きます。

こうすることで無駄な記述を減らすことができ、見事スマートなシーダーファイルの完成です!

いかがでしたでしょうか?
今回はテストデータが少ないのであまり変わりないですが、共通する項目が多いほどすっきりと可読性の高いコードになります。

是非お試しいただければと思います。

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

CentOS7 laravelをインストールしてアプリを作成する

目的

  • CentOS7にlaravelをインストールしてアプリを作成する方法をまとめる

条件

情報

  • 各ソフトバージョンを下記に記載する。

    項目 情報
    MySQL 8.0.23
    PHP 7.4.14
    Composer 2.0.8

方法

  1. 下記コマンドを実行してlaravelをインストールする。

    $ composer global require laravel/installer
    
  2. 下記コマンドを実行してtestという名前のlaravelアプリ(laravelプロジェクト)を作成する。

    $ cd
    $ laravel new test
    
  3. 下記コマンドを実行して作成されたアプリ名ディレクトリに移動する。

    $ cd test
    
  4. 下記コマンドを実行してローカルサーバを起動する。

    $ php artisan serve
    
  5. 下記にアクセスする。

  6. 下記画面が表示されていたらアプリの作成は完了である。(下記はMacで環境構築を行った際のhttp://127.0.0.1:8000の表示であり細部が異なる可能性がある。あくまでイメージとして確認していただきたい。)

    ウィンドウ_と_Laravel.png

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

composerを用いたLaravelの環境構築とチュートリアル

はじめに

本記事はcomposerを用いてLaravelの環境を構築する方法を紹介する。データベースはPostgreSQLを採用している。
また、チュートリアルとしてLaravel5.7入門: 初心者でも簡単! ブラウザだけでLaravelを使ったWeb開発!の内容を利用する。なおこの講座はこの記事を復唱したものだったので、これを読めば十分と感じた。Laravelをとりあえず学びたい方や、環境構築に興味ない方、古いLaravelを使いたい方はこの記事ではなく直接講義を受けることをお勧めする。

環境構築

以下の環境で行った。
MacOS BigSur 11.1
php 8.0.1
composer 2.0.8
Laravel 8.20.0

Composerのインストール

ComposerはPHPのライブラリー管理ツールである。インストールは公式サイトから行うことができる。また、Macであればbrew install composerでインストールができる。インストールが完了したら以下のコマンドで正常にインストールされていることを確認する。

% composer -v

以下のように表示されれば正常にインストールされている。

   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/

Composer version X.X.X XXXX-XX-XX XX:XX:XX
.
.
.

Laravelプロジェクトの作成

Laravelを用いたプロジェクトは以下のコマンドで作成することができる。

% composer create-project "laravel/laravel" myapp

myappは作成されるプロジェクト名(ディレクトリ名)。これによって作成されたプロジェクトで使うLaravelのバージョンを確認したい場合は以下のコマンドを入力する(出力されるバージョンは環境によって異なる)。

% cd myapp
myapp % php artisan -V
Laravel Framework 8.24.0

以下のコマンドであれば好きなバージョンで作成できる。

% composer create-project "laravel/laravel=8.20.0" myapp

ここまで出来たところで一度プロジェクトの実行を行なって初期画面を見てみる。プロジェクトの実行は以下のコマンドを入力する。

myapp % php artisan serve

入力後表示されたURLにアクセスし、以下のようなページが現れたら成功。

データベースの作成

講義ではMYSQLを使っているが、PostgreSQLを用いた。MYSQLで行いたい方は講義の記事を参照して頂きたい。
PostgreSQLはこのサイトからインストールできる。Macであればbrew install postgresqlでも可能。インストールできたら、以下のコマンドを打ちインストールされていることを確認する。

% postgres -V
postgres (PostgreSQL) 13.1

インストールできたら便利のために環境変数に以下を追加する。

export PGDATA=/usr/local/var/postgres

手元の環境によって環境変数の追加方法は異なると考えられるが、zshの場合以下のコマンドで行う。

% echo 'export PGDATA=/usr/local/var/postgres' >> ~/.zshrc
% source ~/.zshrc

追加後以下のコマンドでバックエンドでサーバーを動かすことができる(データベースを使用する間は常にサーバーを動かさなければいけないため)。

% pg_ctl start

また、以下のコマンドで止めることができる。

% pg_ctl stop

次にユーザーを作成する。サーバーを動かした状態で以下のコマンドを入力する。

% createuser -P username
Enter password for new role: passward
Enter it again: passward

usernameとpasswardは任意に設定する。
ユーザーを作成したところでついにデータベースを作成する。データベースは以下のコマンドで作成することができる。

createdb dbname −0username

dbnameは任意で、-Oは新しいデータベースの所有者となるデータベースユーザを指定することを意味している。

データベースの設定

このプロジェクトは.envファイルとconfig/database.phpによってデータベースの設定を行なっている。以下のように二つのファイルを変更する。

.env
DB_CONNECTION=pgsql
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=dbname
DB_USERNAME=username
DB_PASSWORD=passward
config/database.php
'default' => env('DB_CONNECTION', 'pgsql'),

チュートリアル

チュートリアルではこれをもとにタスク管理アプリを作成する。

データベースの利用

データベースのテーブル、モデル、コントローラーを作成する方法について説明する。まず、モデルとはLaravelにおいてデータベースのテーブルなどをオブジェクトのように扱えるようにしてくれるものである(ORM)。また、コントローラーはモデルから受け取ったオブジェクトをもとにビューを作成するものである。これらを作成するには以下のコマンドを入力する。

% php artisan make:model Task -m -c -r

-mはマイグレーションファイル、-cはコントローラ、"-r"でリソース操作に関連したメソッドを作成する。マイグレーションファイルは/myapp/database/migrationsフォルダに作成され、モデルは/myapp/app/Task.app、コントローラーは/myapp/app/Http/Controllers/TaskController.phpのように作成される。 マイグレーションファイルを変更してタスク名を表すnameと言うカラムを追加する。マイグレーションファイルは以下のように変更し、

    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

以下のコマンドで実行する。

% php artisan migrate

実際にテーブルが作成され、タスク名が追加されていることは以下のコマンドを実行することで確かめられる。

% myapp psql -U username dbname
psql (13.1)
Type "help" for help.

mydb=> \d tasks
                                          Table "public.tasks"
   Column   |              Type              | Collation | Nullable |              Default              
------------+--------------------------------+-----------+----------+-----------------------------------
 id         | bigint                         |           | not null | nextval('tasks_id_seq'::regclass)
 name       | character varying(255)         |           | not null | 
 created_at | timestamp(0) without time zone |           |          | 
 updated_at | timestamp(0) without time zone |           |          | 

% myapp % psql -U username dbnameでdbnameの操作が可能となり、\d tasksでtasksテーブルの中身を確認することができる。

コントローラーの設定

コントローラーは前述の通りmyapp/app/Http/Controllers/TaskController.phpに書かれている。内容がない様々なメソッドがあるが、今回はindexstoredestroyメソッド以外は使わないので消す。indexはタスクの一覧を表示させるメソッドである。storeはタスクの追加を行うメソッドである。deleteはタスクを削除するメソッドである。それぞれ以下のように実装する。

TaskController.php
class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::all();
        return view('tasks', ['tasks' => $tasks]);
    }

    public function store(Request $request)
    {
        $task = new Task;
        $task->name = request('name');
        $task->save();
        return redirect('/tasks');
    }

    public function destroy(Request $request, $id, Task $task)
    {
        $task = Task::find($id);
        $task->delete();
        return redirect('/tasks');
    }
}

1.indexTask::all()によってTaskモデルのallメソッドを呼び出すことでテーブル内の全ての内容を取り出し、return view('tasks', ['tasks' => $tasks]);によって/myapp/resources/views/tasks.blade.php(最後に作成)をテンプレートとして全タスクを表示させている。
2.store$tasks=new Task;によって新たなTaskオブジェクトを作成し、$task->name = request('name');で受け取った名前にタスク名を変更し、$task->name = request('name');で保存している。return redirect('/tasks');はリダイレクトすることでindexメソッドを再度呼び出し、追加したタスクを表示させている。
3.delete$task = Task::find($id);で受け取ったidのタスクを探し、$task->delete();でそのタスクを削除している。return redirect('/tasks');はstoreメソッド同様。

ルーティング設定

一つ前でコントローラーを作成した。このコントローラーのメソッドをルーティングに割り当てることでアクションを引き起こさせる。パスが/tasksでGETメソッドの時は一覧を表示、POSTメソッドの時はタスクを追加する。パスが/tasks/idのDELETEメソッドの場合idのタスクを削除する(idは任意のタスクに割り当てられた数字、DELETEメソッドは後で作成)。ルーティングは/myapp/routes/web.phpで設定しており、以下のよう変更することで実装した。

web.php
Route::get('/', function () {
    return redirect('/tasks');
});
Route::get('/tasks', 'TaskController@index');
Route::post('/tasks', 'TaskController@store');
Route::delete('/tasks/{id}', 'TaskController@destroy');

最初のRouteではパスが/の時/tasksにリダイレクトするようにしている。二つ目はコントローラーのindexメソッドをパスが/tasksでGETメソッドの時引き起こすようにしている。三つ目はコントローラーのstoreメソッドをパスが/tasksでPOSTメソッドの時引き起こすようにしている。最後は三つ目はコントローラーのdeleteメソッドをパスが/tasks/idでDELETEメソッドの時引き起こすようにしている。また、デフォルトではコントローラーのフルパスが必要なので/myapp/app/Providers/RouteServiceProvider.phpのコメントアウトされているコードprotected $namespace = 'App\\Http\\Controllers';のコメントアウトを外す必要がある。

ビューの設定

コントローラーを作成した時indexメソッドでビューを使用した。ビューはLaravelのBladeと言うテンプレートをもとに作成するものであり、return view(文字列, 連想配列)のように使われる。文字列の部分はテンプレートファイルを指しており、テンプレートファイルは/myapp/resources/views/文字列.blade.phpのように作成する。連想配列はこのファイル内でキーがそのバリューの変数のように扱う。今回のケースであれば/myapp/resources/views/tasks.blade.phpとテンプレートファイルを作成し、$tasksと言う変数を使える。この変数はタスクの一覧が配列で格納されている。テンプレートファイルは以下のように書く。

tasks.blade.php
@extends('layout')

@section('content')
    <h1>Task List</h1>
    <form action="/tasks" method="POST" class="form-horizontal">
        {{ csrf_field() }}
        <!-- Task Name -->
        <div class="form-group">
            <label for="task" class="col-sm-3 control-label">Task</label>
            <div class="col-sm-6">
                <input type="text" name="name" id="task-name" class="form-control">
            </div>
        </div>

        <!-- Add Task Button -->
        <div class="form-group">
            <div class="col-sm-offset-3 col-sm-6">
                <button type="submit" class="btn btn-default">
                    <i class="fa fa-plus"></i> Add Task
                </button>
            </div>
        </div>
    </form>

    <!-- Current Tasks -->
    <h2>Current Tasks</h2>
    <table class="table table-striped task-table">
        <thead>
            <th>Task</th><th>&nbsp;</th>
        </thead>

        <tbody>
            @foreach ($tasks as $task)
                <tr>
                    <!-- Task Name -->
                    <td>
                        <div>{{ $task->name }}</div>
                    </td>
                    <td>
                        <form action="/tasks/{{ $task->id }}" method="POST">
                            {{ csrf_field() }}
                            {{ method_field('DELETE') }}                    
                            <button>Delete Task</button>
                        </form>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
@endsection

長いので細かいところの説明は省略する。{{ csrf_field() }}はcsrf対策である。@foreach ($tasks as $task)はblade特有の記法で$tasks配列でループを回している。{{ $task->id }}はループ内のタスクのidを抜き出しており、nameだとタスク名を抜き出している。{{ method_field('DELETE') }}ではDELETEメソッドを擬似的に作成している。
また、最初の一文はレイアウトファイル/myapp/resources/views/layout.blade.phpを使用することを意味している。レイアウトファイルは以下のようになっている。

layout.balde.php
<!DOCTYPE html>
<html>
    <head>
        <title>Task List</title>
        <!-- CSS And JavaScript -->
        <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
        <link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
        <link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
    </head>
    <body>
        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

中盤にある@yield('content')にはこのファイルをエクスポートしたbladeテンプレートの@section('content')から@endsection('content')までの文を当てはめると言う意味がある。このコードはCSSなどを追加して見た目をよくしているだけである。

環境の違いによるコードの変更(講義を受けていない人には関係ない)

講義で使用した環境と本記事で構築した環境が異なることによって3つの変更が要請された。

1.タイトル部分

講義ではファイルの編集の練習のため、myapp/resources/view/welcome.blade.php<div class="title m-b-md">に囲まれた文字列を変更している。構築した環境において同名のファイルには前述のタグがなく、他の内容も著しく異なるのでここは割愛した(ファイル編集の練習としているだけなので、無理にしなくても良いと考えた)。

2.HTTPSについて

講義ではPaizaCloudを使っているため、HTTPSプロコトルを使っている。構築した環境ではHTTPを使っているので、myapp/routes/web.appに追加した\URL::forceSchme('https')は不要となる。また、myapp/app/Providers/AppServiceProvider.php内のAppServiceProviderクラスのbootメソッドに追加した\URL::forceSchme('https')も同様に不要。

3.ルーティング設定

構築した環境では以前はあったmyapp/app/Providers/RouteServiceProvider.phpファイルからデフォルトの名前空間がコメントアウトされている。これによってコントローラーの呼び出しをフルパスで与えなければいけなくなっている。これはprotected $namespace = 'App\\Http\\Controllers';のコメントアウトを解除することで解決できる。当然コメントアウトせずに

Route::get('/tasks', 'App\Http\Controllers\TaskController@index');
Route::post('/tasks', 'App\Http\Controllers\TaskController@store');
Route::delete('/tasks/{id}', 'App\Http\Controllers\TaskController@destroy');

としても良い。

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