- 投稿日:2019-11-26T22:59:08+09:00
【Laravel】VirtualBox+Vagrantで環境構築
こんにちは!!
PHPの始めることになりました!
使用するフレームワークがLaravelです
VirtualBox+Vagrantを使って環境構築したので
今回はVirtualBox+Vagrantを使ってLaravelの環境構築のやり方についてまとめました!!やること
ゼロからlaravel-projectという名前のアプリケーションを作ることにします!
内容は、、
まず、VirtualBox+Vagrantを使ってLaravelの環境構築をします。
その後、laravel-projectという名前のアプリケーションを作ります!
それでは始めましょう!!前提
前提条件が以下になります
ホストOS
macOS(Mojave)
使用するツール
virtual box
仮想環境を作成するツール
vagrant
仮想環境を操作するツール仮想環境を構築しましょう
それでは仮想環境を構築していきましょう
VirtualBox、Vagrantのインストール
下記の公式サイトからVirtualBox、Vagrantダウンロードしてください
ダウンロード後は指示に従いインストールしてください
VirtualBox 公式サイト
https://www.virtualbox.org/wiki/Downloads
Vagrant 公式サイト
https://www.vagrantup.com/
インストール終了後、PCを再起動してくださいHomesteadを追加します
Vagrant boxのlaravel/homesteadを追加します
仮想マシンのOSの種類を追加するイメージですちなみに、laravel/homesteadの他にubuntuやCentOsなどがあります
公式サイトから追加可能なboxを見ることができます
興味ある方は他のboxも追加してみてください!ターミナル$ vagrant box add laravel/homestead追加されたboxは
vagrant box list
で確認できますターミナル 実行結果(例)$ vagrant box list laravel/homestead (virtualbox, 8.2.1) ←これが表示されればOKです! ubuntu/bionic64 (virtualbox, 20190705.0.0)Homesteadの設定
Homesteadの取得
Homesteadの設定に必要なファイルをgitで取ってきます
ついでにvagrantで使用する共有ファイル(laravel
好きな名前でOK)も作成します
共有ファイルについては後ほど説明しますターミナル$ mkdir laravel $ cd laravel $ git clone https://github.com/laravel/homestead.git Homestead $ cd Homestead $ bash init.shHomestead.yamlの編集
Homestead.yamlの一部を下記のように編集します
Homestead.yaml(変更前)folders: - map: ~/Code to: /home/vagrant/Code変更前の意味
ホストOS上の~/Code
ディレクトリを
Vagrant上で/home/vagrant/Code
ディレクトリとして扱うHomestead.yaml(変更後)folders: - map: ~/Desktop/laravel to: /home/vagrant/Code変更後の意味
ホストOS上の~/Desktop/laravel
ディレクトリを
Vagrant上で/home/vagrant/Code
ディレクトリとして扱う仮想環境の立ち上げ
vagrantを立ち上げ、接続しましょう
ターミナル$ cd ~/Desktop/laravel/Homestead $ vagrant up $ vagrant ssh接続後、下記が表示されれば完了です
_ _ _ | | | | | | | |__ ___ _ __ ___ ___ ___| |_ ___ __ _ __| | | '_ \ / _ \| '_ ` _ \ / _ \/ __| __/ _ \/ _` |/ _` | | | | | (_) | | | | | | __/\__ \ || __/ (_| | (_| | |_| |_|\___/|_| |_| |_|\___||___/\__\___|\__,_|\__,_|vagrant上でLaravelのアプリケーションの作成
Laravelのアプリケーションを作っていきましょう
composerをダウンロードする
ターミナル$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" $ php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" $ php composer-setup.php $ php -r "unlink('composer-setup.php');"
composer.phar
ができれば完了ですcomposerが導入できているか確認しましょう
ターミナル 実行結果$ composer -v ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.9.1 2019-11-01 17:20:17プロジェクトを作成しよう
下記がプロジェクトの作成コマンドです
laravel-project
の部分を好きなアプリ名に変更してくださいターミナルcomposer create-project laravel/laravel laravel-project --prefer-distサーバーを立ち上げよう
下記がphpサーバーを立ち上げるコマンドです
ターミナルphp artisan serveサーバーが立ち上がっているか確認しましょう
http://192.168.10.10をクリックして確認してください以上で、ゼロからlaravel-projectという名前のアプリケーションを作成完了です!!
参考記事
Laravel公式サイト
https://readouble.com/laravel/mac+VirtualBox+Vagrant with HomesteadでLaravel
https://vamola.info/programming/mac-laravel/疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!
- 投稿日:2019-11-26T20:02:45+09:00
Ajax通信で403エラーになる
Ajax通信が403エラーで返ってきていた原因の調査にとても時間を取られたので、備忘録として残します。
環境
- php: 7.1.30
- Laravel: 5.4.36
- Webサーバー: Apache 2.4
返ってきていたエラー
Ajaxでデータを更新するフォームで、送信したのにうまく更新されていない
->Laravelのログを見てみる
->ログには何も出ていない
->Chromeのコンソールで確認した結果、以下のエラーが返ってきていました。PUT https://xxx/yyy/zzz 403 (Forbidden)Ajax通信が403エラーでうまく動作していません。なぜかアクセス禁止の扱いを受けてしまっています。
調査
- Apacheのエラーログの確認
Laravelのログに何も出ていない->そこまで処理が来ていないということなので、Apacheのエラーログを確認することにしました。(ここまでに結構な時間がかかってしまいました)
Apacheのエラーログを確認すると、以下のエラーが出ていました。
[Thu Nov 21 19:42:57.735794 2019] [authz_core:error] [pid 22270] [client xx.yyy.zzz.nn:12345] AH01630: client denied by server configuration: /home/xxx/public_html/public/xxxxxx, referer: https://xxx/yyy/zzzとりあえず
AH01630: client denied by server configurationのエラー文言で検索してみるも、"アクセス制限の記述方法がApache2.2と2.4で変わっている"系の記事ばかりで、解決に繋がるようなめぼしい情報は見つかりませんでした。
とりあえずApacheの設定をどこか変更するのだろうと、闇雲に設定ファイルの中を覗き回っているうちに、1日が終わってしまいました。
ところで、最初にChromeのコンソールで確認したエラー文言は
- PUTで送信して403のエラー
というものでした。
もしかしてPUTメソッドが禁止されているのでは??
と自分で思いついたわけではなくヒントをいただいたので、該当箇所を確認することに。/etc/httpd/conf.d/userdir.conf <Directory "/home/*/public_html"> AllowOverride FileInfo AuthConfig Limit Indexes Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec Require method GET POST OPTIONS </Directory>↓これ
Require method GET POST OPTIONSPUTは許可されていませんでした。
解決
PUTではなくPOSTを使えばいいということがわかったので、Apacheの設定を変更せずともエラーを解消することができました。
- ルーティングのメソッド変更
Route::put('/xx/{xxx}/xx/{xxx}', 'SomethingApiController@somethingUpdate')↓
Route::post('/xx/{xxx}/xx/{xxx}', 'SomethingApiController@somethingUpdate')
- Ajax時のメソッド変更
type: "PUT",↓
type: "POST",
- 投稿日:2019-11-26T19:59:30+09:00
Laravel Adminでmigrate してデータが消えたら
Laravel Adminを使っていて、
migrate fresh
したら、初期の管理者データが消えてしまいました。
さくっと直せるようにLaravel Adminで使われているSeederを利用して、データを投入できるようにしますSeeder 作成
php artisan make:seeder AdminTablesSeeder
vendor/encore/laravel-admin/src/Auth/Database/AdminTablesSeeder.php
にLaravelAdminのSeederがあるので中身をコピーし、
ディレクトリ構成が異なるので、インポートを多少書き足します。
そうすると下記のように書き換えられます。AdminTablesSeeder<?php use Illuminate\Database\Seeder; use Encore\Admin\Auth\Database\Administrator; use Encore\Admin\Auth\Database\Role; use Encore\Admin\Auth\Database\Permission; use Encore\Admin\Auth\Database\Menu; class AdminTablesSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // create a user. Administrator::truncate(); Administrator::create([ 'username' => 'admin', 'password' => bcrypt('admin'), 'name' => 'Administrator', ]); // create a role. Role::truncate(); Role::create([ 'name' => 'Administrator', 'slug' => 'administrator', ]); // add role to user. Administrator::first()->roles()->save(Role::first()); //create a permission Permission::truncate(); Permission::insert([ [ 'name' => 'All permission', 'slug' => '*', 'http_method' => '', 'http_path' => '*', ], [ 'name' => 'Dashboard', 'slug' => 'dashboard', 'http_method' => 'GET', 'http_path' => '/', ], [ 'name' => 'Login', 'slug' => 'auth.login', 'http_method' => '', 'http_path' => "/auth/login\r\n/auth/logout", ], [ 'name' => 'User setting', 'slug' => 'auth.setting', 'http_method' => 'GET,PUT', 'http_path' => '/auth/setting', ], [ 'name' => 'Auth management', 'slug' => 'auth.management', 'http_method' => '', 'http_path' => "/auth/roles\r\n/auth/permissions\r\n/auth/menu\r\n/auth/logs", ], ]); Role::first()->permissions()->save(Permission::first()); // add default menus. Menu::truncate(); Menu::insert([ [ 'parent_id' => 0, 'order' => 1, 'title' => 'Dashboard', 'icon' => 'fa-bar-chart', 'uri' => '/', ], [ 'parent_id' => 0, 'order' => 2, 'title' => 'Admin', 'icon' => 'fa-tasks', 'uri' => '', ], [ 'parent_id' => 2, 'order' => 3, 'title' => 'Users', 'icon' => 'fa-users', 'uri' => 'auth/users', ], [ 'parent_id' => 2, 'order' => 4, 'title' => 'Roles', 'icon' => 'fa-user', 'uri' => 'auth/roles', ], [ 'parent_id' => 2, 'order' => 5, 'title' => 'Permission', 'icon' => 'fa-ban', 'uri' => 'auth/permissions', ], [ 'parent_id' => 2, 'order' => 6, 'title' => 'Menu', 'icon' => 'fa-bars', 'uri' => 'auth/menu', ], [ 'parent_id' => 2, 'order' => 7, 'title' => 'Operation log', 'icon' => 'fa-history', 'uri' => 'auth/logs', ], ]); // add role to menu. Menu::find(2)->roles()->save(Role::first()); } }seederの呼び出しに追加します
DatabaseSeeder<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call([ AdminTablesSeeder::class, ]); } }Composerのオートローダー再生成
composer dump-autoload実行
php artisan db:seed #または php artisan db:seed --class=AdminTablesSeeder参考資料
- 投稿日:2019-11-26T18:26:58+09:00
【laravel】 DBの存在するテーブルとカラムとカラムの型を取得するあとdoctrine/dbalでjson型が扱えない件
環境
MySQL 5.7
Laravel 5.8存在するテーブル
tableNames = []; foreach (DB::select('SHOW TABLES') as $table) { $dbName = config('database.connections.mysql.database'); $tableNames[] = $table->{'Tables_in_' . $dbName}; }存在するカラムとその型
型を取得
// 存在するカラムを取得 $columns = Schema::connection('mysql')->getColumnListing($tableName); $columnTypes = []; foreach ($columns as $column) { // カラムタイプを取得 $columnTypes[$column] = Schema::connection('mysql')->getConnection()->getDoctrineColumn($tableName, $column)->toArray()['type']; }
int
の場合Doctrine\DBAL\Types\IntegerType
のようにとれる。
これはSQLの型とPHPの型をマッピングするクラス。型判定
if ($type instanceof Doctrine\DBAL\Types\StringType) { // ごにょごにょ }json型でエラー
Doctrine DBALが2.4以下だとjson型がサポート外とのこと。
エラーが出るので以下のようにjson_arrayにマッピングさせると扱えるようになる。DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('json', 'json_array');
- 投稿日:2019-11-26T16:23:00+09:00
『Laravel + Vue.js』プロジェクト導入前の3つの心得
はじめに
Vue #2 Advent Calendar 2019 の7日目の記事です
この記事は
企業レベルで『プロジェクトとしてVue.jsを導入してみての感想』の共有を行うことが目的です、
そしてその中でも私の感じた『Laravel + Vue.jsプロジェクト導入前の3つの心得』についてご紹介致します。
記事内容に、間違いなどございましたらコメント欄でやさしくリプライしていただければ嬉しいです(^^♪
今回初投稿なので・・・前提条件
・ウェブフレームワークは既にLaravelを使用
・プロジェクト目的は、速度改善、脱スパゲッティーコード化、新規技術導入などを含む
・具体的なプロジェクト内容は、スマホのウェブページ、コンポーネント化
・Vue.jsの単一コンポーネントの利用(CDNではない)TL;DR
上記の条件下で私の感じた3つの心得
その1:JSのバンドルサイズ意識しよう
その2:ライブラリ導入の選定は早めに行おう
その3:なんでもVue.jsでやろうとせずスコープを決めようでは詳しく紹介していきます
心得その1:JSのバンドルサイズを意識してコーディングしよう
単一コンポーネントを利用する際、Laravelでは「laravelmix」と呼ばれるwebpackの進化系みたいなもの(warpper)を利用してアセットのコンパイルを行わないといけないのですが、
この際、バンドルされたJSファイルがすぐに肥大化するので最初から意識してコーディングしてねという話です。laravemixの設定ファイルはデフォルトでは以下みたいな感じになっています(Laravel 5.x系)
webpack.mix.jsconst mix = require('laravel-mix'); /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css');webpack知識の重要性
事前に「laravelmix抜き」の"本来のwebpackでVue.js"を使用する場合は、利用者さんがきちんとwebpackを理解してVue.jsを利用している可能性が高いかもしれませんが
あなたがもし今回初めてVue.jsを利用するVue.js初学者さんでwebpack利用経験がないのであれば、
「laravelmixで利用するwebpack」が別物だということを心得ておく必要があるかもしれません。実際Laravelで利用するlaravelmixは、webpackへの詳細な知識がなくてもある程度簡単に動作してくれます。
しかしwebpackについて理解なしにwebpackのwrapperのlaravelmixから利用するのは危険です。
あなたがもしwebpackの知識をきちんと理解しているのであればこの設定ファイルの設定見直しだけでもページの速度改善になります。
ちなみにwebpackの設定はlaravelmixより詳細に設定項目があり、より細かに調整ができます。バンドルサイズ肥大化の原因
webpack.mix.jsmix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css');上記の設定ファイルの中を見てみましょう。
laravelmixでバンドルする対象のファイルの記述が設定されていますね。
(resources/js/app.jsがVue.jsのVueインスタンスファイルにあたります。)
こちらのファイルが1ファイルにバンドルされて、public/js配下にコンパイルされます。(npm run~コマンド実行時)
肥大化する原因は私なりですが以下の2つのいずれかです。原因1:ライブラリの追加しすぎ
バンドルしたJSファイルはデフォルトのままで既に2万行近くあるので、そのままではとても重いです。
加えて使いたいライブラリなどが追加されれば、追加した分だけバンドルサイズが大きくなっていきます。原因2:Atomic Designを細かく分けすぎる
今回のプロジェクトでは部品化する際のデザインパターンを「Atomic Design」に則り進めたのですが、部品を細かくしすぎてもあまりよくありません。Atomレベルでさえ1つの.vueファイルで3KBくらいはあるのでコンパイル時にバンドルするJSサイズが肥大化する原因になってしまうのです。よって、再利用性の少ない「Atomレベルは作成しない」などルールを決めてもよいかもしれません。おまけ:extract設定してライブラリファイル分割する方法
webpack.mix.jsmix .js("resources/assets/js/app.js", "public/js") .extract(["vue", "axios", "vuex"])//bootstrap.js内でimportしているライブラリー名を記述 .version();バンドルで1ファイルになるとお伝えしましたが、laravelmixではextractに記述するだけでライブラリファイル分割可能です。
extractにライブラリ指定してあげれば、指定したライブラリ群がバンドルJSファイルから外にだされ、別ファイルとして(manifest, vendor.js)分割できます。このような設定はlaravelmixのアセットコンパイル公式の各バージョン毎に記述の仕方が書いてあります。Vue.jsをLaravelで書き始める前にぜひご一読ください。バンドルしたJSファイルサイズ目安
JSのバンドルサイズの目安は一休.comさんのスマホサイトのパフォーマンスについてのブログにも記載がありますが、
"一休"さんのスマホサイトのプロジェクト時では、1ページ300KBを上限にして、それ以下に抑えるようにコーディング意識していたりします。私のプロジェクトではこのようなルール決めを一切行いませんでした。結果、バンドル後のJSファイルがかなり肥大化してしまい重いスマホサイトが出来上がってしまいました。( ;∀;)
解消策:より具体的な目標を立てる
プロジェクト開始前にメンバー間で、具体的なバンドルサイズ目標をたてることが効果的です。1ページ300KB以下のバンドルJSファイルと残りのリソース700KBまでに抑えるなどより具体的な数値を設定しましょう。
他には、『Googleのpage speed insights(速度計測ツール)で70点以上を獲得する』などでもよいかもしれません。大事なのは達成できなさそうだとしても「意識すること」です。そのための目標の設定は重要かと思います。ちなみに
グーグル推奨値のリソースの合計サイズが1.6MBとの発表の記事を以前読んだことがあります。これは3G回線のモバイル端末でストレスなくアクセスできる最低の基準です。(実際には1.6MBでもまだ重い気がするので。)1.6MBは全体のリソースサイズなので、この中でJSはさらに小さく抑えるべきです。こういった基準値をプロジェクト前に調べてルール決めを行い認識合わせすることは大事です。
せっかく導入したのなら、ページが重くならないように気を付けましょう。心得その2:ライブラリ導入の選定は早めにしよう
これもかなり悩ましかったのですが、「とりあえずVue導入して、それからあとのこと考えればいいさ」みたいに考えていたので
Vue.js導入を最優先したのですが(ダメ人間)
導入前のライブラリの選定はものすごく大事です。選定を間違えると、先ほどの話のJSのバンドルサイズの肥大化につながります。加えて後から導入すると改修がより困難なものなどもあるので早めに検討してから導入したほうが良いでしょう。
ライブラリ選定も設計の一部だと心得てきちんと選定しましょう初めから導入したかったライブラリ
・vuex(状態管理ライブラリ)
・vue-cli(cli)
・storybook(UIコンポーネント管理系)
・vue-router(ルーティング系)
・SPAも可能ならNuxt.js中でもvuexは最初に導入しておけば手戻りが発生しなかったかと後悔しています。
propsでの受け渡しが親子孫までの関係のあるモジュールを作成した場合、vuexを導入した方がより簡単に人為的ミスを無くしてスマートにコードを書くことができると感じました。しかしvuexも完璧ではなく、全てvuex化してしまうと逆に重くなってしまったりするので、トレードオフを忘れずに。
vue-cliについてはあればおそらく前準備をコマンドライン入力で簡単にはじめられたことでしょう・・・参考サイト:『これでわかるはじめてのvue-cli』
storybookが初めからあればデザイナーとのコンポーネントのデザイン及び設計についてのやり取りがもっとスムーズになったことでしょう・・・参考サイト:Qiita記事『Storybook for Vue 入門』
vue-routerがあれば遅延ローディングルートを利用して各ルートコンポーネントごとに別々にチャンクして、訪れたルートの時だけロードできるようにルーティングできたでしょう・・・参考サイト:公式リファレンス『Vue Router』おまけ:一般的にWeb関連の企業で導入されているnode_modulesのライブラリ群
株式会社LINE様のVue.jsの勉強会で行われたUIT#5イベントでは多くの企業が以下のライブラリを導入してVue.jsのWebページなどを実装していました。参考までに共有します。
・vue-router
・vue-cli
・storybook
・vue-whiteroom(storybookに変わるライブラリ)
・Nuxt(SPA化など検討する場合のVue.jsのライブラリ)イベント情報詳細:UIT#5 わたしたちにとってのVue.js
解消策:プロジェクトスタート前にライブラリ調査・選定
vuexの導入を例に挙げたように使う可能性があるライブラリ群は早めに決めておきましょう。ただし、不要なライブラリは取り除かないとすぐに肥大化します。必要か不必要か今一度メンバーで確認し必要最低限のライブラリ選定を心がけ、同じようなライブラリが存在するか確認、もし存在すれば、より軽いサイズの代用ライブラリを導入しましょう。
又、ライブラリ自体をバンドルJSファイル外で読むこともできるので「バンドルに何を含めるのか」よく考えて選定してください。
心得その3:なんでもVue.jsでやろうとせずスコープを決めよう
今回のVue.jsの導入はLaravelでのVue.js導入だったのですが、プロジェクト開始直後私の頭の中では『新しい技術の良さを他のエンジニアに理解してもらう』ことに比重を置いていたので、Vue.jsをたくさん利用しようと考えていました。広める為にできるだけVue.jsでコーディングしようとしていたのです。その結果、本来Laravel側で事足りるものですらVue化してしまいました。JSバンドルサイズは3万行を超え、SPの初期読み込み時間はtestmysiteで(3Gで)10秒ほども出てしまうほど重いものになりました。JSのファイル数を減らせば読み込みが早くなると思っていた私ですが、1ファイルが非常に長い場合も同様にペナルティーになってしまいます。なんでもVue.jsでやろうとしてしまうと結果ページが重くなることさえありうるのです。(私の設計ミスの可能性もありますが・・・)
プロジェクトが進んでいくにつれ私の作成したVue.jsページはただ単に重い不便なページになっていました。( ;∀;)
ではどうすればよかったのでしょうか・・・私が今回、具体的に失敗した点は、
Laravelで利用できるインクルードなどを捨てすべてVue.jsのコンポーネントに修正していたところにあります。既存の動いているコードはどんなコードであれ資産です。なんでも改修しようとするのでなく、不便さや不要箇所、エラーやバグがある場合のみ改修すればよかったのかもしれません。
既にLaravelのインクルードで実装されていたヘッダー部分の共通部品までVue化を行い、ほかにもフッターなどのすでにあるモジュールですらすべてVueのコンポーネントにしてしまったのです。プロジェクトの目的にもよるとは思うのですが、パフォーマンスの観点でいうと部品が増えればバンドルしたJSファイルのサイズも比例して大きくなるので動いているモジュールすべてをスコープに含むことは間違っていたかもしれません。
Vue.jsの実装パターンはいろいろあるということを忘れてはいけません。
ページ全体をVue化してSPA化したり、一部品だけをVue化したり。
これはすべてに言える話なのですが『大事なのは何の目的で、そのライブラリ、又はそのフレームワークを利用しているのか念頭に置いてコーディングすること』です解消策:Vue.js適用範囲のスコープを決める
『どれがLaravel側で行うべきこと』で、『どれがVue.js側で行うべきこと』かということを意識する。これらは、おそらく「心得レベル」の話ではなく、「設計レベル」の話になってしまうかと思われます。しかし、Vue.jsをプロジェクトに導入する際は、適用範囲のスコープを決めて実装することが重要だと感じました。加えて、スコープの妥当性、適用範囲の妥当性についても考えましょう。
せっかくLaravelでVue.jsを使っているのに、Laravelの良さを捨てて何でもかんでもVue化してしまうのはもったいないこと。
Vue化する際は必ず、よりいい方法をトレードオフすることがとても重要だと強く感じました。結論
その1:バンドルサイズを意識したコーディングを心がけないと重いサイトになる
その2:ライブラリ選定を早めにしないと手戻りが発生する
その3:Vue.jsでなんでもやろうとすると保守しにくいサイトになる総じて
Vue.jsのプロジェクト導入を検討しているそこのアナタ!簡単に導入できるからと言って侮らないこと!
- 投稿日:2019-11-26T16:19:18+09:00
LaravelにESLintとPrettierを導入する時にやったことの全て
はじめに
この記事では
Laravel5.8
にESLint
とPrettier
を導入します。
Laravel
のMVCモデルでは、Viewの部分は*.blade.php
ファイルによって書くことが一般的です。
しかし最近では、JavaScriptの描画技術の幅やページの描画スピードの速さなどを理由として、bladeは完全に捨ててVue.jsにフロントエンドの全てを任せる構成をとるプロジェクトが多いと感じています。今回、ESLintを導入する対象となった
Laravel5.8
のプロジェクトは僕がPMをしているプロジェクトです。このプロジェクトでも、
Laravel
のbladeはほとんど使用しておらず、画面描画はresourses/js/
に置かれているVue.js
によって行っています。今回の記事の対象者は、LaravelでVue.jsを使っているor使いたい方全てです。
なぜESLintとPrettierを使うべきなのか
「なぜ
ESLint
とPrettier
を併用して使うべきなのか」ということについては、Laravel
に限らず、広く議論されていることですので、ここでは触れません。
この議論の結論を言えば、「ESLint
だけ導入」あるいは「Prettier
だけ導入」というのは諸事情があって微妙だからどちらも使おうね、ということです。以下では
ESLint
とPrettier
を導入するにあたり、僕が感じている利点について触れておきます。集団開発でコードフォーマットが統一されていないのはかなりしんどい
「何を綺麗と感じるか」は人それぞれです。
しかし、一方で個々の感性が異なるために、しばしば地獄をみることになります。
- 「あれ?末尾のセミコロンは前全部消したはずなのにまたAさんが足し直してる、、、」
- 「キャメル?パスカル?命名規則が混在している、、、」
- 「シングルクォーテーションとダブルクォーテーションが混在している、、、」
これを十人十色などと言ってはいけません。阿鼻叫喚です。
コードのフォーマットや変数の命名規則が理由で議論が始まったりプルリクエストが通らないとしたら、せっかくLaravel
を使っているのにあなたの開発スピードは早いとは言えなくなりませんか?
- フォーマットや変数の命名規則は開発者間で統一する
- 開発者ごとのフォーマット調整による余計な差分はなくし、コードレビューの可読性をあげる
この重要性を理解しましょう。コードレビューは常にロジックにのみ集中するべきです。
1人で開発するからといってフォーマットが煩雑なのは許されない
僕の価値観では、コードというものは「誰が見ても美しくあるべき」です。あなたがフォーマッターを使わずに書いたコードは、他人が見ても必ず美しいと思えるでしょうか。また、開発者が感じる「美しさ」は誰が保証してくれるのでしょうか?
「ソースコードは1人で扱うから」とは言っても、いつかもう1人のメンバーが加わる場合もあります。
そう言った際に無駄なコストを払わないように、1人だとしてもフォーマットには基準を設けて整形しておくのがマナーだと僕は考えています。
ESlint
とPrettier
は非常に優秀なフォーマッターなので、「ESLintとPrettierが共通のルールによって整形してくれたのだから信じよう」と開発者は思うのです。いつESLintとPrettierを導入するのか
上記で「
ESLint
とPrettier
を導入しなさい」と布教したのですが、僕も最近になってようやくこれらのフォーマッターを導入したのです。実は、
ESLint
とPrettier
を導入するまでにGitHubでのcommitは500を超えており、そこそこの規模のプロジェクトになっていました。ですから、かなりの数の*.js
*.vue
ファイルが存在していました。導入パターン
ESLint
を全てのファイルにかける(=現存する*.js
ファイルや*.vue
ファイルを一気に整形する)場合、非常にコンフリクトが発生しやすくなります。対象の全てのファイルの全体に対して改行ポイントやスペース、コンマや命名規則、変数宣言や比較演算子などありとあらゆるコードを修正することを想像すれば「コンフリクトが発生しやすい」というのは火を見るより明らかです。そして、そのコンフリクトは激しくなりがちであることも理解できるでしょう。
ESLint
を導入するタイミングはおおよそ次のパターンに分類できます。
今からLaravelをセットアップするあなた
→後悔はさせないので、いますぐ導入しましょう。最もEasyです。このタイミングで気づいたあなたは偉い!すでにそこそこの規模になっているあなた
→プロジェクト全体で大きな変更がないタイミングを伺いましょう。そこそこの努力と忍耐が必要です。かなりの規模になっていて集団開発が盛んになっているあなた
→この時点で何かしらのフォーマッターを入れていないのはまずい状況ですが、導入のタイミングも難しいです。大量のコンフリクトを覚悟して慎重にタイミングを見計らいます。開発者全体で打ち合わせなどをする必要があるかもしれません。僕は2番に該当していたつもりですが、それでも一気に
ESLint
を全てのファイルにかけて本当に血の海を見ました。全ファイルが血を流して倒れました。それでもなんとかコンフリクトが少なくなるようなタイミングを見計って行えば、ギリギリ耐えます。
あなたのフォーマッターへの想いだけを信じてください。ESLintとPrettierを導入する
やっと本題です。しかしやることはそんなに多くありません。
今回は、プロジェクトの途中から
ESLint
とPrettier
を導入します。(上記での2or3番を想定しています。1番でももちろん全く問題なし!)
ここから先はLaravel
がどうこうというより、ESLint
とPrettier
自体をよく理解していることが重要です。パッケージをインストールする
今回必要なパッケージは以下になります。たくさんあります。
以下のパッケージは開発時にしか必要ありませんので--save-dev
オプションをつけてインストールしてあげます。$ npm install babel-eslint eslint eslint-config-prettier eslint-config-standard eslint-friendly-formatter eslint-loader eslint-plugin-html eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-standard eslint-plugin-vue laravel-mix-eslint --save-devESLintの設定ファイルを作成する
続いて、
.eslintrc.js
ファイルを新しく作成します。
このファイルではESLintとPrettierの整形ルールを定めます。$ cd yourLaravelProject $ vim .eslintrc.js.eslintrc.jsmodule.exports = { root: true, parserOptions: { parser: 'babel-eslint', }, env: { browser: true, }, extends: [ 'standard', 'plugin:vue/recommended', 'plugin:prettier/recommended', 'prettier/vue', ], plugins: [ 'vue', 'prettier', // prettierをESLintと併用します ], rules: { // ESLintが使用する整形ルールのうち、自分がoffにしたいルールなどを指定する 'vue/no-v-html': 'off', // v-htmlの使用について 'vue/prop-name-casing': 'off', // Propsの変数の命名規則について 'no-console': 'off', // console.log()の使用について 'no-unused-vars': 'off', // 使われていない変数について 'camelcase': 'off', // camelcaseについて // この先はPrettierのルール "prettier/prettier": [ "error", { printWidth: 120, tabWidth: 2, useTabs: false, singleQuote: true, trailingComma: 'all', bracketSpacing: true, arrowParens: 'avoid', semi: false, }, ] } }この設定ファイルは結構厳しめにルールを設けているつもりです。
そもそもせっかくコードフォーマッターを導入するのにルールを緩くしておく必要はないからです。しかし、もしプロジェクトの途中での導入に際してあまりルールを厳しく入れたくないという場合は、
extends:[]
の部分を見直すと良いと思います。このあたりは下記サイトが参考になりました。
(参考)Vue.jsスタイルガイドとeslint-plugin-vue検証ルールのマッピングまた
Prettier
のルールもあなたの好みです。
Prettier
のルールは公式ドキュメントを参考に変更すると良さそうです。
(参考)Options - Prettierファイル変更時にESLintを使用する
webpack
が上記の整形ルールを使用してファイル変更をwatch
してくれるようにwebpack.mix.js
を編集します。webpack.mix.jsconst mix = require('laravel-mix') /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ // ESLintに関する設定(この部分を丸ごと追記するイメージです) if (!mix.inProduction()) { // 本番環境ではESLintは使用しない mix.webpackConfig({ module: { rules: [ { enforce: 'pre', exclude: /node_modules/, loader: 'eslint-loader', test: /\.(js|vue)?$/, }, ], }, }) } // watchするファイルやポート番号などに関する設定(今回の内容とは関係ありません) mix .js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css') .browserSync({ // browserSyncの設定 files: ['resources/js/**/*', 'resources/sass/**/*', 'resources/views/**/*', 'public/css/**/*'], port: 3000, ui: { port: 3001, }, proxy: 'localhost:8000', //php artisan serveで立ち上げた8000番をProxyする }) // 本番環境ではバージョン付けによるキャッシュ対策を施す(今回の内容とは関係ありません) if (mix.inProduction()) { mix.version() }これで基本的なセットアップは全て終了です。
$ npm run watchを唱えることによっていつも通り開発を開始しましょう。コンソールでESLintとPrettierが大量のエラーを吐いてくれるはずです。
実際に動作を確認する
あなたのコンソールを見てください。
例えば次のようになっている場合、/Users/yourName/Laravel/resources/js/pages/A/B.js 350:1 error Delete `····` prettier/prettierこれは
Prettier
が怒っている様子です。このエラーが指し示すところは、
「/js/pages/A/
にあるファイルB.js
の350行目の1列目から無駄な空白があるから消せ」
ということです。これらのエラーは地道に解消しても良いですが、あなたの見ているエラーの量を見てください。地獄ですよね。
でも安心してください、自動でこれらのエラーをほとんど解消してくれる
ESLint
のコマンドがあります。エラーを吐いているファイル名とそのディレクトリを確認して、コンソールで
// ESLintとPrettierの両方を実行する $ eslint resources/js/pages/*/*.js --fixを実行しましょう。
ファイル名はresources/js/pages/*/*.js
のようにワイルドカードを使用して指定できます。
これによってresources/js/pages/*/*.js
に該当するファイルのエラーが自動で解消されていきます。
eslint ファイル名 --fix
コマンドによってESLintとPrettierの両方が実行されることに注意してください。
これはすでに上記で導入したeslint-config-prettier
の恩恵を受けています。
ESLintとPrettierの併用に関する詳しい説明は以下の記事などがわかりやすいと思います。
(参考)ESLint - Prettier連携のやり方と仕組みこれでLaravelの
*.js
ファイルや*.vue
ファイルに対してESlintとPrettierを導入することができました。どのようなエラーを吐いているかはあなたのプロジェクト次第です。
コンソールでのエラーを見ながら
.eslintrc.js
ファイルで整形方法を調整しましょう。特定の整形ルールをoff
にすることもできますから、あなたの開発チームメンバーにとって最良の設定を記述し、幸せな開発サイクルを目指しましょう。最後に
この設定を行ったことで、あなたのLaravelの
*.js
ファイルや*.vue
ファイルは全ての開発者間でフォーマットが統一されました。今後の展望としては
1. git commitのときにESLintを自動で強制する
2.*.php
ファイルに対しても何かしらのLint
を施す本記事ではこれらの設定はオーバーワークになりますので、別記事で紹介しようと思います。
長くなりましたが、ありがとうございました。
- 投稿日:2019-11-26T14:55:31+09:00
実際に運用しているサービスのLaravelのファイル構成を共有してみる。
Laravelを始めるにあたって、どんなファイル構成が正しいのかわからない
実際私たちも全然わからなかったので、今の運用形式を共有してみる。だれかの助けになったり、指摘をいただけたらとても嬉しいと思います
階層毎に h タグの階層をつけてます
app
アプリケーションファイル、基本的なアプリケーションコードはここに入れている
ほかにはKernel.phpという、最初に実行されうファイルがおいてある。console
コンソールから利用するアプリケーションコードはここに入れている
Command
コンソールから利用するコマンドを入れている
日別実行、バッチファイルなどが入っているうちのチームではECSのタスクのスケジューリングから
dockerコンテナ内でcronを設定するようにして、これらのファイルを実行している。Exceptions
例外処理を入れるためのフォルダだと思われる。
しかし使っていない。
例外のハンドリングができていないのでいずれやりたい。Helpers
グローバルなメソッドを定義している
どこでも関数名()
でその処理を呼び出せるので便利だが、グローバル変数環境がすごく汚染される
極力使わないようにしているif (! function_exists('hoge')) { /** * hogeする * * @param Eroquent\Collection Hoge * @param Integer 取得したい hoge_id * @return String 内容 */ function hoge($hoge, $piyo_id) { return $hoge->hogeSections()->where('piyo_id', $piyo_id)->first(); } }Http
ControllerやMiddlewware、Requests、ViewComposersが入っている。
Controller
いろんなControllerが入ってる
管理画面は /admin 以下にControllerをまとめている
そのほかにも /markdown や /auth などがある。自由に使い分けて良いと思う。
Middleware
Controllerの前に実行するミドルウェアをたくさん入れているフォルダ
EncryptCookiesでCookieの暗号化
Authenticate、認証ミドルウェア
CheckForMaintenanceModeといったメンテナンス用ページ作成するときに使う
TrimStrings、getパラメータから特定のkeyを削除してくれる(password)などそのほかに、サイト計測の値を操作したり、CSRFトークンチェックなどを実装して入れている
Requests
postされた値のバリデーションルールを定義しているファイルが配置されている
ViewComposers
viewで利用する共有処理をまとめておける。
グローバルなメソッドは定義せず、極力ここにまとめておきたい。Jobs
非同期処理を行うことを前提としたコードを入れておく。
railsでいう sidekiq 的なものが内装されている。Logging
ログフォーマットを独自のものに書き換えたりするときの処理を入れておく。
例えばjson形式で吐き出して、ログをS3に保存して、CloudWatchに送り、Athenaとかで解析するなどの使い道がある。メールを送信するときの処理を書いておく
私たちはメール内容をDBに文字列で保存(ビジネスチームが編集するため)
送信処理はこのディレクトリのファイルに記述
送信先や送信タイミングは各Controllerに任せている。Models
Laravelを学習し始めるとき、多くの開発者はmodelsディレクトリが存在しないことに戸惑います。しかし、意図的にこのディレクトリを用意していません。多くの別々の人達にとって、その意味合いは様々なため、"models"という言葉の定義は曖昧であることに私達は気づきました。
https://readouble.com/laravel/5.5/ja/structure.html
Laravel公式でも議題にしているが、どこに配置するのが良いのか議論した結果、私たちは
app
配下に置いている
Rails を使ったサービスが多いので、 Rails と同じ配置ならわかりやすいだろうという判断だ。私たちはRailsに慣れ親しんでいるので私たちの定義ではここに配置するのが良いだろうという判断なので、正解はないような気がしている。
Controllerと階層を合わせて
http
以下に入れるというのもありだが。
Modelはデータを管理する場所であって、あまりhttp
とは関係ないなと思い外に出している。Providers
正直全然使ったことないので、Laravel公式の文章を持ってくる
https://readouble.com/laravel/5.3/ja/providers.htmlLaravelアプリケーション全体の起動処理における、初めの心臓部です。皆さんのアプリケーションと同じく、Laravelのコアサービス全部もサービスプロバイダを利用し、初期起動処理を行っています
色々なプロバイダがデフォルトで配置されているが
私たちのアプリケーションでは、
routes
ファイルたちの読み込み順をRouteServiceProvider
で変更している
通常のroutesファイルの処理を行う前に、redirect処理をしたかったからだ。
redirect.php
というファイルをroutes
ディレクトリ以下に新規追加し、そちらを優先的に処理するよう記述している。また、先ほどの
viewComposer
で作成したファイルをどのviewファイルで利用できるようにするかを定義し、読み込んでいるComposerServiceProvider
というファイルもある。Services
システム全体で利用する、他システムとの連携部の共通処理をまとめている。
私たちのアプリケーションではAWSへの画像アップロード処理、CSV作成処理、マークダウンをHTMLに変換するときの処理などを共通にしている。
詳しくはこの記事を読むとわかりやすい、おすすめ。
https://qiita.com/nunulk/items/6b7a7bbda17192f6b2f5Bootstrap
あのデザインフレームワーク!と思う人も多いと思いますが違います。
アプリケーションを実行するときに最初に読み込まれるファイルです。この子たちを呼び出したりしております。
- httpのkernel
- consoleのkernel
- exceptionsのkernel
この記事がわかりやすいのでおすすめ。
Laravelのリクエスト開始からコントローラにたどり着くまで
https://qiita.com/takyam/items/c2d397dd486c047dcbb1#step-3-bootstrapConfig
基本的にLaravelに初めから入っている、認証機能、メール機能、非同期処理機能などの設定はここで設定が可能です。
便利ですね。
database
DB周りのファイルが入っている
factories
テストコードを実行するときに必要なサンプルデータを作る処理を入れておく
migrations
DB構造の変更を履歴を残して管理してくれているおなじみのディレクトリ。
DB変更のたびにファイルが増える。seeds
アプリケーションを実行する上で必要となるマスターデータを入れるための処理を記述するファイルを配置する。
全部を記述する必要はないと考え都道府県データ
性別データこのようなマスターデータのみseedsでgit管理している。
public
静的なファイルを配置したいときに利用する
faviconを配置したり、robots.txtなどを配置している。また、Laravelのアプリケーション動作時はこのディレクトリにコンパイルされたファイルたちが配置される。
CSS、JS
SCSSのコンパイル結果がここに保存されていく。
基本的にはgitignore
するべき。fonts
こちらも同じくコンパイルされたものが入る
管理画面でfont-awesome
を利用しているのでそれはここに入っている。images
基本的な画像はここに配置している。
フォルダ構成は極力viewファイルと合わせているresorces
コンパイル前のjs、scss、viewファイルなどフロント周りのファイルが入っている
js
私たちのチームでは生jsを利用。
アプリケーションの形式上、jsを使う部分がほぼないため、フレームワークは導入せず。フォルダとファイルの規則はシンプルで、viewファイルの構成に合わせることにしている。
生のjsで書いている、無論 jquery も使っていない。
必要十分で非常にやりやすい、チャンスがあればフレームワーク良い感じにしたい。lang
多言語対応用のファイル、入社直後の中国人エンジニアが英語を日本語に翻訳してくれた。
それ以降特に問題なく使えている、すごい、ありがとう。下層ディレクトリ
- en
- ja
バリデーション周りの和訳設定'accepted' => ':attributeを承認してください。', 'active_url' => ':attributeが有効なURLではありません。', 'after' => ':attributeには、:dateより後の日付を指定してください。', 'after_or_equal' => ':attributeには、:date以前の日付を指定してください。', 'alpha' => ':attributeはアルファベットのみがご利用できます。',sass
cssはsass形式で書いている。
ディレクトリ構成はviewファイルと1:1対応するようにしている。それ以外のファイルとしてはこれらを用意している
- layouts
- mixins
- module
- elements
- variables
views
viewファイルです、デフォルトのこの配置のまま利用しています。
admin
front
lpこのようなディレクティブだけ切っております。
routes
ルーティング系ファイルですね
- admin.php
- web.php
- redirect.php
おもに、この3つを使ってます。
adminとredirectは独自のファイルですね。リダイレクトはもちろんngixでやったほうが早いのですが、メンテナンスコストなどを鑑みてこうしております。
storage
今日までこのファイルを認識しておりませんでした。
私たちは独自でS3との連携を行う処理を、Serviceディレクトリ以下に作りましたがこちらに作るのが本来正しいのかもしれません。
https://readouble.com/laravel/5.5/ja/filesystem.html
LaravelはFrank de Jongeさんが作成したありがたいほど素晴らしい、抽象ファイルシステムであるFlysystem PHPパッケージを提供しています
LaravelとFlysystemの統合によりローカルのファイルシステム、Amazon S3、Rackspaceクラウドストレージを操作できる、シンプルなドライバが提供できました。更に素晴らしいことにそれぞれのシステムに対し同じAPIを使用しているため、ストレージをとても簡単に変更できるのです。tests
テストコードを配置してます。
FeatureテストとUnitテストの2種類ですね。Featureテストではリクエストテストを最低限カバーし、Unitテストで重要な部分のみテストを書いております。
カバー率を上げていきたいところです。
- 投稿日:2019-11-26T10:39:13+09:00
【Laravel開発ノート】ドメインを設定するポイント
1.目的
ローカル環境で「http://192.168.10.10/」→「http://weibo.test」と表示したい。
2.設定のポイント
2.1 「.env」ファイル
APP_DEBUG=true APP_URL=http://weibo.test2.2 「homestead.yaml」ファイル
sites: - map: weibo.test # <--- ここ! to: /home/vagrant/code/Laravel/public2.3 「/etc/hosts」ファイル
$ vim /etc/hosts最後の一行に以下の内容を追加
192.168.10.10 weibo.test
- 投稿日:2019-11-26T00:41:52+09:00
【Laravel】メール送信失敗時のログを残す
Laravel で複数のメールアドレスへの送信処理を書いた時、メール送信が失敗したアドレスの一覧を取得したかったが、ドキュメントには載っていなかったのでメモとして残しておく。
環境
Laravel 6.X
やり方
Mail::failures()
で送信失敗メールアドレスが配列で取得できる。(参考)
例えば、Mailable クラスの設定後、送信処理をコントローラに書く場合、下記のような感じ。MailSendController.phpnamespace App\Http\Controllers; use App\Mail\SampleMailableClass; use Illuminate\Support\Facades\Mail; use Illuminate\Http\Request; use Log; class MailSendController extends Controller { public function send() { $emails = ['hoge@example.com', 'huga@example.com'] Mail::to($emails)->send(new SampleMailableClass()); if (count(Mail::failures()) > 0) { Log::channel('slack')->warning(Mail::failures()); }; } }Slack へのログ投稿の方法は こちらの記事 に詳しく書かれていました。
参考
- 投稿日:2019-11-26T00:23:57+09:00
Laravel6でRedisとSSE(Server-Sent Events)を使った簡易チャットを作成してみた
私はLaravel初心者です。基本的にググりながら書いています。よろしくお願いします。
Laravelで簡単にチャットを作ってみようと思ったのですが、1秒ごとにDBにアクセスするようなのは避けたかったので、Redisでやってみるといいんじゃないかと思い、作成してみたので書かせていただきます。m(_ _)m
Redisを使ってチャットをするというやり方はこちらが参考です。
RedisとServer Sent EventでJavaScriptでチャットを作ってみた:電脳ヒッチハイクガイド:電脳空間カウボーイズZZ(電脳空間カウボーイズ)
このような簡単なチャットを作成します
自動生成ファイルもありますが、自分が最終的に変更したのは↓です
https://github.com/okumurakengo/laravel-sse-chat/pull/1/files
1. Homesteadで環境構築しました
参考:Laravel Homestead - Laravel - The PHP Framework For Web Artisans
vagrant@homestead:~$ pwd /home/vagrant vagrant@homestead:~$ composer create-project --prefer-dist laravel/laravel code #laravelのプロジェクト作成 /* 省略 */ vagrant@homestead:~$ cd code #laravelのルートに移動
/home/vagrant/code/public
の内容が読み込まれて、Laravelの最初のページを表示できました。
Laravelのバージョンは6.2です。2. コントローラー、ビュー作成
2-1. ルートを定義する
web.php
に1行追加routes/web.phpRoute::get('/chat', 'ChatController@index');2-2. コントローラー作成
$ php artisan make:controller ChatController #app/Http/Controllers/ChatController.php が作成されるコントローラーで画面が表示されるように変更します
app/Http/Controllers/ChatController.php<?php namespace App\Http\Controllers; use Illuminate\View\View; use Illuminate\Http\Request; class ChatController extends Controller { /** * チャット画面表示 * * @return View */ public function index(): View { return view('chat'); } }2-3. bladeファイル作成
chat.blade.php
を作成resources/views/chat.blade.php<!DOCTYPE html> <meta charset=UTF-8> <title>Document</title> <h1>Simple Chat</h1>
これで
/chat
にアクセスしてhtmlを表示することができますここまでの変更分はこのようになりました
https://github.com/okumurakengo/laravel-sse-chat/commit/b4792d091f8279d00a403ea7303b2ede9ddd8f17
3. vuejsを使えるようにする
参考:JavaScript & CSS Scaffolding - Laravel - The PHP Framework For Web Artisans
3-1. vueの足場(雛形)を作成
$ composer require laravel/ui --dev #vueの足場(雛形)を作成するために、laravel/uiをインストールする $ php artisan ui vue #vueの足場(雛形)を作成
php artisan ui vue
実行後は↓のような差分になりましたhttps://github.com/okumurakengo/laravel-sse-chat/commit/5c46d127f3fed00b252e32e75c4c8523ed0677ec
3-2. vueのコンポーネントを画面に表示する
自動生成された
ExampleComponent.vue
を表示してみます
app.js
とchat.blade.php
を変更resources/js/app.jsconst app = new Vue({ el: "#app", + template: '<example-component />', });
resources/views/chat.blade.php<!DOCTYPE html> <meta charset=UTF-8> <title>Document</title> + <script src={{ mix('js/app.js') }} defer></script> <h1>Simple Chat</h1> + <div id=app></div>$ yarn #ライブラリなどをインストール $ yarn dev #js、cssをビルドしてpublic/配下に出力。yarn watchとしたら変更を監視してくれるvueのコンポーネントを画面に表示することができました。
yarn dev
のビルドした出力結果も含まれていますが、ここで変更したコミットは以下です。
※今回は特に気にしませんでしたが、app.js
などの自動生成ファイルは.gitignore
に設定しましょうhttps://github.com/okumurakengo/laravel-sse-chat/commit/5ee0df386ceb298331d382ba2dafe3bc03afd7a9
4. チャットの画面を作成
app.js
でApp.vue
を読み込むように変更resources/js/app.jsVue.component('example-component', require('./components/ExampleComponent.vue').default); + Vue.component('App', require('./components/App.vue').default); const app = new Vue({ el: "#app", - template: '<example-component />', + template: '<App />', });
resources/js/components/App.vue
を作成resources/js/components/App.vue<template> <div id="app" class="container"> <div class="flex"> <div class="users"> <select v-model="selectUser"> <option v-for="user in users"> {{ user }} </option> </select> </div> <div class="chat"> </div> </div> <form> <input> <input type="submit" value="送信"> </form> </div> </template> <script> const users = ['Bob', 'Alice', 'Carol']; export default { data() { return { users, selectUser: users[0], } }, } </script> <style lang="scss" scoped> #app { font-family: Verdana; &.container { width: 500px; padding: 10px; background: #eee; .users { width: 30%; height: 300px; border-right: 1px solid gray; } .chat { width: 70%; height: 300px; padding: 10px; overflow: scroll; p { margin: 0; } } } .flex { display: flex; } } </style>ここでの変更、※
public/js/app.js
はyarn dev
かyarn watch
で生成されたファイルhttps://github.com/okumurakengo/laravel-sse-chat/commit/85f2216270110b2075b26a248c45cf2794812747
5. テーブル、モデル作成
チャットの内容をMySQLにも保存します
5-1. MySQL接続の設定
参考
【Laravel Homestead - Laravel - The PHP Framework For Web Artisans】
https://laravel.com/docs/6.x/homestead#connecting-to-databasesuser:homestead
password:secret
でMySQLに接続できるので、.env
を変更します.env- DB_CONNECTION=mysql - DB_HOST=127.0.0.1 - DB_PORT=3306 - DB_DATABASE=laravel - DB_USERNAME=root - DB_PASSWORD= + DB_CONNECTION=mysql + DB_HOST=127.0.0.1 + DB_PORT=3306 + DB_DATABASE=homestead + DB_USERNAME=homestead + DB_PASSWORD=secret5-2. テーブル作成
migrationファイル作成
$ php artisan make:migration create_chat_table --table chat Created Migration: 2019_12_11_145549_create_chat_table以下のように変更
database/migrations/2019_12_11_145549_create_chat_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateChatTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('chats', function (Blueprint $table) { $table->increments('id'); $table->string('user'); $table->string('post'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('chats'); } }
php artisan migrate
を実行する。※チャットのテーブル以外にもデフォルトであるマイグレーションファイルも実行される$ php artisan migrate Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.08 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (0.12 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (0.18 seconds) Migrating: 2019_12_11_145549_create_chat_table Migrated: 2019_12_11_145549_create_chat_table (0.2 seconds)
chats
テーブルができていればOK5-3. モデル作成
$ php artisan make:model Chat
生成された
app/Chat.php
を変更app/Chat.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Chat extends Model { protected $fillable = ['user', 'post']; }ここでの変更のコミットです
https://github.com/okumurakengo/laravel-sse-chat/commit/f8d4b20e68d10b3a7372165df761fef991466b73
6. チャットの登録をする
チャットの入力フォームからサブミットすると、ajaxでapiにデータを送るように変更します
resources/js/components/App.vue<template> <div id="app" class="container"> <div class="flex"> <div class="users"> <select v-model="selectUser"> <option v-for="user in users"> {{ user }} </option> </select> </div> <div class="chat"> </div> </div> - <form> - <input> - <input type="submit" value="送信"> - </form> + <form @submit.prevent="addPost"> + <input v-model="textValue"> + <input type="submit" value="送信"> + </form> </div> </template> <script> const users = ['Bob', 'Alice', 'Carol']; export default { data() { return { users, selectUser: users[0], + textValue: '', } }, + methods: { + async addPost() { + if (!this.textValue.trim()) { + return + } + await axios.post('/api/chat/add', { user: this.selectUser, post: this.textValue }) + this.textValue = '' + }, + }, } </script> // ...
api.php
に追加routes/api.php// ... Route::post('/chat/add', 'Api\\ChatController@add');Controllersフォルダに新たに
Api
フォルダを作成し、そこに新しくChatController.php
を作成します
add
メソッドで、MySQLとRedisにデータを保存します。app/Http/Controllers/Api/ChatController.php<?php namespace App\Http\Controllers\Api; use App\Chat; use Illuminate\View\View; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Redis; class ChatController extends Controller { /** * チャット保存 * * @param Request $request * @return JsonResponse */ public function add(Request $request): JsonResponse { $chat = Chat::make([ 'user' => $request->get('user'), 'post' => $request->get('post'), ]); $chat->save(); Redis::set('latest_created_at', $chat->created_at->toDateTimeString()); return response()->json(['result' => 'ok']); } }この状態でチャットで送信してみると
データを保存することができました
ここでのコミットです
https://github.com/okumurakengo/laravel-sse-chat/commit/3002bf6db1c1f0fc23b6c4ef1f154e1d8d2a4b27
7. チャットの一覧取得
チャットで別の人が打ち込んだ内容を取得するためにServer Sent Eventsを使って取得します。
Server Sent Events(SSE)とはサーバーからプッシュ通知を受信するためのHTTP接続をしてくれるAPIです。
よくwebsocketと比較されます、websocketはブラウザとサーバーで双方向通信できるが、SSEはサーバーからブラウザへの一方向の通信を行います。参考 : Server Sent Events using Laravel and Vue
created()
でServer Sent Eventsを使ってHTTP接続を開き、接続しっぱなしにしてくれます。resources/js/components/App.vue<template> <div id="app" class="container"> <div class="flex"> <div class="users"> <select v-model="selectUser"> <option v-for="user in users"> {{ user }} </option> </select> </div> - <div class="chat"> - </div> + <div class="chat" ref="chat"> + <div v-for="({ user, post, created_at }) in posts"> + <p><strong>{{ user }}</strong> <small>{{ created_at }}</small></p> + <p>{{ post }}</p> + <hr> + </div> + </div> </div> <form @submit.prevent="addPost"> <input v-model="textValue"> <input type="submit" value="送信"> </form> </div> </template> <script> const users = ['Bob', 'Alice', 'Carol']; export default { data() { return { users, selectUser: users[0], textValue: '', + posts: [], } }, + created() { + const es = new EventSource('/api/chat/event'); + es.addEventListener('message', e => { + const { posts } = JSON.parse(e.data) + if (posts.length) { + this.renderList(posts) + } + }); + }, methods: { async addPost() { if (!this.textValue.trim()) { return } await axios.post('/api/chat/add', { user: this.selectUser, post: this.textValue }) this.textValue = '' }, + renderList(posts) { + this.posts = [...this.posts, ...posts] + // 下に追加したのでスクロールする + this.$nextTick(() => this.$refs.chat.scrollTop = this.$refs.chat.scrollHeight) + }, }, } </script> // ...
api.php
に追加routes/api.php// ... Route::post('/chat/add', 'Api\\ChatController@add'); + Route::get('/chat/event', 'Api\\ChatController@event');
Server Sent EventsでJSONを返すようにします
app/Http/Controllers/Api/ChatController.phpuse App\Chat; use Carbon\Carbon; use Illuminate\View\View; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Redis; use Symfony\Component\HttpFoundation\StreamedResponse; // ... /** * チャット一覧取得 * * @return StreamedResponse */ public function event(): StreamedResponse { // 最近のチャット5件と、一番最近のcreated_atを取得 $chats = Chat::orderBy('created_at', 'desc')->limit(5)->get()->sortBy('created_at')->values(); $tmpLatestCreatedAt = optional($chats->last())->created_at ?? Carbon::minValue(); $response = new StreamedResponse(function() use ($chats, $tmpLatestCreatedAt) { printf("data: %s\n\n", json_encode(['posts' => $chats])); ob_flush(); flush(); while(true) { $latestCreatedAt = is_null(Redis::get('latest_created_at')) ? Carbon::minValue() : Carbon::parse(Redis::get('latest_created_at')); if ($latestCreatedAt->gt($tmpLatestCreatedAt)) { // チャットに更新があった場合はテーブルから取得 $latestChats = Chat::where('created_at', '>', $tmpLatestCreatedAt)->orderBy('created_at', 'asc')->get(); $tmpLatestCreatedAt = $latestCreatedAt; } echo 'data: ' . json_encode(['posts' => $latestChats ?? []]) . "\n\n"; ob_flush(); flush(); $latestChats = null; sleep(1); } }); $response->headers->set('Content-Type', 'text/event-stream'); $response->headers->set('X-Accel-Buffering', 'no'); $response->headers->set('Cach-Control', 'no-cache'); return $response; } // ...チャットの内容を更新に合わせてリアルタイムに取得できました。
ここでの変更です
https://github.com/okumurakengo/laravel-sse-chat/commit/7515b4cd22c67d4a074166b51655688673b66668
8. 「
Bobが入力中です
」と表示させるresources/js/components/App.vue<template> <div id="app" class="container"> <div class="flex"> <div class="users"> <select v-model="selectUser"> <option v-for="user in users"> {{ user }} </option> </select> </div> <div class="chat" ref="chat"> <div v-for="({ user, post, created_at }) in posts"> <p><strong>{{ user }}</strong> <small>{{ created_at }}</small></p> <p>{{ post }}</p> <hr> </div> </div> </div> <form @submit.prevent="addPost"> - <input v-model="textValue"> + <input v-model="textValue" @keyup="typing"> <input type="submit" value="送信"> + {{ typingMessage }} </form> </div> </template> <script> const users = ['Bob', 'Alice', 'Carol']; export default { data() { return { users, selectUser: users[0], textValue: '', posts: [], + typingUsers: [], } }, created() { const es = new EventSource('/api/chat/event'); es.addEventListener('message', e => { - const { posts } = JSON.parse(e.data) + const { posts, typing_users: typingUsers = [] } = JSON.parse(e.data) if (posts.length) { this.renderList(posts) } + if (!_.isEqual(this.typingUsers, typingUsers)) { + this.typingUsers = typingUsers + } }); }, methods: { async addPost() { if (!this.textValue.trim()) { return } await axios.post('/api/chat/add', { user: this.selectUser, post: this.textValue }) this.textValue = '' }, renderList(posts) { this.posts = [...this.posts, ...posts] // 下に追加したのでスクロールする this.$nextTick(() => this.$refs.chat.scrollTop = this.$refs.chat.scrollHeight) }, + typing: _.throttle(async function () { + await axios.post('/api/chat/typing', { user: this.selectUser }) + }, 700), }, + computed: { + typingMessage() { + const typingOtherUsers = this.typingUsers.filter(user => user !== this.selectUser) + if (typingOtherUsers.length === 0) { + return '' + } + if (typingOtherUsers.length === 1) { + return `${typingOtherUsers[0]}が入力しています` + } + if (typingOtherUsers.length > 1) { + return '複数人が入力しています' + } + } + }, } </script> // ...
api.php
に追加routes/api.php// ... Route::post('/chat/add', 'Api\\ChatController@add'); + Route::post('/chat/typing', 'Api\\ChatController@typing'); Route::get('/chat/event', 'Api\\ChatController@event');
入力中のユーザー情報を保存して、配列で返すように変更
app/Http/Controllers/Api/ChatController.php+ /** + * 入力中の人の情報を保存 + * + * @param Request $request + * @return JsonResponse + */ + public function typing(Request $request): JsonResponse + { + Redis::sadd('typing_users', $request->get('user')); + return response()->json(['result' => 'ok']); + } /** * チャット一覧取得 * * @return StreamedResponse */ public function event(): StreamedResponse { // 最近のチャット5件と、一番最近のcreated_atを取得 $chats = Chat::orderBy('created_at', 'desc')->limit(5)->get()->sortBy('created_at')->values(); $tmpLatestCreatedAt = optional($chats->last())->created_at ?? Carbon::minValue(); $response = new StreamedResponse(function() use ($chats, $tmpLatestCreatedAt) { echo 'data: ' . json_encode(['posts' => $chats]) . "\n\n"; ob_flush(); flush(); while(true) { $latestCreatedAt = is_null(Redis::get('latest_created_at')) ? Carbon::minValue() : Carbon::parse(Redis::get('latest_created_at')); + $typingUsers = Redis::smembers('typing_users'); if ($latestCreatedAt->gt($tmpLatestCreatedAt)) { // チャットに更新があった場合はテーブルから取得 $latestChats = Chat::where('created_at', '>', $tmpLatestCreatedAt)->orderBy('created_at', 'asc')->get(); $tmpLatestCreatedAt = $latestCreatedAt; } - echo 'data: ' . json_encode(['posts' => $latestChats ?? []]) . "\n\n"; + echo 'data: ' . json_encode(['posts' => $latestChats ?? [], 'typing_users' => $typingUsers]) . "\n\n"; ob_flush(); flush(); $latestChats = null; + Redis::del('typing_users'); sleep(1); } }); $response->headers->set('Content-Type', 'text/event-stream'); $response->headers->set('X-Accel-Buffering', 'no'); $response->headers->set('Cach-Control', 'no-cache'); return $response; }入力中の人の情報を出せることができました
ここでの変更です
https://github.com/okumurakengo/laravel-sse-chat/commit/4ff8bfeb455b19d22276c50b4689d7ee6db28c25
最後まで見ていただいてありがとうございましたm(_ _)m
- 投稿日:2019-11-26T00:18:50+09:00
AWS Coud9でphpMyAdmin画面へアクセスしたときに 404|not foud エラーを解決する方法
Cloud9で開発環境を整えて、時間がたってからなぜかphpMyAdminへアクセスできず、ログイン画面で見れなくなる症状に躓きました。
いくら調べても解決しなかったんですが、もしかしたら稀にいるかもしれないと思って記事を書くことにしました。
解決のために事前チェックその1
サーバーを起動しているか確認
サーバー起動コマンド$ ^Cec2-user:~/environment/project1 $ php -S $IP:$PORT PHP 7.3.11 Development Server started at Mon Nov 25 15:22:41 2019 Listening on http://127.0.0.1:8080 Document root is /home/ec2-user/environment/project1 Press Ctrl-C to quit.下記のコマンドで起動したサーバーは phpMyAdminのページへアクセスしても 404|not foudとなります。
サーバー起動コマンド(1)$ ec2-user:~/environment/project1 $ php artisan serve Laravel development server started: http://127.0.0.1:8000サーバー起動コマンド(2)$ ec2-user:~/environment/project1 $ php artisan serve --port=8080 Laravel development server started: http://127.0.0.1:8080事前チェックその2
Laravelのプロジェクト(ディレクトリ)内でmysqld を起動しているか確認
mysqld起動コマンド$ sudo service mysqld start ec2-user:~/environment/project1 $ sudo service mysqld start Starting mysqld: [ OK ]mysqldの状態確認コマンド$ ec2-user:~/environment/project1 $ sudo service mysqld status mysqld (pid 20873) is running... [ OK ]running...と表示されていれば、起動中です。
一応、mysqld 停止コマンドも明記しておきます。
mysqldの停止コマンド$ ec2-user:~/environment/project1 $ sudo service mysqld stop Stopping mysqld: [ OK ]事前チェックその3
phpMyAdminのURLを "https://~~.vfs.cloud9.~~.amazonaws.com/phpMyAdmin/index.php" まで指定している
それでも解決しなかった場合、Cloud9上で作成した他のプロジェクト内でmysqldが起動している可能性があるので、それを停止します。
(アクセス権限に関しても調べて変更してみましたが、結果的に関係ありませんでした。)
linuxコマンドに詳しい方はもうわかると思うので、読み進める必要はないです。念のためlinux初心者の方のために細かいコマンド実行の様子を記しておきます。
まずはディレクトリの移動
親ディレクトリ移動$ ec2-user:~/environment/project1 $ cd ../そこでlsコマンドを実行すると他にもプロジェクトがあることがわかります。
ec2-user:~/environment $ ls project1 project2 README.mdproject1 のディレクトリから project2 (別のプロジェクト)のディレクトリを移動します。
親ディレクトリ移動$ ec2-user:~/environment/ $ cd project2その中で再度、mysqldの状態を確認してみると、、、
mysqldの状態確認コマンド$ ec2-user:~/environment/project2 $ sudo service mysqld status mysqld (pid 20873) is running... [ OK ]動いてる。。。。(このプロジェクト、ずっと触ってなかったのに、、)
それで、もしかしたらと思い停止コマンドを実行して停止し、
project1 の方のディレクトリに移ってから、mysqldを起動してみるとmysqldの停止コマンド$ ec2-user:~/environment/project2 $ sudo service mysqld stop Stopping mysqld: [ OK ] $ ec2-user:~/environment/ $ cd ../project1 ec2-user:~/environment/project1 $ sudo service mysqld start Starting mysqld: [ OK ]無事表示されました!!
以上です。 うっかり他のプロジェクトでのmysqldを停止し損ねて、
別プロジェクトで使おうとしたときに起きた症状の対策方法でした。AWSはまだ触り始めたばかりなので、わからないことだらけで苦戦していますが、
上記の方法以外で解決方法をしっていたり、設定方法をご存知の方がいらっしゃいましたら
ぜひコメントいただけるとありがたいです。