20210129のlaravelに関する記事は20件です。

いいね機能をLaravelとVue.jsで実装②

こちらの記事はパート2です。
前回の記事はこちらから↓↓
https://qiita.com/tanaka2020/items/8ec8ae0fdd3ad4409c74

実装

4.ルートの設定

routesディレクトリ内のweb.phpを編集

Route::get('/posts/{post?}/firstcheck', 'LikeController@firstcheck')->name('like.firstcheck');・・・1
Route::get('/posts/{post?}/check', 'LikeController@check')->name('like.check');・・・2

1.Vue読み込み時にLikeControllerのfirstCheck()アクション呼び出し
2.いいねボタンクリック時に記事id別にLikeControllerのCheck()アクション呼び出し

5.Vue Components作成

resouces
 |ーjs
  |ーcomponents
    |ーLikeComponent.vue

<template>
 <div>
  <button v-if="status == false" type="button" @click.prevent="like_check" class="btn btn-outline-warning">&#9825;</button><a v-if="status == false" href="#">{{count}}</a>
  <button v-else type="button" @click.prevent="like_check" class="btn btn-warning">&#9829;</button><a v-if="status == true" href="#">{{count}}</a>
 </div>
</template>

<script>
export default {
 props: ['post_id'],・・・1
 data() {
   return {
     status: false,・・・2
     count: 0,
   }
 },
 created() {
   this.first_check()・・・3
 },
 methods: {
   first_check() {
     const id = this.post_id
     const array = ["/posts/",id,"/firstcheck"];
     const path = array.join('')
     axios.get(path).then(res => {
       if(res.data[0] == 1) {
         console.log(res)
         this.status = true
         this.count = res.data[1]
       } else {
         console.log(res)
         this.status = false
         this.count = res.data[1]
       }
     }).catch(function(err) {
       console.log(err)
     })
   },
   like_check() {
     const id = this.post_id
     const array = ["/posts/",id,"/check"];
     const path = array.join('')
     axios.get(path).then(res => {
       if(res.data[0] == 1) {
         this.status = true
         this.count = res.data[1]
       } else {
         this.status = false
         this.count = res.data[1]
       }
     }).catch(function(err) {
       console.log(err)
     })
   },
 }
}
</script>

1.blade側はより投稿記事のidが渡されます。

<like-component :post_id="{{$post->id}}"></like-component>

2.statusがfalseだといいねされていない状態です。
3.Vue読み込み時にLikeControllerのfirstCheck()アクションが実行されます。

まとめ

status = falseスクリーンショット 2021-01-29 23.16.28.pngstaatus = trueスクリーンショット 2021-01-29 23.16.38.png

今回はAxiousで実装しており非同期でLikeContorollerのアクションを呼び出して、いいね機能を実装しています。もっと簡単で出来る方法や綺麗な記述ありましたらご教授お願いします。

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

いいね機能をLaravelとVue.jsで実装①

投稿型のサイトを作成していて、
いいね機能を実装したいと思ったので作成しました。

環境

・PHP 7.4.9
・Composer version 1.10.13
・Laravel Framework 7.30.1
・vue/cli 4.3.1

流れ

1.投稿機能の実装(今回の記事では省略)
2.いいね機能用のdatabeseテーブルの作成
3.Vueコンポーネントの作成
4.Controllerの作成
5.ルート設定
6.view表示

実装

1.投稿機能の実装

 今回は省略!!

2.いいね機能用のdatabeseテーブルの作成

$ php artisan make:model Like -m

-mをつけることでモデルと一緒にマイグレーションファイルも作ってくれます。
※databaseの設定は省略します。設定はconfigフォルダdatabase.phpと.envを編集

作成したマイグレーションファイルの編集

public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->id();
            $table->integer('posts_id');
            $table->integer('user_id')->unable();
            $table->integer('like')->default(0);
            $table->timestamps();
        });
    }

likeカラムの値が0か1かでいいねされているかを判断するように実装します

作成したモデルLike.phpの編集

class Like extends Model
{
     protected $fillable = [
          'posts_id','user_id','like'
     ];
}

"fillable"はホワイトリストです。$fillableで指定したカラムは値が代入可能です。

マイグレーションの実行

php artisan migrate

3.コントローラーの作成

php artisan make:controller LikeController

Vueのコンポーネントから呼び出されるアクションを書いていきます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;//投稿機能のモデル
use App\Like;
use Auth;

class LikeController extends Controller
{
    //コンポーネント初期読み込み時(created)に呼び出される
    public function firstcheck($post) {
     $user = Auth::user();
     $likes = new Like();
     $like = Like::where('posts_id',$post)->where('user_id',$user->id)->first();
     if($like) {
          $count = $likes->where('posts_id',$post)->where('like',1)->count();
          return [$like->like,$count];
     } else {
          $like = $likes->create([
               'user_id' => $user->id,
               'posts_id' => $post,
               'like' => 0
          ]);
          $count = $likes->where('posts_id',$post)->where('like',1)->count();
          return [$like->like,$count];
     }
    }

    //いいねボタンを押した時に呼び出される
    public function check($post) {
     $user = Auth::user();
     $likes = new Like();
     $like = Like::where('posts_id',$post)->where('user_id',$user->id)->first();
     if($like->like == 1) {
          $like->like = 0;
          $like->save();
          $count = $likes->where('posts_id',$post)->where('like',1)->count();
          return [$like->like,$count];
     } else {
          $like->like = 1;
          $like->save();
          $count = $likes->where('posts_id',$post)->where('like',1)->count();
          return [$like->like,$count];
     };
    }
}

長くなってきたので次に続く。。。。。。

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

laravel php artisanコマンドであってるはずなのに、何かしらのエラーが出る場面でまずやってみること

php artisanで何かしらのコマンドでエラーになり、間違ってないはずなのにエラーが出る。
これで何度も激おこになったことあるのでメモ。

terminal
php artisan clear-compiled
composer dumpautoload

今現在記憶に残っているのは、
・socialite
・seeder

思い出したら追記します。

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

laravel php artisanコマンドであってるはずのに、何かしらのエラーが出る場面でまずやってみること

php artisanで何かしらのコマンドでエラーになり、間違ってないはずなのにエラーが出る。
これで何度も激おこになったことあるのでメモ。

terminal
php artisan clear-compiled
composer dumpautoload

今現在記憶に残っているのは、
・socialite
・seeder

思い出したら追記します。

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

Laravel SailでM1 MacBookのDocker開発環境構築

前置き

ご購入検討中のエンジニアさん
結論、“まだ”M1のMacはおすすめできません。
VagrantやVirtualBox、何を入れるにも一苦労。
導入できたとしても仮想環境は起動しない・・・。
(ARM対応してないものが多い)

私はかれこれ2週間近く試してやっとLaravelが動く方法を見つけました。
PHP + MySQL(mariaDB)使いたい方は参考になると思います。
私が実際に試して導入できた方法をご案内します。

LaravelSail導入

Laravel公式のDocker 開発環境構築ツール使います。
ターミナルからコマンドを実行。

curl -s https://laravel.build/sample-app | bash

[sample-app]の部分はお好みで。

起動します

cd sample-app
./vendor/bin/sail up

うまく行かない・・・ので

docker-compose.ymlの1箇所修正

image: 'mysql:8.0'

の部分を↓

image: 'mariadb'

再トライで、無事に起動

./vendor/bin/sail up

以上です。

まとめ

今まではHomestead使っていたので、
M1 MacではVirtualBoxとVagrantが動かないのがわかったとき、めっちゃ焦りました。

いろいろ調べてLaravelSailというのを見つけて、初めて導入しました。
かなりいいです。Laravel開発のスタンダードになりそうな予感。

よし、開発すすめるぞー

開発中のツーリングアプリ
https://touringbook.com/

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

初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!(Laravel偏)

皆さんこんにちは!

3日前くらいに書いた記事(初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!)が中々好評だったので、改めてenvファイルについて書いていこうと思いました。

前回書いた記事では、Vue.jsでenvファイルの使用方法を書きました。

今回はタイトルにも書いてる通り、「Laravel」偏です。

今一度、envファイルについて説明しようと思います。

知っている方は、スルーで結構です。

Javascriptでバックエンドとやり取りを行うとき、恐らく多くの方が以下のように書かれると思います。

axios.get('http://127.0.0.1')

ここで問題なのが、これを実際にサーバーにアップロードする時って、http://127.0.0.1のようなローカルホストとは接続を行いませんよね?

また、その都度バックエンドのURLをコピーしてくるのもナンセンス。

そんなこんなで出てくるのがenvファイル!!

・一度値を設定してしまえば、使いまわしOK!

・シークレットキーなどのセキュリティ面でも活躍!(ファイルに直接キーを入力するのは、githubにあげるときに見られてしまうため)

・ローカルと本番環境の使い分けができる!(今回はここはやりません)

こんなに魅力的なんです!

まぁ話はこの辺にしておいて、当然の如くVue.jsとLaravelではやり方が違うので、使い方を一緒に見ていきましょう!

envファイルに定数を設定

プロジェクト直下に.envというファイル名があると思うので、それを開いてみてください。

そしたら、以下を例に値を設定して見て下さい。

RECAPTCHA_SECRET_KEY = 'ciascihsiachuhewurvuvurw'

定数名は、自分で分かりやすいように設定してください!

configファイルで値の設定

次に、configディレクトリを開き、app.phpを開いてください。

Vue.jsでは.envファイルに記述して終わりだったのですが、Laravelでは.envファイルを読み込まない設定なので、configで設定します。

config/app.php
'RECAPTCHA_SECRET_KEY' => env('RECAPTCHA_SECRET_KEY')

先ほどenvファイルで設定した名前をenv(name)で呼び出し、'RECAPTCHA_SECRET_KEY'のように適当に名前を付けて下さい!

呼び出し

最後に、ここで設定した値を呼び出します。

の前に、いったんキャッシュを消去します。

コマンドプロンプトにて、以下のコマンドをうちます

php artisan config:cache

最後に呼び出していきます。

index.php
$secret = config('app.RECAPTCHA_SECRET_KEY');

このapp.は何かというと、configディレクトリのapp.phpRECAPTCHA_SECRET_KEYを呼び出すという意味です。

このようにして、固定値を呼び出すことができます。

いかがだったでしょうか?

簡単かつ作業効率もあがるのでぜひこの機会にガンガン使ってください!!

以上、「初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!(Laravel偏)」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

Laravelでほんの少しハイレベルな検索機能を作ってみた。(初心者向け)

こんにちは、しろうです。

現在、Laravelで小説サイトを作成しています。

そんな中で、(自分的には)ちょっとだけハイレベルな検索機能を作ってみたので、忘れないようにメモとして残しておきます。

検索機能を実装したい人はぜひ、参考にしてください。

もしミスとかあればコメントして頂けると大変有り難いですm(_ _)m

前置き

・クラス名とかひどいのあるかと思いますが、気にしないで頂けると幸いです。
・Formファサードを使ったり、素のformを使ったり混在していますが、許してくださいm(_ _)m
・cssは貼り付けていないので、皆さん側で好きなように変更してください。

今回作る検索機能

・複数条件での絞り込み検索ができる。(例:キーワードとジャンルで絞り込む。)
・現在の検索条件がタグとして表示される。
・タグを削除すれば、検索条件から削除される。

検索機能のイメージ動画

ちなみにイメージ動作は下記の通りです。

ezgif.com-gif-maker.gif

下記のように、他のページからジャンルとかをクリックしたら、そのジャンル名で検索してくれる機能もつけてます。

ezgif.com-gif-maker (1).gif

さて、じゃあどんどん作っていきます。

とりあえず検索機能を作る

viewファイルの作成(検索項目の部分)

スクリーンショット 2021-01-29 13.53.31.jpg

とりあえず検索項目を作成していきます。

今回は「キーワード検索」と「ジャンル検索」のみを作成しています。
また、CSSは貼り付けないので、皆さんの方で好きなように変更よろしくお願いしますm(_ _)m

search.blade.php
{{ Form::open(['route'=>'search','method'=>'GET','id'=>'side_search_form','autocomplete'=>"off"]) }}
<div class="search_keyword">
    <div class="search">
        <input name="keyword" value="{{request('keyword')}}" type="search" class="searchTerm" placeholder="キーワード検索">
        <button type="submit" class="searchButton" form="side_search_form">
            <i class="fa fa-search fa-xs"></i>
        </button>
    </div>
</div>

<div class="search_genre search_side_item_border">
    <h4>ジャンル</h4>
    <div class="ripples_radio">
        {{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])
        }}
        {{ Form::label('search_genre_none', '指定なし', []) }}
    </div>
    @foreach ($genres as $index => $genre)
    <div class="ripples_radio">
        {{ Form::radio('genre',$genre, $genre == request()->genre ? true :false,
        ['id'=>'search_genre'.$index]) }}
        {{ Form::label('search_genre'.$index, $genre, []) }}
    </div>
    @endforeach
</div>

{{ Form::close() }}

解説していきます。

'autocomplete'=>"off"・・・これをつけると検索履歴が表示されなくなります。お好みでどうぞ。
value="{{request('keyword')}}"・・・このようにするとvalue値が「keywordというパラメーター(クエリ文字列)の値」になります。
requestメソッド・・・リクエストデータを取得できるやつです。

@foreach ($genres as $index => $genre)・・・この$genresはコントローラーから検索できるジャンル名を返しています。単純にジャンル名を1つずつ処理しているだけです。

下記少しわかりにくいかと思います。

{{ Form::radio('genre',$genre, $genre == request()->genre ? true :false,
        ['id'=>'search_genre'.$index]) }}

Form::radioは第三引数がtrueならcheckedをつけるので、ジャンル名とパラメーターのジャンル名(現在検索しているジャンル名)が一致するなら、checkedをつけるようにしています。

例えば、http://127.0.0.1:8000/search?genre=恋愛(現世)とかなら、request()->genreによって、恋愛(現世)という文字列を取得できます。

なので、$genre == request()->genreとすることで、現在検索しているジャンルボタンにのみcheckedをつけることができます。

指定なしというラジオボタンもほしいので、下記コードで作成しています。

{{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])}}
{{ Form::label('search_genre_none', '指定なし', []) }}

value属性''(空文字)にしておけば、コントローラー側の処理でいい感じにできます。(解説は後ほど。)

web.phpにルーティングを追加

とりあえず、なんでもいいのでルーティングを追加します。

web.php
Route::get('search', 'UsersController@search')->name('search');

ジャンル用のファイルを作成

今回はconfig/getValue/radio.phpにジャンル用の配列を作成しました。

config/getValue/radio.php
<?php
return [
    'genre' =>[
        '1' =>'恋愛(異世界)',
        '2'=>'恋愛(現世)',
        '3'=>'ラブコメ',
        '4'=>'ホラー',
        '5'=>'推理(ミステリー小説)',
        '6'=>'異世界ファンタジー',
        '7'=>'現代ファンタジー',
        '8'=>'コメディ',
        '9'=>'SF',
        '10'=>'詩・エッセイ・童話',
        '11'=>'歴史・戦国',
        '12'=>'その他',
    ],
];

このようにすることでconfig('getValue.radio.genre')とすれば、ジャンル名の配列を取得することができます。

コントローラー内でこのジャンル名の配列を取得します。

コントローラーに処理を追加

次にコントローラー側に検索処理を記述していきます。

UsersController.php
public function search(Request $request)
    {
       //SQL文を書くためにqueryメソッドを使う。
        $query = Novel::query();

       //ジャンル名を取得。(viewファイルに返すだけ。)
        $genres = config('getValue.radio.genre');

      //keywordがあるかどうか。
        if ($request->filled('keyword')) {
            //検索キーワードとタイトルが一致するレコードを絞り込む
            $query->where('title', 'like', '%'.$request->get('keyword').'%');
        }

      //genreがあるかどうか。
        if ($request->filled('genre')) {
            //検索ジャンルとジャンル名が一致するレコードのidをgenresテーブルから取得
            $genre_id = Genre::where('name', $request->genre)->first()->id;
            //genre_idが一致するレコードをnovelsテーブルから絞り込む
            $query->where('genre_id', $genre_id);
        }

       //条件に一致するレコードを作成日で降順に並び替えて取得
        $novels = $query->latest('novels.created_at')->paginate(50);

        //viewファイルは好きなファイルにしてください。
        return view("search", compact('novels', 'genres'));
    }

たぶん、ほとんど読めばわかるんじゃないかと思います。

$request->filled('genre')を使えば、リクエストに値が存在して、かつ、空でない場合にtrueを返してくれます。

似たものに$request->has('genre')というのがありますが、これだと空であってもtrueになります。

それだと、下記のような指定なし(value値が空文字列)の場合でも実行されてしまうので、今回はfilledメソッドを使用しています。

{{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])}}
{{ Form::label('search_genre_none', '指定なし', []) }}

ちなみにlatest()を使えば、降順に並び替えてくれます。(下記の通り)

$query->latest('novels.created_at')->paginate(50);

あとは、好きなようにviewファイル側でデザインしてあげれOK。

試しにsearch.blade.phpとかで{{dd($novels)}}とかで中身を確認してみてください。

現在の検索条件を表示する機能の作成

スクリーンショット 2021-01-29 14.04.43.jpg

なんて説明すればいいかわかりませんが、ここからは上記の画像のやつを作っていきます。笑

方法としては、そこまで難しくありませんので、ご安心ください!!!

まずはview側に処理を追加

とりあえず、viewファイルに処理を追加していきます。

下記のようにすれば現在の検索条件を表示することができます。

search.blade.php
<div class="search_condition">
    <ul>
        @if(!empty(request()->keyword))
        <li class="search_condition_item">
            {{request()->keyword }}<a href="keyword" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
        </li>
        @endif

        @if(!empty(request()->genre))
        <li class="search_condition_item">
            {{request()->genre }}<a href="genre" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
        </li>
        @endif
    </ul>
</div>

見て大体わかると思いますので、ざっくり説明していきます。

まずempty()は引数が空ならtrueを返すので、!(エクスクラメーション)をつけて、中身が空ではない時にif文内のhtmlが表示されるようにします。

そしてif文の条件を「!empty(request()->keyword)」みたいな感じにすることで、現在keywordで検索をしているのかどうか、がわかります。(このkeywordというのはinputのname属性のことです。)

例えば、http://127.0.0.1:8000/search?keyword=&genre=みたいな感じで、keywordが空になっているなら、処理は実行されません。

逆にhttp://127.0.0.1:8000/search?keyword=人生楽しいね&genre=とかだと、「人生楽しいね」というキーワードで検索されていることになるので、if文内の処理が実行されます。

そして、if文内の処理は下記のようにします。

<li class="search_condition_item">
      {{request()->keyword }}<a href="keyword" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
</li>

まず{{request()->keyword }}とすることで「人生楽しいね」が表示されます。
でもって、aタグのhref属性keywordと記述して、JavaScriptでこいつを操作していきます。

JavaScriptを記述

ここからはJavaScript(今回はjQuery)の出番です。

なんでもいいので、JavaScritのファイルを作って、下記のように記述してください。

search.js
$(function () {
    $('.search_condition_a').each(function () {
       //href属性を取得
        attr = $(this).attr('href');
       //初期化
        var url = new URL(window.location);
       //パラメーター削除
        url.searchParams.delete(attr);
        if (url.search) {
            $(this).attr('href', 'search' +url.search);
        } else {
            $(this).attr('href', 'search');
        }
    });
});

まず、$('.search_condition_a').each(function () {}とすることで、search_condition_aクラスの要素(aタグの部分)をループさせています。

attr = $(this).attr('href');とすることで、ループされたaタグのhref属性を取得しています。

例えば、下記の場合はkeywordという文字列が取得できます。

<a href="keyword" class="search_condition_a">

次にURLオブジェクトを作成し、searchParamsメソッドdeleteメソッドを使用して、先ほど取得した、href属性の初期値と一致するパラメーターを削除します。

//初期化(URLオブジェクトの作成)
var url = new URL(window.location);
//パラメーター削除
 url.searchParams.delete(attr);

もう少し詳しく解説していきます。

URLオブジェクトとは、URLを作成したり、編集したりするときに使えるメソッドが沢山入っている、JavaScriptが用意してくれている便利なやつです。()

参考:URL()

そして、引数にはwindow.location として、現在開いているURLを与えます。

次にsearchParamsで、URLのパラメーター(URLの?以降の部分)を取得、deleteメソッドで引数に与えたものと一致するパラメーターを削除します。

例えば、attrkeywordという文字列が入っている場合は、url.searchParams.delete(attr);によって、

http://127.0.0.1:8000/search?keyword=人生楽しいね&genre=恋愛(異世界)
からkeywordの部分が削除されるので、
http://127.0.0.1:8000/search?genre=恋愛(異世界)
こんな感じになります。

最後にパラメーターがあるかないかで条件分岐させています。
こうしないと、他のページから単一条件で検索ページに飛んだときに、1つのパラメーターを削除すると、全てのパラメーターがなくなり、期待通りの動作をしてくれなかったからです。(もっと良い方法もあるかもです...)

if (url.search) {
            $(this).attr('href', 'search' +url.search);
        } else {
            $(this).attr('href', 'search');
        }

url.searchとすることで、URLのパラメーターを取得できます。

あとはattrメソッドを使って、href属性の値を変更してあげればOK。

これでタグの部分は完成です。

別ページから条件検索をしたい時

別ページから検索したい時は下記のように、直接href属性にパラメーターを仕込んでおけばOK。

test.blade.php
<a href="{{route('search')}}?genre={{$novel->genre->name}}">{{$novel->genre->name}}</a>

並び替え機能も実装

スクリーンショット 2021-01-29 16.15.34.jpg

ついでに並び替え機能の紹介もしておきます。(「新着順」と「更新順」だけ)

viewファイルに追加

下記のような感じで実装しました。

search.blade.php
<div class="search_sort">
    <button
        class="sort_btn {{strpos(request()->fullUrl(), 'new') !== false  || strpos(request()->fullUrl(), 'sort') === false ? 'sort_active' : ''}}"
        name="sort" value="new" form="side_search_form" type="submit">
        <span class="spot"></span>新着順
    </button>

    <button class="sort_btn {{strpos(request()->fullUrl(), 'update') !== false ? 'sort_active' : ''}}"
        name="sort" value="update" form="side_search_form" type="submit">
        <span class="spot"></span>更新順
    </button>
</div>

順に解説していきます。

まずformタグの外側でボタンを作る時はform属性formタグのid属性を指定します。

下記の2つの部分は現在のURLによってsort_activeクラスを付与するかどうか三項演算子を利用して、決定しています。

{{strpos(request()->fullUrl(), 'new') !== false  || strpos(request()->fullUrl(), 'sort') === false ? 'sort_active' : ''}}

//省略

{{strpos(request()->fullUrl(), 'update') !== false ? 'sort_active' : ''}}

strpos()は文字列の中に指定した文字列があるかどうかを判定するメソッドです。
request()->fullUrl()で現在のURLが取得できます。

コントローラーに処理を追加

最後に下記のように処理を追加・変更します。

SearchController.php
public function search(Request $request){

//省略

  if ($request->filled('sort') && $request->sort === 'new') {
      $query->latest('novels.created_at');
  }elseif ($request->filled('sort') && $request->sort === 'update') {
      $query->latest('novels.updated_at');
  } else {
      $query->latest('novels.created_at');
  }

  //こっちのlatestは消す。
  $novels = $query->paginate(50);

  return view("search", compact('novels', 'genres'));

}

これで並び替え機能も完成です。

簡単簡単。

最後に

これで一応、イメージ動画みたいな感じの検索機能が実装できたかと思います。

昔から検索機能を作るのは苦手なので、少し苦労しました...なんか条件分岐が多いですし疲れますね。(コードのせいかも。)

駆け足だったので、わかりにくい部分があるかも・・・その時はコメントしてくださいませm(_ _)m

たぶん、もう少し仕様変更とかしますが、ひとまずこれにて終了です!お疲れ様でした。

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

【Laravel】php artisanコマンドメモ(composerも少しだけあり)

自分用のメモとして残します。
3,4年前にメモしたものをqiitaに残すだけなので、未整理です。
もしかしたら、最新のLaravelバージョンでは使えないものもあるかも。

よく使うやつは整理するかもです。

【基本】

▼Eloquentモデル作成(単数形が公式推奨)

php artisan make:model テーブル名

▼シーダー作成(ダミーデータ作成)

php artisan make:seeder テーブル名(アッパーキャメル)TableSeeder

▼シーダー実行(database\seeds\DatabaseSeeder.phpに登録されているものが対象)

php artisan db:seed

▼シーダー実行(単体)

php artisan db:seed --class=シーダークラス名

▼ルーティング確認

php artisan route:list

▼マイグレーション作成

php artisan make:マイグレーション名
例:php artisan make:migration create_users_table

▼コントローラ作成(リソース(おまかせ)を使用する場合は-rを使う)

php artisan make:controller Admin/AdminUsersController
php artisan make:controller Admin/AdminUsersController -r
php artisan make:controller Admin/AdminUsersController --resource --model=Photo

▼ページネーション作成

php artisan vendor:publish --tag=laravel-pagination

▼バッチの作成

php artisan make:command バッチ名(アッパーキャメル)Command

【その他】

▼キャッシュ削除

php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear

▼DB初期化(マイグレート実行&初期データ実行)

php artisan migrate:refresh --seed

▼IDEヘルパー更新

php artisan ide-helper:generate

▼最適化まとめ

composer dump-autoload
php artisan clear-compiled
php artisan optimize
php artisan config:cache
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS S3のパッケージインストール時に、composer require league/flysystem-aws-s3-v3を叩くと、Your requirements could not be resolved to an installable set of packages.とエラーが出るときの解決策。

前提条件

・macOS
・Laravel6
・初学者

composer require league/flysystem-aws-s3-v3を叩いた結果

test@test laravel % composer require league/flysystem-aws-s3-v3

Using version ^2.0 for league/flysystem-aws-s3-v3
./composer.json has been updated
Running composer update league/flysystem-aws-s3-v3
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - league/flysystem-aws-s3-v3[2.0.0, ..., 2.x-dev] require league/flysystem ^2.0.0 -> found league/flysystem[2.0.0-alpha.1, ..., 2.x-dev] but the package is fixed to 1.1.3 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - league/flysystem-aws-s3-v3[2.0.0-alpha.1, ..., 2.0.0-alpha.2] require league/flysystem 2.0.0-alpha.1 -> found league/flysystem[2.0.0-alpha.1] but the package is fixed to 1.1.3 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - league/flysystem-aws-s3-v3[2.0.0-alpha.4, ..., 2.0.0-beta.1] require league/flysystem 2.0.0-alpha.3 -> found league/flysystem[2.0.0-alpha.3] but the package is fixed to 1.1.3 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - league/flysystem-aws-s3-v3[2.0.0-beta.2, ..., 2.0.0-beta.3] require league/flysystem ^2.0.0-beta.1 -> found league/flysystem[2.0.0-beta.1, ..., 2.x-dev] but the package is fixed to 1.1.3 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - league/flysystem-aws-s3-v3 2.0.0-RC1 requires league/flysystem ^2.0.0-RC1 -> found league/flysystem[2.0.0-RC1, ..., 2.x-dev] but the package is fixed to 1.1.3 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - Root composer.json requires league/flysystem-aws-s3-v3 ^2.0 -> satisfiable by league/flysystem-aws-s3-v3[2.0.0-alpha.1, ..., 2.x-dev].

Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.

Installation failed, reverting ./composer.json and ./composer.lock to their original content. 

解決方法

バージョンがあってないよ〜。と言われている気がしたので、

test@test laravel % composer require league/flysystem-aws-s3-v3 ^1.0

バージョン指定を入れることで解決できました。

参考記事

https://www.ritolab.com/entry/8

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

Permission Denied Error Doker Laravel

Dockerでlaradockを導入し、laravelを起動したとき、以下のエラー文が出ました。
docker The stream or file "/var/www/storage/logs/laravel.log" could not be opened in append mode: failed to open stream: Permission denied

chown -R www-data:www-data /var/www
chmod -R 777 /var/www/storage

権限を変更してあげると、無事にlaravelを表示することができました。

以下のサイトを参考に、しました。
https://stackoverflow.com/questions/48619445/permission-denied-error-using-laravel-docker

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

【PHP】peclとは何か?pecl install と docker-php-ext-installの違い

DockerにNginxとphp-fpmを使ってLaravel環境を構築する際に、php-fpm対して実行する処理の中にpeclがあったので調べてみた。

▼peclの使用例

php-fpmビルド用のDockerfile
RUN pecl install zip \
    && docker-php-ext-enable zip

RUN pecl install redis \
    && docker-php-ext-enable redis

目次

  1. peclとは?
  2. 使い方(拡張機能のインストール)
  3. 拡張パッケージの有効化
    1. dockerの場合
    2. macやwindowsの場合
  4. pecl installとdocker-php-ext-installの違い
  5. peclとpearの違い
  6. peclとapt-getの違い
  7. 参考リンク


peclとは?

PHPの拡張パッケージ(拡張モジュール)を提供しているサービス。
PHP Extension Community Libraryの略でピクルと読む。

パッケージのインストール後は有効化する必要がある。(php.iniで有効化した場合はサービスの再起動が必要)


使い方(拡張機能のインストール)

peclはPHPにバンドルされているので、すぐにコマンドとして使える。

以下コマンドでパッケージをインストールできる。

pecl install 拡張パッケージ名[-バージョン]

バージョンの指定がなければ安定版をインストール。



▼バージョン指定の例

$ pecl install extname-0.1


拡張パッケージの有効化

peclでインストールしたのみではパッケージは有効にならないので有効化する必要がある。

1. dockerの場合

docker-php-ext-enable 拡張パッケージ名

2. macやwindowsの場合

この場合2つの方法がある。

php.iniを編集する

php.iniを開いて編集する。

(1)まずはファイルの所在を確認。

php.ini
##場所を確認
$ php -i | grep php.ini

Configuration File (php.ini) Path => /etc

##ファイルの存在確認
$ ls /etc | grep php.ini
php.ini.default

##ファイルの作成
$ cp /etc/php.ini.default /etc/php.ini

上記状態だと、php.iniは存在しないので、php.ini.defaultをコピーして、php.iniを作成する。



▼php.ini.defaultとphp.iniの違いとは?
php.iniはデフォルトでは存在しない。phpをインストールした際に(OSによってはデフォルトで)php.ini.defaultがある。

php.iniのテンプレートなので、これをコピーしてファイル名をphp.iniにする。



▼grepコマンドとは?
grepコマンドは前の処理結果の中で、指定した値を含む処理結果のみを表示する絞り込みコマンド。

処理 | grep 値


(2)php.iniの編集

ファイルが出来たら、Dynamic Extensionsと書かれているところの下に;extension=拡張パッケージ名があるので、冒頭の;を外す。

拡張パッケージ名がない場合は、extension=拡張パッケージ名を追加する。

これで有効化完了。

php.iniの例
;;;;;;;;;;;;;;;;;;;;;;
; Dynamic Extensions ;
;;;;;;;;;;;;;;;;;;;;;;

省略

;extension=bz2
;extension=curl
;extension=fileinfo

↓ curlを有効化

;extension=bz2
extension=curl
;extension=fileinfo



本番反映のため、php.iniで有効化した場合はサービスの再起動をする。


dl関数を使う

dl関数を使って指定した拡張パッケージを有効かできる。

dl ( string $extension_filename ) : bool

$ dl (curl): true

PHP公式


pecl installとdocker-php-ext-installの違い

Dockerfileによっては、pecl installdocker-php-ext-installの2つが使われている場合がある。

なぜPHP拡張パッケージをインストールにするのにコマンドがいくつもあるのか?について。

RUN pecl install redis \
    && docker-php-ext-enable redis

RUN docker-php-ext-install pdo_mysql

結論

docker-php-ext-installはPHPに標準で備わっている拡張パッケージのインストール&有効化ができる。

peclはPHPに標準で備わっていないパッケージをインストールできる。


PHPに標準で備わっている拡張パッケージ一覧

https://www.php.net/manual/ja/extensions.php

▼一例
image.png

Zip, Zlib, memcache, podなどが用意されている。


peclでinstallできる拡張パッケージ一覧

公式ページで検索できる。
https://pecl.php.net/package-search.php

image.png

redis, xdebug, memcachedなどが用意されている。


peclとpearの違い

peclもpearもPHPの拡張パッケージをインストールできるコマンド。

pearはPHP言語で書かれている。peclはC言語。peclの方が高速。

pearで用意されている拡張ライブラリはpeclでも使える。

なお、アイコンは名前にちなんで、pearは洋梨、peclはピクルスとなっている。


peclとapt-getの違い

Dockerfileによっては、apt-get installも同時に使われている場合がある。

RUN apt-get install -y zlib1g-dev libzip-dev unzip \
    && pecl install zip \
    && docker-php-ext-enable zip

aptはDebian系Linux(DebianやUbuntuなど)のパッケージ管理ツール。

なので、apt-getでインストールするのはOS用のパッケージ

peclでインストールするのはPHP用のパッケージ。


参考リンク

PHP公式 pecl入門
PHP公式 pecl installの使い方
PHP公式 php.iniディレクティブ
pecl公式 peckとは?
docker hub公式 php-fpm

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

【Docker・Laravel】docker-composer.ymlとDockerfileの作り方。

docker-composeを使ってDocker上にLaravelを構築する方法(Nginx & php-fpm & MySQL)で使った、docker-compose.ymlとDockerfileの中身について。

docker-compoer.yml

docker-compoer.yml
version: '3'
services:

  #PHP Service
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: app_php-fpm
    container_name: app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - app-network
    links:
      - webserver
      - db

  #Nginx Service
  webserver:
    image: nginx:alpine
    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    networks:
      - app-network

  #MySQL Service
  db:
    image: mysql:5.7.22
    container_name: db
    restart: unless-stopped
    tty: true
    ports:
      - "3307:3307"
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: password
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - dbdata:/var/lib/mysql/
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    networks:
      - app-network

#Docker Networks
networks:
  app-network:
    driver: bridge
#Volumes
volumes:
  dbdata:
    driver: local

version: '3'
docker-composeはバージョンにより記述方法が異なる。基本的にはバージョン3を使用する。


1. php-fpmのイメージとコンテナを作成

php-fpmのイメージとコンテナをサービス名appとして定義している。

docker-compoer.yml
#PHP Service
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: app_php-fpm
    container_name: app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - app-network
    links:
      - webserver
      - db

1-1. イメージのビルド

PHPサービスのイメージを作成するための記述はbuildimage

buildでイメージを作成し、作成したイメージ名をimageで指定している。

docker-compoer.yml
    build:
      context: .
      dockerfile: Dockerfile
    image: app_php-fpm

イメージの作成にはDockerfileを指定している。
contextで指定したパスから見て、dokerfileで指定した場所の中のDockerfileを探す。

・contextディレクティブ

Dockerfileを探すために基準となるパスを指定する。必須項目。

・dockerfileディレクティブ

dockerfileはファイル名の指定がなければ、デフォルトで指定したディレクトリのDockerfileを探す

contextで指定した場所にDockerfileがある場合は記述しなくてもいい。

なので、上記設定は以下でも同じ動きになる。

    build:
      context: .
    image: app_php-fpm


1-2. Dockerfile

Dockerfile
FROM php:7.3-fpm

# Copy composer.lock and composer.json
COPY composer.lock composer.json /var/www/

# Set working directory
WORKDIR /var/www

# Install dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl \
    libonig-dev \
    libzip-dev

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install extensions
RUN docker-php-ext-install pdo_mysql zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Copy existing application directory permissions
COPY --chown=www:www . /var/www

# Change current user to www
USER www

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

・FROM イメージ名:タグ名

ベースとなるイメージを指定。
ここではphp:7.3-fpmを指定している。タグを指定していない場合はlatestとなる。

buildを実行すると、まずはローカルで指定したイメージを探し、なければdocker hubを探しにいく。


・バンドル関連ファイルのコピー

# Copy composer.lock and composer.json
COPY composer.lock composer.json /var/www/

COPY <ファイル1> <ファイル2>,,, <ディレクトリパス>
指定したローカルのファイルをdockerのディレクトリにコピーする。


・WORKDIR パス

コンテナ起動時の

# Set working directory
WORKDIR /var/www

以降のコマンドCOPY、RUN、CMD、ENTRYPOINTを実行する大元となるDockerコンテナ内のディレクトリを指定する。

コンテナ内に指定したディレクトリが存在しない場合は作成してくれる(エラーにならない)



▼複数記述することも可能
複数記述した場合は、そのディレクトリが追加され、ディレクトも移動してく。(cdコマンドみたいな感じ)

WORKDIR app
WORKDIR b
WORKDIR c
RUN pwd

//現在地
app/b/c


Linux(Debian系)依存パッケージのインストール

# Install dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl \
    libonig-dev \
    libzip-dev

apt

Linux(DebianやUbuntuなど)のパッケージ管理コマンド。Advanced Package Toolの略。

apt-get

パッケージの操作・管理を行うコマンド。

apt-get update

インストール可能なパッケージの一覧を更新する。(一覧の更新のみでインストールはしない)

一覧は/etc/apt/sources.listにある。

apt-get install

sources.listに沿ってパッケージをインストール or アップデートする。
-yオプション:すべてyesを選択する。

パッケージ 内容
build-essential Debian系のOSを使うために必須
libpng-dev png画像の読み込み・書き込み・編集
libjpeg62-turbo-dev jpeg画像の読み込み・書き込み・編集
libfreetype6-dev フォント関連の様々な操作をサポート(FreeType)
locales 言語設定
zip zip操作の圧縮。zipコマンドが使えるようになる
jpegoptim jpg画像の圧縮
optipng png画像の圧縮(画質を下げることなく圧縮。圧縮率低め)
pngquant png画像の圧縮(optipngよりも圧縮。画質は低下)
gifsicle gif画像とアニメーションの読み込み・書き込み・編集
vim テキストエディタ
unzip zip操作の解凍。unzipコマンドが使えるようになる
git ソースコードなどの変更履歴を記録・追跡
curl URLシンタックスを用いてファイルを送信または受信するコマンドラインツール
libonig-dev 正規表現ライブラリ。鬼車(onigruma)
libzip-dev zipアーカイブの読み取りと書き込み

Linux(Debian系)の構築や必要なコマンドライブラリを一気にインストール。

・-devの意味
パッケージ名の後ろの-devは、developmentの略。プログラム開発で必要なヘッダーやツール、ライブラリなどが入っている。


aptのキャッシュ削除

apt-get installでパッケージをインストールすると、キャッシュが追加され容量が大きくなっていく。

キャッシュは不要なので削除する。

ダウンロードしたパッケージは.deb形式で、var/cache/apt/ archives/ディレクトリに保存されます。これらのインストーラパッケージは、アンインストール後もローカルストレージに保持されます。

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

apt-get clean
キャッシュを一掃する。

rm -rf /var/lib/apt/lists/*
パッケージリストが削除する。apt-getで再度パッケージがインストールされたり、キャッシュファイルが増加するのを防ぐ。

▼補足
実行すると、パッケージに関する情報がないため、apt-get installはE: Unable to locate package でエラーを返す。

apt-get updateを実行すればリストが復活する。


PHP拡張機能(エクステンション)のインストール

# Install extensions
RUN docker-php-ext-install pdo_mysql zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd

docker-php-ext-install

DockerにPHPの拡張機能をインストールするためのコマンド。

・インストールする拡張機能

PHP拡張機能 内容
pdo_mysql PHPからMySQLへのアクセスを可能にする
zip zipのアーカイブとその内部のファイルに対する透過的な読み書きを可能にする
exif 画像のメタデータ扱えるようになる。画像のヘッダ情報を読み込むなど
pcntl プロセス制御機能。プロセスの並行処理を行う
gd 画像をPHPスクリプトで生成する。画像のリサイズやグラフ作成などの描画機能を持たせることができる。

docker-php-ext-configure

docker-php-ext-configure <拡張機能> <オプション>

指定した拡張機能の環境設定をする。環境設定は各拡張機能に割り当てられたオプションを使う。

GDの主要オプション

RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/

GDの環境設定をしている。jepgやpngを使うGDをビルドするためにはlibpegやfreetype, libpngが必要なためインストールの設定をする。

オプション 意味
--with-gd[=DIR] GDライブラリの保存場所を指定
--with-jpeg-dir[=DIR] libjpegの保存場所
--with-freetype-dir[=DIR] FreeType 2の保存場所
--with-png-dir[=DIR] libpngの保存場所

次の行でGDをインストール。

RUN docker-php-ext-install gd
GDインストール時の注意点

PHP7.4以降からGDインストール時のオプションが変更となっている。(上記設定だとエラーが出る)

configure: error: unrecognized options: --with-gd, --with-freetype-dir, --with-jpeg-dir, --with-png-dir

PHP7.4以降は引数での環境設定が不要になる。必要なパッケージはapt-get installでインストールしておく。

なので、docker-php-ext-config gd ~は不要。

修正後
# Install extensions
RUN docker-php-ext-install pdo_mysql zip exif pcntl
RUN docker-php-ext-install gd


composerのインストール

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

curl -sS

-sオプション
--silent。通常表示される進捗状況を表示しない。エラーメッセージも表示しない。

-Sオプション
--show-error。エラーメッセージを表示する。-sとセットで使うことでエラーを表示する。

curl --helpでオプション一覧が確認可能。

curl -sS https://getcomposer.org/installer

composerのinstallerをダウンドロードする。(composer-setup.phpを取得)

|

コマンドA | コマンドB

パイプライン。コマンドAの実行結果をコマンドBに渡す。

php -- --install-dir=/usr/local/bin --filename=composer

php [options] -- [args...]なので、引数で、インストール先のディレクトリとファイル名を指定。

  • --install-dir=/usr/local/bin
  • -filename=composer


ユーザーとグループの追加

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

・groupadd

groupaddコマンドで新しいグループ

groupadd [オプション] [グループID] <グループ名>

-g: --gidと同じ。作成するグループのID(GID)を指定。
指定がない場合は、未使用の番号を自動で割り振る。



▼作成したグループの確認
グループの情報は /etc/group に保存されている。コンテナの中で確認できる。

$ docker exec -it app bash
www@a1984dd1d4a4:/var/www$ cat /etc/group
www:x:1000:

①グループ名:②PW:③GID[④:所属メンバー名]

  1. グループ: www
  2. PW: x (セキュリティのため表示されない。設定なしの場合は*)
  3. GID: 1000
  4. 所属メンバー:なし


・useradd

useraddコマンドは新規ユーザーを作成し、ユーザーごとの設定ができる。

コマンドは長いが、最初のuseraddと最後のwww以外はすべてオプション。

useradd [オプション] ユーザー名

-u: ユーザーID(UID)の指定
指定がない場合は、未使用の番号を自動で割り振る。

-m: ユーザーのhomeディレクトリが存在しない場合に作成。
指定しない場合は、「/」配下にユーザー情報が保存される。

-s [dir]: ユーザーのログインシェルをフルパスで指定(指定しなかった場合はデフォルト設定)

-g [グループ名/gid]: 指定したユーザーをグループに追加する。



▼作成したユーザー情報の確認
ユーザー情報は /etc/passwd に保存されている。コンテナの中で確認できる。

$ docker exec -it app bash
www@a1984dd1d4a4:/var/www$ cat /etc/passwd
www:x:1000:1000::/home/www:/bin/bash

①ユーザー名:②PW:③UID:④GID::⑤/home/<ユーザー名>:⑥シェルのパス

  1. ユーザー名: www
  2. PW: x (セキュリティのため表示されない。設定なしの場合は*)
  3. UID: 1000
  4. GID: 1000
  5. ユーザーのホームディレクトリ: home/www
  6. ユーザーが使用するシェル: /bin/bash


ローカルのPJをコンテナ内にコピー & ファイルオーナーの変更

# Copy existing application directory permissions
COPY --chown=www:www . /var/www

COPY [--chown=<user>:<group>] <ローカルdir> <コンテナdir>

ローカルの「.」をコンテナ内の「/var/www」にまるごとコピー。ユーザー名をwww、グループ名をwwwに変更。


ユーザー名を指定

# Change current user to www
USER www

USER <user名 or uid>[:<group名 or gid>]


ポート開放とphp-fpmの起動

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

EXPOSE ポート番号

指定したポートでリッスン(外部からのアクセスに備えて待機)する。

ここでは、php-fpmの9000番ポートを開けておく。

CMD [コマンド,引数1,引数2,,,]

CMDはコンテナ実行時のデフォルト処理を定める。Dockerfileに必須。記述は1つのみ。

php-fpmコマンドを実行して、php-fpmを起動。



以上がDockerfileの中身。
php:7.4-fpmイメージをベースに、必要なパッケージをインストール、ユーザー名や権限変更をしたのち、9000番ポートを開けてphp-fpm起動するイメージが出来上がり。


イメージ名の設定

最後に作成したイメージに名前をつけて完了。(ここではapp_php-fpm)

image: app_php-fpm

(参考)Dockerfile リファレンス


1-3. コンテナの設定

1-2までがイメージのビルド。続いてコンテナ起動時に実行される処理の確認。

docker-compose.yml
    #php-fpmサービスの続き
    container_name: app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - app-network
    links:
      - webserver
      - db

container_name: コンテナ名

コンテナを作成した時の名前。起動・停止・削除に使うのでわかりやすく短めの名前がおすすめ。

restart: コンテナ停止時の処理フラグ

コンテナが停止した時の処理をフラグで指定する。

フラグ 意味
no 再起動しない。デフォルト
on-failure エラー発生時にコンテナを再起動(終了コードがゼロ以外)
always 常に再起動。手動で停止時はDockerデーモンが再起動した場合にコンテナを起動する
unless-stopped 手動停止時を除いて再起動。Dockerデーモンを起動しても再起動しない。

▼再起動ポリシー

  • コンテナ起動後が10秒以上立たない場合は自動再起動しない。(再起動ループを避ける)
  • 手動でコンテナを停止すると、Dockerデーモンを再起動するか、コンテナを手動で再起動するまで再起動ポリシーを無視する。(再起動ループを避ける)
  • 再起動ポリシーを適用できるのはコンテナのみです。(swarm サービスは対象外)

Docker公式 restart

tty: <boolean>

疑似ターミナル (pseudo-TTY) の割り当て。
tty: trueで、ポート開放していないコンテナも起動状態をキープできる。(ないとコンテナがすぐに終了してしまう)

docker execコマンドでコンテナに入って操作する際に必要。


environment

環境変数の設定。
書き方は2種類。Key: Valueまたは、- Key=Value

environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev

以下と同じ。

environment:
     - SERVICE_NAME=app
     - SERVICE_TAGS=dev
  • 値を指定しない場合は、シェルの値を適用する。
  • DockerfileのARGやENVで指定した環境変数と被る場合は、docker-composeの環境変数が優先される。
  • env_fileで読み込んだ環境変数と被る場合は、environmentが優先される。



▼環境変数の優先度

  1. environment
  2. env_file
  3. DockerfileのARG, ENV

env_fileは環境変数を定義したファイルを指定する。たくさんの環境変数を一括で読み込む場合に便利。

.env_fileの例
    env_file:
     - ./Docker/api/api.env

Docker-compose における環境変数


working_dir

コンテナ内のデフォルトの作業用ディレクトリを指定する。

 working_dir: /var/www

コンテナに入ると/var/wwwに入る。ここはプロジェクトがコピーされるディレクトリ(Dockerfileにて設定)。


Volumes

ローカルのディレクトリをコンテナ内のディレクトリにマウントする。

    volumes:
      - ./:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini

- ホストのパス:コンテナ内の絶対パス
コンテナ側は絶対パス。ホスト側は相対でも絶対でもいい。ボリューム名を定義指定いる場合は、ボリューム名でもいい。

- ./:/var/www
ローカルの現在のディレクトリをコンテナの「var/www」と同期させる。

これで、ローカルのプロジェクトディレクトリで行った変更はコンテナ内に自動コピーされる。

自動同期したくないディレクトリやファイルは、.dockerignoreに記載する。



▼ボリューム名について
ボリューム名はserviceの外でvolumesを使って定義する。参照する際のボリューム名は「プロジェクト名_ボリューム名」となる。

ボリューム名を指定しない(ホストのパスを指定した)場合はハッシュ値となる。

docker-compose.ymlの例
#PJ名:laravel

version: '3'
services:
  #MySQL Service
  db:
    #省略
    volumes:
      - dbdata:/var/lib/mysql/
      - ./mysql/my.cnf:/etc/mysql/my.cnf
volumes:
  dbdata:
    driver: local

 ↓

ボリューム名
$ docker volume ls
DRIVER    VOLUME NAME
local     laravel_dbdata
local     f0f9aa131ed1d74c0d2e9ff87b065170a1b142bd6a90a76ce9dcddbee3c69984

networks

docker-composeはnetworkを指定しない場合、プロジェクト名のネットワークが自動生成され、すべてのサービスがそのネットワーク内に入る。

networkを自分で定義すると、指定したサービスだけをそのnetworkで繋ぐことができる。

version: '3'
services:
  app:
    #省略
    networks:
      - app-network

    #省略

networks:
  app-network:
    driver: bridge

▼ネットワークの確認
ネットワーク名は指定の有無により名前が変わる

  • 指定なし: プロジェクト名_default
  • 指定あり: プロジェクト名_ネットワーク名
ネットワークの確認
$ docker network ls
NETWORK ID     NAME                         DRIVER    SCOPE
2cee68b9dc5a   django_default               bridge    local
761212ed4b49   laravel_app-network   bridge    

▼bridgeとは?
bridgeとはネットワークのブリッジモードのこと。複数のネットワークをつなげて、ひとまとめのネットワークにしている状態。

links

サービスを関連づける。サービスを起動すると、linksで記述されているサービスも一緒に起動する。

version: '3'
services:
  app:
    #省略
    links:
      - webserver
      - db

上記の場合、appにdbとwebserverがlinkしているため、appのみを起動すれば自動的にdbとwebserverも起動する。

起動の順序はlinksに記述したコンテナから。

起動例
$ docker-compose up -d app
Starting webserver ... done
Starting db        ... done
Starting app       ... done



以上で一つ目のphpサービスのdocker-composeの記述内容が完了。

処理をまとめると、
(1)イメージのビルド
Dockerfileを元に、php:7.4-fpmをベースイメージとして、必要なパッケージなどを保持した新たなイメージを作成。

(2)コンテナの起動
環境変数、ボリューム、ネットワーク、関連サービスを指定(リンク)してコンテナを起動。最後はDockerfileに記載されたCMDが実行され、イメージ通りのphp-fpmが起動する。


2. Nginxのコンテナを作成

php-fpmのコンテナが作成できるようになったので、続いてWEBサーバーとなるNginxを作成する。

.yml
  #Nginx Service
  webserver:
    image: nginx:alpine
    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    networks:
      - app-network

2-1. イメージのビルド

イメージの作成は1行のみ。

image: nginx:alpine

docker-hubのnginx:alpineをイメージとして使用。(alpine上に構築したnginx)



▼alpineとは?
alpineは超軽量のLinuxディストリビューション。

▼Linuxディストリビューションとは?
LinuxのコアとなるプログラムをLinuxカーネルという。この状態では普通のOSとして使うことが難しい。

使いやすいようにパッケージをくっつけたのがLinuxディストリビューション。distributionは配布という意味なので、配布用Linuxということ。

Debian, Ubuntu, CentOSと同じ並びにalpineがくる。


2-2. コンテナの定義

    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    networks:
      - app-network

php-fpmサービスで使った内容とほぼ同じ。

作成したコンテナ名をwebserverとし、コンテナ手動停止(&エラー)以外は自動再起動する。

コンテナのbashを起動できるように擬似ターミナル(tty)を開けておく。

ports

ホスト側:コンテナ側でポート番号を指定する。

- "80:80"
であれば、localhost:80にアクセスすると、Nginxの80番ポートに繋がるという意味。

443ポートはhttpsプロトコル用のポート。
https://xxxで接続すると、xxxのIPアドレスの443ポートに接続する。

volumes

- ./:/var/www
ローカルの現在のディレクトリを、Dockerコンテナ内のNginxの/var/wwwと同期。

- ./nginx/conf.d/:/etc/nginx/conf.d/
ローカルの./nginx/conf.d/をnginxの/etc/nginx/conf.d/と同期。

networks

「project名_app-network」というネットワークに入ったサービス同士とつなげる。


2-3. DBの定義

  #MySQL Service
  db:
    image: mysql:5.7.22
    container_name: db
    restart: unless-stopped
    tty: true
    ports:
      - "3307:3307"
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: password
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - dbdata:/var/lib/mysql/
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    networks:
      - app-network

設定はNignxサービスとほぼ同じ。
ベースイメージに、mysql:5.7.22を使用。
ポート番号はホスト側の3307をローカル側の3307につなげるよう設定。

environment

環境変数でDB名やPWなどを渡している。


2-4 networkとvolumeの定義

全体で使用するnetworkと、volume名をつけるものを定義している。。

#Docker Networks
networks:
  app-network:
    driver: bridge

app-networkを定義。ドライバーはbridge(複数のネットワークを一つとみなす)

#Volumes
volumes:
  dbdata:
    driver: local

名前ボリュームを設定する。名前ありボリュームを使うとデータを永続化できる。

ここでは、DBのデータはコンテナを終了しても残しておきたいので、dbdataボリュームを作成し、/var/lib/mysql/と連動させている。

コンテナ終了時にDocker内のデータは消えるが、ローカルにデータが残る。

Dockerではボリューム名dbdataでローカルのパスを保存してあるため、次のコンテナ起動時にそのローカルに接続し、/var/lib/mysql/配下にデータを保存する。(ローカルと同じ状態にする)


まとめ

php-fpmは自分のLaravelプロジェクトを反映するためイメージの再構築をしているのでビルドに時間がかかる。

NginxとDB(MySQL)は公式のイメージを取得するのみなのでビルドもあっさり。(ただdocker-hubからプルするだけ)

コンテナ起動時にポート番号、環境変数、ボリューム、ネットワークを指定している。

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

[PHP] 親クラスで定義した変数を呼ぶ

子クラス共通の変数を扱いたい場合、下記のように親クラスで定義する必要があります。

class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    protected static $userId = 1;
}

このように定義した後、下記のように子クラスで呼び出します。

class ControllerTest extends \TestCase
{

    public function setUp(): void
    {
        $this->user = User::find(self::$userId);
    }
}

このようにself::$userIdと書くことにより、extendsしているクラスなら扱うことができます。

参照

【PHP】継承とprotectedと参照パターン

会社の紹介

私は現在、株式会社ダイアログという物流×ITの会社に勤務しております。
様々な職種を募集しているので、Wantedlyのページをご覧ください。

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

npm run watch-pollがエラーになった

Laravel初心者です。
以下環境でアプリを作成しています。

バージョン
Docker 20.10.2
Docker-compose 1.27.4
PHP 7.4.14
Laravel 8.24.0
mysql 8.0.23

npm run watch-pollでファイルの変更を反映

cssなどを変更してブラウザをリロードしても反映されず、
この場合はDockerコンテナ内で

コンテナ
npm run watch-poll

を入力するとファイルの変更を監視してくれて
リアルタイムでコンパイルされるのだそうです。

入力すると
スクリーンショット 2021-01-29 10.25.17.png

ターミナルがこのような画面になりブラウザに変更が反映されます。

ちなみに

npm run watch

との違いは
npm run watchはファイルの変更を監視しているため特定のシステムの場合はうまくいかないようです。
npm run watch-pollは変更を1秒単位で確認するようです。

npm run watch-pollでエラーが出た

ここからが本題です。
いつものようにnpm run watch-pollをしようとしたところ

スクリーンショット 2021-01-28 0.16.58.png

このようにエラーになってしまいました。

どこがエラー文なのかわからないけど

ターミナル
Local package.json exists, but node_modules missing, did you mean to install?

って出てるのでpackage.jsonnode_modulesがなんかおかしいのかなと推測。

解決

こちらの記事がとても分かりやすくて助かりました!
ありがとうござます。

一般的な解決策としてnode_modulesとpackage-lock.jsonを消してキャッシュをクリアにし、再度npmをインストールしたあと、実行しなおすという手があります

とのこと。
あ、なんかこれっぽいと思って

コンテナ
rm -rf node_modules && rm package-lock.json && npm cache clear --force && npm cache clean --force && npm i

とした後に再度

コンテナ
npm run watch-poll

で無事に解決しました!

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

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

LaravelでFullcalendarを実装する方法

はじめに

今回はLaravelでFullcalendarを実装する方法について解説します。
LaravelでFullcalendarを実装するにあたり、参考記事があまりなく、実装に苦労したので、同じような方の助けになればと思います。

-各バージョン
-Laravel 6.x
-PHP 7.4.9
-MySQL 5.7.30
-Fullcalendar v5

Fullcalendarはバージョンによって記述方法が異なるので注意してください。
v4の記事を参考にしてもうまくいかないことが多かったです。

Fullcalendarをダウンロードする

公式ドキュメントよりダウンロードできます。
https://fullcalendar.io/docs/getting-started

NPMやCDNを利用する方法がありますが、ダウンロード方法はお好きな方法で問題ないです。
今回はzipファイルをダウンロードする方法し、以下のように追加しました。

event.blade.php
<!-- fullcalendar dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.0/moment.min.js"></script>

<!-- fullcalendar script -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.js"></script>

<!-- fullcalendar style -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.css">

モデルとデーブルを作成する

わたしの場合はModelsファイルの中にモデルを格納したいので、以下のように書いています。必要がなければ、Modelsのところはカットして問題ないです。

php artisan make:model Models/Event -m

まずはテーブルから書いていきます。
Fullcalendarには様々なプロパティが用意されていて、以下のサイトより確認することができます。
https://fullcalendar.io/docs/event-parsing
今回はシンプルに、予定のタイトルと登録する日付、文字の色を変更できるように設定していきます。

events.table.php
public function up()
    {
        Schema::create('events', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title', 100);
            $table->date('start');
            $table->string('textColor');
            $table->timestamps();
        });
    }

マイグレーションします。

php artisan migrate

カレンダーに登録したデータを表示する

カレンダーの表示とカレンダーに登録したデータを表示するControllerとルーティングを書いていきます。

php artisan make:controller EventController

indexアクションにカレンダーの表示とDBに保存されているデータをカレンダーに表示させるコードを書きます。
今回はDBファサードを利用するので、useで宣言が必要です。

EventController.php
use App\Models\Event;

public function index(Request $request) {

  if ($request->ajax()) {
    $data = DB::table('events')->select('title', 'start', 'textColor')->get();
    return response()->json($data);
    }

return view('/event');
}

$request->ajax()でデータがajax通信かどうかを確認できるメソッドです。
if内では、eventsテーブルからデータを持ってきて、response()->json()を利用し、json形式で保存するようにしています。
このようにすることによって、PHPで保存したデータをJavaScriptで利用できるようになります。

続いて、ルーティングは以下のように設定します。

web.php
Route::get('/index', EventController@index);

カレンダーの情報を登録する

カレンダーの情報を登録するためのコードを先ほど作成したEventControllerに書いていきます。データの登録後、カレンダーが表示されているページに戻るようにしています。

EventController.php
public function store(Request $request)
    {
        $event = new Event;

        $event->title = $request->input('title');
        $event->start = $request->input('start');
        $event->textColor = $request->input('textColor');
        $task->save();

        return redirect('/event');
    }

続いてルーティングです。

web.php
Route::post('/store', EventController@store);

カレンダーの表示と登録フォームを設定する

カレンダーの表示自体はとても簡単で、カレンダーを表示させたい場所に以下のコードを書くだけで表示できます。

event.blade.php
<div id="calendar"></div>

フォームはCSSを考慮せずに書いているのでご了承ください。

event.blade.php
<form method="POST" action="{{ route('/store') }}">
   @csrf
  <input type="text" name="title">
  <input type="date" name="start">
  <input type="color" name="textColor">
  <button type="submit">登録</button>
</form>

データの登録なので、methodはPOSTを指定し、actionにはフォームが送信されたらEvercontrollerのstoreアクションにつながるようにしたいので、先ほどweb.phpで指定したroute('store')を指定しています。

type属性にはそれぞれ入力方式に適したものを入力し、name属性にはeventsテーブルで指定したカラム名を入力します。

続いてはJSを書いていきます。
今回はevent.blade.phpに直接書きましたが、便宜JSだけディレクトリを分けてもいいかと思います。

event.blade.php
<script>
  $(document).ready(function () {
    $('#calendar').fullCalendar({
      // はじめりの曜日を月曜日に変更 デフォルトは日曜日になっており、日=0,月=1になる
      firstDay: 1,
      headerToolbar: {
                     right: 'prev,next'
                     },
      events: '/home',
    });
  });
</script>

eventsにはカレンダーに保存している情報を表示するアクションが書かれている、EventControllerのindexメソッドにつながるように書く必要があります。
そのため、先ほどweb.phpで指定した/homeを指定しています。

今回はシンプルなカレンダーになっていますが、Fullcalendarには様々なオプションが用意されています。
簡単なものだと、headerToolbarはrightだけではなく、left,centerのカスタマイズもできますし、eventをドラッグしたり、日にちを選択して登録することなんかもできます。
詳しいオプションに関しては、Fullcalendarの公式マニュアルをご確認ください。
https://fullcalendar.io/docs#toc

これで、カレンダーにデータの登録と、登録したデータの表示ができるようになりました!

さいごに

今回は登録フォームを直接書いていますが、今後は日付から選択をしてデータの登録をしたりなど、いろいろとカスタマイズをしてみたいと思います!

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

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

手軽にLaravelのクエリをログ出力する

Laravelで実行されたSQLを確認する方法は幾つもありますが、
必要な時だけ、手軽に確認ができるようにする方法をメモしておきます。

試した環境

Laravel: v6.14.0

コード

まずは、切り替え用の config を作成します。
本番環境等では特に意識しなくて良いよう、デフォルトは false です。

.env
SQL_DEBUG=true
config/app.php
'sql_debug' => env('SQL_DEBUG', false),

また、コードを追記するのは、以下のファイルのみです。

app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    //...

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        if (config('app.sql_debug')) {
            DB::listen(function ($query) {
                Log::info([
                    'sql' => $query->sql,
                    'bindings' => $query->bindings,
                    'time' => $query->time.' ms'
                ]);
            });
        }
    }
}

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

[Laravel7.x]外部キー制約の記述に関する備忘録

今回の題

Laravelの外部キー制約について、7.xからよりシンプルに書けるようになっていた事を最近になって知ったので備忘録として書きました。
内容薄いですが勘弁してください。

前提

前提として、主テーブルusersと従テーブルpostsは1対多の関係にあるとする。
そして、postsusersのidカラムを参照するuser_idカラムを外部キーとして定義する。

何がどうなったのか

外部キーを定義するとき、6.xまでは以下の書き方だけだった。

Schema::table('posts', function (Blueprint $table) {  
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
});

ただこの書き方は冗長であるという事で、
7.xからは加えて以下の書き方も出来るようになった。

Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});

1行でカラムの定義と外部キーとしての設定が出来るようになったのですごく楽。

参考

readouble - 外部キー制約

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

Artisanコマンドをスクリプトから呼ぶときのメモ

Laravel外のスクリプトからArtisanコマンドを呼びたくなったときに

<?php
require_once(PATH_TO.'/vendor/autoload.php');
$app = require(PATH_TO.'/bootstrap/app.php');
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
// stdoutをstringで受け取りたいとき↓
$output = new Symfony\Component\Console\Output\BufferedOutput;
// そのままコンソールに表示したいとき↓
// $output = new Symfony\Component\Console\Output\ConsoleOutput;
$kernel->call('migrate', [ '--force'=>true, '--quiet'=>true ], $output);
// ↓ stringで取り出す
echo $output->fetch();

shell_exec('php artisan migrate')とかしてもいいのだろうけど…

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

ArtisanコマンドをLaravel外のスクリプトから呼ぶときのメモ

ArtisanコマンドをLaravel外のスクリプトから呼びたくなったときに

<?php
require_once(PATH_TO.'/vendor/autoload.php');
$app = require(PATH_TO.'/bootstrap/app.php');
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
// stdoutをstringで受け取りたいとき↓
$output = new Symfony\Component\Console\Output\BufferedOutput;
// そのままコンソールに表示したいとき↓
// $output = new Symfony\Component\Console\Output\ConsoleOutput;
$kernel->call('migrate', [ '--force'=>true, '--quiet'=>true ], $output);
// ↓ stringで取り出す
echo $output->fetch();

shell_exec('php artisan migrate')とかしてもいいのだろうけど…

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

Quill editor 画像をS3にアップロードし表示する方法

使用言語
・PHP(Laravel)
・javascript(jQuery)
・HTML

使用ライブラリ
https://quilljs.com/

実装したい機能

画像を選択 → apiを叩く → s3にアップロード → s3のパスで画像を表示

※Quillのeditor内に画像を挿入すると、
デフォルトだと、ローカルのパスで画像が表示される。

作業フロー

①quill jsを読み込む
②javascriptを書く(ajax通信を行う)
③PHP(laravel)でajaxのリクエスト受け取ってs3に保存などもろもろ行う
④apiのルート確保

①quill jsを読み込む

https://quilljs.com/
公式サイトよりドキュメント参照。

↓ページquill jsクイックスタート
https://quilljs.com/docs/quickstart/

<!-- Include stylesheet -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

<body>
<!-- エディター部分 -->
<form>
<div id="editor"  style="height: 200px;"></div>
<input type="hidden" name="main" id="project_contents_inner">
<button type="submit" name="subbtn">投稿</button>
<form>
</body>


<!-- Include the Quill library -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>

<!-- 後ほど書くjavascript読み込む -->
<script src="{{ asset('/js/main.js') }}"></script>

②javascriptを書く(ajax通信を行う)

main.js
//ここのツールバーはカスタムできます。
var toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
  [{ 'size': ['small', false, 'large', 'huge'] }],  // custom dropdown
  ['link'],
  ['image'],
  ['video'],
  [{ 'list': 'ordered' }, { 'list': 'bullet' }],     // superscript/subscript
  [{ 'align': [] }],
  [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme

  ['clean']                                         // remove formatting button
];

const editor = new Quill('#editor', {
  bounds: '#editor',
  modules: {
    toolbar: this.toolbarOptions
  },
  placeholder: 'なんか書いてー
',
  theme: 'snow'
});


// /**
//  * Step1. select local image
//  *
//  */
function selectLocalImage() {
  const input = document.createElement('input');
  input.setAttribute('type', 'file');
  input.setAttribute('name', 'up_file');
  input.click();

  // Listen upload local image and save to server
  input.onchange = () => {
    const file = input.files[0];
    // file type is only image.
    if (/^image\//.test(file.type)) {
      saveToServer(file);
    } else {
      console.warn('You could only upload images.');
    }
  };
}


// /**
//  * Step2. save to server
//  */
function saveToServer(file) {
  /* Ajax経由で画像登録 */
  var fd = new FormData();
  fd.append('up_file', file); // 画像
  $.ajax({
    url: 'https://hoge/upload/image', // 画像登録処理を行うPHPファイル(api)
    type: 'POST',
    data: fd,
    cache: false,
    contentType: false,
    processData: false,

  }).done(function (data) {
    const url = data.path;
    console.log(data)
    insertToEditor(url);
  }).fail(function (jqXHR, textStatus, errorThrown) {
    console.log('ERROR', jqXHR, textStatus, errorThrown);
  });
  return false;
}

// /**
//  * Step3. insert image url to rich editor.
//  *
//  * @param {string} url
//  */
function insertToEditor(url) {
  // push image url to rich editor.
  const range = editor.getSelection();
  editor.insertEmbed(range.index, 'image', url);
}

// quill editor add image handler
//画像が選択されたら↑の関数がstep1~順番に走る
editor.getModule('toolbar').addHandler('image', () => {
  selectLocalImage();
});

//プロジェクトを投稿する際にエディター内のものを#project_contents_innerに入れる。
$(function () {
  $("#project_form").on("submit", function () {
    $("#project_contents_inner").val($(".ql-editor").html());
  })
})

③PHP(laravel)でajaxのリクエスト受け取ってs3に保存などもろもろ行う

php artisan make:controller Api/ProjectUploadController
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ProjectUploadController extends Controller
{
    public function upload_image(Request $request)
    {
    $filePath = '/picture/contents';
    $image = $request->file('up_file');
    $t = Storage::disk('s3')->putFile($filePath, $image, 'public');
    $imagePath = Storage::disk('s3')->url($t);

         return response()->json([
            'message' => 'ok',
            'path' => $imagePath,
        ], 200, [], JSON_UNESCAPED_UNICODE);
    }
}

※s3への保存方法は別途調べてください。

④apiのルート確保

api.php
Route::post('hogehoge/image/upload','Api\ProjectUploadController@upload_image');

完成

これでeditor内部にs3からのパスで画像を表示できているでしょう。
あとはquillで作成したHTML入りの文章をDBに保存するなりで大丈夫でしょう。

動かなかったら質問お願いします。
日本語の記事見かけてないので書きました。

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