- 概要
- より良い設計をするために
- 前提知識
- 設計
- インストール
- Homestead
- Homestead: サブドメインと作業ディレクトリを追加
- Homestead: 同一LAN内からアクセス
- メール送信
- SMTPでメール送信
- SMTP認証でメール送信
- GmailのSMTPでメール送信
- AmazonSESでメール送信
- SendGridでメール送信
- Mailgunでメール送信
- gitの設定
- 更新(他の開発者の更新を取り込む手順)
- 認証
- マルチ認証(管理者とユーザで別々に認証)
- API認証(API利用時にPassportでOAuth2認証)
- 強制ログイン
- Basic認証
- アプリのWebView用に認証
- URLを .env の内容から生成する
- ルーティング
- テンプレート
- ファイル
- データベース
- 実装メモ
- 基本的なアプリケーションの作成
- 簡易ブログの作成
- テスト・デバッグ
- トラブル対応
概要
Laravel - Wikipedia
https://ja.m.wikipedia.org/wiki/Laravel
Laravelソースコードの歩き方
https://kore1server.com/326
Laravelのここがすごい - Qiita
http://qiita.com/nunulk/items/78d70ac809948b470bbc
■特徴
・Symfonyベース。他にも大量のライブラリ。重いとは言われている
・Composerでインストール&依存解決
・ファイル構成はある程度開発者任せ。規約で強制はされない。Ver5からMVCではない
・artisan(アルチザン / 「職人」の意味)でプログラムの雛形作成など
・DIとサービスコンテナが活用されている
・ファサード(後にStatic proxyと呼び方変更)でどこからでも簡単に静的に命令を呼べる
ただしファサードは強制されず他の呼び出し方もできるので、好みに合った形式を選択できる
・マイグレーションとシーダーに対応
・データベースは代理キーでの管理が前提
・Illiminateという名前空間 https://kore1server.com/326
■バージョンごとの差(ググって解説を探すとき混乱しないように)
・Laravel3まではシンプルで理解しやすい、が売りだった。4からは大きいシステムの制作に耐えられるよう大幅に改良された
・4まではMVCだが、モデルの扱いが曖昧なので5ではMVCを捨てた。モデルの置き場所は開発者が決める
・5からはappはアプリケーションのロジックのみの置き場に
■どのバージョンを使うべきか
2017年9月時点で、LTS版は5.1と5.5
PHP7環境なら、5.5を使っておくといい
PHP5環境なら、5.1か5.4のどちらかになりそうだが、サポート終了期間に大差ないので5.4で良さそう
Laravelのリリース日と修正期間のまとめ - Qiita
http://qiita.com/ykhirao/items/576a9eea735f91350072
■軽量版
Laravelの軽量版としてLumenがある
LumenとLaravelの違い - ララ帳
https://laravel10.wordpress.com/2015/04/21/lumen%E3%81%A8laravel%E3%81%AE%E9%81%95%E3%81%84/
■2018年11月時点での良さそうな入門記事
Laravel入門情報 2018年10月版 - Qiita
https://qiita.com/studio15/items/8519fed2d6d5b1a573c0
PHPフレームワークLaravel5.5チュートリアル、CRUD一回り作ってみる - 名古屋のWebシステム開発 iNet Solutions
https://www.inet-solutions.jp/technology/laravel-tutorial/
より良い設計をするために
Laravelでウェブアプリケーションをつくるときのベストプラクティスを探る - Qiita
http://qiita.com/nunulk/items/b1e2da51b5dabab92da0
Laravel5のアーキテクチャから学ぶより良いクラス設計 - Qiita
http://qiita.com/nunulk/items/2c637d3952096ef74677
LaravelのORMで初心者から職人へ - Qiita
http://qiita.com/henriquebremenkanp/items/cd13944b0281297217a9
Laravel Recipes日本語版
http://recipes.laravel.jp/
Laravel リファレンス
https://book.impress.co.jp/books/1114101107
https://github.com/laravel-jp-reference/chapter8
■Laravel経験者に話を聞いたときのメモ(2017/09/13)
・バージョン5.4を採用した
・Homesteadはあくまでも開発環境用として使った。本番環境はEC2にPHPなどをインストールして構築した
・本番反映は「デプロイ → マイグレーション → シード」を実行
・基本構成は MVC + Service + Repository
・DIとEloquentには慣れが必要だった
・「php artisan make:auth」を使うとカラム名の変更などが大変なので、認証はmiddlewareを使って一から作った
■追加考察メモ
・リポジトリのContractsは、個別に作るよりも「DatabaseRepository」というインターフェースに統一する方がいいか
これでもテスト時にリポジトリを差し替えたりは支障ないはず
と思ったが、DIではContractsを注入するので共通のDBインターフェースにするのはまずいかも
AppServiceProviderで関連付けも行っている
サービスからリポジトリを呼ぶとき、実態を直接呼ばずにContractsを呼ぶべきかも
・APIなどを使う場合、サービス内に直接接続処理を書かずにリポジトリ内で処理するか
・サービスはドメイン駆動設計をもとに、リポジトリはデータベースや相手APIの仕様をもとにするか
前提知識
■Composer
PHPの依存性管理ツール
依存ライブラリ郡も含めて、まとめてインストールできる
また、composer.json に使っているライブラリが記録される(ファイルが無ければ作成される)
composer require 使いたいライブラリ
もしくは
composer.json に使いたいライブラリを記述して
composer install
とコマンドを実行するとインストールされる
この場合、自分で一つ一つ依存ライブラリをダウンロードする必要が無い。つまり環境の再現が容易にできる
ダウンロードしたファイルは vendor フォルダ内に格納される
PHP開発でComposerを使わないなんてありえない!基礎編 - Qiita
http://qiita.com/niisan-tokyo/items/8cccec88d45f38171c94
Laravel5のプロジェクトをGitで管理する - Qiita
https://qiita.com/zaburo/items/bc448a9fbf2d35194302
php - vendorディレクトリをGit管理下に置かないのは何故? - スタック・オーバーフロー
https://ja.stackoverflow.com/questions/23725/vendor%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%...
■オートローディング
名前空間に従って自動的にファイルを読み込む
require_once などで明示的にファイルを読み込む必要がない
Composerを使うことで簡単に導入できる
PHPでクラスをspl_autoload_registerを使ってオートロードする - バカンス駆動開発
http://egapool.hatenablog.com/entry/2013/11/17/195045
PHPのオートロード(autoload) - Qiita
http://qiita.com/atwata/items/5ba72d3d881a81227c2a
src/Refirio/Greeting/Hello.php
<?php
class Hello
{
function say()
{
echo 'Hello World!';
}
}
sample.php
<?php
require_once "src/Refirio/Greeting/Hello.php";
$hello = new Hello();
echo $hello->say();
↓オートロードに対応させる
composer.json
{
"autoload": {
"psr-4": {
"myapp\\": "src/"
}
}
}
Composerでインストール
composer install … composer.json に設定した内容に合わせて vender ディレクトリ内にプログラムが作られる
以下のようにしてオートロードを利用できる
src/Refirio/Greeting/Hello.php
<?php
namespace myapp\Refirio\Greeting;
class Hello
{
function say()
{
echo 'Hello World!';
}
}
sample.php
<?php
require_once "vendor/autoload.php";
$hello = new myapp\Refirio\Greeting\Hello();
echo $hello->say();
↓名前空間のエイリアスを作成
<?php
require_once "vendor/autoload.php";
use myapp\Refirio\Greeting\Hello;
//use myapp\Refirio\Greeting\Hello as Hello; // クラスに別名を指定する場合
$hello = new Hello();
echo $hello->say();
■デザインパターン
勉強メモ
http://refirio.org/memos/study/20161028_design_pattern/slide/
http://refirio.org/page/memo/design_pattern
■trait
限定的な多重継承を行う
PHP5.4 の新機能 trait のまとめと実際の利用例 | 株式会社インフィニットループ技術ブログ
https://www.infiniteloop.co.jp/blog/2014/08/php54-traits/
PHP5.4以降で導入されたトレイト(trait)というしくみ - ts0818のブログ
http://ts0818.hatenablog.com/entry/2015/10/15/183555
<?php
trait Facebook
{
function post($body)
{
echo 'Facebookに投稿しました:' . $body . "\n";
}
}
trait Twitter
{
function post($body)
{
echo 'Twitterに投稿しました:' . $body . "\n";
}
}
class Mailer
{
public function send($body)
{
echo 'Mailerで送信しました:' . $body . "\n";
}
}
class SendmailMailer extends Mailer
{
use Facebook;
/*
// 同名のメソッドがある場合
use Facebook, Twitter {
Facebook::post insteadof Twitter; // instead of ... 代わりに
Twitter::post as tweet; // as ... として
}
*/
public function send($body)
{
echo 'Sendmailで送信しました:' . $body . "\n";
}
public function getPath()
{
return '/usr/sbin/sendmail';
}
}
$mailer = new SendmailMailer();
$mailer->send('こんにちは!');
$mailer->post('こんにちは!');
//$mailer->tweet('こんにちは!');
■DI
DIとは?DIコンテナとは?試してみた(前編)[PHP][DI] - あざらし備忘録。
http://shiro-goma.hatenablog.com/entry/2014/06/22/102236
Dependency Injectionを特定のDIコンテナに頼らず実現する - Qiita
http://qiita.com/Hiraku/items/48fbdfca4b63c74494e9
DI・DIコンテナ、ちゃんと理解出来てる・・? - Qiita
http://qiita.com/ritukiii/items/de30b2d944109521298f
DIコンテナの本当の使いどころ | 技術トピックス | ウルシステムズ株式会社
https://www.ulsystems.co.jp/topics/025
DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる - Qiita
https://qiita.com/okazuki/items/a0f2fb0a63ca88340ff6
DIコンテナのテスト以外での利点について - Qiita
https://qiita.com/crexista/items/606976d941728a90b42b
勉強メモ
http://refirio.org/page/memo/di
■リフレクション
クラスに関する情報を参照したり、変更したりすることができる
PHPでリフレクション : アシアルブログ
http://blog.asial.co.jp/751
パラメータとしてタイプヒントされたクラスを取得
http://php.net/manual/ja/reflectionparameter.getclass.php
タイプヒントからクラスを取得する例
class Message {
}
class Hello
{
function say(Message $message) {
}
}
$reflection = new ReflectionClass('Hello');
$parameters = $reflection->getMethod('say')->getParameters();
echo $parameters[0]->getClass()->getName(); // 「Message」と表示される
■ドメイン駆動設計
厳密にドメイン駆動設計に沿っているわけでは無いようだが、知っておくと理解が進みそう
ドメイン駆動設計
http://qiita.com/haazime/items/6119097071149a362f7f
Domain駆動開発入門 | キャスレーコンサルティング 技術ブログ
http://www.casleyconsulting.co.jp/blog-engineer/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%...
おい!なんでその処理をコントローラに書いているんだい?
https://zenn.dev/marty_ojiya/articles/ada4528d2b619c
■Mockery
作成前のクラスを、すでに存在するかのようにテストできる
例えば
<?php
class SampleExecuter
{
public function doSomething($checkType, $param)
{
$factory = new CheckerFactory($checkType);
$checker = $factory->create();
if ($checker->checkSomething($param)) {
return 'trueが返ったよ';
} else {
return 'falseが返ったよ';
}
}
}
このような SampleExecuter があった場合、このテストは CheckerFactory が完成するまで行うことができない
が、モックとなる CheckerFactory を作り、テストできるようにする
MockeryとPHPUnitを使う(最低限) - Qiita
https://qiita.com/zaburo/items/b559782179565bb1c538
Mockery 0.8.0 日本語ドキュメント
https://kore1server.com/202/Mockery+0.8.0+%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%83%89%E3%82%AD%E3%83%A5%E3%...
「Mockery」を使ってサクッとPHPのテストを書いてみる | アライドアーキテクツ エンジニアブログ
http://tech.aainc.co.jp/archives/3918
composerでインストール
(「--dev」をつけると通常の「composer install」ではインストールされないものにできる。「composer install --dev」とする。)
cd C:\localhost\home\test\public_html\mockery
composer require --dev phpunit/phpunit
composer require --dev mockery/mockery
tests\test1.php を作成して以下を入力
<?php
require_once 'vendor/autoload.php';
class SampleTest1 extends PHPUnit_Framework_TestCase
{
public function testSample1()
{
//Mockを設定
$m = \Mockery::mock('Hoge');
$m->shouldReceive('Hello')
->with('foo')
->andReturn("Hello foo");
//Helloメソッドを実行
$m->Hello('foo');
$this->assertEquals("Hello foo", $m->Hello('foo'));
}
}
以下のコマンドでテストを実行
vendor\bin\phpunit tests/test1.php
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 85 ms, Memory: 3.25MB
OK (1 test, 1 assertion)
設計
Laravelを最低限扱えるようになったら気を付けたいこと
■選択項目の管理
項目自体は設定ファイルで管理する
config\option.php
'administrator' => [
// 利用状況
'status' => [
'operation' => '運用中',
'stop' => '運用一時停止中',
],
],
ビューに表示する際はモデルを使用する
app\DataAccess\Eloquent\Administrator.php
public function getStatusLabelAttribute()
{
return config('option.administrator.status')[$this->attributes['status']];
}
■ミドルウェア
リクエストのフィルタリングとレスポンスの変更を行う
Laravel5.1以前ではコントローラでbeforeフィルターを設定するような仕様だったらしい
現在は非推奨となり、代わりにミドルウェアが推奨されている
ミドルウェア 5.4 Laravel
https://readouble.com/laravel/5.4/ja/middleware.html
Laravelでアクション実行前・後などで共通の処理をさせたいあなたに!コントローラーフィルターまとめ - Qiita
http://qiita.com/kenguy/items/321faa1bd2570a39ea35
以下のコマンドで、リクエストの雛形を作成
php artisan make:middleware CheckCount
引き続き内容を調整
\test\app\Http\Middleware\CheckCount.php
セッションの値によって、ページの表示を制御するものとする
public function handle($request, Closure $next)
{
$count = session('count', 0);
if ($count < 10) {
return redirect('/home');
}
return $next($request);
}
\test\app\Http\Kernel.php
ミドルウェアを登録する
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
〜略〜
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'count' => \App\Http\Middleware\CheckCount::class, // ←追加
];
\test\routes\web.php
ルーティングでミドルウェアを呼び出す
// ルートプレフィックス
Route::prefix('/admin')->group(function () {
Route::get('/session', function () {
$count = session('count', 0);
return 'Admin count=' . $count;
})->middleware('count');
});
Kernel.php の $routeMiddleware に登録すると、ルーティングから必要に応じてミドルウェアを呼び出せる
Kernel.php の $middleware に登録すると、すべてのルーティングに対して自動的に実行される
■サービスコンテナ
Laravelでは以下のようなコードが頻繁に登場する
これだけでEntryServiceクラスのオブジェクトが $entry に入る。newを書かなくてもいい
public function __construct(EntryService $entry, CommentService $comment)
{
$this->entry = $entry;
$this->comment = $comment;
}
大まかな仕組みとしては、リフレクションを使用して自動でクラスを判断し、展開したものを自動で渡すようになっているみたい
以下では、個別にサービスコンテナを設定する方法などをまとめる
サービスコンテナ - ララ帳
https://laravel10.wordpress.com/tag/%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%86%...
サービスコンテナ
https://readouble.com/laravel/5.4/ja/container.html
LaravelのDIコンテナはどう使われているのか - 新人Webエンジニアの記録。
http://blog.fagai.net/2016/09/17/laravel-dependency-injection/
\test\routes\web.php
interface SenderInterface {
public function send($message);
}
class MailSender implements SenderInterface {
public function send($message) {
return "メールで $message を送りました。";
}
}
class BikeSender implements SenderInterface {
public function send($message) {
return "バイク便で $message を届けました。";
}
}
class Messenger {
protected $sender;
public function __construct(SenderInterface $sender) {
$this->sender = $sender;
}
public function send($message) {
return $this->sender->send($message);
}
}
//App::bind('sender', 'MailSender'); // DIコンテナにバインド
//App::singleton('sender', 'MailSender'); // シングルトンとしてDIコンテナにバインド
//$instance = new MailSender();
//App::instance('sender', $instance); // インスタンスをバインド
App::bind('SenderInterface', 'MailSender');
Route::get('send/{message?}', function(Messenger $sender, $message = '合格通知') {
//$sender = new BikeSender(); // 通常のクラス作成
//$sender = App::make('sender'); // DIコンテナから取得
//$sender = App::make('MailSender'); // DIコンテナから取得
//$sender = App::make('Messenger');
return $sender->send($message);
});
■DIとサービスコンテナの使い分け
Dependency Injection と Service Container (Service Locator) の使い分け
https://qiita.com/nunulk/items/2c637d3952096ef74677#2-dependency-injection-%E3%81%A8-service-contain...
2.3. 使い分け
コンストラクタインジェクションやメソッドインジェクションを使えば、
依存オブジェクトを引数として表現できるので、できるだけこちらを使った方がいいと思います。
サービスコンテナは便利な反面、どこからでもアクセスできてしまうので、
気をつけないと依存オブジェクトがどんどん増えてしまう恐れがあります。
サービスコンテナを使う場合でも、依存オブジェクトが多くなってきたな、と思ったら、
集約 (Aggregate) パターンを新たに作るなどして、依存関係を減らすように努めると、設計も洗練されて行くんじゃないかと思います。
■サービスプロバイダ
要勉強
サービスコンテナへのバインド処理で利用する機能をサービスプロバイダと呼ぶ
コントローラなどでは処理をクラス名で呼び出さずにコントラクト(インターフェース)をもとに呼び出し、サービスプロバイダ内で実際のクラスメイトコントラクトを紐付ける
これにより、サービスプロバイダ内の処理を変更するだけで処理の差し替えができる
サービスの初期処理(命令の初期起動処理)を行う
サービスプロバイダーとは - ララ帳
https://laravel10.wordpress.com/2015/04/22/%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%83%97%E3%83%AD%E3...
サービスプロバイダー 5.4 Laravel
https://readouble.com/laravel/5.4/ja/providers.html
Laravel5でカスタムライブラリ使いたい - Qiita
http://qiita.com/zaburo/items/e8e786624f88c253c87d
■イベント
要勉強
Laravelが様々な処理実行時にイベントを発行するため、
必要に応じてフックして処理を差し込むことができる
例えば illuminate.query はデータベースにクエリを発行したあとに発生するイベント
イベント 5.4 Laravel
https://readouble.com/laravel/5.4/ja/events.html
■ブロードキャスト
要勉強
websocketを利用して、ブラウザにイベントを通知するなど、リアルタイム性のあるアプリケーションを作れる
サーバサイドだけで完結せず、JavaScriptも関連する
ブロードキャスト 5.4 Laravel
https://readouble.com/laravel/5.4/ja/broadcasting.html
■フォームリクエストによるバリデーション
以下のコマンドで、リクエストの雛形を作成
php artisan make:request StoreArticlePost
引き続き内容を調整
\test\app\Http\Requests\StoreArticlePost.php
public function authorize()
{
return true;
}
public function rules()
{
return [
'subject' => 'required|max:255',
'detail' => 'required|max:255',
];
}
\test\app\Http\Controllers\ArticleController.php
use App\Http\Requests\StoreArticlePost;
public function store(StoreArticlePost $request)
{
/* リクエストが正しければここが実行される */
}
バリデーションと専用の項目名は、フォームリクエストで定義する
\test\app\Http\Requests\StoreArticlePost.php
汎用的に使われる項目名は、以下のファイル内にある「attributes」で設定する
モデルごとの特別な項目名は、各フォームリクエストで設定や上書きをする
\test\resources\lang\ja\validation.php
インストール
※以下検証したときは Laravel 5.4 だったが、特に理由がなければ今なら Laravel 5.5 を使用するといい
5.4 Laravel
https://readouble.com/laravel/5.4/ja/
リリースノート 5.4 Laravel
https://readouble.com/laravel/5.4/ja/releases.html
Composerのcreate-projectが何をやっているのか調べてみた - Qiita
http://qiita.com/DQNEO/items/74f4bb8fe447e4582a97
以下の内容も踏まえて、実際に簡易ブログを作ったときの手順を Laravel_Blog.txt に記載している
また、実際にEC2上にLaravel環境を構築したときの手順を Laravel_EC2.txt に記載している
■インストール(インストーラーを使う場合)
https://readouble.com/laravel/5.4/ja/installation.html
解説どおり global を付けると、Windows環境では C:/Users/XXX/AppData/Roaming/Composer に作成されるみたい
少し試すくらいなら、以下のように global なしでインストールする方が手軽かも
composer require "laravel/installer"
ただし基本的には、以下で紹介するようにプロジェクトの作成で良さそう
■インストール(プロジェクトを作成する場合)
https://readouble.com/laravel/5.4/ja/installation.html
composer create-project --prefer-dist laravel/laravel blog
create-project ... プロジェクトを作成
--prefer-dist ... 安定版をダウンロード
laravel/laravel ... Laravelをダウンロード
blog ... 「blog」ディレクトリ内に作成
以下のようにアクセスすると、Laravelの初期画面が表示される
http://localhost/~test/laravel/test/public/
Laravelのバージョンを指定してプロジェクトを作成する場合、以下のようにする
composer create-project --prefer-dist laravel/laravel blog 5.3
Composerで古いバージョンのLaravelをインストールする - Qiita
http://qiita.com/busyoumono99/items/c207b2bd8d388d41adad
■日本語化
リポジトリに含まれているファイルのコメントを日本語に翻訳する
最初に行っておくと良い
cd blog
composer require laravel-ja/comja5
vendor\bin\comja5 … コマンドの使用方法を表示
vendor\bin\comja5 -c … コメントを翻訳
vendor\bin\comja5 -f … 日本語の言語ファイルを resouces/lang/ja 内に作成
laravel-ja/comja5: コメント日本語変換
https://github.com/laravel-ja/comja5
■設定
/config/ 内に設定ファイルがある。例えば
/config/app.php に「'name' => env('APP_NAME', 'Laravel'),」のような設定があるが、これは /.env の設定が反映される
/.env は環境ごとの設定をまとめておくためのファイル
/.gitignore によって、はじめからgitの管理対象外にされている
初めてのLaravel 5.1 : (6) データベースの環境設定
https://laravel10.wordpress.com/2015/02/21/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3...
フレームワーク本体は以下にある
/vendor/laravel
/vendor/symfony
■タイムゾーンとロケールを設定
/test/config/app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
■データベース
utf8mb4_unicode_ciでデータベースを作成しておき、
/.env に接続情報を設定する。以下は設定例
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=test
DB_USERNAME=root
DB_PASSWORD=1234
■バージョンを指定してインストールする場合
$ composer create-project --prefer-dist laravel/laravel blog ... インストール可能な最新バージョンをインストール
$ composer create-project --prefer-dist "laravel/laravel=5.5.*" blog ... バージョンを指定してインストール
$ cd blog/
$ php artisan --version
Laravel Framework 5.5.44
Laravel(ララベル)のバージョンを指定してプロジェクトを作成する方法
http://engineer-ez.net/create-laravel-specify-version-project/
Homestead
※2019年ごろに検証した内容。2021年に改めて検証した内容は Laravel6.txt に記載している
Homesteadをインストールするだけで
Ubuntu16 + PHP7 + Nginx + MySQL
の環境を構築できる
※Dockerによる開発環境構築は、Docker.txt の「その他の環境構築例」と「Laradockによる開発環境構築」を参照
Homesteadは、1つのPCに複数インストールできない(無理矢理インストールすることは不可能では無いようだが)
よって案件名をもとにしたフォルダ内にHomesteadをインストールせず、「homestead」という場所にインストールして、
1つのHomesteadで複数のプロジェクトを管理する方が無難
詳細は「環境構築のトラブル事例」を参照
Laravel Homestead 5.5 Laravel
https://readouble.com/laravel/5.5/ja/homestead.html
↑インストール方法など詳しく書かれている
【Laravel超入門】開発環境の構築(VirtualBox + Vagrant + Homestead + Composer) - Qiita
http://qiita.com/7968/items/97dd634608f37892b18a
Laravel HomesteadでLaravel5.4の環境を作りながら、レンタルサーバにwebアプリケーションを公開する(追記あり) - Qiita
http://qiita.com/Fendo181/items/a6b9017f6ef490995aba
Windows10でLaravel Homestead環境構築 - Qiita
http://qiita.com/ricoirico/items/9745160bcf9983fa30ad
Laravel Homestead - Laravel - The PHP Framework For Web Artisans
https://laravel.com/docs/5.5/homestead
以下、VirtualBox + Vagrant の環境は構築済みとする
■Homestead Vagrant Box のインストール
>cd C:\vagrant
>vagrant box add laravel/homestead
==> box: Loading metadata for box 'laravel/homestead'
box: URL: https://atlas.hashicorp.com/laravel/homestead
This box can work with multiple providers! The providers that it
can work with are listed below. Please review the list and choose
the provider you will be working with.
1) parallels
2) virtualbox
3) vmware_desktop
Enter your choice: … virtualboxで使うので2を選択
==> box: Adding box 'laravel/homestead' (v3.0.0) for provider: virtualbox
box: Downloading: https://vagrantcloud.com/laravel/boxes/homestead/versions/3.0.0/providers/virtualbox.box
box: Progress: 100% (Rate: 3645k/s, Estimated time remaining: --:--:--)
==> box: Successfully added box 'laravel/homestead' (v3.0.0) for 'virtualbox'! … 5分ほどで完了
■Homesteadのインストール
ここでは C:\vagrant\homestead にインストールするものとする
※作業ファイルではなくHomesteadの仕組みそのもののインストールなので、
案件ごとの作業領域ではなく C:\vagrant\homestead などHomestead用の領域にインストールする方が良さそう
>cd C:\vagrant\homestead
>git clone https://github.com/laravel/homestead.git Homestead
>cd Homestead
>git checkout v6.2.2 … 最新の安定バージョンは https://github.com/laravel/homestead/releases で確認。v6は「v6.6.0」が最終版
>init.bat … Windowsの場合。Mac/LinuxもしくはWindowsgit bash環境なら「bash init.sh」とする
インストールしたHomesteadのバージョンは、以下に記載されている
C:\vagrant\homestead\Homestead\bin\homestead
$app = new Symfony\Component\Console\Application('Laravel Homestead', '6.2.2');
■鍵の作成
gitを使っているなら Git Bash で作成できる
恐らくPoderosaなどを使っても大丈夫と思われる
WindowsでGitを始めたらまず確認!Git Bashの設定&ショートカット | 株式会社グランフェアズ
http://www.granfairs.com/blog/staff/gitbash-setting-shortcut
$ mkdir ~/.ssh
$ cd ~/.ssh
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/refirio/.ssh/id_rsa): … 空ENTER
Enter passphrase (empty for no passphrase): … 空ENTER
Enter same passphrase again: … 空ENTER
Your identification has been saved in /c/Users/refirio/.ssh/id_rsa.
Your public key has been saved in /c/Users/refirio/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:750v6UHSVnX/ymH37ZbFIQTM94y3Tlhad4WPXfpCIS8 refirio@DESKTOP
The key's randomart image is:
+---[RSA 2048]----+
| o.. .o|
| o.oo.=|
| oo=*+|
| .E+=B*|
| S . +oXoB|
| . + =.**|
| . ..=.=|
| . .oo +.|
| ..+o...|
+----[SHA256]-----+
これで
C:\Users\ユーザ名\.ssh
内に鍵ファイルが作成される。ここはHomesteadからみると
~/.ssh/
にあたる
■Homesteadの設定
C:\vagrant\homestead\Homestead\Homestead.yaml
を編集する
鍵の設定は以下にあるが、上の手順で作成した場所がデフォルトとなっているため変更は不要
他の方法で鍵を作成した場合、必要に応じて設定を変更する
authorize: ~/.ssh/id_rsa.pub
keys:
- ~/.ssh/id_rsa
■hostsの設定
http://homestead.test/ でアクセスできるようにする
C:\Windows\System32\drivers\etc\hosts
192.168.10.10 homestead.test
■Vagrantの起動
cd C:\vagrant\homestead\Homestead
vagrant up
しばらく待つ…がエラーが表示された
Windows10環境での不具合で、最新版のVirtualBoxでは修正されているらしい
Progress state: E_INVALIDARG
VBoxManage.exe: error: Failed to create the host-only adapter
VBoxManage.exe: error: Assertion failed: [!aInterfaceName.isEmpty()] at 'F:\tinderbox\win-5.1\src\VBox\Main\src-server\HostNetworkInterfaceImpl.cpp' (74) in long __cdecl HostNetworkInterface::init(class com::Bstr,class com::Bstr,class com::Guid,enum __MIDL___MIDL_itf_VirtualBox_0000_0000_0038).
VBoxManage.exe: error: Please contact the product vendor!
VBoxManage.exe: error: Details: code E_FAIL (0x80004005), component HostNetworkInterfaceWrap, interface IHostNetworkInterface
VBoxManage.exe: error: Context: "enum RTEXITCODE __cdecl handleCreate(struct HandlerArg *)" at line 94 of file VBoxManageHostonly.cpp
Windows8.1 から Windows10 にUpgrade して発生したvagrantのエラー - Qiita
https://qiita.com/joniyjoniy/items/8176ece2b57c5a40121e
https://www.virtualbox.org/ticket/14040
から
VBox-Win10-fix-14040.exe
をダウンロードし、管理者として実行。再度起動コマンドを試すとVagrantが起動した
その後 http://homestead.test/ にアクセスすると、「No input file specified.」とだけ表示される
いったん成功
Vagrantを終了する場合は以下のコマンド
vagrant halt
■SSHで接続
「vagrant ssh」コマンドを使うと、SSHで接続するための情報が表示される
Poderosaを使って以下の情報で接続する
>vagrant ssh
`ssh` executable not found in any directories in the %PATH% variable. Is an
SSH client installed? Try installing Cygwin, MinGW or Git, all of which
contain an SSH client. Or use your favorite SSH client with the following
authentication information shown below:
Host: 127.0.0.1
Port: 2222
Username: vagrant
Private key: C:/localhost/home/homestead/public_html/Homestead/.vagrant/machines/homestead-7/virtualbox/private_key
以下を参考に時差の調整を行っておくといい
Laravel Homesteadの日本時間設定をする - Qiita
https://qiita.com/shalman/items/42fcd4506b1f523c5553
■プロジェクトを作成
※最初は案件用ではなく、動作確認用のプロジェクトを作成する方がいいかも
その後、別途サブドメインと作業フォルダを作る方がいいかも
C:\vagrant\homestead\code
を作成
C:\vagrant\homestead\Homestead\Homestead.yaml
を編集(folders の map のみ)
folders:
- map: C:\vagrant\homestead\code
to: /home/vagrant/code
この状態でVagrantを再起動
C:\vagrant\homestead\code\public\index.html
を作成すると、これが http://homestead.test/ に表示される(SSH経由で /home/vagrant/code/public/index.html を作成しても同じ)
このディレクトリの内容は C:\vagrant\homestead\code と同期している
ここまで来れば最低限、Homesteadを開発環境として使用できる
以降で引き続き、Laravelのインストールを試す
■Laravelインストール
いったん /home/vagrant/code/public/ を削除しておく
$ cd /home/vagrant/code
$ sudo composer self-update
$ composer create-project laravel/laravel laravel --prefer-dist … とても時間がかかる
Installing laravel/laravel (v5.5.0)
- Installing laravel/laravel (v5.5.0): Downloading (100%)
Created project in Laravel
〜略〜
Application key [base64:WHQKmMRLnXNH9uw3CQRl+fgcwPdwhx9CftI0vBLdgFE=] set successfully.
上でインストールしたLaravelの公開ディレクトリは /home/vagrant/code/laravel/public なので変更する
sites:
- map: homestead.test
to: /home/vagrant/code/public
↓
sites:
- map: homestead.test
to: /home/vagrant/code/laravel/public
Vagrantを再起動するも、設定は反映されない
Homestead.yaml の sites 設定は「初回起動時にsitesの内容をもとに、Nginxの設定ファイルを調整する」という動作みたい
つまり2回目以降、上記設定は無視される
Laravel : Homesteadで開発環境を構築 | DN-Web64
http://www.dn-web64.com/archives/web/homestead/
別途Nginxの設定ファイルを編集し、Nginxを再起動する
$ sudo vi /etc/nginx/sites-available/homestead.test … バージョンによっては「homestead.app」という名前になっている?
以下のURLでLaravelの画面が表示される
http://homestead.test/
■Laravel動作確認
server {
listen 80;
listen 443 ssl http2;
server_name homestead.test;
#root "/home/vagrant/code/public";
root "/home/vagrant/code/laravel/public"; … 公開ディレクトリを変更
$ sudo service nginx restart
$ cd /home/vagrant/code/laravel/
$ php artisan --version
Laravel Framework 5.5.13
引き続き、Composerやデータベース接続などを試す
以下のページも参考になりそう
Laravel : Homesteadで開発環境を構築 | DN-Web64
http://www.dn-web64.com/archives/web/homestead/
実際の作業環境追加は「Homesteadにサブドメインと作業ディレクトリを追加」の項目を参照
■環境構築のトラブル事例1
vagrant up 後「Warning: Authentication failute. Retrying...」が出続けてタイムアウトエラーが出る
(vagrant 自体は起動するがなんか気持ち悪い)
[原因]
ssh の秘密鍵とゲストOS で設定されている公開鍵が合っていない為発生。
[解決方法]
http://kiraba.jp/vagrant-error-authentication-failure-retrying/
こちらのサイトの通りにやることで Warning は出ずに起動するようになった。
■環境構築のトラブル事例2
vagrant が起動した後、サイトにつながらない
(http://homestead.test/ が見れない)
[原因]
一度不完全な状態で起動した(上記の Warning など)為、nginx の設定ファイルが正常に作成されていない為。
[解決方法]
vagrant halt で一旦停止後、
vagrant up --provision
で起動する。
--provision を付与することで再度 Homestead.yaml に沿って設定ファイルを作成してくれる。
これで http://homestead.test/ にアクセスすると「No input file specified.」が表示されるようになった。
vagrant up --provision すれば鍵の不一致も解決するかも(?)。
試していないので断定はできないが、あとで見ると鍵がコピーされていた。
■環境構築のトラブル事例3
複数のHomesteadを起動できない
C:\localhost\home\test\public_html\Homestead>vagrant up
Bringing machine 'homestead-7' up with 'virtualbox' provider...
==> homestead-7: Box 'laravel/homestead' could not be found. Attempting to find and install...
homestead-7: Box Provider: virtualbox
homestead-7: Box Version: >= 4.0.0
==> homestead-7: Loading metadata for box 'laravel/homestead'
homestead-7: URL: https://atlas.hashicorp.com/laravel/homestead
==> homestead-7: Adding box 'laravel/homestead' (v4.0.0) for provider: virtualbox
homestead-7: Downloading: https://vagrantcloud.com/laravel/boxes/homestead/versions/4.0.0/providers/virtualbox.box
==> homestead-7: Box download is resuming from prior download progress
homestead-7: Progress: 100% (Rate: 101k/s, Estimated time remaining: --:--:--)
==> homestead-7: Successfully added box 'laravel/homestead' (v4.0.0) for 'virtualbox'!
==> homestead-7: Importing base box 'laravel/homestead'...
==> homestead-7: Matching MAC address for NAT networking...
==> homestead-7: Checking if box 'laravel/homestead' is up to date...
A VirtualBox machine with the name 'homestead-7' already exists.
Please use another name or delete the machine with the existing
「homestead-7」はすでに存在すると言われた
Oracle VM VirtualBox(GUIツール)を立ち上げて、以前作成した「homestead-7」を削除して、再度「vagrant up」を実行した
Homesteadはそのままでは複数起動できないらしい
box名を変更するなどの対応が必要らしい
1つのboxで複数のプロジェクトを管理する方が無難かも
同一のboxだとPHPのバージョンを変更したりが難しいが、
Homesteadは「特定の案件に合わせた環境を構築する」というより「最新版Laravelの開発環境を構築する」ものなので、
以下の方法でディレクトリを分ける程度の方がいいかも
Homesteadを使って複数のLaravelプロジェクトを作成する簡単な方法 - Qiita
https://qiita.com/Yorinton/items/08ec8fefcbec71513399
複数のLaravel環境をHomestead上で動かす - Qiita
https://qiita.com/miutex/items/ebc13fc78da4a19a3da4
以下のような方法も紹介されているが、Homesteadがバージョンアップした場合に対応できなるなる可能性がありそう
また、「何故か一部だけ動作しない」になったときに解決が難しいかも
Laravel5.2のHomesteadを複数たててみた - Qiita
https://qiita.com/you-me/items/b886dd0fc8e5047c4bc6
Homesteadを複数作りたい場合にやること - めものようなもの
http://fumikony.hatenablog.com/entry/2015/04/22/145639
現状以下が良さそうかも
・Homestead環境は一つで、サブドメインで分岐させる
・例えば C:\localhost\home\homestead にHomesteadを配置して /home/vagrant/code と同期して、
/home/vagrant/code/laravel/public
で最初のLaravelの動作確認をして、
/home/vagrant/code/test1/public
/home/vagrant/code/test2/public
などにプロジェクトを配置して
laravel.homestead.test
test1.homestead.test
test2.homestead.test
などでアクセスできるようにする。これなら過去案件への影響も小さい
・データベースも分ける
■環境構築のトラブル事例4
AWSのAPIを呼び出すと、以下のようなエラーになる
sns signaturedoesnotmatch:Signature expired: 20190117T041039Z is now earlier than 20190117T041213Z (20190117T042713Z - 15 min.)
サーバ時間のズレが影響しているようなので設定する
時差を調整してもエラーのままだったことがあるが、WindowsとVagrant自体を再起動するとエラーにならなくなった
Laravel Homesteadの日本時間設定をする - Qiita
https://qiita.com/shalman/items/42fcd4506b1f523c5553
Aws::RDS::Errors::SignatureDoesNotMatch というエラーが出た際の対処 | cloudpack.media
https://cloudpack.media/20686
■環境構築のトラブル事例5
EC2でLaravelのインストールに失敗する
エラーメッセージにあるように、メモリ不足が原因
スワップを作成するか、マシンスペックを上げる
$ composer create-project --prefer-dist "laravel/laravel=6.0.*" logisquare
Installing laravel/laravel (v6.0.2)
- Installing laravel/laravel (v6.0.2): Downloading (100%)
Created project in logisquare
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 84 installs, 0 updates, 0 removals
- Installing symfony/polyfill-ctype (v1.12.0): Downloading (100%)
The following exception is caused by a lack of memory or swap, or not having swap configured
Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details
PHP Warning: proc_open(): fork failed - Cannot allocate memory in phar:///usr/local/bin/composer/vendor/symfony/console/Application.php on line 952
Warning: proc_open(): fork failed - Cannot allocate memory in phar:///usr/local/bin/composer/vendor/symfony/console/Application.php on line 952
[ErrorException]
proc_open(): fork failed - Cannot allocate memory
create-project [-s|--stability STABILITY] [--prefer-source] [--prefer-dist] [--repository REPOSITORY] [--repository-url REPOSITORY-URL] [--dev] [--no-dev] [--no-custom-installers] [--no-scripts] [--no-progress] [--no-secure-http] [--keep-vcs] [--remove-vcs] [--no-install] [--ignore-platform-reqs] [--] [<package>] [<directory>] [<version>]
Homestead: サブドメインと作業ディレクトリを追加
※動作確認用の /home/vagrant/code/laravel とは別に、案件ごとの作業領域を作る
Homesteadを使って複数のLaravelプロジェクトを作成する簡単な方法 - Qiita
https://qiita.com/Yorinton/items/08ec8fefcbec71513399
複数のLaravel環境をHomestead上で動かす - Qiita
https://qiita.com/miutex/items/ebc13fc78da4a19a3da4
Homesteadでサクッとプロジェクト追加 - Qiita
https://qiita.com/Nshota/items/f2c3eba8005672f6c429
■Homesteadの設定
C:\localhost\home\Homestead\Homestead.yaml
のsite部分に以下を追記(必要に応じて、databases にもデータベース名を追加しておくといいが、別途手動で作成してもいい)
- map: blog.homestead.test
to: /home/vagrant/code/blog/public
■起動
Homesteadをオプション付きで再起動(Homestead.yaml の内容が再読込される)
vagrant up --provision
[Laravel] Homestead環境でHomestead.yamlの設定を修正した時の反映方法
https://agatayoshimi.blogspot.com/2015/10/laravel-homesteadhomesteadyaml.html
環境によっては、以下のファイルが参照されていることがあるようなので注意
C:\Users\[ユーザ名]\.homestead\Homestead.yaml
■nginxの設定
※大抵の解説ではこの手順に触れられていないので、本来はprovisionで起動した際にHomesteadが自動で行うのかも?
$ sudo su -
# cd /etc/nginx/sites-available
# cp xxx.homestead.test blog.homestead.test ... 既存の設定を流用
# vi blog.homestead.test
※再起動に失敗する場合、「nginx -t」でnginx設定ファイルの書式を確認できる
何らかの理由でSSL証明書が作成されないことがあるみたい。SSLアクセスを無効にして起動することはできる
設定ファイルを複製した際、以下のdefault_serverが他サイトと重複して指定されていないか確認する。複数のサイトに指定されていると、nginxの起動時にエラーになる
server_name blog.homestead.test;
root "/home/vagrant/code/blog/public";
error_log /var/log/nginx/blog.homestead.test-error.log error;
ssl_certificate /etc/nginx/ssl/blog.homestead.test.crt;
ssl_certificate_key /etc/nginx/ssl/blog.homestead.test.key;
# service nginx restart
server {
listen 80 default_server;
listen 443 ssl http2 default_server;
■MySQLの設定(必要に応じて)
$ mysql -u root -p
secret
GRANT ALL PRIVILEGES ON blog.* TO homestead@localhost IDENTIFIED BY 'secret';
$ mysql -u homestead -p
secret
CREATE DATABASE blog CHARACTER SET utf8mb4;
■PostgreSQLの設定(必要に応じて)
$ psql -U homestead -h localhost
secret
CREATE DATABASE blog ENCODING UTF8;
LaravelのHomestead環境でPostgreSQL初期設定を行う - Qiita
https://qiita.com/a_shiba/items/762af4ec58395669d435
■Windows側の設定
C:\localhost\home\homestead\code\blog\public\index.php
<?php phpinfo() ?>
C:/windows/System32/drivers/etc/hosts
192.168.10.10 blog.homestead.test
■アクセス
以下でアクセスできる
http://blog.homestead.test
何らかの理由で /etc/nginx/sites-available にシンボリックリンクが張られないことがあるみたい(設定が認識されない)
その場合、以下のように手動でリンクを張ればアクセスできることがある
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/blog.homestead.test blog.homestead.test
※Homesteadは
「/etc/nginx/nginx.conf から /etc/nginx/sites-enabled 内の設定ファイルをすべて読み込むようにし、
/etc/nginx/sites-available 内のファイルから必要に応じて /etc/nginx/sites-enabled 内のファイルにシンボリックリンクを張る」
という仕組みになっている
つまり sites-enabled に有効にしたいサイトのシンボリックリンクを並べる仕組みになっている
■Laravelインストール
いったん /home/vagrant/code/blog を削除してから作業開始
$ cd /home/vagrant/code
$ composer create-project --prefer-dist laravel/laravel blog
以下でアクセスできる
http://blog.homestead.test
引き続き以下を参考に、大抵のプロジェクトで必要になりそうな部分を作業
https://github.com/refirio/laravel-blog/commits/master
$ cd blog
$ composer require laravel-ja/comja5
$ vendor/bin/comja5
$ vendor/bin/comja5 -c
$ vendor/bin/comja5 -f
タイムゾーンとロケールを変更
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
config\database.php を編集
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
/.env を編集(基本的に DB_DATABASE の設定のみでいい)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=homestead
DB_PASSWORD=secret
Userモデルを移動(移動先は App\DataAccess\Eloquent では無く App\Entities なので注意)
app\Entities\User.php
namespace App\Entities;
不要なマイグレーションを削除
database\migrations\2014_10_12_100000_create_password_resets_table.php
デフォルトのマイグレーションを調整
database\migrations\2014_10_12_000000_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name', 85)->comment('名前');
$table->string('email', 85)->unique()->comment('メールアドレス');
$table->string('password', 60)->comment('パスワード');
$table->rememberToken();
$table->timestamps();
});
DB::statement('ALTER TABLE users COMMENT \'ユーザ\'');
}
作業ディレクトリへ移動
cd /home/vagrant/code/blog
マイグレーションを実行
php artisan migrate
Homestead: 同一LAN内からアクセス
Vagrantfile に config.vm.network の設定を追加する
if defined? VagrantPlugins::HostsUpdater
config.hostsupdater.aliases = settings['sites'].map { |site| site['map'] }
end
# ローカルからのアクセスを許可
config.vm.network "forwarded_port", guest: 80, host: 80
end
これで、自身のPCから 127.0.0.1 でHomesteadにアクセスできる
この状態なら、同一LAN内からIPアドレスを指定すればアクセスできる
(PCのIPアドレスはipconfigで調べることができる)
XAMPPを併用している場合、以下のようにすると
PCのIPアドレスにアクセスするとXAMPPが表示され、「:8080」を付けてアクセスするとHomesteadが表示され…とできる
config.vm.network "forwarded_port", guest: 80, host: 8080
バーチャルホストを複数設定している場合、IPアドレスでアクセスすると最初のバーチャルホストに対してアクセスされる
この場合、いずれかのバーチャルホストに対して「default_server」を指定すると、それがIPアドレスでアクセスしたときのホストとなる
server {
listen 80 default_server;
listen 443 ssl http2 default_server;
server_name test.homestead.test;
■Homestead.yaml での指定
以下のように Homestead.yaml で指定することもできるらしい(未検証)
Laravel Homestead 5.5 Laravel
https://readouble.com/laravel/5.5/ja/homestead.html#network-interfaces
メール送信
Sendmailなら以下の設定変更のみで送信できた
.env
MAIL_DRIVER=smtp
↓
MAIL_DRIVER=sendmail
PHP - Laravelでsendmailコマンドを利用したメール送信はできるでしょうか?|teratail
https://teratail.com/questions/112874
ただし、普通に Mail::send でメールを送るとHTMLメール扱いになるので注意
詳細は「トラブル対応」の「メールを送信すると、本文が Attachment.html という添付ファイルになる」を参照
また、以下の設定部分で送信元アドレスと送信者名を設定できる
送信元アドレスが未設定だとエラーログにその旨が記録され続けるようなので、設定しておく
.env
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
SMTPでメール送信
ロリポップのコントロールパネルでメールアドレスを作成し、
そのSMTP情報を使ってメールを送信したときのメモ
.env で以下のように設定した
MAIL_DRIVER=smtp
MAIL_HOST=smtp.lolipop.jp
MAIL_PORT=465
MAIL_USERNAME=メールアドレス
MAIL_PASSWORD=パスワード
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=送信元アドレス
MAIL_FROM_NAME=送信者名
SMTP認証でメール送信
※未検証
SMTP認証が必要な場合、以下の方法で送信できるみたい
LaravelでSMTP認証(SMTP-AUTH)メール送信エラーの対応方法 | 株式会社スリースターソフトウェア
https://www.threestarsoftware.co.jp/laravel/laravel%E3%81%A7smtp%E8%AA%8D%E8%A8%BC%E3%83%A1%E3%83%BC...
GmailのSMTPでメール送信
GmailのSMTPでメールを送信したときのメモ
専用のアプリパスワードを発行し、それを使ってメールを送信できる
設定手順は Google.txt も参照
[Laravel5]gmailが送信できない? : ラブサファリ
http://lovesafari.blog.jp/archives/21272100.html
以下はアプリパスワードについては触れられていないが、プログラムは参考になる
Laravelでメール送信機能を実装する方法【Gmailを利用】
https://manablog.org/laravel-send-email/
■.envの設定
.env で以下のように設定する
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=Gmailアドレス
MAIL_PASSWORD=アプリパスワード
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=送信元アドレス
MAIL_FROM_NAME=送信者名
■ファイルの作成
/resources/views/emails/test.blade.php でメールの本文を定義する
<p>$Test={{ $test }}</p>
/app/Http/Controllers/Admin/HomeController.php など適当なコントローラーに送信処理を書く。ファイル冒頭でMailクラスをuseし、コントローラー内に実際の送信処理も書く
use Illuminate\Support\Facades\Mail;
$data = [ 'test' => 'テスト' ];
Mail::send('emails.test', $data, function($message){
$message->to('refirio.work@gmail.com')
->subject('ここがタイトルです');
});
exit('OK');
■送信確認
認証エラーが出る場合、Googleで2段階認証とアプリパスワードを設定しているか確認する
(通常のログインパスワードとは別にアプリパスワードが必要。アプリパスワードの設定には2段階認証の設定が必要)
これでメールが送信されたが、メールのFromが意図したアドレスにならない
メールヘッダを確認すると、以下のように X-Google-Original-From に反映されていた
Sender: "山野寛和" <refirio.work@gmail.com>
From: refirio@example.com
X-Google-Original-From: example@example.com
これはGmailを使う場合の仕様かも
php - Laravel Sender or From not working as expected - Stack Overflow
https://stackoverflow.com/questions/39362131/laravel-sender-or-from-not-working-as-expected
■メール送信時に「SSL operation failed」のエラーになる
SwiftMailerでメールを送信しようとするとSSL operation failed with code 1. エラーが出る - ぃぐわ!まにあっくす
http://wigwamania.hatenablog.com/entry/2016/04/09/150000
上記の解説から少しコードが変わったようだが、
vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
このファイルを以下のように修正すると、Gmailでメール送信できるようになった
258: $options = [];
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['verify_peer_name'] = false;
259: if (!empty($this->params['sourceIp'])) {
260: $options['socket']['bindto'] = $this->params['sourceIp'].':0';
260: }
AmazonSESでメール送信
※未検証
Laravelのメール送信にAWSのSESを利用する方法 | 実践的Web開発メソッド
https://blog.hiroyuki90.com/articles/laravel-ses/
Hello, Webエンジニア: Amazon SESを使ったLumen(Laravel)のメール送信
http://rx93g.blogspot.com/2016/03/amazon-seslumenlaravel.html
SendGridでメール送信
※未検証
Laravel + SendGrid Web API でメール送信する - Qiita
https://qiita.com/komatzz/items/7873ad6a86aedac46863
Laravelでメール送信する際にSendGridを使う - いっきのblog
http://kzkohashi.hatenablog.com/entry/2018/04/11/201951
Mailgunでメール送信
※未検証
開発者向けのメール送信サービスであるMailgunを使う方法
Laravel5.5のメール送信をMailgun API(無料)で行う〜アカウント開設、DNS設定からMailableクラスでの送信まで〜
https://www.ritolab.com/entry/39
gitの設定
Windowsのローカル開発環境で、SourceTreeでComposer用のファイルを除外する方法
ツール → オプション → Git → グローバル無視リスト → ファイルを編集
#Ignore composer file
composer.bat
composer.phar
更新(他の開発者の更新を取り込む手順)
gitからプルした後、SSHから以下を実行する
(作業用ディレクトリに移動し、Composer・マイグレーション・シーダー・キャッシュクリアを実行)
$ cd /home/vagrant/code/test
$ composer install
$ composer dump-autoload
$ php artisan migrate
$ php artisan db:seed
$ php artisan cache:clear
$ php artisan config:clear
$ php artisan route:clear
$ php artisan view:clear
キャッシュのクリアは以下も参考にして再考したい
Laravel キャッシュクリア系コマンドなど - Qiita
https://qiita.com/Ping/items/10ada8d069e13d729701
■composerの内容変更
開発環境でcomposerによって新たにライブラリをインストールした場合、
当然ながら本番環境などでもライブラリをインストールしないと正しく動作しない
実際にその環境でインストールしたライブラリは composer.lock に記録される
よってこのファイルをコミット&プッシュしておけば、
他の環境では単に composer install を実行すれば、開発環境と同じライブラリがインストールされる
composer update を実行すると、各ライブラリが一斉に最新版に更新されるので注意
根本の動作に影響する可能性はゼロではないので、実行するタイミングは要検討(意図的に行うのなら問題ない)
認証
artisanから認証の仕組みを組み込める
ルーティングの設定などが書き換わるので、導入するなら初めに導入しておく方が良さそう
cd test
php artisan make:auth
php artisan migrate
以下のエラーが表示される場合、
データベースの文字コードがutf8mb4の場合にVARCHARの最大文字数が191文字に制限されるため
詳細は「トラブル対応」を参照
[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
oo long; max key length is 767 bytes (SQL: alter table `users` add unique `
users_email_unique`(`email`))
[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
oo long; max key length is 767 bytes
完了すると、「LOGIN」と「REGISTER」が追加される
ユーザ登録、ログイン、パスワード再発行が実装される
認証処理のルーティング
\test\routes\web.php に Auth::routes(); が追加され、認証用のルーティングが有効になる。ここから
\test\vendor\laravel\framework\src\Illuminate\Support\Facades\Auth.php
の routes() が呼ばれる。ここから
\test\vendor\laravel\framework\src\Illuminate\Routing\Router.php
の auth() が呼ばれる。ここから
\test\app\Http\Controllers\Auth
内のファイルが呼ばれる…となる。多くの処理はtraitになっており
\test\vendor\laravel\framework\src\Illuminate\Foundation\Auth
内のファイルが呼ばれる
認証処理の流れ(調査中)
\test\vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers
の login() がログイン時に呼ばれる処理
同トレイとの attemptLogin() で認証を行う。内部では guard() が呼ばれており、
Auth::guard() の attempt() が実行される
認証の判定(調査中)
\test\app\Http\Kernel.php
の $routeMiddleware で
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
と定義されており、コントローラなどで
$this->middleware('auth');
を呼ぶことで、認証が必要になる
Authenticate の内部では handle() から authenticate() が呼ばれ、$this->auth->guard($guard)->check() 部分で認証が確認される
この命令は Auth クラスに実装されており、実際の処理は Illuminate\Auth\AuthManager に guard() がある
そこから Illuminate\Auth の check() が呼ばれ、実際の処理は Illuminate\Auth\GuardHelpers に check() がある
内部では $this->user() に値があるか否かを調べている
Laravelの標準Authentication(Auth)の動きを調べてみる
http://qiita.com/zaburo/items/9fcf0f4c771e011a4d35
Laravel : デフォルト認証機能の動作確認
http://www.dn-web64.com/archives/web/laravel_auth_default/
Laravel ユーザなら知っておくべきAuthオートログインのこと - Shin x blog
http://www.1x1.jp/blog/2014/04/lararavel-artisan-should-know-auto-login-by-auth.html
マルチ認証(管理者とユーザで別々に認証)
Laravelでマルチ認証(マルチログイン)を実装する
https://leben.mobi/blog/laravel5_multi_login/php/
基本的には上記の解説を参考に作成
ただし解説どおりだと、認証エラーのメッセージが表示されなかったので以下の内容は変更している
/app/Http/Controllers/Auth/AdminLoginController.php
users テーブルに加えて admins テーブルを作成し、管理者情報を格納するものとする
モデルは /app/Entities 内に置いているものとする
■Admin用のマイグレーションとモデルを作成
php artisan make:model Admin -m
マイグレーションの内容をusersと合わせる
Schema::create('admins', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name', 85)->comment('名前');
$table->string('email', 85)->unique()->comment('メールアドレス');
$table->string('password', 60)->comment('パスワード');
$table->rememberToken();
$table->timestamps();
});
DB::statement('ALTER TABLE admins COMMENT \'管理者\'');
モデルの内容をusersと合わせる
<?php
namespace App\Entities;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
use Notifiable;
/**
* 複数代入を行う属性
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* 配列には含めない属性
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
■マイグレーションを実行
php artisan migrate
■認証設定ファイルにAdmin用の設定を追加
/config/auth.php
<?php
return [
'defaults' => [
'guard' => 'user', // web から user に変更
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
// ここから追加
'user' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'admin-api' => [
'driver' => 'token',
'provider' => 'admins',
],
// ここまで追加
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Entities\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
// ここから追加
'admins' => [
'driver' => 'eloquent',
'model' => App\Entities\Admin::class,
],
// ここまで追加
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
// ここから追加
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 15,
],
// ここまで追加
],
];
■認証失敗処理を追加
/app/Exceptions/Handler.php
use Illuminate\Auth\AuthenticationException;
〜中略〜
/**
* 認証エラーがあればリダイレクト
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
$guard = array_get($exception->guards(), 0);
switch ($guard) {
case 'admin':
$login = 'admin.login';
break;
default:
$login = 'login';
break;
}
return redirect()->guest(route($login));
}
■認証成功処理を追加
/app/Http/Middleware/RedirectIfAuthenticated.php
public function handle($request, Closure $next, $guard = null)
{
switch ($guard) {
case 'admin':
if (Auth::guard($guard)->check()) {
return redirect()->route('admin.dashboard');
}
break;
default:
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
break;
}
return $next($request);
}
■Admin用のコントローラを作成
/app/Http/Controllers/AdminController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdminController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:admin');
}
/**
* Show the application dashboard.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('admin');
}
}
/app/Http/Controllers/Auth/AdminLoginController.php を作成
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Auth;
use Illuminate\Validation\ValidationException;
class AdminLoginController extends Controller
{
public function __construct()
{
$this->middleware('guest:admin');
}
public function showLoginForm()
{
return view('auth.admin-login');
}
public function login(Request $request)
{
// Validate the form data
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:6'
]);
// Attempt to log the user in
if (Auth::guard('admin')->attempt(['email' => $request->email, 'password' => $request->password], $request->remember)) {
// if successful, then redirect to their intended location
return redirect()->intended(route('admin.dashboard'));
}
return $this->sendFailedLoginResponse($request);
}
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
throw ValidationException::withMessages([
'email' => [trans('auth.failed')],
]);
}
}
■Admin用のルーティングを追加
/routes/web.php
Route::prefix('admin')->group(function() {
Route::get('/login', 'Auth\AdminLoginController@showLoginForm')->name('admin.login');
Route::post('/login', 'Auth\AdminLoginController@login')->name('admin.login.submit');
Route::get('/', 'AdminController@index')->name('admin.dashboard');
});
■Admin用のビューを作成
resources/views/home.blade.php を複製して resources/views/admin.blade.php を作成
resources/views/auth/login.blade.php を複製して resources/views/auth/admin-login.blade.php を作成
admin-login.blade.php
<div class="panel-heading">ログイン</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
以下のように変更
<div class="panel-heading">Adminログイン</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('admin.login.submit') }}">
■動作確認
ユーザ情報の登録は作成していないので、usersテーブルの内容をもとに手動でadminsテーブルにユーザを登録する
http://blog.homestead.test/
http://blog.homestead.test/admin/
以下は要検証
・AdminLoginController.php の内容を再度考えたい
・web.php の「->name('admin.login.submit');」は、ユーザ側には無いので省略するか
・マイグレーションのファイル名は、最初の構築以降は日付を含めるか
API認証(API利用時にPassportでOAuth2認証)
API認証(Passport) 5.5 Laravel
https://readouble.com/laravel/5.5/ja/passport.html
Laravel5.5でAPI認証のパッケージ(Laravel Passport)を利用する - Qiita
https://qiita.com/niiyz/items/fffff94acb6061ecc9d4
Laravel5.6 PassportでAPI認証を実装 - Crunchtimer - Medium
https://medium.com/crunchtimer/laravel5-6-passport%E3%81%A7api%E8%AA%8D%E8%A8%BC%E3%82%92%E5%AE%9F%E...
Laravel PassportでWeb APIの認証を実装する【初期設定編】 | 大阪のシステム開発なら 株式会社ウィズテクノロジー
https://www.whizz-tech.co.jp/1442/
Laravel PassportでWeb APIの認証を実装する【実装編】 | 大阪のシステム開発なら 株式会社ウィズテクノロジー
https://www.whizz-tech.co.jp/1453/
■インストール
$ composer require laravel/passport
- Installation request for laravel/framework (locked at v5.5.44, required as 5.5.*) -> satisfiable by laravel/framework[v5.5.44].
- Installation request for laravel/passport ^7.0 -> satisfiable by laravel/passport[v7.0.0, v7.0.1, v7.0.2, v7.0.3].
インストールできない
バージョンを指定してインストール
$ composer require laravel/passport=~4.0
- Installation request for paragonie/random_compat (locked at v9.99.99) -> satisfiable by paragonie/random_compat[v9.99.99].
インストールできない
Your requirements could not be resolved to an installable set of packages - Issue #774 - laravel/passport
https://github.com/laravel/passport/issues/774
composer - Laravel5.5のクライアント認証のためのパッケージがインストールできない|teratail
https://teratail.com/questions/115287
を参考にrandom_compatとpassportのバージョンを指定してインストール
$ composer require paragonie/random_compat:2.*
$ composer require laravel/passport=~4.0
インストールできた
(passportのバージョン指定が無いと、相変わらずエラーになった)
データベースにテーブルを作成(マイグレーションは /vendor/laravel/passport/database/migrations 内のファイルが実行されるみたい)
$ php artisan migrate
Migrating: 2016_06_01_000001_create_oauth_auth_codes_table
Migrated: 2016_06_01_000001_create_oauth_auth_codes_table
Migrating: 2016_06_01_000002_create_oauth_access_tokens_table
Migrated: 2016_06_01_000002_create_oauth_access_tokens_table
Migrating: 2016_06_01_000003_create_oauth_refresh_tokens_table
Migrated: 2016_06_01_000003_create_oauth_refresh_tokens_table
Migrating: 2016_06_01_000004_create_oauth_clients_table
Migrated: 2016_06_01_000004_create_oauth_clients_table
Migrating: 2016_06_01_000005_create_oauth_personal_access_clients_table
Migrated: 2016_06_01_000005_create_oauth_personal_access_clients_table
データベース管理ツールなどで、テーブルが作成されたことを確認しておく
■アクセストークンを作成
この作業により暗号キーが生成され、
oauth_clients テーブルに「パーソナルアクセス」クライアントと「パスワードグラント」クライアントも作成される
$ php artisan passport:install
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client Secret: Dww67qb3VFgBf5eXasC5weRSR1nBN9wAZ5xbhGLB
Password grant client created successfully.
Client ID: 2
Client Secret: BGp9oHDnNRD3Q45rd8Zd4VBKJbCYuhOChjiolQld
■認証ユーザを作成
この作業により、oauth_clients テーブルに対象ユーザ用の認証情報が登録される
$ php artisan passport:client
Which user ID should the client be assigned to?:
> 1
What should we name the client?:
> OAuth Test
Where should we redirect the request after authorization? [http://localhost/auth/callback]:
> http://localhost/~test/oauth/callback.php
New client created successfully.
Client ID: 3
Client secret: syHivsywmRTVTf0Cbh72u14ICDdntiDBzFHiLChc
■プログラムの修正
UserモデルにHasApiTokensトレイトを追加
<?php
namespace App;
use Laravel\Passport\HasApiTokens; // 追加
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable; // HasApiTokensを追加
}
AuthServiceProviderに Passport::routes を追加
<?php
namespace App\Providers;
use Laravel\Passport\Passport; // 追加
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* アプリケーションのポリシーのマップ
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* 全認証/認可サービスの登録
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes(); // 追加
}
}
config/auth.php の設定を変更
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport', // tokenをpassportに変更
'provider' => 'users',
],
routes/api.php にテスト用APIを追加
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
// テスト用API
Route::middleware('auth:api')->get('/test', function (Request $request) {
return json_encode(array_merge(['status' => 'OK', 'message' => 'test!']));
});
■認証クライアントを作成
「認証ユーザを作成」で作成したユーザで認証するプログラムを作成してみる
認証を開始するプログラム。ブラウザから直接アクセスする想定
http://localhost/~test/oauth/start.php
<?php
$queries = array(
'client_id=3',
'redirect_uri=http://localhost/~test/oauth/callback.php',
'response_type=code',
'scope=',
);
header('Location: http://blog.homestead.test/oauth/authorize?' . implode('&', $queries));
exit;
認証すると、以下にリダイレクトされる(実際はURLの後ろに「?code=XXXXX」のような値が付く)
http://localhost/~test/oauth/callback.php
<?php
session_start();
if (empty($_GET['code'])) {
exit('Code is empty.');
}
$result = file_get_contents(
'http://blog.homestead.test/oauth/token',
false,
stream_context_create(
array(
'http' => array(
'method' => 'POST',
'header' => implode(
"\r\n",
array(
'Content-Type: application/x-www-form-urlencoded'
)
),
'content' => http_build_query(
array(
'grant_type' => 'authorization_code',
'client_id' => '3',
'client_secret' => 'syHivsywmRTVTf0Cbh72u14ICDdntiDBzFHiLChc',
'redirect_uri' => 'http://localhost/~test/oauth/callback.php',
'code' => $_GET['code'],
)
)
)
)
)
);
$json = json_decode($result, true);
$_SESSION['access_token'] = $json['access_token'];
header('Location: ./request.php');
exit;
アクセストークンを取得後、以下にリダイレクトされる
http://localhost/~test/oauth/callback.php
<?php
session_start();
if (empty($_SESSION['access_token'])) {
exit('Access Token is empty.');
}
$result = file_get_contents(
'http://blog.homestead.test/api/test', // テスト用API
//'http://blog.homestead.test/api/user', // ユーザ情報取得API
false,
stream_context_create(
array(
'http' => array(
'method' => 'GET',
'header' => "Accept: application/json\r\n" .
"Authorization: Bearer " . $_SESSION['access_token'] . "\r\n"
)
)
)
);
echo '<pre>' . $result . '</pre>';
exit;
以下のようにJSONが表示されれば成功(/api/user を呼び出した場合は、JSONでユーザ情報が表示される)
{"status":"OK","message":"test!"}
アクセストークンは oauth_refresh_tokens テーブルに access_token_id として記録される
アクセストークンでAPIを呼び出すときは以下の箇所で認証情報を送信している
Authorization: Bearer " . $_SESSION['access_token'] . "
以下のようにBase64デコードを行うと、送信しているデータの内容を確認できる
echo base64_decode($_SESSION['access_token']);
exit;
以下のような情報を確認できるが、このうち「2c7896d9f0809d4b77a531bd7562c9ce9146c9d9ed10e1c70fb88aaf9bd69f05c38ec60739e29d57」が access_token_id と一致する
{"typ":"JWT","alg":"RS256","jti":"2c7896d9f0809d4b77a531bd7562c9ce9146c9d9ed10e1c70fb88aaf9bd69f05c38ec60739e29d57"}以下略
Laravel における Bearer 認証 - chatbox blog
https://www.chatbox.blog/2018/05/29/laravel-%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B-bearer-%E8%AA%8D%E8...
Authorization Bearer ヘッダを用いた認証 API の実装 - げっとシステムログ
https://www.getto.systems/entry/2017/10/19/004734
■認証クライアントを作成
取得したアクセストークンをファイル内に直接書いておけば、任意のタイミングでAPIを呼び出せるようになる
<?php
define('ACCESS_TOKEN', 'アクセストークンの内容');
$result = file_get_contents(
'http://laravel.homestead.test/api/test',
false,
stream_context_create(
array(
'http' => array(
'method' => 'GET',
'header' => "Accept: application/json\r\n" .
"Authorization: Bearer " . ACCESS_TOKEN . "\r\n"
)
)
)
);
echo '<pre>' . $result . '</pre>';
exit;
■トークン持続時間
「デフォルトでPassportは、再生成する必要のない、長期間持続するアクセストークンを発行します。」とある
短くしたい場合はコードで指定する
API認証(Passport) 5.5 Laravel
https://readouble.com/laravel/5.5/ja/passport.html#configuration
■以下デバッグ時のメモ
app/Http/Kernel.php に以下のコードは手動で追加した?不要かも?
'client' => CheckClientCredentials::class,
Laravel 5.5 API User Authentication with Passport Package
https://www.itechempires.com/2017/09/laravel-5-5-api-user-authentication-passport-package/
Laravel 5.4でWeb APIを作る - アシアルブログ
http://blog.asial.co.jp/1498
API Authentication Error: {"error":"invalid_client","message":"Client authentication failed"} - Issue #221 - laravel/passport
https://github.com/laravel/passport/issues/221
PHP - Laravelで認証画面が表示されない|teratail
https://teratail.com/questions/98712
Laravel5.5でAPI認証のパッケージ(Laravel Passport)を利用する - Qiita
https://qiita.com/niiyz/items/fffff94acb6061ecc9d4
強制ログイン
Laravel Recipes日本語版 | ユーザーのIDを利用してログインする
http://recipes.laravel.jp/recipe/77
authentication - Laravel - Auth multi guard loginUsingId is not working as expexted - Stack Overflow
https://stackoverflow.com/questions/52154633/laravel-auth-multi-guard-loginusingid-is-not-working-as...
/config/auth.php で以下のように設定されているものとする
(標準のユーザとして user があり、追加のユーザとして developer があるものとする)
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'user' => [
'driver' => 'session',
'provider' => 'users',
],
'developer' => [
'driver' => 'session',
'provider' => 'developers',
],
'developer-api' => [
'driver' => 'token',
'provider' => 'developers',
],
],
■userで強制ログイン
users テーブルに taro@example.com ユーザがいる場合、以下のコードで強制的に認証してログイン済みにできる
Auth::attempt() でユーザ名とパスワードが正しいか確認できる
$id には users テーブルの代理キーが入り、その代理キーを Auth::loginUsingId() に渡すとログイン状態になる
…と思ったが、代理キーではなく「成功すれば1」が入っている。IDを得るにはメールアドレスをもとにデータベースを検索するくらいか
要検討
/*
* 強制ログインのテスト
*/
Route::get('/enter', function () {
$id = Auth::attempt(['email' => 'taro@example.com', 'password' => 'abcd1234']);
if ($id) {
Auth::loginUsingId($id);
return 'OK';
} else {
return 'NG';
}
});
■developerで強制ログイン
developers テーブルに developer@example.com ユーザがいる場合、以下のコードで強制的に認証してログイン済みにできる
$id には developers テーブルの代理キーが入る
…と思ったが、代理キーではなく「成功すれば1」が入っている。IDを得るにはメールアドレスをもとにデータベースを検索するくらいか
要検討
/*
* 強制ログインのテスト
*/
Route::get('/enter', function () {
$id = Auth::guard('developer')->attempt(['email' => 'developer@example.com', 'password' => 'abcd1234']);
if ($id) {
Auth::guard('developer')->loginUsingId($id);
return 'OK';
} else {
return 'NG';
}
});
Basic認証
■nginxによる設定
例えば以下のように設定されていたとして、
location / {
try_files $uri /index.php?$query_string;
}
以下のようにすれば /var/www/main/.htpasswd; によってBasic認証が設定される
location / {
auth_basic "Auth";
auth_basic_user_file /var/www/main/.htpasswd;
try_files $uri /index.php?$query_string;
}
NginxでBasic認証(Laravel) | Qrunch(クランチ)
https://qrunch.net/@G8AaKC38xzUeSbBe/entries/u9XM60aUWnMgTkts
■プログラムによる設定
ベーシック認証(自作)について - Laravel学習帳
https://laraweb.net/practice/1095/
基本的には上の解説の流れで実装したが、ユーザ名とパスワードは .env で管理できるようにした
「テスト環境では、特定のURLに対してBasic認証をかける」という用途で使用
プログラムで制御すると
・nginx環境ではBasic認証の設定に「service nginx reload」が必要なので、気軽に設定変更できない
・「特定のディレクトリのみに設定」もPHPの実行が絡むとややこしい
・またロードバランサー環境では「IPアドレスによっては認証不要」もやりづらい
を解決できるかも
app\Http\Middleware\AuthenticateForTest.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Response;
class AuthenticateForTest
{
/**
* 送られてきたリクエストの処理
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (empty(env('AUTH_FOR_TEST'))) {
return $next($request);
}
$request_user = $request->getUser();
$request_pass = $request->getPassword();
$authentications = explode(',', env('AUTH_FOR_TEST'));
foreach ($authentications as $authentication) {
if (empty($authentication)) {
continue;
}
list($user, $pass) = explode(':', $authentication);
if($request_user == $user && $request_pass = $pass) {
return $next($request);
}
}
return new Response('Invalid credentials.', 401, ['WWW-Authenticate' => 'Basic']);
}
}
app\Http\Kernel.php
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.test' => \App\Http\Middleware\AuthenticateForTest::class, … 追加
routes\web.php (「'middleware' => 'auth.test'」を指定することでミドルウェアを利用できる)
Route::group(['middleware' => 'auth.test'], function () {
〜略〜
});
Route::group(['prefix' => '/admin', 'namespace' => 'Admin', 'as' => 'admin', 'middleware' => 'auth.test'], function () {
〜略〜
});
.env
AUTH_FOR_TEST=guest1:aaaa1234,guest2:bbbb1234
.env に追記した AUTH_FOR_TEST の内容が認証情報
ユーザ名とパスワードを「:」区切りで記載し、「,」区切りで複数指定できる
アプリのWebView用に認証
Laravel 5 - Laravel5.5でスマホアプリのサーバ開発時の認証|teratail
https://teratail.com/questions/106165
【Swift】ユーザー認証APIを通した後、同一セッションとしてUIWebViewを表示する - Qiita
https://qiita.com/ktanaka117/items/e4921f061f6522ed5a63
URLを .env の内容から生成する
※未検証
サーバにIPアドレスでアクセスされると、IPアドレスでのURLがビューに使われてそれがキャッシュされる可能性がある
別ドメインで該当サーバにアクセスした場合も、その別ドメインでキャッシュされる可能性がある
以下のように、初期構築の段階でURLは .env の内容から作成されるようにしておくと無難そう
IPアドレスでのアクセス自体は、nginx側で拒否しておくのが正攻法と思われる
LaravelのURLジェネレーターで.envに設定したホスト名を使用する。 - Qiita
https://qiita.com/niiyz/items/f0b63e7afeb540a8b4b1
ルーティング
■ルーティングの設定と確認
ルーティング 5.4 Laravel
https://readouble.com/laravel/5.4/ja/routing.html
コントローラ 5.4 Laravel
https://readouble.com/laravel/5.4/ja/controllers.html
Laravel 5.4から可能になった新しいRouteの書き方 | cupOF Interests
http://co.bsnws.net/article/194
// 通常のルーティング
Route::get('/hello', function () {
return 'Hello World';
});
// パラメータを取得
Route::get('/user/{id?}', function ($id = null) {
return 'User ' . $id;
});
// ルートプレフィックス
Route::prefix('/admin')->group(function () {
Route::get('/user/{id?}', function ($id = null) {
return 'Admin User ' . $id;
});
});
以下のコマンドでルーティングを確認できる
php artisan route:list
■Resourcefulにルーティング
Laravelのルーティング書き方まとめ - Qiita
http://qiita.com/michiomochi@github/items/de19c560bc1dc19d698c
Route::resource() で典型的なCRUDルートを設定できる
Route::resource('photos', 'PhotoController');
only で特定のメソッドのみ有効にできる
Route::resource('photos', 'PhotoController', ['only' => ['store']]);
以下、CRUDの具体例
\test\routes\web.php
// Resourcefulルーティング
Route::resource('/restful', 'RestfulController');
\test\app\Http\Controllers\RestfulController.php
<?php
namespace App\Http\Controllers;
class RestfulController extends Controller
{
// getでrestful/にアクセスされた場合
public function index()
{
return 'RestfulController@index()';
}
// getでrestful/createにアクセスされた場合
public function create()
{
return 'RestfulController@create()';
}
// postでrestful/にアクセスされた場合
public function store()
{
return 'RestfulController@store()';
}
// getでrestful/messageにアクセスされた場合
public function show($message)
{
return 'RestfulController@show()';
}
// getでrestful/message/editにアクセスされた場合
public function edit($message)
{
return 'RestfulController@edit()';
}
// putまたはpatchでrestful/messageにアクセスされた場合
public function update($message)
{
return 'RestfulController@update()';
}
// deleteでrestful/messageにアクセスされた場合
public function destroy($message)
{
return 'RestfulController@destroy()';
}
}
■外部から直接POSTする
普通にPOSTでリクエストすると、VerifyCsrfTokenによってアクセスが拒否される
VerifyCsrfToken の $except に対象URLを記載することにより、VerifyCsrfTokenの対象から除外できる
以下は「/enter」を除外する例
/app/Http/Middleware/VerifyCsrfToken.php
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* CSRFの確認を除外するURI
*
* @var array
*/
protected $except = [
//
'/enter',
];
}
テンプレート
■Bladeテンプレート
/resources/views/test.php
test: <?php echo date('Y-m-d H:i:s') ?>
※テンプレートは通常のPHP
/resources/views/test.blade.php
test: <?php echo date('Y-m-d H:i:s') ?>
<hr>
csrf: {{ csrf_field() }}
※ファイル名を変えるとbladeの記法が使える
「test.php」と「test.blade.php」の両方が存在する場合、「test.blade.php」が使われる
@extends('layouts.app')
@section('content')
test: <?php echo date('Y-m-d H:i:s') ?>
<hr>
csrf: {{ csrf_field() }}
@endsection
※このようにするとレイアウトファイルが使われ、コンテンツが「layouts/app.blade.php」の@yield('content') 部分に埋め込まれる
※yield=譲渡する・委ねる
$articles = Article::orderBy('created_at', 'asc')->get();
return view('test', [
'articles' => $articles
]);
このように割り当てて、テンプレート側では以下のように参照する
@foreach ($articles as $article)
{{ $article->subject }} : {{ $article->detail }}<br>
@endforeach
■エスケープ
{{ 〜 }}
でHTMLがエスケープされる
{!! 〜 !!}
でHTMLがそのまま表示される。必要に応じて自前でエスケープする
ただし Htmlable を実装したクラスのインスタンスの場合はエスケープされない
具体的には {{ csrf_field() }} のようなコードが該当する
http://www.msng.info/archives/2016/01/laravel-blade-braces-dont-always-escape.php
Laravel4では
{{{ 〜 }}}
でHTMLがエスケープされる
{{ 〜 }}
でHTMLがそのまま表示される。必要に応じて自前でエスケープする
という挙動だった
■文章を改行して表示
{!! nl2br(e($body)) !!}
もしくは
/app/Providers/ViewServiceProvider.php
の boot() メソッド内で以下を定義する
\Blade::setEchoFormat('nl2br(e(%s))');
これで
{{ 〜 }}
でHTMLがエスケープされる。改行は改行タグが追加される
{{{ 〜 }}}
でHTMLがエスケープされるのみ
{!! 〜 !!}
でHTMLがそのまま表示される。必要に応じて自前でエスケープする
となる
How do I use nl2br() in Laravel 5 Blade
https://stackoverflow.com/questions/28569955/how-do-i-use-nl2br-in-laravel-5-blade
ファイル
■ファイルの保存(ローカル)
ファイルストレージ 5.5 Laravel
https://readouble.com/laravel/5.5/ja/filesystem.html
コントローラなどで以下を読み込む
use Illuminate\Support\Facades\Storage;
以下のコードを実行すると
Storage::put('test/1.txt', 'test1');
以下にファイルが保存される。ファイル内には「test1」と書き込まれる
storage\app\test\1.txt
■ファイルの保存(S3)
Laravel 5.4でS3ファイルアップロード - Qiita
https://qiita.com/zz22394/items/cd960124c3aec164a24d
ファイルのアップロード - ララジャパン
http://www.larajapan.com/tag/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%A2%E3%83%83%E3%83%9...
あらかじめAWSのコンソールで、S3のバケットを作成しておく
ドライバをインストール
composer require league/flysystem-aws-s3-v3
S3を使うために、.envに設定を追加する
AWS_KEY=XXXXX
AWS_SECRET=YYYYY
AWS_REGION=ap-northeast-1
AWS_BUCKET=laravel-test
コントローラなどで以下を読み込む
use Illuminate\Support\Facades\Storage;
以下のコードを実行するとアップロードされる
Storage::disk('s3')->put('test/1.txt', 'test1', 'public');
AWSのコンソールで、ファイルが作成されていることを確認する
なお、以下のようにしてアップロードするようにしておくと、
.env に「FILESYSTEM_CLOUD=public」を追加することで「ローカル環境の場合のみS3を使用しない」などができるようになる
(ローカル環境なら storage\app\public\test に保存されるようになる)
$image = $request->file('image')->storeAs(
'test', 'image.jpg', config('filesystems.cloud')
);
■ファイルアップロード
一例だが、以下のようなフォームからアップロードできる
<input type="file" id="image" name="image">
@if (isset($information) && $information->image)
<label>
<input type="checkbox" id="image_delete" name="image_delete" value="{{{ $information->image }}}"> ファイルを削除する
</label>
@endif
@if ($errors->first('image'))
<span class="help-block">
<strong>{{ $errors->first('image') }}</strong>
</span>
@endif
サービスで受け取って以下のように処理する
いったん一時的なディレクトリに保存し、データベースを更新できたら正式な場所に移動させている
public function postInformation($id, InformationCreateRequest $request)
{
$input = $request->only([
'title',
'body',
'datetime',
]);
$temporary_directory = 'temp/' . $request->session()->getId() . '/';
$extension = pathinfo($request->file('image')->getClientOriginalName(), PATHINFO_EXTENSION);
$file_path = $request->file('image')->storeAs($temporary_directory, 'image.' . $extension, config('filesystems.cloud'));
$input['image'] = basename($file_path);
try {
return DB::transaction(function () use ($id, $input, $temporary_directory) {
$result = $this->informationRepository->save($id, $input);
$data_directory = 'information/' . $result->id . '/';
if (isset($input['image_delete'])) {
Storage::disk(config('filesystems.cloud'))->delete($data_directory . $input['image_delete']);
} elseif (isset($input['image'])) {
Storage::disk(config('filesystems.cloud'))->delete($data_directory . $input['image']);
Storage::disk(config('filesystems.cloud'))->move($temporary_directory . $input['image'], $data_directory . $input['image']);
Storage::disk(config('filesystems.cloud'))->setVisibility($data_directory . $input['image'], 'public');
}
Storage::disk(config('filesystems.cloud'))->deleteDirectory($temporary_directory);
});
} catch (\Exception $e) {
return null;
}
}
InformationCreateRequest では以下のようにして入力チェックできる
public function rules()
{
return [
'title' => 'required|string|max:191',
'body' => 'required|string|max:1000',
'datetime' => 'required|date',
'image' => 'nullable|mimes:jpeg,gif,png|max:1000',
];
}
ビューで以下のようにすることで、ファイルを表示できる
@if ($information->image)
<img src="{{ Storage::disk(config('filesystems.cloud'))->url('information/' . $information->id . '/' . $information->image) }}">
@endif
S3使用時でも、ローカル環境で .env に以下を追記することで、storage\app\public\ 内にファイルが保存される
FILESYSTEM_CLOUD=public
ローカルに保存する場合、以下のコマンドを実行すると storage\app\public\ 内のファイルを /storage/ で参照できるようになる
(シンボリックリンクが張られる)
$ php artisan storage:link
The [public/storage] directory has been linked.
homestead - Laravel 5.3 storage:link -> symlink(): Protocol error - Stack Overflow
https://stackoverflow.com/questions/39496598/laravel-5-3-storagelink-symlink-protocol-error
PHP - Laravel5.4で画像をアップロードし、public/imagesに保存したい|teratail
https://teratail.com/questions/89984
Laravelで画像ファイルアップロードをする簡単なサンプル - Qiita
http://qiita.com/makies/items/0684dad04a6008891d0d
Laravel上画像アップロード - Qiita
http://qiita.com/samudra/items/3cb97bd6f1750c3f8134
■ファイルアップロード(保存先が異なる場合の補足)
デフォルトで storage/app/public が参照されるが、
例えば storage/app 直下に products などを作って保存する場合には問題がある
この場合、以下のように手動でシンボリックリンクを作成するといい
# rm /var/www/public/storage
# ln -s /var/www/storage/app /var/www/public/storage
laravel - Laravel-ローカルストレージのシンボリックリンクを元に戻して更新する方法
https://www.webdevqa.jp.net/ja/laravel/laravel%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%B9%E3%83%88...
■ファイルアップロード(Docker環境での補足)
PHPのコンテナ内で、通常どおり以下を実行すると対応できる
# php artisan storage:link
■ファイルアップロード(Vagrant環境での補足)
$ php artisan storage:link
In Filesystem.php line 228:
symlink(): Protocol error
このようなエラーになる場合、いったん
>vagrant halt
でHomesteadを終了し、コマンドプロンプトを右クリックして「管理者として実行」で起動する
その状態で再度以下を実行する
$ php artisan storage:link
それでも解決しなければ、Windowsのコマンドプロンプトから以下のように実行する
ショートカットのようなものが作成されるが、これはシンボリックリンク
(コマンドプロンプトは管理者権限で起動しないと、「この操作を実行するための十分な特権がありません。」のエラーになるので注意)
>cd C:\Vagrant\test\code\main\public
>mkdir storage
>cd storage
>mklink /D gift_item ..\..\storage\app\gift_item
gift_item <<===>> ..\..\storage\app\gift_item のシンボリック リンクが作成されました
WindowsでLaravelのphp artisan storage:linkが実行できなかった場合の対処法 - Qiita
https://qiita.com/shioharu_/items/608d024c48d9d9b5604f
コマンドプロンプト | シンボリックリンクの作成(MKLINK)
https://www.javadrive.jp/command/dir/index8.html
■CSSファイルやJSファイルのパスに、自動でファイルのタイムスタンプを追加する
LaravelでBladeを拡張する(@asset( )) - Qiita
https://qiita.com/reflet/items/a24b3c8e809f75fdbc19
実際は、以下のようにして組み込んだ
app\Providers\BladeServiceProvider.php を作成
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
class BladeServiceProvider extends ServiceProvider
{
/**
* Bladeサービスの初期化処理
*
* @return void
*/
public function boot()
{
/**
* CSSファイルやJSファイルのパスに、ファイルのタイムスタンプを追加
*
* @param string $file
* @return string
*/
Blade::directive('asset', function ($file) {
$file = str_replace(["'", '"'], '', $file);
$path = public_path() . '/' . $file;
try {
$timestamp = "?<?php echo \File::lastModified('{$path}') ?>";
} catch (\Exception $e) {
$timestamp = '';
}
return asset($file) . $timestamp;
/*
// ロードバランサーを考慮する場合(環境によってはURLを自動取得できないので、.envのAPP_URLを参照)
$app_url = env('APP_URL');
if (empty($app_url)) {
return asset($file) . $timestamp;
} else {
return $app_url . '/' . $file . $timestamp;
}
*/
});
}
/**
* アプリケーションサービスの登録
*
* @return void
*/
public function register()
{
//
}
}
config\app.php を編集
'providers' => [
〜略〜
App\Providers\BladeServiceProvider::class,
ビュー内で以下のように asset を使用すると、ファイル名の最後に「?1541565515」のような値が追加される
<script src="@asset('js/app.js')"></script>
<script src="@asset('js/plugins/datetimepicker/jquery.datetimepicker.full.min.js')"></script>
データベース
■マイグレーションとモデルの作成
php artisan make:migration create_articles_table ... 「create_articles_table」というマイグレーションクラスを作る
php artisan make:migration create_articles_table --table=articles ... 「articles」テーブルを作成するカラのコードを追加する
php artisan make:migration create_articles_table --create=articles ... 「articles」テーブルを作成・削除するコードを追加する
php artisan make:model Article ... 「Article」というモデルを作成
php artisan make:model Person -m ... Personモデルとpeopleテーブルのマイグレーションをセットで作成。Person は people になる
Model created successfully.
Created Migration: 2016_12_05_151532_create_people_table
※コマンドラインで操作
※artisan=職人
■マイグレーションでエンジンを設定
$table->engine = 'InnoDB';
■マイグレーションで真偽値を設定
available TINYINT(1) NOT NULL COMMENT '有効',
MySQLでこのような定義を行いたい場合、以下のように指定する
$table->boolean('available')->comment('有効');
■マイグレーションでコメントを設定
[Laravel]マイグレーションでテーブルにコメントを入れる|atuweb - ヒーローになるIT人材育成研究室
https://ameblo.jp/atuweb/entry-12204289098.html
Laravel - Laravel のマイグレーションでテーブルカラムにコメントを設定する(91732)|teratail
https://teratail.com/questions/91732
■既存のテーブル定義からマイグレーションを作成
Laravelで既存のDBからmigrationsファイルを作成する「migrations-generator」 - Qiita
https://qiita.com/busyoumono99/items/a7173ad6b9b041da09dd
■基本的なリレーション
モデルで以下のようなメソッドを定義
class User extends Authenticatable
{
public function entries()
{
return $this->hasMany('App\Entry');
}
コントローラで以下のようにして取得
$users = App\User::get();
foreach ($users as $user) {
$result .= $user->id . ' | ' . $user->name . ' | ' . $user->email . '<br>';
foreach ($user->entries as $entry) {
$result .= $entry->id . ' | ' . $entry->title . '<br>';
}
$result .= '<br>';
}
Eloquent:リレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent-relationships.html
親子関係のテーブルでのクエリーの作成(Eloquent編) - ララジャパン
https://www.larajapan.com/2016/07/09/%E8%A6%AA%E5%AD%90%E9%96%A2%E4%BF%82%E3%81%AE%E3%83%86%E3%83%BC...
■リレーション先の情報で検索
【Laravel・whereHas】リレーション先でクエリ実行 | とものブログ
https://se-tomo.com/2018/10/06/laravel%E3%81%A7%E3%81%AE%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A...
Laravelでリレーション先の条件で検索 | webOpixel
https://www.webopixel.net/php/1412.html
以下、リポジトリ内での検索例
if (isset($conditions['staff_company_number'])) {
$query->whereHas('staffCompanies', function($query) use($conditions) {
$query->where('company_number', $conditions['staff_company_number']);
});
}
■N+1問題
以下のように with を使うと、あらかじめ関連するデータを取得する
$users = App\User::get();
↓
$users = App\User::with('entries')->get();
Eloquent:リレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent-relationships.html
「Eagerローディング」の項目を参照
width で読み込んだデータを対象に、whereで絞り込みを行うこともできる
「Eagerロードへの制約」の項目を参照
Laravel|STEP UP BLOG
http://capella.3rin.net/Category/2/
■記事とカテゴリ、記事とタグなど多対多のリレーション
「(33) 多対多のリレーション モデル/DB編」の解説によると、
・「記事が複数のタグを持てる」のようなリレーションを行う場合は中間テーブルを使う
・中間テーブルは、テーブル名を関連するモデル名をアルファベット順で並べた名前にする(デフォルトの規約)
・中間テーブルのタイムスタンプを更新したい場合、withTimestamps() を指定する
・中間テーブルにオートインクリメントな主キーは、あっても無くてもいい
みたい
Eloquent:リレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent-relationships.html#many-to-many
初めてのLaravel 5.1 : (33) 多対多のリレーション モデル/DB編 - ララ帳
https://laravel10.wordpress.com/2015/03/30/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AElaravel-5-33-%E5%A4%9...
初めてのLaravel 5.1 : (34) 多対多のリレーション UI編 - ララ帳
https://laravel10.wordpress.com/2015/03/31/%e5%88%9d%e3%82%81%e3%81%a6%e3%81%aelaravel-5-34-%e5%a4%9...
Laravel5.4で基本的なリレーションを学んでみる | webOpixel
http://www.webopixel.net/php/1261.html
Laravel5.2で多対多(BelongsToMany)のリレーション | will STYLE Inc.|神戸にあるウェブ制作会社
https://www.willstyle.co.jp/blog/353/
■外部キー制約
要検証
以下のようにすれば、entriesテーブルやtagsテーブルのデータが削除された時、自動でentry_tagのデータも削除される
が、代理キーで管理するなら原則として削除されないことになる
「想定外のidが入らないように」という点では有効だが、外部キー制約を使うか否かは要検討
また、外部キー制約を付けるとしても、マイグレーションでは行わずにシーダーで行うほうがいい?
Schema::create('entry_tag', function(Blueprint $table)
{
$table->integer('entry_id')->unsigned()->index();
$table->integer('tag_id')->unsigned()->index();
$table->foreign('entry_id')->references('id')->on('entries')->onDelete('cascade');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});
■子モデル更新時に親モデルのタイムスタンプを更新
未検証
子モデル側の $touches プロパティにリレーション先を設定する
protected $touches = ['post'];
Eloquent:リレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent-relationships.html
■主キーをUUIDにする
LaravelでUUIDを使う際の設定 - Qiita
https://qiita.com/NewBieChan/items/3d0f8025accd770bd6d3
UUID - Qiita
https://qiita.com/hththt/items/801bf4664332a0346650
UUIDはなぜ、分散環境で好き勝手に生成しても衝突しないのか。RFC4122規格とUUIDの性質。 | 三度の飯とエレクトロン
http://blog.katty.in/5124
■主キーを複合主キーにする
代理キーで管理するのが前提となっているみたい。
複合主キーに対応できないわけではないみたいだが、代理キーを使っておくのが無難
他の主要フレームワークも代理キー前提が基本なので、
「複合主キーを使いたいからLaravelは採用しない」とはならなさそう
複合主キーにしてリレーションに問題が起きないかは要検証
[Laravel]Eloquent ORMで複合主キーはオススメしない
http://atuweb.net/201610_laravel-eloquent-orm-id-agreement/
PHP - Laravelで複合主キーを使ったモデルを更新できない(36627)|teratail
https://teratail.com/questions/36627
Laravel5で複合キーを扱う - KaokenとLaravel
https://laravel.cg0.xyz/handle-compound-keys-with-laravel-5/
LaravelのEloquentを使って複合主キーを使用したテーブルを操作する方法 - Qiita
http://qiita.com/wrbss/items/7245103a5fef88cbdde9
Eloquentで多対多のリレーション - Laravel Room
https://laravel-room.com/many-to-many-relationshop
■ユニークな複合キー
$table->unique(['first', 'last']);
データベース:マイグレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/migrations.html
■データを検索して一覧表示
検索機能の作成 - Laravel学習帳
http://laraweb.net/tutorial/607/
■操作したユーザの自動記録
Laravelのeloquentのeventでcreated_byとかupdated_byとか更新するobserverとtrait - Qiita
https://qiita.com/maimai-swap/items/6597c04721adbc48fec2
Soft delete: "deleting" event does not automatically update the model before it is soft deleted - Issue #4990 - laravel/framework
https://github.com/laravel/framework/issues/4990
Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans
https://laravel.com/docs/5.3/eloquent#events
Eloquent:利用の開始 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent.html#events
各テーブルで
created_by_user_id, updated_by_user_id, deleted_by_user_id
これらの値を更新する必要があるが、都度コントローラから指定すると更新し忘れが発生する
自動で更新する仕組みを導入するといい
各モデルで必要に応じて以下のtraitを宣言すれば、自動で更新されるようにしている
use RegisterAuthor;
■トランザクション
以下のように記述すると、ロールバックやコミットを自動で制御してくれる
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
もしくは以下を個別に呼び出す
DB::beginTransaction();
DB::rollBack();
DB::commit();
データベース:利用開始 5.4 Laravel
https://readouble.com/laravel/5.4/ja/database.html
Laravel5.3でselect for update する方法 - Qiita
https://qiita.com/taichi_akippa/items/465e986462be05a3ae42
Laravelで行ロックとテーブルロックする方法【lockForUpdate】 | みんたく
https://mintaku-blog.net/laravel-lockforupdate/
■生SQLの指定
Laravel5のアーキテクチャから学ぶより良いクラス設計 - Qiita
http://qiita.com/nunulk/items/2c637d3952096ef74677
$users = DB::table('users')
->join('contracts', 'contracts.user_id', '=', 'users.id')
->select('users.id', DB::raw('count(*) as contract_count'))
->orderBy('users.id')
->groupBy('users.id')
->having(DB::raw('count(*)'), '>', $count)
->get()
;
↓
$sql =<<<EOQ;
SELECT u.id, count(*) as contract_count
FROM users
INNER JOIN contracts c
ON c.user_id = u.id
GROUP BY u.id
HAVING count(*) > ?
ORDER BY u.id
EOQ;
$users = DB::select($sql, [$count]);
■ソフトデリート
マイグレーションを作成
php artisan make:migration alter_articles_table
マイグレーションを修正
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->softDeletes();
//$table->timestamp('deleted_at')->nullable()->after('updated_at');
});
}
public function down()
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
マイグレーションを実行
php artisan migrate
モデルを修正
C:\localhost\home\test\public_html\laravel\test\app\Article.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
/**
* 日付へキャストする属性
*
* @var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
}
データベース:マイグレーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/migrations.html
Eloquent:利用の開始 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent.html
削除処理の流れ
データ削除時、通常は
\test\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php
内の delete() が呼ばれる
その中で performDeleteOnModel() が呼ばれており、これにより削除処理が走る
SoftDeletesトレイトでこのメソッドを上書きしているので、これにより削除時の挙動が変わる
取得処理の流れ
Laravelの機能で、モデルは「boot + トレイト名」を最初に実行する
よって bootSoftDeletes() が最初に実行される
このメソッド内でスコープを渡し、データ取得時はそれによって構築されるクエリが変化する
http://qiita.com/niisan-tokyo/items/d3be588b53df8fa0278c
■複数代入
\test\app\Article.php
/**
* 複数代入する属性
*
* @var array
*/
protected $fillable = ['subject', 'detail'];
\test\app\Http\Controllers\ArticleController.php
// 通常の代入
$article = new Article;
$article->subject = $request->subject;
$article->detail = $request->detail;
$article->save();
// 複数代入
$article = new Article;
$article->fill($request->all())->save();
// このようにも書ける
Article::create($request->all());
複数代入を使用する場合、意図しない代入を防ぐためあらかじめモデルで
$fillable で代入を許可する項目を指定するか
$guarded で代入を許可しない項目を指定するか
を定義しておく必要がある。原則として $fillable での指定で良さそう
// 編集ならこのように書けば良さそう
Article::find(15)->update($request->all());
// 削除ならこのように書けば良さそう
Article::find(15)->delete();
Article::findOrFail(15)->delete();
【Laravel:Eloquentクラス】fillableとguardedの指定はどちらかだけでいい - Qiita
http://qiita.com/kk_take/items/3e0639ed605f74c34619
Article::findOrFail(15)
の書き方については、以下の「Not Found例外」を参照
Eloquent:利用の開始 5.4 Laravel
https://readouble.com/laravel/5.4/ja/eloquent.html
■モデルの型キャスト
【Laravel】DBから取得した値を$castsで型変換する【Attribute Casting】 - Qiita
http://qiita.com/amymd/items/7cba844b03754f9c849b
実装メモ
■APP_KEYの設定
他環境で実行する場合、GitからCLONEやPULLしたあとに以下の操作で APP_KEY の生成が必要
もしくは、キーを .env.example に含めるという手段もありそう
$ cp .env.example .env
$ composer install --no-dev
$ php artisan key:generate
複数台環境の場合、Web1で上記手順を実行して、
Web2では、上記作業によって .env に記録された APP_KEY の値を手動で設定
…とすると良さそう
(データの暗号化などに使われるようなので、すべてのサーバで同じ値に統一しておく)
Laravelについてのメモ - Qiita
https://qiita.com/reflet/items/76ce4d7743c7d9a8a963
[Laravel5.5] APP_KEY の行方を追う - Qiita
https://qiita.com/yk2220s/items/dcbf54c6d1f33a0cb06f
[Laravel] アプリケーションキー(APP_KEY)を設定する - 端くれプログラマの備忘録
http://www.84kure.com/blog/2015/11/17/laravel-%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7...
初めてのフレームワーク-Laravel5-4エラー時の対策メモ - Qiita
https://qiita.com/k-okada/items/0fa94f076d08584ee314
■クラスの省略呼び出し
use App\Article;
と宣言しておくと、以下のように省略して呼び出せる
$article = new App\Article;
↓
$article = new Article;
■enumを扱う
Laravel-enumをインストールすれば使える
Laravel-enumの使い方 - Qiita
https://qiita.com/sayama0402/items/4e8a885fed367090de12
ただし定番の方法は言いづらい
以下などを参考に自作する方が無難かも
PHPで列挙型(enum)を作る - Qiita
https://qiita.com/Hiraku/items/71e385b56dcaa37629fe
Laravelでつくる、深くて表現豊かなモデル - Qiita
https://qiita.com/nunulk/items/0820fd19690066d092d5
■セッション
// セッション
Route::get('/session', function () {
$count = session('count', 0) + 1;
session(['count' => $count]);
return 'count=' . $count;
});
// 以下のような書き方もできる
$request->session()->get('key');
複数台構成でセッションをデータベースに保存する場合、以下が参考になる
HTTPセッション 5.5 Laravel
https://readouble.com/laravel/5.5/ja/session.html
セッションをRedisに保存する場合、以下が参考になる
Laravel 5.3でセッション管理にredisを使用する方法 - Qiita
https://qiita.com/LowSE01/items/5f8a9c9b09948a6403dc
AWS EC2にredisをインストールする - Qiita
https://qiita.com/stoshiya/items/b8c1d2eb41770f92ffcf
404ページなどでセッションが使えない場合、以下が参考になる
($middleware に StartSession::class を追加する)
Laravel5 例外ハンドラで指定した遷移先ページでセッションを使う方法 - Qiita
https://qiita.com/papyrus9/items/c7394346ca509d016e70
■メールアドレスとパスワードではなく、ユーザ名とパスワードで認証する
未検証
LoginController で username() を定義する
public function username()
{
return 'username';
}
認証 5.4 Laravel
https://readouble.com/laravel/5.4/ja/authentication.html
「ユーザー名のカスタマイズ」を参照
Laravelでユーザー名またはメールアドレスでログインする - むらさきラボ
http://y6rasaki.hatenablog.com/entry/2016/11/17/222713
■キャッシュ
例えば以下のようにすると、Laravelでキャッシュを扱える
<?php
namespace App\Http\Controllers\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
/**
* テストページ
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function test(Request $request)
{
if (Cache::has('test')) {
$value = Cache::get('test') + 1;
} else {
$value = 1;
}
Cache::put('test', $value, 1);
return 'cache_test test=[' . $value . ']';
}
}
標準ではキャッシュはファイルに保存される
Redisを使用する場合、
$ composer require predis/predis
を実行する
.env
CACHE_DRIVER=file
REDIS_HOST=xxxxx.cache.amazonaws.com
REDIS_PASSWORD=null
REDIS_PORT=6379
キャッシュ 5.5 Laravel
https://readouble.com/laravel/5.5/ja/cache.html
Laravel Recipes日本語版 | クエリー結果をキャッシュする
http://recipes.laravel.jp/recipe/748
Laravel5.5でAWSのElastiCacheでRedisを使用する場合は以下が参考になりそう
Laravelのセッション管理にRedisを指定し、AWSのElastiCacheを利用する - 商売力開発ブログ
https://www.prj-alpha.biz/entry/2018/05/06/230232
【Elasticache】ELB配下の複数インスタンス間でのセッション管理【Laravel例】 - 備忘録の裏のチラシ
https://norikone.hatenablog.com/entry/2016/02/08/%E3%80%90Elasticache%E3%80%91ELB%E9%85%8D%E4%B8%8B%...
■Laravel+Redis の putCache と putManyCache
キャッシュへの保存は putCache を使うが、大量の情報を保存する場合は putManyCache を使うと速度が劇的に向上する可能性がある
以下、速度が向上する理由のメモ
putManyCache は vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php の100行目あたりに処理がある
基本的には配列の内容を put しているだけだが、前後で multi と exec が呼ばれている
/**
* Store multiple items in the cache for a given number of minutes.
*
* @param array $values
* @param float|int $minutes
* @return void
*/
public function putMany(array $values, $minutes)
{
$this->connection()->multi();
foreach ($values as $key => $value) {
$this->put($key, $value, $minutes);
}
$this->connection()->exec();
}
multi は vendor/predis/predis/src/Transaction/MultiExec.php の250行目あたりに処理がある
/**
* Finalizes the transaction by executing MULTI on the server.
*
* @return MultiExec
*/
public function multi()
{
if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) {
$this->state->unflag(MultiExecState::CAS);
$this->call('MULTI');
} else {
$this->initialize();
}
return $this;
}
キャッシュへの保存前に MULTI という文字を送っていることが判る
MULTI はRedisに用意されたトランザクション機能で、速度を向上させるために実装されているもの。これによって速度が向上する
ただしRedisのトランザクションは整合性を保つためのものではなく、あくまでもパフォーマンスを向上させるための機能なので注意
詳細は以下を参照
トランザクション - Redis Documentation (Japanese Translation)
https://redis-documentasion-japanese.readthedocs.io/ja/latest/topics/transactions.html
Redisのトランザクションとパフォーマンス | よしやのブログ
https://ameblo.jp/myon53/entry-11797609797.html
■Queue
未検証
キューが正しく動作しなくなったら、以下で再起動すると正常になる…ことが多いらしい
キューを削除しても動き出さない…という場合、SQSの処理中メッセージが無くなってから再起動すれば大丈夫のことがあったらしい
要検証
php artisan queue:restart
Laravel の Queue で非同期処理を実装する(beanstalkd / IronMQ / SQS) - Shin x blog
http://www.1x1.jp/blog/2014/08/laravel-queue-guide.html
Laravel5でAWSのSQSを使う 複数のキューを処理する方法 - ECサイト運営開発記
http://nohohox.hateblo.jp/entry/2016/02/26/030616
キュー 5.4 Laravel
https://readouble.com/laravel/5.4/ja/queues.html
キュー - ララ帳
https://laravel10.wordpress.com/2015/05/07/%E3%82%AD%E3%83%A5%E3%83%BC/
■APIの作成
APIアクセスの例
/api/example
routes\api.php にAPI用のルーティングを設定し、
app\Http\Controllers\Api\ExampleController.php で処理を行う
■コマンドの作成
Artisanコンソール 5.5 Laravel
https://readouble.com/laravel/5.5/ja/artisan.html
タスクスケジュール 5.5 Laravel
https://readouble.com/laravel/5.5/ja/scheduling.html
タスクスケジュール 6.x Laravel
https://readouble.com/laravel/6.x/ja/scheduling.html
laravelでバッチ作ってcronで動かしてみた - Qiita
https://qiita.com/ritukiii/items/a70d89fa988b2d9afbc4
Laravelで定期実行(スケジューラー)を設定する方法 - ポッポプログラミング
https://poppotennis.com/posts/laravel-scheduler
【Laravel】 Cron タスクスケジューラの onOneServer() と withoutOverlapping() の違い - Qiita
https://qiita.com/mpyw/items/15d14d920250a3b9eb5a
php artisan make:command Example
を実行すると
app\Console\Commands\Example.php
が作成されるので、これを編集する
php artisan list
コマンドの登録を確認
php artisan example
コマンドを実行
なお /etc/crontab にCronを記載する場合、
* * * * * php /var/www/vhosts/test/artisan schedule:run >> /dev/null 2>&1
ではなく、以下のように実行ユーザとパスも指定する必要があるので注意
(実行ユーザを指定していない場合、日時ログファイルが root 権限で作られて nginx から書き込めずエラーになる可能性がある)
* * * * * nginx /usr/bin/php /var/www/vhosts/test/artisan schedule:run >> /dev/null 2>&1
Laravel6の場合、以下のように「プロジェクトの場所へ移動してから実行」という書き方に変わっている
Laravel6を使うなら、こちらに合わせておく方が無難かも
* * * * * nginx cd /var/www/vhosts/test && php artisan schedule:run >> /dev/null 2>&1
重複起動はwithoutOverlappingで防止できるが、複数台サーバだと防止にならないので注意
Laravel Scheduler のコードリーディング - Qiita
https://qiita.com/mikakane/items/e9d2a70174e8af584db0
Laravelでタスクをスケジュール実行する | scratchpad
https://takayukii.me/20160824836
また、onOneServerで複数台サーバでの重複起動を防止できるが、
デフォルトキャッシュドライバとして、memcached か redis を使用する必要がある
タスクスケジュール 6.x Laravel
https://readouble.com/laravel/6.x/ja/scheduling.html
■関数やクラスのパスを取得
exit(ArticlesTableSeeder::class);
exit(event::class);
■動作環境の確認
php artisan env
Current application environment: local
.envの以下の部分で設定される
APP_ENV=local
production(実働環境)とtesting(ユニットテスト環境)は標準で予約されている
それ以外、local(開発環境)やstaging(デプロイ候補環境)など自由に設定できる
production に設定されている場合、
マイグレーションやシーディングの実行時に確認が求められる
■バージョンの確認
php artisan --version
Laravel Framework 5.4.34
Artisanコマンドライン 5.dev Laravel
https://readouble.com/laravel/5.dev/ja/artisan.html
\vendor\laravel\framework\src\Illuminate\Foundation\Application.php
コマンドで確認できるが、このファイルにある
const VERSION = '5.4.34';
この部分の数値が表示されている
■SSLの強制
/routes/web.php の最後に以下を追加する
// 本番環境ではSSL用のURLを生成
if (app()->environment('production')) {
URL::forceScheme('https');
}
productionでhttpsにしたい - ド忘れ防止雑記帳
http://dolphin.hatenablog.jp/entry/2015/04/27/010635
ただしこれは「LaravelにSSL用のURLを生成させる」処理なので、SSLにリダイレクトさせたいなら別途処理が必要
ロードバランサーが無い場合、以下で対応できそう(未検証)
Laravel5で.htaccessを使用せず常時SSL化対応する方法 - Qiita
https://qiita.com/qwe001/items/7cd0bcb149b5b5cc0fd7
ロードバランサーがある場合、以下のようにnginxで「$http_x_forwarded_proto」の条件を追加する
# vi /etc/nginx/nginx.conf
Laravel 5 - laravel5.5 httpからhttpsへリダイレクトさせたい|teratail
https://teratail.com/questions/98537
【Nginx】HTTPSへのリダイレクト設定〜rewriteではなくreturnでいこう〜 | ぴぐろぐ
https://pig-log.com/nginx-redirect-return/
■万年カレンダーを表示する
Laravel+Carbonでカレンダーの作り方 - Crieit
https://crieit.net/posts/Laravel-Carbon
/routes/web.php
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name laravel.example.com;
root /var/www/vhosts/laravel/public;
if ($http_x_forwarded_proto = 'http') {
return 301 https://$server_name$request_uri;
}
# service nginx configtest
# service nginx restart
use Carbon\Carbon;
Route::get('/calendar', function () {
$year = date('Y');
$month = date('m');
$dateStr = sprintf('%04d-%02d-01', $year, $month);
$date = new Carbon($dateStr);
// カレンダーを四角形にするため、前月となる左上の隙間用のデータを入れるためずらす
$date->subDay($date->dayOfWeek);
// 同上。右下の隙間のための計算
$count = 31 + $date->dayOfWeek;
$count = ceil($count / 7) * 7;
$dates = [];
for ($i = 0; $i < $count; $i++, $date->addDay()) {
// copyしないと全部同じオブジェクトを入れてしまうことになる
$dates[] = $date->copy();
}
return view('calendar', [
'dates' => $dates,
'year' => $year,
'month' => $month,
]);
});
/resources/views/calendar.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>カレンダー</title>
<style>
table tr th, table tr td {
border: 1px solid #666;
}
table tr td.secondary {
color: #aaa;
}
</style>
</head>
<body>
<h1>カレンダー</h1>
<table class="table table-bordered">
<thead>
<tr>
@foreach (['日', '月', '火', '水', '木', '金', '土'] as $dayOfWeek)
<th>{{ $dayOfWeek }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($dates as $date)
@if ($date->dayOfWeek == 0)
<tr>
@endif
<td
@if ($date->month != $month)
class="secondary"
@endif
>
{{ $date->day }}
</td>
@if ($date->dayOfWeek == 6)
</tr>
@endif
@endforeach
</tbody>
</table>
</body>
</html>
■確認画面のある問い合わせフォームを作る
マイグレーションを作成
php artisan make:migration create_contacts_table --create=contacts
マイグレーション内容を修正
Schema::create('contacts', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email');
$table->string('subject');
$table->text('body');
$table->timestamps();
$table->softDeletes();
});
マイグレーションを実行
php artisan migrate
モデルを作成
php artisan make:model Contact
モデルを修正
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Contact extends Model
{
use SoftDeletes;
/**
* 日付へキャストする属性
*
* @var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* 複数代入する属性
*
* @var array
*/
protected $fillable = ['name', 'email', 'subject', 'body'];
}
フォームリクエストを作成
php artisan make:request StoreContactPost
フォームリクエストを修正
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreContactPost extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|max:255',
'subject' => 'required|max:255',
'body' => 'required|max:255',
];
}
}
ルーティングを追加
Route::prefix('/contact')->group(function () {
Route::get('/', 'ContactController@index');
Route::post('/confirm', 'ContactController@confirm');
Route::post('/send', 'ContactController@store');
Route::get('/complete', 'ContactController@complete');
});
コントローラを作成
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Requests\StoreContactPost;
use App\Http\Controllers\Controller;
use App\Contact;
class ContactController extends Controller
{
/**
* Display a input form.
*
* @param Request $request
* @return Response
*/
public function index(Request $request)
{
return view('contact.index');
}
/**
* Confirm a new contact.
*
* @param Request $request
* @return Response
*/
public function confirm(StoreContactPost $request)
{
$data = $request->all();
$request->session()->put($data);
return view('contact.confirm', compact('data'));
}
/**
* Create a new contact.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
if ($request->get('action') === 'back') {
return redirect('/contact')->withInput($request->session()->all());
}
Contact::create($request->session()->all());
$request->session()->flush();
return redirect('/contact/complete');
}
/**
* Display a complete.
*
* @return Response
*/
public function complete()
{
return view('contact.complete');
}
}
ビューを作成: contact/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<div class="panel panel-default">
<div class="panel-heading">
Contact
</div>
<div class="panel-body">
<!-- Display Validation Errors -->
@if (count($errors) > 0)
<!-- Form Error List -->
<div class="alert alert-danger">
<strong>Whoops! Something went wrong!</strong>
<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- Contact Form -->
<form action="{{ url('contact/confirm')}}" method="POST" class="form-horizontal">
{{ csrf_field() }}
<!-- Contact Name -->
<div class="form-group">
<label for="contact-name" class="col-sm-3 control-label">Name</label>
<div class="col-sm-6">
<input type="text" name="name" id="contact-name" class="form-control" value="{{ old('name') }}">
</div>
</div>
<!-- Contact Email -->
<div class="form-group">
<label for="contact-email" class="col-sm-3 control-label">Email</label>
<div class="col-sm-6">
<input type="text" name="email" id="contact-email" class="form-control" value="{{ old('email') }}">
</div>
</div>
<!-- Contact Subject -->
<div class="form-group">
<label for="contact-subject" class="col-sm-3 control-label">Subject</label>
<div class="col-sm-6">
<input type="text" name="subject" id="contact-subject" class="form-control" value="{{ old('subject') }}">
</div>
</div>
<!-- Contact Body -->
<div class="form-group">
<label for="contact-body" class="col-sm-3 control-label">Body</label>
<div class="col-sm-6">
<textarea name="body" cols="50" rows="10" class="form-control">{{ old('body') }}</textarea>
</div>
</div>
<!-- Confirm Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">
<i class="fa fa-btn fa-plus"></i>Confirm
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
ビューを作成: contact/confirm.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<div class="panel panel-default">
<div class="panel-heading">
Contact
</div>
<div class="panel-body">
<!-- Contact Form -->
<form action="{{ url('contact/send')}}" method="POST" class="form-horizontal">
{{ csrf_field() }}
<!-- Contact Name -->
<div class="form-group">
<label for="contact-name" class="col-sm-3 control-label">Name</label>
<div class="col-sm-6">
{{ $data['name'] }}
</div>
</div>
<!-- Contact Email -->
<div class="form-group">
<label for="contact-email" class="col-sm-3 control-label">Email</label>
<div class="col-sm-6">
{{ $data['email'] }}
</div>
</div>
<!-- Contact Subject -->
<div class="form-group">
<label for="contact-subject" class="col-sm-3 control-label">Subject</label>
<div class="col-sm-6">
{{ $data['subject'] }}
</div>
</div>
<!-- Contact Body -->
<div class="form-group">
<label for="contact-body" class="col-sm-3 control-label">Body</label>
<div class="col-sm-6">
{!! nl2br(e($data['body'])) !!}
</div>
</div>
<!-- Send Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" name="action" value="back" class="btn btn-default">
Back
</button>
<button type="submit" class="btn btn-default">
<i class="fa fa-btn fa-plus"></i>Send
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
ビューを作成: contact/complete.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<div class="panel panel-default">
<div class="panel-heading">
Contact
</div>
<div class="panel-body">
Complete
</div>
</div>
</div>
</div>
@endsection
Laravel5で確認画面を経由したDB登録を実装する | Laravel | Ayumi Folio
http://aym.sakura.ne.jp/system/20160521/
Laravel 5で確認画面付き問い合わせフォームを作る - Qiita
http://qiita.com/ponko2/items/fd7ac38b964e10f16f52
基本的なアプリケーションの作成
■テーブル追加
マイグレーションを作成
php artisan make:migration create_articles_table --create=articles
マイグレーションファイルに以下を追加
$table->string('subject');
$table->text('detail');
マイグレーションを実行
php artisan migrate
マイグレーションを巻き戻す場合(最後に「一度に」実行したマイグレーションをまとめて巻き戻す)
php artisan migrate:rollback
以下のテーブルが作成される
CREATE TABLE `articles` (
`id` int(10) UNSIGNED NOT NULL,
`subject` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`detail` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
■モデル作成
php artisan make:model Article
■初期値登録(登録する場合)
シーダーを使用する。1件登録するだけなら
/test/database/seeds/DatabaseSeeder.php
にデータ登録コードを追加すればいい
ただし以下のように、Seeder用のファイルを作成するほうがいい
php artisan make:seeder ArticlesTableSeeder
ArticlesTableSeeder.phpに以下を追加
$article = new App\Article;
$article->subject = 'subject:' . str_random(10);
$article->detail = 'detail:' . str_random(50);
$article->save();
もしくは、直接insertしてもいい
use Carbon\Carbon;
$now = Carbon::now();
DB::table('articles')->insert([
'subject' => 'subject:' . str_random(10),
'detail' => 'detail:' . str_random(50),
'created_at' => $now,
'updated_at' => $now,
]);
DatabaseSeeder.phpに以下を追加して、ArticlesTableSeederを呼び出す
$this->call(ArticlesTableSeeder::class);
シーダーを実行する
php artisan db:seed
以下のエラーになる。作成したクラスを参照できていない
[ReflectionException]
Class ArticlesTableSeeder does not exist
require_once で該当クラスを読み込めば参照できるが、Laravelではオートロードの仕組みを使用する
/test/vendor/composer/ 内にオートロードの設定が記載されている
以下のコマンドを実行すると設定が更新され、クラスを参照できるようになる
(存在するはずのクラスを参照できない場合、この方法を試すといい)
composer dump-autoload
また、以下のようにすると特定のシーダーのみ実行できる
php artisan db:seed --class=ArticlesTableSeeder
Laravelで名前空間を指定してオートロードされなかったら見る場所。 - Qiita
http://qiita.com/niiyz/items/5b83ef5255a1ec64d9d6
composer dump-autoloadが面倒くさい Laravel - Qiita
http://qiita.com/ytake/items/98c438d6a006f61df54a
■テーブルの参照例
すべて取得
$articles = DB::table('articles')->get();
$articles = App\Article::all();
ビューでの表示
<ul>
@foreach ($articles as $article)
<li>{{ $article->subject }}</li>
@endforeach
</ul>
追加
$article = new App\Article;
$article->subject = 'Subject';
$article->detail = 'Detail';
$article->save();
更新
$article = App\Article::find(2);
$article->subject = 'Subject2';
$article->detail = 'Detail2';
$article->save();
削除
$article = App\Article::find(2);
$article->delete();
※追加・更新・削除は、fillで複数代入する書き方を推奨(後述)
■モデルの位置を変更する場合
ファイルを移動
\test\app\Article.php
↓
\test\app\Models\Article.php
モデルの呼び出しを変更
$articles = App\Article::all();
↓
$articles = App\Models\Article::all();
モデル内の宣言を変更
namespace App;
↓
namespace App\Models;
■CRUD
\test\routes\web.php
use Illuminate\Http\Request;
/**
* Show Article
*/
Route::get('/article', function () {
return view('article', [
'articles' => App\Article::orderBy('created_at', 'asc')->get()
]);
});
/**
* Add New Article
*/
Route::post('/article', function (Request $request) {
$validator = Validator::make($request->all(), [
'subject' => 'required|max:255',
'detail' => 'required|max:255',
]);
if ($validator->fails()) {
return redirect('/article')
->withInput()
->withErrors($validator);
}
$article = new App\Article;
$article->subject = $request->subject;
$article->detail = $request->detail;
$article->save();
return redirect('/article');
});
/**
* Delete Article
*/
Route::delete('/article/{id}', function ($id) {
App\Article::findOrFail($id)->delete();
return redirect('/article');
});
\test\resources\views\article.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<div class="panel panel-default">
<div class="panel-heading">
New Article
</div>
<div class="panel-body">
<!-- Display Validation Errors -->
@if (count($errors) > 0)
<!-- Form Error List -->
<div class="alert alert-danger">
<strong>Whoops! Something went wrong!</strong>
<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- New Article Form -->
<form action="{{ url('article')}}" method="POST" class="form-horizontal">
{{ csrf_field() }}
<!-- Article Subject -->
<div class="form-group">
<label for="article-subject" class="col-sm-3 control-label">Subject</label>
<div class="col-sm-6">
<input type="text" name="subject" id="article-subject" class="form-control" value="{{ old('subject') }}">
</div>
</div>
<!-- Article Detail -->
<div class="form-group">
<label for="article-detail" class="col-sm-3 control-label">Detail</label>
<div class="col-sm-6">
<input type="text" name="detail" id="article-detail" class="form-control" value="{{ old('detail') }}">
</div>
</div>
<!-- Add Article Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">
<i class="fa fa-btn fa-plus"></i>Add Article
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Current Articles -->
@if (count($articles) > 0)
<div class="panel panel-default">
<div class="panel-heading">
Current Articles
</div>
<div class="panel-body">
<table class="table table-striped article-table">
<thead>
<th>Article</th>
<th> </th>
</thead>
<tbody>
@foreach ($articles as $article)
<tr>
<td class="table-text"><div>{{ $article->subject }}: {{ $article->detail }}</div></td>
<!-- Article Delete Button -->
<td>
<form action="{{ url('article/'.$article->id) }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-danger">
<i class="fa fa-btn fa-trash"></i>Delete
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
</div>
</div>
@endsection
■CRUD:コントローラーを使用する場合
Route::get('/article', 'ArticleController@index');
Route::post('/article', 'ArticleController@store');
Route::delete('/article/{article}', 'ArticleController@destroy');
\test\app\Http\Controllers\ArticleController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Article;
class ArticleController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Display a list of all of the user's article.
*
* @param Request $request
* @return Response
*/
public function index(Request $request)
{
return view('article', [
'articles' => Article::orderBy('created_at', 'asc')->get()
]);
}
/**
* Create a new article.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$validator = $this->validate($request, [
'subject' => 'required|max:255',
'detail' => 'required|max:255',
]);
$article = new Article;
$article->subject = $request->subject;
$article->detail = $request->detail;
$article->save();
return redirect('/article');
}
/**
* Destroy the given article.
*
* @param Request $request
* @param string $id
* @return Response
*/
public function destroy(Request $request, $id)
{
Article::findOrFail($id)->delete();
return redirect('/article');
}
}
■基本的なバリデーションについて
$this->validate() を呼ぶことにより、エラーがあれば $errors にエラーメッセージが格納される
Validator::make() と $this->validate() の違いについても、以下のページで触れられている
実際のアプリケーションでは、フォームリクエストの仕組みでバリデーションする方が良さそう
バリデーション 5.4 Laravel
https://readouble.com/laravel/5.4/ja/validation.html
Laravelバリデーション指南書:フォームリクエストを使おう - Qiita
http://qiita.com/sakuraya/items/abca057a424fa9b5a187
Laravel5.0:FormRequestを使ったValidation - メドピア開発者ブログ
http://tech.medpeer.co.jp/entry/2015/06/16/171115
Laravel 5.1 入門記 その14(Form Request とメッセージのカスタマイズ編) - 日記
http://tnamao.hatenablog.com/entry/2015/10/04/175345
簡易ブログの作成
詳細な変更内容は以下のコミットログも参照
https://github.com/refirio/laravel-blog/commits/master
■プロジェクト作成
cd C:\localhost\home\test\public_html\laravel
composer create-project --prefer-dist laravel/laravel blog
http://localhost/~test/laravel/blog/public/
■日本語化
cd blog
composer require laravel-ja/comja5
cd C:\localhost\home\test\public_html\laravel
vendor\bin\comja5
vendor\bin\comja5 -c
vendor\bin\comja5 -f
■初期設定
config\app.php を編集
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
データベースを作成
laravel-blog
文字コードは utf8_general_ci もしくは utf8mb4_general_ci
config\database.php を編集
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
/.env を編集
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel-blog
DB_USERNAME=root
DB_PASSWORD=1234
■作成の準備
モデルを移動(移動先は App\DataAccess\Eloquent では無く App\Entities の方が良さそう)
app\DataAccess\Eloquent\User.php
不要なマイグレーションを削除
database\migrations\2014_10_12_100000_create_password_resets_table.php
コメントのモデルとマイグレーションを作成
php artisan make:model DataAccess\Eloquent\Comment -m
エントリーのモデルとマイグレーションを作成
php artisan make:model DataAccess\Eloquent\Entry -m
マイグレーションを調整
マイグレーションを実行
php artisan migrate
■ユーザ登録と認証
認証処理を調整
認証処理を調整後
composer dump-autoload
この時点で、ユーザ登録とログインの動作確認ができる
ユーザ登録処理をオーバーライド
Illuminate\Foundation\Auth\RegistersUsers の register() をもとにする
イベントについて
https://kore1server.com/358/Laravel5.4%E3%80%81%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E7%99%BB%E9%8C%B...
http://programmer-jobs.blogspot.jp/2017/04/laravel-5-4-get-login-event-record-user-login-time.html
http://qiita.com/bmf_san/items/e96bb3533de0792f9382
https://xzxzyzyz.blogspot.jp/2017/06/laravel5.html
$this->create() は同じクラス内にメソッドが定義されている
ユーザ登録のフォームリクエストを作成
php artisan make:request UserRegisterRequest
ユーザ登録処理をリファクタリング
Class 'App\Http\Controllers\Auth\Registered' not found
https://stackoverflow.com/questions/40836577/class-app-http-controllers-auth-registered-not-found
言語ファイルを追加
項目名は以下で指定できるが、モデルなど他の場所でも指定できたかも?どのように指定するのがいいか要検証
C:\localhost\home\test\public_html\laravel\blog\resources\lang\ja\validation.php
'attributes' => [],
ログインのフォームリクエストを作成
php artisan make:request LoginRequest
ログイン処理をオーバーライド
Illuminate\Foundation\Auth\AuthenticatesUsers の login() をもとにする
P.400 認証機能のカスタマイズ
は後回し
■記事
記事管理のコントローラを作成
php artisan make:controller Admin\EntryController
記事のフォームリクエストを作成
php artisan make:request EntryStoreRequest
php artisan make:request EntryUpdateRequest
記事の一覧・追加・更新を実装
http://qiita.com/michiomochi@github/items/de19c560bc1dc19d698c
http://qiita.com/fagai/items/a1bf55b6249aee03a624
http://recipes.laravel.jp/recipe/92
https://github.com/laravel/framework/issues/17550
http://qiita.com/naga1460/items/ecff7e439b534a021a68
ページネーションを追加
キャッシュは後回し
■論理削除
php artisan make:migration alter_users_table
php artisan make:migration alter_entries_table
php artisan make:migration alter_comments_table
マイグレーションを調整
マイグレーションを実行
php artisan migrate
モデルを修正
■キャッシュ
プログラムを実装
https://github.com/Zizaco/entrust/issues/468
https://github.com/Zizaco/entrust/issues/749
http://recipes.laravel.jp/recipe/122
fileとdatabaseはタグをサポートしていないらしい
arrayにすればエラーは出なくなるが、これは「キャッシュしない」という設定みたい
タグを使わないようにプログラムを調整した
C:\localhost\home\test\public_html\laravel\blog\app\Providers\AppServiceProvider.php
の以下の部分がキャッシュの有効期限。この場合60分
new DataCache($app['cache'], 'entry', 60)
■認可
php artisan make:policy EntryPolicy
php artisan make:middleware SelfEntry
プログラムを実装
この方式で何が便利になるのかは要勉強
https://readouble.com/laravel/5.4/ja/authorization.html
checkメソッドはallowsメソッドのエイリアス。5.4では紹介されていないので、非推奨と思われる
https://readouble.com/laravel/5.1/ja/authorization.html
composer dump-autoload
■ビューコンポーザー
プログラムを実装
ビューがレンダリングされるたびに実行した処理をまとめる
https://readouble.com/laravel/5.4/ja/views.html
■記事表示を実装
管理ページ用に作成したプログラムを流用して実装
■タグを実装
php artisan make:model DataAccess\Eloquent\Tag -m
タグを登録
$ php artisan tinker
>>> App\DataAccess\Eloquent\Tag::create(['name' => 'diary']);
>>> App\DataAccess\Eloquent\Tag::create(['name' => 'work']);
>>> App\DataAccess\Eloquent\Tag::create(['name' => 'hobby']);
>>> App\DataAccess\Eloquent\Tag::create(['name' => 'family']);
>>> App\DataAccess\Eloquent\Tag::all()->toArray();
>>> exit
記事とタグを関連付け
$ php artisan tinker
>>> $entry = App\DataAccess\Eloquent\Entry::first();
>>> $entry->tags()->attach(1);
>>> DB::select('select * from entry_tag');
>>> $entry = App\DataAccess\Eloquent\Entry::first();
>>> $entry->tags->toArray();
>>> $tag = App\DataAccess\Eloquent\Tag::first();
>>> $tag->entries->toArray();
>>> $entry = App\DataAccess\Eloquent\Entry::first();
>>> $entry->tags()->attach(2);
>>> $entry = App\DataAccess\Eloquent\Entry::find(2);
>>> $entry->tags()->attach(1);
>>> $entry->tags()->attach(3);
プログラムから記事とタグを一覧表示
Route::get('/test', function () {
$result = '';
$entries = App\DataAccess\Eloquent\Entry::with('tags')->get();
//$entries = App\DataAccess\Eloquent\Entry::get();
foreach ($entries as $entry) {
$result .= $entry->id . ' | ' . $entry->title . '<br>';
foreach ($entry->tags as $tag) {
$result .= $tag->id . ' | ' . $tag->name . '<br>';
}
$result .= '<br>';
}
return $result;
});
■備忘録・疑問点
外部キーを設定すると、マイグレーションのロールバックを実行できない?
外部キー制約はSeederで設定するほうがいい?
Laravelが自動作成するルーティングは以下なので、スラッシュは省略しない方がいいかも?
ルーティングのグループ化などへの影響を確認する
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Laravel5.4では、ユーザ登録関連でイベントが発行されるようになっている
それなら「記事登録時」「コメント登録時」など、自分で作る処理でもイベントを発行すべき?
サービスとか、スーパークラスとして専用のクラスを定義して継承させる?
でもUserRepositoryやUserRepositoryInterfaceは親クラスは不要?それなら無くていいかも?
$entries に記事データを渡して $entry で一件ずつ表示
…とすると、単数形と複数形が同じ単語の場合に問題がある
一件ずつ表示する際は常に $row でいいかも
levisのサンプルもそのようにする?
と思ったけど、ループ内でループさせる場合に名前がややこしい。要検討
created_at と updated_at はNULLを禁止にする?
モデル、リポジトリ、サービスのメソッド名は要検討
すべてで統一するのか区別するのか
投稿する場合も save なのか add なのか create なのか、など
=> のインデントを揃えるか否か
オートインクリメントな代理キーで管理している際、
代理キーの連番とは別に、例えばカテゴリごとに連番を登録したい場合はどのように処理するのがいいか
UIの作成には Form::open() を使う?
紐付けテーブルに代理キーを持たせるべきか、持たせないべきか
論理削除にすべきか、物理削除にすべきか
WMSは「代理キーあり、論理削除」となっている
論理削除の際、UNIQUE制約のある列はどうすべきか?
例えばユーザ名がUNIQUEだとして、deleted_atで削除しても再度同じユーザ名は使えない
以下の方法で、UNIQUE制約のある列に「DELETED 20171002210832 」を付与するか
Laravelのeloquentのeventでcreated_byとかupdated_byとか更新するobserverとtrait - Qiita
https://qiita.com/maimai-swap/items/6597c04721adbc48fec2
laravel Hashファサード 文字列ハッシュ化までの道 - Qiita
https://qiita.com/miriwo/items/b3fa105c28566d5f8cb7
パスワードのハッシュは、tinkerを使って作ることができる
$ php artisan tinker
> Hash::make('password')
もしくは、以下のようにすることもできる(デフォルトではHash::makeによってbcryptが呼び出されるらしいが、同じ結果を得られるかどうかは設定次第なので要確認)
> bcrypt('password')
その他 Laravel.txt で「未検証」としているものは検証したい
■解決済みメモ
useは Illuminate と App のどちらを先に書くか、統一したい
→先にAppを書く方が一般的みたい
公式のサンプルもそのようになっているので、そちらに合わせる
1ページあたりの表示件数などは、設定ファイルで管理する?
設定ファイルにはどのように定義するのがいいか
→設定ファイルで以下のようにすると良さそう
https://github.com/laravel-jp-reference/chapter8/blob/master/config/blog.php
テスト・デバッグ
■デバッグ
デバッグモード
.env
APP_DEBUG=true
デバッグバー
composer require barryvdh/laravel-debugbar
config/app.php
'providers' => [
Barryvdh\Debugbar\ServiceProvider::class,
'aliases' => [
'Debugbar' => Barryvdh\Debugbar\Facade::class,
アプリケーションログ
/storage/logs
Laravel5.5: Laravel Debugbarを使う - Qiita
https://qiita.com/sutara79/items/9fd442a81001842aeba1
config/app.php で設定しているドメインと異なるドメインでアクセスすると、デバッグバーは表示されないようなので注意
(正しいドメイン、もしくはIPアドレスでのアクセスなら表示されるみたい)
Laravel5.0でlaravel-debugbarを使う - Qiita
https://qiita.com/naga1460/items/4a5a5ede493ef008fe90
■単体テスト
テスト: テストの準備 5.4 Laravel
https://readouble.com/laravel/5.4/ja/testing.html
Laravel5でテスト(PHPUnit) - Qiita
https://qiita.com/zaburo/items/839c81a1e166a48fe3fa
テストを実行
cd C:\localhost\home\test\public_html\laravel\blog
phpunit
実行すると以下のエラーになる
PHP Fatal error: Call to undefined method PHPUnit_Util_Configuration::getTestdoxGroupConfiguration() in C:\localhost\home\test\public_html\laravel\test\vendor\phpunit\phpunit\src\TextUI\TestRunner.php on line 1066
LaravelプロジェクトとJenkinsの連携 - ハマログ
https://blog.e2info.co.jp/2017/05/10/laravel_and_jenkins/
PHPUnitのバージョンが問題みたいだが、解説のようにcomposerを実行しても解決せず
C:\localhost\home\test\public_html\laravel\test\vendor\phpunit\phpunit\src\TextUI\TestRunner.php
//$testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration();
1066行目をコメントアウトしたら一応動く。ただし何らかの機能が削られていると思われる
テストを実行
phpunit
以下のテストがはじめから作成されているので、これが実行される
HTTPテストやChromeドライバーによるテストもできるみたい
\tests\Feature\ExampleTest.php
\tests\Unit\ExampleTest.php
設定ファイルを指定してテストを実行
phpunit --configuration phpunit.xml
特定のテストのみ実行
phpunit tests/Feature/RootTest.php --configuration phpunit.xml
テストを作成
php artisan make:test ArticleTest --unit
\test\tests\Unit\ArticleTest.php にテストが作成される
具体的なテストの作成については、以下が参考になる
chapter8/app at master - laravel-jp-reference/chapter8
https://github.com/laravel-jp-reference/chapter8/tree/master/app
LaravelでHTTPテスト - Qiita
https://qiita.com/Frog_woman/items/6a143af0a042dc853e88
■データベースを利用する単体テスト
\config\database.php
の「connections」に以下の設定を追加(データベースにテスト時の値が登録されないように、SQLiteのインメモリ機能を利用する)
'testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
'option' => [
PDO::ATTR_PERSISTENT => true,
],
],
\phpunit.xml
テストの際に上記設定を使用する
<env name="DB_CONNECTION" value="testing"/>
テスト実行時
「PHP Fatal error: Class 'Doctrine\DBAL\Driver\PDOSqlite\Driver' not found」
と表示されるようなら、doctrine/dbal をインストールする
composer require doctrine/dbal
以下も参考にする
データベースのテスト 5.5 Laravel
https://readouble.com/laravel/5.5/ja/database-testing.html
laravelでDBテストコードを書く前の設定すべきこと - Qiita
https://qiita.com/kuriya/items/4c9dbefc19514f415374
■ブラウザテスト(Selenium)
$ composer require --dev laravel/dusk … とても時間がかかった。PHP5環境ではインストールできない?
Using version ^2.0 for laravel/dusk
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Installing facebook/webdriver (1.4.1): Downloading (100%)
- Installing laravel/dusk (v2.0.7): Downloading (100%)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: laravel/dusk
Package manifest generated successfully.
app\Providers\AppServiceProvider.php
に登録
php artisan dusk:install
を実行すると、テストの雛形が作成される
php artisan dusk
でテストを実行できる。「--configuration phpunit.xml」のオプションは使えないみたい?
php artisan dusk tests/Browser/AdminLoginTest.php
で特定のテストのみを実行できる
php artisan dusk tests/Browser/AdminLoginTest.php --filter=testStaffLoginSuccess
で特定のテスト内のメソッドのみを実行できる
通常アクセスかテストかを判定するなら、
$_SERVER['HTTP_USER_AGENT']
の値を調べるか
ブラウザテスト(Laravel Dusk) 5.4 Laravel
https://readouble.com/laravel/5.4/ja/dusk.html
Laravel 5.4 で手軽にテストを書こう! | 株式会社インフィニットループ技術ブログ
https://www.infiniteloop.co.jp/blog/2017/05/laravel-5-testing/
Laravel 5.4で導入されたLaravel Duskをテスト後にDBリセットさせるようにして試してみた - Qiita
https://qiita.com/n_mogi/items/b96ccc1df31aa5fb859a
Laravel5でPC/SPを振り分ける方法 - Qiita
https://qiita.com/qwe001/items/0064adc3c893f603fe21
【Laravel 5.4】Laravel Duskによるブラウザテストの作成方法 - Qiita
https://qiita.com/amymd/items/0a5f2705e29972d0d22e
ブラウザテスト(Laravel Dusk) 5.4 Laravel
https://readouble.com/laravel/5.4/ja/dusk.html
なお
「Your requirements could not be resolved to an installable set of packages.」
のエラーになってインストールできない場合、
以下でインストールできることがある
$ composer require --dev laravel/dusk:"^2.0"
ブラウザテスト(Laravel Dusk) 5.5 Laravel
https://readouble.com/laravel/5.5/ja/dusk.html
また
「Laravel Facebook\WebDriver\Exception\SessionNotCreatedException: session not created exception: Chrome version must be >= 62.0.3202.0」
のようなエラーが表示される場合、Chromeのバージョンが低い可能性がある
Homestead内でupdateを実行する
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get dist-upgrade
Laravel-dusk does not work - Stack Overflow
https://stackoverflow.com/questions/48471515/laravel-dusk-does-not-work
コードによってはテスト時にデータが消去されるので注意
避けたいなら、テスト用にデータベースを作っておく必要があるみたい
Laravel DuskでDBデータが飛んだって話(メモ) - Qiita
https://qiita.com/Frog_woman/items/6f3581809a38dfe655e4
Laravel5.7: ブラウザテストを記述する - Qiita
https://qiita.com/sutara79/items/9190c8444a49842ca25a
■アプリケーションログ
アプリケーションにログを記録する際、ログレベルを意識しておくと対応の切り分けなどに役立つ
ログレベルは PSR-3 で定義されている
【PHP】PSR-3 Logger Interface(ロガーインタフェース)
https://www.ritolab.com/entry/95
どのログレベルを使用するかなど、以下が参考になる
ログレベルちゃんと使い分けてますか? - OTOBANK Engineering Blog
https://engineering.otobank.co.jp/entry/2016/09/20/181756
Laravelでログを使う場合、以下などを参考にする
エラーとログ 5.5 Laravel
https://readouble.com/laravel/5.5/ja/errors.html
Laravel5.6 での ログ設定について - Qiita
https://qiita.com/hrdaya/items/b01d5621937a0710ca64
ログを日付ごとのファイルにする場合、config/app.php もしくは APP_LOG の項目で設定できる
日付ごとにした場合、デフォルトではログは5日間保持される
例えば14日間保持したい場合、config/app.php の
'log' => env('APP_LOG', 'daily'),
'log_level' => env('APP_LOG_LEVEL', 'debug'),
この部分に以下を追加する(Laravel5.5ではデフォルトでは項目自体が存在しないので、項目を追加する)
'log_max_files' => '14',
Laravelのlogファイルを日付ごとに分ける - Qiita
https://qiita.com/isao_e/items/304c66f7807f141b6f43
トラブル対応
■マイグレーションを実行できない
以下のエラーが表示される場合、
データベースの文字コードがutf8mb4の場合にVARCHARの最大文字数が191文字に制限されるため
[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
oo long; max key length is 767 bytes (SQL: alter table `users` add unique `
users_email_unique`(`email`))
[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
oo long; max key length is 767 bytes
/test/app/Providers/AppServiceProvider.php の宣言に
use Illuminate\Support\Facades\Schema;
を追加し、さらに boot() 内に以下を追加するとマイグレーションできるようになる
(なお、191を例えば255にすると「長すぎる」といってエラーになる)
Schema::defaultStringLength(191);
個別のマイグレーションを編集して対応はできるが、
他にもLaravelがマイグレーションファイルを作成することがあるため、この設定を入れておく方が無難
MySQL - Laravel 5.4 デフォルトで設定されているマイグレーションファイルを実行するとSQLエラーが出る(63441)
https://teratail.com/questions/63441
MySQLのインデックスサイズに767byteまでしかつかえない問題と対策 - ハマログ
https://blog.e2info.co.jp/2017/04/17/mysql%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%...
■意図したファイルを読み込めない・参照できない
ファイルを参照できないときに試すこと
以下のように直接読み込めば参照できるが、正しい方法ではない
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
require_once $baseDir . '/database/seeds/ArticlesTableSeeder.php';
$this->call(ArticlesTableSeeder::class);
このような場合、
・use を指定しているか
・/config/app.php でパスは設定されているか
・/config/ 内の他のファイルにもパス指定がある
・/app/Http/Kernel.php でパスは設定されているか
・/app/Providers/AppServiceProvider.php でパスは設定されているか
・/app/Providers/ 内の他のファイルにもパス指定がある
などを確認する。また
composer dump-autoload
でオートロードの設定を更新できる
php artisan config:clear
で設定(.env や config/*.php のファイル)のキャッシュを削除できる
artisan view:clear
でビューのキャッシュを削除できる
artisan route:clear
でルートのキャッシュを削除できる
php artisan cache:clear
でその他のキャッシュを削除できる(ElastiCacheなどの内容だと思われる)
Laravel キャッシュクリア系コマンドなど - Qiita
https://qiita.com/Ping/items/10ada8d069e13d729701
Laravelで名前空間を指定してオートロードされなかったら見る場所。 - Qiita
http://qiita.com/niiyz/items/5b83ef5255a1ec64d9d6
[Laravel]デプロイ時の最適化 - Qiita
http://qiita.com/qiita-kurara/items/d37dbc5b67e6b6dfbe1d
Laravel キャッシュクリア系コマンドなど - Qiita
https://qiita.com/Ping/items/10ada8d069e13d729701
■「APP_ENV=production」にすると動作しない
$ vi .env
APP_ENV=production
と設定するとエラーになって動作しない。「It is unsafe to run Dusk in production.」というエラーになっている
Laravel5.4から5.5へ更新する - Qiita
https://qiita.com/sutara79/items/c07882be23a53c498482
「解決策: discoverの対象からDuskを外す」
$ vi composer.json
"extra": {
"laravel": {
"dont-discover": [
"laravel/dusk" … 追加
]
}
},
$ composer install
これで大丈夫…のはずだが、試した時は以下の警告も表示された。(composer install は実行できる)
Package phpoffice/phpexcel is abandoned, you should avoid using it. Use phpoffice/phpspreadsheet instead.
調べてみると
composer.json 内に「excel」の文字は無いが composer.lock 内にはある
という状態だった
composer.lock 内に「phpoffice/phpexcel」の記述があるので、composer.lock 自体を削除してから再度実行
$ composer install
これで大丈夫だった
それでも駄目なら、composer.lock を削除して composer install --no-dev を実行すると完了できた
[Laravel] プロダクション環境にはLaravel Duskをインストールしない - 端くれプログラマの備忘録
http://www.84kure.com/blog/2017/09/17/laravel-%E3%83%97%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7...
■「APP_ENV=production」にするとマイグレーションとシーダーを直接実行できない
$ vi .env
APP_ENV=production
と設定すると、マイグレーションやシーダーの実行時に確認が表示される
これ自体は有用な仕組みだが、シェルスクリプトなどから実行したい場合には煩わしい
以下のように「--force」を付ければ、確認なしにマイグレーションやシーダーを実行できる
$ php artisan migrate --force
$ php artisan db:seed --force
■「PHP Fatal error: Maximum function nesting level of '512' reached, aborting!」というエラーになる
以下の現象ではあるが、上限を上げても解決しない
無限ループになってしまっているみたい
PHP - Fatal error: Maximum function nesting level of '100' reached, aborting!(169)|teratail
https://teratail.com/questions/169
複数のクラスから同じオブジェクトを注入しようとしたとき、このような現象になることがあるみたい?
Laravelのコンストラクタインジェクションの仕様らしい?
部分的にコンストラクタインジェクションではなくメソッドインジェクションを使うか、
AppServiceProviderに登録するなどで回避できるみたい
どういった方法がベストなのかは要検討
LaravelのDIの挙動を勘違いしていてドハマリした件 - Qiita
https://qiita.com/ak-ymst/items/00e7d3cee16ba6d3e710
■FormRequestのカスタムバリデーション属性名を attributes() で指定しても認識されない
app\Providers\ValidatorServiceProvider.php
ValidatorServiceProvider の boot() の処理内容を修正する
Laravel自体の不具合かも?でも修正前の状態でも属性名が反映されている案件がある。要調査
public function boot()
{
\Validator::resolver(function ($translator, $data, $rules, $messages) {
return new RuleValidator($translator, $data, $rules, $messages);
});
}
↓
public function boot()
{
\Validator::resolver(function ($translator, $data, $rules, $messages, $attributes) {
return new RuleValidator($translator, $data, $rules, $messages, $attributes);
});
}
Laravel の Validation を正しく拡張する - Qiita
https://qiita.com/moobay9/items/f1cdd3c8f995fdcf0963
■コンストラクタでユーザ情報を取得できない
Laravel5.3で変更された仕様
5.3からは middleware を挟むことで取得できる
コンストラクタでAuth::user()を取得する方法 - Qiita
https://qiita.com/capybara1229/items/98d5179f9fa599011de0
Laravel 5.3 コントローラのコンストラクタの重要な変更 - ララジャパン
https://www.larajapan.com/2016/09/24/laravel-5-3%e3%80%80%e3%82%b3%e3%83%b3%e3%83%88%e3%83%ad%e3%83%...
■composer install でエラーになる
開発環境でライブラリを追加し、composer.json と composer.lock が更新されたものを検収環境に反映
…のようなときに発生した
$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Installation request for facebook/webdriver 1.6.0 -> satisfiable by facebook/webdriver[1.6.0].
- facebook/webdriver 1.6.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.
Problem 2
- facebook/webdriver 1.6.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.
- laravel/dusk v2.0.14 requires facebook/webdriver ~1.0 -> satisfiable by facebook/webdriver[1.6.0].
- Installation request for laravel/dusk v2.0.14 -> satisfiable by laravel/dusk[v2.0.14].
ext-zip が無いと言われるが、zipコマンドはインストールされている
composer.lock を削除して再実行すると完了できた
他環境用にバージョンが固定されてしまっていたからかも?
composer.lock の削除は良い方法では無いと思われるので、
以下などを参考にしつつベストな管理方法を考えたい
にわかエンジニアの備忘録: composer.lockはGit管理すべき話
https://sho-memo01.blogspot.com/2019/08/composerlockgit.html
作業ブランチではcomposer.lockの変更を最小化してください - Qiita
https://qiita.com/tanakahisateru/items/ff4118ffd6a404bceb64
■メールを送信すると、本文が Attachment.html という添付ファイルになる
HTMLメールを softbank.ne.jp のアドレスで受信すると、本文が添付ファイルになってしまう
iPhoneでHTMLメールのメルマガが読めない場合 - リザーブストックの使い方
http://reservestock.hatenablog.jp/entry/2015/10/17/050502
メール送信時、view() ではなく text() にする
その際、本文内の改行タグや実体参照も不要になるので注意
Laravel 5.3 でメールを送る - Qiita
https://qiita.com/apricoton/items/d93b358bb8960b803b19
手軽に Mail::send で送るなら、第一引数を調整すればテキストメールになる
[Laravel] Mailableクラスを作らずに、Mail::sendでサクッとメールを送信する - YoheiM .NET
https://www.yoheim.net/blog.php?q=20181207
以下の場合はHTMLメールになる
Mail::send('emails.test', [ 'test' => 'テストメール' ], function ($message) {
$message
->from('from@example.com')
->to('to@example.com')
->subject('テストメール');
});
以下の場合はテキストメールになる(Mail::send の第一引数のみ変更している)
Mail::send(['text' => 'emails.test'], [ 'test' => 'テストメール' ], function ($message) {
$message
->from('from@example.com')
->to('to@example.com')
->subject('テストメール');
});
ただしSendGridを使用している場合で、一括送信メールを特定の宛先のみに届いたように見せかけるために
return $this->text('emails.remind')
->to($this->tos)
->subject('【食品ロスダイアリー】この1 週間 食品ロスはありませんでしたか?')
->with([
'body' => $this->body,
'email' => ':email',
])
->sendgrid([
'personalizations' => $tos,
]);
と sendgrid() を使用すると、本文内にimgタグが埋め込まれてしまう
これによって、Gmailなどで確認するとHTMLメール扱いになってしまう問題がある
対処法は要検証
■データベースから取得した数値が文字列として扱われる
PHP+PDOでMySQLからデータを取得した際、数値型が文字列型として扱われてしまう
よって「===」などで比較を行うと意図した結果にならないことがある
PDOでフェッチした数値型カラムの値が文字列で取得されるのでなんとかしようと頑張った。 - erio_nk://memo
http://d.hatena.ne.jp/erio_nk/20120621/1340267044
Laravelの場合、Attribute Casting により型を厳密に扱うことができるらしい
[Laravel5][Eloquent] Attribute Castingによりデータ型を厳密に取り扱う|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット)
https://nextat.co.jp/staff/archives/140
Laravel 5.5 Pivot Casting - Laravel News
https://laravel-news.com/laravel-5-5-pivot-casting
Laravel 5 でのセキュリティ対策 (PHP) | ラボラジアン
https://laboradian.com/sec-laravel5/
■ブラウザを閉じてもセッションが終了されない
Laravelの初期設定がそのようになっている
app/config/session.php で expire_on_close を false にすればいい
Laravel セッションクッキーの有効期間をブラウザを閉じるまでにする - Qiita
https://qiita.com/shin1x1/items/af68541d0c1bbb4c66df
■.env の値に「#」を使用できない
Symfony3.txt の「トラブル」を参照
(「#」や「$」が含まれている場合の挙動が怪しい)