- 投稿日:2021-01-14T22:13:12+09:00
[PHP] Wordファイル.docxの文字数をカウントする(取り消しやコメントも取得可能)
やりたいこと
PHPを使って、Wordファイル.docxの文字数をカウントしたい。
まずは、本文の文字数カウントのやり方を紹介します。
応用編として、取り消された文字やコメントの文字を取得するコードを紹介します。環境
- PHP 8.0.0
- Windows 10
手順
1: .docxファイルを解凍して、.xmlファイルを拾う。
実際にzipファイルを見るとわかりますが、目的のファイルは、wordフォルダ内のdocument.xmlです。
2: 段落(pタグ)ごとに、テキスト(tタグ)を連結していく。
getElementsByTagName
メソッドで、所望のタグ要素を取得できます。3: 連結した文字列の文字数を数える。
文字数のカウントには
mb_strlen()
関数を使います。コード
countWords.php$NL = "\n"; $contents = ""; $file_path = $request->file("testFile"); // フォームで受け取る想定です。 $zip = new \ZipArchive(); if ($zip->open($file_path) === true) { $xml = $zip->getFromName("word/document.xml"); if ($xml) { $dom = new \DOMDocument(); $dom->loadXML($xml); // 段落(pタグ)抽出 $paragraphs = $dom->getElementsByTagName("p"); foreach ($paragraphs as $p) { // テキスト(tタグ)抽出 $texts = $p->getElementsByTagName("t"); foreach ($texts as $t) { $contents .= $t->nodeValue; } } } } $wordscount = mb_strlen($contents);応用
上記のやり方だと、取り消し線で消された文字や、コメントの文字が拾えない。
そこで、取り消し線で消された文字はdelTextタグ、コメントはword/comments.xmlから拾ってくる。
下記がそれらを含んだコード。countWords_adv.php$NL = "\n"; $contents = ""; $file_path = $request->file("testFile"); // フォームで受け取る想定です。 $zip = new \ZipArchive(); if ($zip->open($file_path) === true) { // 本文の文字列を抽出 $xml = $zip->getFromName("word/document.xml"); if ($xml) { $dom = new \DOMDocument(); $dom->loadXML($xml); // 段落(pタグ)抽出 $paragraphs = $dom->getElementsByTagName("p"); foreach ($paragraphs as $p) { // テキスト(tタグ)抽出 $texts = $p->getElementsByTagName("t"); foreach ($texts as $t) { $contents .= $t->nodeValue; } // 取り消しされたテキスト(delTextタグ)抽出 $delTexts = $p->getElementsByTagName("delText"); foreach ($delTexts as $dt) { $contents .= $dt->nodeValue; } $contents .= $NL; } } // コメントの文字列を抽出 $xmlComment = $zip->getFromName("word/comments.xml"); if ($xmlComment) { $dom = new \DOMDocument(); $dom->loadXML($xmlComment); // 段落(pタグ)抽出 $paragraphs = $dom->getElementsByTagName("p"); foreach ($paragraphs as $p) { // テキスト(tタグ)抽出 $texts = $p->getElementsByTagName("t"); foreach ($texts as $t) { $contents .= $t->nodeValue; } // 取り消しされたテキスト(delTextタグ)抽出 $delTexts = $p->getElementsByTagName("delText"); foreach ($delTexts as $dt) { $contents .= $dt->nodeValue; } $contents .= $NL; } } } $wordscount = mb_strlen($contents);まとめ
やりたいことに対応するタグ要素さえ見つけられれば、紹介したコードで応用が利くと思います。
- 投稿日:2021-01-14T20:05:32+09:00
【Laravel】Route::resourceとは?resouceメソッドの意味と使い方
Laravelのルートで使われる
Route::resource
について、
Route::resource
はリソースコントローラという特殊なコントローラを作成し、そのルートを登録するためのメソッド。リソースコントローラとは?
DBへのCRUD操作を行うために必要なアクション(メソッド)が定義されているコントローラ。
CRUD操作が必要なページの処理を記述するための叩き台。
▼CRUDとは?
Create(登録)、Read(読み出し)、Update(変更)、Delete(削除)の4つの機能のこと。
リソースコントローラの作成方法
$ php artisan make:controller リソースコントローラ名 --resource
--resource
はショートオプションの-r
でもOK。
▼実行例$ php artisan make:controller TestResourceController -r Controller created successfully.app > Http > Controllers > TestResourceController.php が生成される。
リソースコントローラの内容
メソッドの概略(1) public function index() (2) public function create() (3) public function store(Request $request) (4) public function show($id) (5) public function edit($id) (6) public function update(Request $request, $id) (7) public function destroy($id)上記の7つのアクション(メソッド)が記載されている。
実際の処理は自分で記述する。CRUD操作に必要なメソッドの側を用意してくれている。
TestResourceController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TestResourceController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } }
リソースルートの登録
作成したアクションのルートはRouteファサードのresourceメソッドで登録できる。
Route::resource('$URI', 'リソースコントローラー名')
web.phpRoute::resource('aaas', 'TestResourceController');
▼作成したルートの確認$ php artisan route:list +--------+-----------+-------------------+---------------+-----------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+-----------+-------------------+---------------+-----------------------------------------------------+------------+ | | POST | aaas | aaas.store | App\Http\Controllers\TestResourceController@store | web | | | GET|HEAD | aaas | aaas.index | App\Http\Controllers\TestResourceController@index | web | | | GET|HEAD | aaas/create | aaas.create | App\Http\Controllers\TestResourceController@create | web | | | DELETE | aaas/{aaa} | aaas.destroy | App\Http\Controllers\TestResourceController@destroy | web | | | PUT|PATCH | aaas/{aaa} | aaas.update | App\Http\Controllers\TestResourceController@update | web | | | GET|HEAD | aaas/{aaa} | aaas.show | App\Http\Controllers\TestResourceController@show | web | | | GET|HEAD | aaas/{aaa}/edit | aaas.edit | App\Http\Controllers\TestResourceController@edit | web | +--------+-----------+-------------------+---------------+-----------------------------------------------------+------------+各ルートに対してアクションが指定されている。
ビューを開く
あとは、各アクションの中に呼び出すビューを指定すればいい。
public function index() { return view('users'); }/aaasにアクセスすると、users.blade.phpが表示される。
各アクション毎に対応するビューや処理を指定していけば完了。
まとめ
Route::resource
はCRUD操作用のアクションの叩き台が記述されたコントローラー(リソースコントローラーと呼ぶ)を登録するためのメソッド。
- 投稿日:2021-01-14T19:28:07+09:00
【Laravel】引数のRequest $requestとは何か?メソッドインジェクション(依存注入)
Laravelのコントローラーでメソッドの引数にクラスが指定されていることがある。これらの役割について。
▼例
public function index(Request $request){ }public function send(RequestContactSend $request){}
メソッドインジェクション
function 関数名(Request $request)
のように、関数の引数にクラス名と変数を記述すると、指定した変数に、指定したクラスのインスタンスが入る。これをメソッドインジェクション(依存注入)という。
メソッドインジェクションのメリット
メソッドインジェクションを使うメリットは、コードを簡略化できること。
▼(例)Requestクラスのallメソッドを呼び出す
メソッドインジェクションなしpublic function index() { $request = new Request(); $request->all() }↓
メソッドインジェクションありpublic function index(Request $request) { $request->all(); }メソッドインジェクションを使うことて、
new クラス名
のようにインスタンスを作成する手間を省ける。
- 投稿日:2021-01-14T18:58:45+09:00
PHP ログ出力調査
/tmp/dump.logに吐くようにする
$tar1 = '調査対象1'; $tar2 = '調査対象2'; $tar3 = '調査対象3'; ob_start(); var_dump( $tar1,$tar2,$tar3 ); $dump = ob_get_contents(); ob_end_clean(); file_put_contents( '/tmp/dump.log', $dump ,FILE_APPEND);
- 投稿日:2021-01-14T18:51:24+09:00
EasyBotterで複数のリプライファイルから一つを選ぶ
EasyBotterで複数のリプライファイルから一つを選ぶ
まえおき、やりたいこと
EasyBotterさん!いつもお世話になっております!
TwitterのキャラクターBotを運営?してまして、
そちらは四人のキャラが呟くBotとなっています。それで、前までは『キャラ名.*おはよう』(正規表現)みたく、
キャラ名を前に置いた上で反応単語を呟くとキャラを指定してリプライする感じにしてました。
キャラの指定がなければ全員の中の誰かが反応するという感じ。以下は例。reply_pattern.php"(凪砂|なぎさ).*(馬鹿|ばー?か|バー?カ)"=> array( "凪砂:……ふふ。全知全能の神ならぬ我らは等しく馬鹿であると言える。私が馬鹿なら、{name}さんも馬鹿かな?", ), "(日和|ひよ|おひい).*(馬鹿|ばー?か|バー?カ)"=> array( "日和:む……、ぼくは馬鹿じゃないもんね!", ), "(茨|いばら).*(馬鹿|ばー?か|バー?カ)"=> array( "茨:馬鹿って言ったほうが馬鹿なんですよ", ), "(ジュン|じゅん).*(馬鹿|ばー?か|バー?カ)"=> array( "ジュン:……まぁオレは馬鹿でしょうねぇ、自分で分かってますよっ", ), "(馬鹿|ばー?か|バー?カ)"=> array( "凪砂:……ふふ。全知全能の神ならぬ我らは等しく馬鹿であると言える。私が馬鹿なら、{name}さんも馬鹿かな?", "日和:む~っ、ぼくは馬鹿じゃないもんね!", "茨:馬鹿って言ったほうが馬鹿なんですよ", "ジュン:……まぁオレは馬鹿でしょうねぇ、自分で分かってますよっ", ),これを、名前が前だけじゃなくて後ろでも反応するようにしたいんですよ…。
なので、EasyBotter.phpの中で、先にキャラ名を拾ってから
どのreply_pattern_???.phpを読み込むか決めれるようにしたいんですよ…。これ正規表現でどうにかできるのかなとも思うんですけど、
別のところで作っているMastodonのBotの仕組みとしても分けたいところなので、
今回はキャラごと+キャラ指定なし の5つにファイル自体を分けてしまうことにしました。
どっちのほうが保守が楽なのかというアレはありますけど…。実践
EasyBotter.phpif(count($replies2) != 0){ //リプライの文章をつくる // ■ ↓ ここから $replyText = $rep["text"]; if(preg_match("/(凪砂|なぎさ)/", $replyText)){ $replyPatternFile = "reply_pattern_n.php"; } elseif(preg_match("/(日和|ひよ|おひい)/", $replyText)){ $replyPatternFile = "reply_pattern_h.php"; } elseif(preg_match("/(茨|いばら)/", $replyText)){ $replyPatternFile = "reply_pattern_i.php"; } elseif(preg_match("/(ジュン|じゅん)/", $replyText)){ $replyPatternFile = "reply_pattern_j.php"; } else{ echo "※ だれもちがいます。<br /><br />"; //確認用 } // ■ ↑ここまで $replyTweets = $this->makeReplyTweets($replies2, $replyFile, $replyPatternFile);以上です!
EasyBotter.phpのコメントアウトされた //リプライの文章をつくる
ってところの下に、■のコメントアウトがされてる範囲を突っ込むだけです。\$replyText = $rep["text"] には、相手からのリプライ内容が入ってます。
リプライの中にキャラ名が含まれていたらその特定のキャラのリプライファイルを指定して、
else… どのキャラ名にも当てはまらなかった場合(たぶん消していい)は元の
reply_pattern.php(ここでは四キャラのランダムなリプライが入っています)が指定されます。もちろん、今回の私のものはキャラ名が含まれた際にリプライファイルを分ける…というかたちなので
ここを参考にされる方がどうやって利用されるかはわかりませんが、
reply_pattern.php外でファイルを別にしてリプライさせたいときに有用では… ないでしょうか。完。
- 投稿日:2021-01-14T18:01:07+09:00
Laravel 【php artisan db:seed】時のエラーに関して
Laravel 【php artisan db:seed】を実行した時のエラー処理に関して
【目標】
Laravelで掲示板を作成中に、ダミーデータを生成し、一覧に表示したい【手順】
seederファイルを下記のコードで生成するPhp artisan make:seeder ファイル名生成したseederファイルを編集後に、
Php artisan db:seedで実行したものの、下記のようなエラーが発生した・・・
Call to undefined method App\Models\Blog::factory()
とあるので、Blog フォルダの、blogファイルに問題がありそうなので検索!【原因】
下記の記事がヒットする、、
https://teratail.com/questions/304871
記事によると、HasFactory
がModels/ blogファイルに抜けている模様。。。下記コードのように修正
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Factories\HasFactory; class Blog extends Model { use HasFactory; ←新たに追加 protected $table = 'blogs'; protected $fillable = [ 'title', 'content' ]; }再度実行すると成功
Php artisan db:seedOnce you stop learning, you start dying
誰かの学習の一助になりますように
- 投稿日:2021-01-14T15:50:20+09:00
【Laravel】Routingとは?getメソッドを使ったルーティング方法の実例まとめ。
Routingを理解する。getメソッドを使った主要なルーティング方法の実例まとめ。
目次
- Routingとは?
- Routingファイルの場所
- Routerの主なメソッド一覧
- Route::getメソッド
- 名前付きルート
- middlewareメソッドでフィルターをかける
- groupメソッドで複数のルートをまとめる
- redirectメソッドで転送する
- ルートとルート名の一覧を表示する
Routingとは?
入力されたURLに対し、表示するファイル(またはテキストなど)を指定する処理。
Routingファイルの場所
routes/web.phpに記述する。
routingの例use Illuminate\Support\Facades\Route; Route::get('greeting', function () { return 'Hello World'; });上記処理では、
/greeting
にアクセスすると、Hello World
を表示する。・
use Illuminate\Support\Facades\Route;
Routeファサードを使う宣言をする。
ファサードとはクラスのインスタンス的なもの。その中のメソッドを簡単に呼び出せるようになる。・statelessやRESTfull APIの場合
セッション情報を保持しないstatelessなどのルーティングはapi.phpに記述する。
ルートファイルはApp\Providers\RouteServiceProvider
によって自動的に読み込まれる。
Routerの主なメソッド一覧
メソッドではHTTP verbを指定する。
▼基本的な構文
・Route::メソッド("$URI", 関数)
メソッド 内容 実例 get URIのデータ取得 Route::get("/", $callback); post URIのリソースを作成 Route::post("/", $callback); prefix URIを共通化 Route::prefix('main')->group(function () { }); name ルート名を共通化 Route::name('main.')->group(function () { }); group URI、ルート名を共通化 Route::view('/welcome', 'welcome'); view ビューファイルを指定 Route::view('/welcome', 'welcome'); put URIのリソースを新規作成または置換(更新) Route::put("/", $callback); patch リソースの部分置換 (更新) Route::patch("/", $callback); delete URIの内容を削除 Route::delete("/", $callback); options URIに対して利用できるメソッドの一覧を取得 Route::option("/", $callback); match 複数のHTTP verbを指定 Route::match(['get', 'post'], '/', function () { }); any すべてのHTTP verbに応答 Route::any('/', function () { }); redirect 指定したURIにリダイレクト Route::redirect('/here', '/there'); よく使うのは
get
、group
、prefix
あたり。残りは使う頻度が低い。
Route::view
はよりシンプルなviewヘルパーで代替できる。>HTTP verb (HTTP動詞)
get, post, put, patch, delete, optionsが該当。
Route::getメソッド
書き方が複数存在する。
1. 文字列を指定
Route::get('xxx', function () { return 'Hello World'; });/xxxにアクセスしたら「Hello World」を返す。
2. viewを指定
Route::get('xxx', function () { return view('hello'); });/xxxにアクセスしたら、hello.balde.phpを表示する。
データも渡す場合
Route::get('xxx', function () { return view('hello', ['age'=>'25']); });25を格納したageという変数を渡す。hello.balde.phpの中で$ageで呼び出せる。
変数を格納することも可能。
view('hello', ['age'=>'$var']
3. コントローラーとメソッドを指定
Route::get('xxx', 'HelloController@message')/xxxにアクセスしたら、HelloControllerのmessageメソッドを実行する。
▼コントローラーの例<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HelloController extends Controller { public function message(){ $msg = 'こんにちは'; $title = 'this is title!'; return view('child', ['hello'=>$msg, 'title'=>$title]); } }>コントローラーでページを表示する方法はこちら
4. パラメータを渡す
ルーティングのURIで
{パラメータ名}
と記述すると、該当するパスがパラメータとして変数に格納される。functionの引数に渡せば、そのパラメータを利用できる。
・
Route::get('/パス/{パラメータ名}, function($パラメータ名){処理}'
Route::get('user/{id}', function($id){ return 'ユーザー'.$id; });
▼ブラウザの表示
5. パラメータを複数渡す
パラメータは複数渡すことも可能。
Route::get('user/{name}/{id}', function($username, $number){ return 'こんにちは。ユーザーID:'.$number.'の'.$username.'さん'; });
▼ブラウザの表示
6. パラメータの有無で条件分岐させる
パラメータ名に
?
をつけると、パラメータ名がない場合に変数に代入する値を指定できる。Route::get('/user/{name?}', function ($name = 'John') { return $name; });/user/にアクセスすると、指定したJhonを返す。
/user/$名前/にアクセスすると、入力された名前を返す。
7. コントローラーにパラメータを渡す
第1引数にパラメータのあるURIを指定し、第2引数にコントローラーのメソッドを指定した場合、パラメータがコントローラーのメソッドの引数に渡される。
web.phpRoute::get('user/{name}/{id}', 'HelloController@message');
▼コントローラーの例HelloController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HelloController extends Controller { public function message($name, $id){ return 'こんにちは、ID:'.$id.'の'.$name.'さん'; } }
▼ブラウザの表示
8. whereメソッドでパラメータを指定する
where
メソッドを使うことでパラメータのフォーマットを指定できる。・
where('パラメータ名', '正規表現')
Route::get('/user/{name?}', function ($name = 'John') { return $name; })->where('name', '[A-z]+');※注意点
・whereメソッドで指定するパラメータ名は、getのURIで指定しているパラメータ名と合わせる。functionの引数の変数名ではない。
▼指定したフォーマットに合わなかった場合
該当するページがないと判断され404 | Not Foundとなる。
パラメータを2つ以上指定する場合
配列にして、ダブルアローで指定する。
・
where(['パラメータ名'=>'正規表現', 'パラメータ名'=>'正規表現')
->where(['name'=>'[ぁ-ん]+', 'id'=>'[1-9]+'])実例Route::get('user/{name}/{id}', function($username, $number){ return 'こんにちは。ユーザーID:'.$number.'の'.$username.'さん'; })->where(['name'=>'[ぁ-ん]+', 'id'=>'[1-9]+']);
もしくは、->where()
をメソッドチェーンでつなげるのでもOK。->where('name', '[ぁ-ん]+')->where('id', '[1-9]+');
その他の指定方法
整数やアルファベットなどよく使うフォーマットには専用のメソッドが用意されている。
メソッド 内容 whereNumber() 整数 whereAlpha() アルファベット whereAlphaNumeric() アルファベットか数値 whereUuid() ユニークな整数 ▼使用例
->whereNumber('id')->whereAlpha('name')
Route::get('/user/{id}/{name}', function ($id, $name) { // })->whereNumber('id')->whereAlpha('name');
9. パラメータをグローバルに指定する
App\Providers\RouteServiceProvider
クラスのboot
メソッド内でpattern
メソッドを使って指定すると、グローバルにパラメータのフォーマットを制限できる。・
Route:pattern('パラメータ名', '正規表現')
RouteServiceProvider.phppublic function boot() { Route::pattern('id', '[0-9]+'); }指定したパラメータを使用する際は、自動的に指定したフォーマットが適用される。
名前付きルート
各ルートに名前をつけることができる。
名前をつけると、routeメソッドを使って、そのURIを呼び出すことができる。
▼名前付きルートのメリット
元のURIはルートファイル(web.php)で定義するので、URIを変更する場合に、ルートファイルのみ変更すればいい。
名前付きルートではなくURIを指定していた場合、該当するすべてのURIを探し出し変更しなくてはならない。
このため、基本的にルートには名前をつける。
▼ルートに名前をつける方法
2つある。1. name('ルート名')
2. 'as' => 'ルート名'
nameメソッド
・
name('ルート名')
Route::get('パス', function () { })->name('usertop');▼実例
Route::get('user', function () { return "ユーザーページTOP"; })->name('usertop'); Route::get('user/{id}/profile', function ($id) { return redirect()->route('usertop'); });コントローラーアクションにもルート名を付けられる。
Route::get('user/profile', 'UserProfileController@show')->name('profile');
パラメータの値を指定する
パラメータありのルートをパスにした場合に、routeメソッドで呼び出し時にパラメータを指定できる。
Route::get('/user/{id}/profile', function ($id) { // })->name('profile'); $url = route('profile', ['id' => 1]); // /user/1/profile
データを渡す
パラメータ名以外の変数を指定した場合は、getでそのデータを渡せる。
Route::get('/user/{id}/profile', function ($id) { // })->name('profile'); $url = route('profile', ['id' => 1, 'photos' => 'yes']); // /user/1/profile?photos=yes
asでルート名を指定する
getメソッドの第2引数を配列化し、
'as' => 'ルート名'
とすることでも指定できる。Route::get('user', ['as' => 'usertop', function () { return "ユーザーページTOP"; }]);ルーティングで
as
が使われていたら、ルート名前を指定しているということ。
コントローラーアクションを指定する場合
コントローラーの指定に
uses
を使う。・
['uses' => 'コントローラー名@アクション名']
Route::get('xxx', ['as' => 'hello', 'uses' => 'StringController@hello']);
middlewareメソッドでフィルターをかける
middlewareメソッドを使うと、HTTP Requestをコントローラーに渡す前や、Responseをクライアントに返す前に、設定した条件のフィルターを介すことができる。
groupメソッドで複数のルートをまとめる
groupメソッドを使うと、共通のURI接頭語やルート名接頭語を省略表記できる。
また、middlewareメソッドにgroupメソッドをチェーンでつなぐこともできる。
redirectメソッドで転送する
301や302転送を指定して転送することができる。
ルートとルート名の一覧を表示する
・
php artisan route:list
$ php artisan route:list +--------+----------+-------------------+---------------+----------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+-------------------+---------------+----------------------------------------------+------------+ | | GET|HEAD | / | | Closure | web | | | GET|HEAD | api/user | | Closure | api | | | | | | | auth:api | | | GET|HEAD | blade | | Closure | web | | | GET|HEAD | msg | | App\Http\Controllers\HelloController@title | web | | | GET|HEAD | topics | topics.index | App\Http\Controllers\TopicsController@index | web | | | GET|HEAD | topics/detail | topics.detail | App\Http\Controllers\TopicsController@detail | web | | | GET|HEAD | topics/show | topics.show | App\Http\Controllers\TopicsController@show | web | | | GET|HEAD | user | usertop | Closure | web | | | GET|HEAD | user/{id}/profile | | Closure | web | | | GET|HEAD | xxx | hello | App\Http\Controllers\StringController@hello | web | +--------+----------+-------------------+---------------+----------------------------------------------+------------+各URI毎の、ルート名、アクション(関数かコントローラーか)、適用されているミドルウェアを確認することができる。
- 投稿日:2021-01-14T15:32:32+09:00
【Laravel】リダイレクト処理。Routeのredirectメソッドについて
Laravelのルーティングでリダイレクトを設定する方法について。
redirectメソッド
Routeファサードのredirectメソッドを使う。
デフォルトの設定(302)
・
Route::redirect('here', 'there');
┗ here: requestされたパス
┗ there: リダイレクト先のパスデフォルトでは302リダイレクト(一時的なリダイレクト)になる。
▼実例
Route::redirect('xxx', '/');xxxにアクセスすると、/が表示される。
恒久的なリダイレクト(301)
・
Route::redirect('here', 'there', 301);
▼実例
Route::redirect('xxx', '/', 301);
恒久的なリダイレクト (permanentRedirectメソッド)
permanentRedirectメソッドを使っても301転送ができる。
・
Route::permanentRedirect('here', 'there');
▼実例
Route::permanentRedirect('xxx', '/');
- 投稿日:2021-01-14T14:58:47+09:00
【Laravel】ルーティングのミドルウェアとは?作成手順と実例 (Route::midlewareの意味など)
ルーティングで使われる
midleware
とは何かについて。▼こういうの
Route::middleware(['first', 'second'])->group(function () { Route::get('/', function () { //省略 });Route::group(['middleware' => 'auth.very_basic'], function () { Route::get('/', function () { //省略 });
目次
- ミドルウェアとは?
- フローで見るMiddleware
- Middlewareを実際に使うまでの流れ
- ミドルウェアの作成(条件ファイルの作成)
- Middlewareの中身
- ミドルウェアの条件作成
- ミドルウェアの登録
- ミドルウェアをルートに適用
ミドルウェアとは?
HTTPリクエスト(またはレスポンス)をチェックする機能のこと。
middlewareは自分で関数を定義して作成できる。▼用途
- 指定した変数が条件を満たしているか
- 認証ユーザーかどうか
など、、
フローで見るMiddleware
▼通常のフロー
通常のフローHTTP Request ↓ Route ↓ Controller ↓ View(Application) ↓ Response
▼ミドルウェアがある場合ミドルウェアありのフローHTTP Request ↓ Route ↓ **Before Middleware** ↓ Controller ↓ View(Application) ↓ **After Middleware** ↓ Response・Before Middleware:リクエストをチェックする機能
・Afrter Middleware:レスポンスをチェックする機能ミドルウェアは複数設置可能。
参考:midium.com
Middlewareを実際に使うまでの流れ
Middlewareを実際に使用するまでには大きく3つのステップがある。
1. ミドルウェアの作成(条件ファイルの作成)
2. ミドルウェアの登録
3. ミドルウェアをルートに適用
ミドルウェアの作成(条件ファイルの作成)
artisanコマンドの、make:middlewareで生成できる。
・
php artisan make:middleware ミドルウェア名
$ php artisan make:middleware test Middleware created successfully.▼保存場所
app > HTTP > Middleware > ミドルウェア名.php
Middlewareの中身
作成したミドルウェアの中身は以下のようになっている。
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class test { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) { return $next($request); } }・
namespace App\Http\Middleware;
app > HTTP > Middlewareに保存されるので、名前空間も対応している。・
use Closure;
Closureクラスをインポート。▼Clouseクラスとは?
無名関数と呼ぶ。https://www.php.net/closure
ここでは、次の処理に進める変数$next
を使えるようにしている。・
use Illuminate\Http\Request;
Laravelのコンポーネントである、Requestクラスをインポート。・
説明文(PHPDocs)
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */
受け取ったrequestを処理する。
@param
: パラメータの説明
ここでは、$request
と$next
を使っていますという説明。参照元のクラスが示してある。
@return
: 戻り値
ここでは、戻り値の型はmixed(何でもとりうる)であることを示している。
・処理内容
functionにミドルウェアの処理を記述する。public function handle(Request $request, Closure $next) { //処理 return $next($request); }
return $next($request);
リクエストを次のステップに進めるという意味。他にミドルウェアが設定されてない場合は、リクエストがコントローラーに渡される。
ミドルウェアの条件作成
実際にミドルウェアの条件部分を作ってみる。
Before Middlewareの作成
requestに対して実行するBefore Middlewareの実例。
class BeforeMiddleware { public function handle($request, Closure $next) { if ($request->age >= 50) { return redirect('home'); } return $next($request); } }requestで渡されたデータageの値が50を超えている場合は、ルート名homeにリダイレクトする。
50以下の場合は次の処理に進む。
After MiddleWare
responseに対して実行するAfter Middlewareの実例。
class AfterMiddleware { public function handle($request, Closure $next) { $response = $next($request); if ($response->age >= 50) { return redirect('home'); } return $response; } }
Before MiddlewareとAfter Middlewareの違い
▼Before Middleware
-$request
に対して条件を指定
-return $next($request);
を返す(通常の処理)
▼After Middleware
-$response = $next($request);
に対して条件を指定
-return $response;
を返す(通常の処理)
ミドルウェアの登録
ミドルウェアをルートに登録する方法は3つある。
- グローバル登録
- ルート登録
- グループ登録
ルート全体に通すか、個別にルートを指定するか、複数のミドルウェアをまとめて登録するかの違い。
いずれも
App\Http\Kernel
クラスの中に記述する。(デフォルトでミドルウェアが登録されている。)1. グローバル登録
すべてのルートにミドルウェアを適用するには、app/Http/Kernel.phpの
$middleware
プロパティに追加する。・
protected $middleware = [ ];
Kernel.phpprotected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Fruitcake\Cors\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ];
2. ルート登録
各ルートに個別にミドルウェアを適用するには、app/Http/Kernel.phpの
$routeMiddleware
プロパティに追加する。protected $routeMiddleware = [ 'ミドルウェア名' => 完全な名前空間::class];Kernel.phpprotected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ];
3. グループ登録
ミドルウェアをひとまとめにして適用するには、app/Http/Kernel.phpの
$middlewareGroups
プロパティに追加する。protected $middlewareGroups = [ 'グループミドルウェア名' => [ 完全な名前空間::class, ,,,, ];デフォルトでミドルウェア名webとapiが用意されている。(webはURIに、apiはAPIルートに適用する目的)
Kernel.phpprotected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
ミドルウェアをルートに適用
ルート登録したミドルウェア
$routeMiddleware
をルートに適用する方法。・
->middleware('ミドルウェア名', 'ミドルウェア名',,,)
▼一つだけ適用する場合Route::get('admin/profile', function () { // })->middleware('auth');URIにadmin/profileが入力された時、ミドルウェアauthを実行数する。
▼複数適用する場合Route::get('/', function () { // })->middleware('first', 'second');ホームディレクトリにアクセスした場合に、ミドルウェアfirstとsecondを実行する。
▼名前空間で指定
ミドルウェア名ではなく、名前空間で指定もできる。use App\Http\Middleware\CheckAge; Route::get('admin/profile', function () { // })->middleware(CheckAge::class);
グループ登録したミドルウェア
ルート登録した場合と同じように名前で指定できる。
Route::get('/', function () { // })->middleware('web');
グループ化したルートにミドルウェアを適用する
冒頭の
Route::middleware
や、
Route::group(['middleware' => 'auth.very_basic']
などの表記について。これらが何をやっているか。
Route::middleware
これまでの
->middleware
を冒頭で呼び出している。後ろにグループ化したルートがくる場合に使われる。・
Route::middleware('ミドルウェア名')->group()
後ろに続くグループ全体にミドルウェアを適用する。
Route::middleware(['web'])->group(function () { // });
Route::group(['middleware' => 'ミドルウェア名'], function(){ ルート });
groupメソッドの引数で適用するmiddlewareを指定している。
Route::group(['middleware' => 'auth.very_basic'], function () { Route::get('/', ['as' => 'top', 'uses' => 'TopController@index']); Route::get('privacy', 'PrivacyController@index')->name('privacy'); Route::get('optout', 'OptoutController@index')->name('optout'); });
(補足)auth.very_basic
Laravel標準のauth.basicとは違い、実際のデータベースの情報を使うことなくBasic認証を追加するミドルウェア。
>https://github.com/olssonm/l5-very-basic-auth/blob/master/README.jp.md
- 投稿日:2021-01-14T13:18:34+09:00
【Laravel】ルートのグループ化。Route::groupメソッドの役割について。(prefixとnameメソッドも))
Laravelのルートファイルで頻繁に登場する
Route::group
の意味について。▼こういうの
web.phpRoute::group(['prefix' => 'topics', 'as' => 'topics.'], function() { Route::get('/', 'TopicController@index')->name('index'); Route::get('{filename}.html', 'TopicController@show')->name('show'); });
目次
Route::group
の意味Routeファサードのgroupメソッドを呼び出している。
このメソッドで、URIの接頭部や名前空間の接頭部を共通化できる。類似したルートを複数記述する場合に便利なメソッド。
groupメソッドの前に別のメソッドがきて、チェーンでつなぐパターンもある。
・例:
Route::middleware()->group();
groupメソッドの構文
group(['prefix' => '共通するURI', 'as' => '共通するルート名' ]{ Route::get(); Route::get();,,, })
prefix
とas
はどちらか一方、あるいはどちらも無しでも作動する。
実例
view/topicsディレクトリに、index, show, detailの3つのビューがある場合
通常のルーティング
それぞれのルートを作成する場合は以下のようになる。
Route::get('topics', function () { return view('topics.index'); })->name('topics.index'); Route::get('topics/show', function () { return view('topics.show'); })->name('topics.show'); Route::get('topics/detail', function () { return view('topics.detail'); })->name('topics.detail');ここでは、URIの
topics
と、ルート名(nameメソッド)のtopics.
が共通。↓ グループ化
Route::group(['prefix' => 'topics', 'as' => 'topics.'], function() { Route::get('/', function () { return view('topics.index'); })->name('index'); Route::get('show', function () { return view('topics.show'); })->name('show'); Route::get('detail', function () { return view('topics.detail'); })->name('detail'); });groupメソッドを使うと表記を省略できる。グルーピングできるので視認性も向上する。
コントローラアクションの場合
Route::get('topics', 'TopicsController@index' )->name('topics.index'); Route::get('topics/show', 'TopicsController@show' )->name('topics.show'); Route::get('topics/detail', 'TopicsController@detail' )->name('topics.detail');↓ グループ化
Route::group(['prefix' => 'topics', 'as' => 'topics.'], function() { Route::get('/', 'TopicsController@index' )->name('index'); Route::get('show', 'TopicsController@show' )->name('show'); Route::get('detail', 'TopicsController@detail' )->name('detail'); });
ミドルウェアを適用する場合
Route::group(['middleware' => 'auth.very_basic'], function () { Route::get('/', function () { // }); Route::get('/user/profile', function () { // }); });
middlewareメソッドを使って、groupメソッドをチェーンすることもできる。Route::middleware(['first', 'second'])->group(function () { Route::get('/', function () { // }); Route::get('/user/profile', function () { // }); });上記例のgroupはprefixやnameの指定がないバージョン。指定したミドルウェアを適用するために個別にルートをまとめる時はこのような記述になる。
prefixメソッド
prefixメソッドで共通するURIを記述してから、groupに適用する方法もある。
・
prefix('共通するURI接頭部')
Route::prefix('topics')->group(['as' => 'topics.'], function() { Route::get('/', 'TopicsController@index' )->name('index'); Route::get('show', 'TopicsController@show' )->name('show'); Route::get('detail', 'TopicsController@detail' )->name('detail'); });nameメソッド
naemメソッドで共通するルート名を記述してから、groupに適用する方法もある。
・
prefix('共通するURI接頭部')
Route::name('topics.')->group(['prefix'=>'topics'], function() { Route::get('/', 'TopicsController@index' )->name('index'); Route::get('show', 'TopicsController@show' )->name('show'); Route::get('detail', 'TopicsController@detail' )->name('detail'); });prefixメソッドとnameメソッドをチェーンする
各メソッドのチェーニングも可能。
Route::name('topics.')->group(['prefix'=>'topics'], function() { Route::get('/', 'TopicsController@index' )->name('index'); Route::get('show', 'TopicsController@show' )->name('show'); Route::get('detail', 'TopicsController@detail' )->name('detail'); });
- 投稿日:2021-01-14T11:03:16+09:00
【Laravel】コメントアウトの@paramや@returnとは何か?読み方や書き方を理解する。
artisanコマンドでファイルを自動生成すると、コメントアウトで
@param
や@return
が記載されていることがある。この意味について。
目次
実例
例えば、
php artisan make:middleware ミドルウェア名
でミドルウェアを作成した場合、以下のようなファイルが自動生成される。<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class test { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) { return $next($request); } }このコメントアウトの部分。
PHPDoc
これは関数の説明を一定のルールに沿って記述したもの。
このルールをPHPDocs、コメントアウトで囲まれた人まとまりの説明文をDocブロックと呼ぶ。
Docブロックの書き方/** * 説明の要約 * * 説明の詳細(複数行) * * 要素(タグ)の説明 * */冒頭のアットマークの意味
@param
のように冒頭が@で始まる文字列をタグと呼ぶ。
タグづけすることで明示的にして説明をわかりやすくしている。各タグの書き方と種類
タグの書き方
タグの説明文の書き方の基本形は下記となる。
・
@タグ名 [型/クラス] [名前/変数名] [説明文]
タグ名と型のみ記述されていたり、説明文が無かったりなど、記述はまちまち。
//クラスと変数 * @param \Illuminate\Http\Request $request //型のみ * @return mixed
タグの種類
よく使われているタグ
タグ 内容 実例 @param
引数のデータ(1個) * @param \Closure $next
@return
関数やメソッドの戻り値。 * @return mixed
@version
バージョン情報 * @version 1.0.1
@method
使用されているマジックメソッド * @method setString(integer $integer)
@author
作成者情報 * @author My Name <my.name@example.com>
タグ一覧
かなりの量のタグが用意されている。
PHPDocタグ一覧
- 投稿日:2021-01-14T11:00:14+09:00
PHP学習まとめ2~データ型、条件分岐、繰り返し処理~
データの型
データは主に以下のような型がある。
- 文字列:string
- 数値:int
- 浮動小数:float
- NULL:null
- 真偽値:true/false
- 配列:array
var_dump(変数)で変数の型を表示することができる。
変数 = (型)'~~' で型を指定(変換)して代入もできる。例$name = (string)'mizuno'; // $nameという変数に'mizuno'という文字列をstring型で代入
条件分岐
if、switch、for、while文
ifやswitch、for、whileはブロックの形で表記。
※最後にセミコロン;は要らないので注意⚠️例if ($number === 1 || $number === 2) { echo 'number は 1 または 2 です。' . PHP_EOL; } else { echo 'number は 1 でも 2 でもありません。' . PHP_EOL; } switch ($number) { case 1: //比較する値と:(コロン)を書く case 2: echo 'number は 1 または 2 です。' . PHP_EOL; break; default: echo 'number は 1 でも 2 でもありません。' . PHP_EOL; }ifとswitchの違い
ifはAじゃなかったらB、BでもなかったらC、のように順番に処理。
条件式に不等号も使える。
switchはifでいう条件式が、number === 1だったらコレとか、number === 2だったらコレと
断定できる条件が複数ある場合に、ちょうどスイッチの切り替えのように使用する条件分岐。
(Rubyでいうif case whenみたいな使い方か?)
またswitchの場合、caseとマッチした時に実行されるのは
「switchの終わり、または最初にbreak;が出てくるまで」処理が実行される。繰り返し処理
単純な繰り返し処理にはfor文
for($i=1; $i<=10; $i++) { // 〜処理内容〜を$iが10以下になるまで繰り返す }
繰り返し処理回数を可変的にするならwhile文を使う。
while文は条件を満たしている間は繰り返す為、無限ループにならないよう終了条件の書き方に注意⚠️$i = 10 while ($i > 0) { // 〜処理〜 $i -= 1 }
条件が最初から満たしていない場合(例:$i = 0など)処理が実行されないので、
とりあえず1回は処理を行い、条件次第で実行する場合は以下のような書き方もできる。do { // 〜処理〜 $i -= 1 } while ($i > 0); // ※最後にセミコロンを使う<ループ処理中にスキップ、停止する方法>
スキップ:continue;
停止:break;
- 投稿日:2021-01-14T08:55:33+09:00
laravel6 メールキャッチャーを使ったメール送信時にエラーが発生した
目的
- laravel6とメールキャッチャーの組み合わせでメールの送信を実施したところエラーが発生したため解決までの経緯をまとめる
環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2 GHz クアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.11 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする Laravel バージョン 6.X commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.21 for osx10.15 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 情報
- 筆者はDockerなどは使用せずに直接MacにLaravelの環境構築とメールキャッチャーのサーバを立てて検証を行っていた。
- メールキャッチャーは下記の方法でサーバを立てた。
エラーまでの経緯
- laravel6のアプリでメール送信機能を作成した。
- アプリからメールを送信した。
エラーの内容
下記のエラーがブラウザ上に表示された。
Connection could not be established with host 127.0.0.1 :stream_socket_client(): unable to connect to 127.0.0.1:1025 (Connection refused)
下記画像ではsmtpのポート番号が1026になっているがこれは解決後、この記事のために意図的にエラーを発生させたため1026となってしまっている。みなさんの環境だと1025と表示されているはず。
解決までの経緯
下記コマンドを実行してメールキャッチャーをデフォルト設定で起動し直した。
$ mailcathcerlaravelのアプリ名ディレクトリで下記コマンドを実行して設定のキャッシュをリセットした。
$ php artisan config:cache問題は解決した。
本件でお困りの皆さんへ
- 筆者の場合メールキャッチャーが起動していなかったことが本件の原因であった。
- このエラーの場合、メールキャッチャーとlaravelアプリの通信がうまく行ってない状態である。
- 起動しているメールキャッチャーのsmtpポート、smtpのIPが何になっているのか、また、.envファイルのMAIL_PORTとMAIL_HOSTの記載が間違えていないかを確認しよう。
- 今起動しているメールキャッチャーの情報がわからなくなってしまったなら
$ mailcatcher
というコマンドを実行することでデフォルト設定でメールキャッチャーが再度起動する。(デフォルトだとsmtpポートは1025、 smtpのIPは127.0.0.1)- .envファイルの記載が正しいようなら
$ php artisan config:cache
を実行して設定のキャッシュをクリアしてみよう。
- 投稿日:2021-01-14T01:57:39+09:00
【Todoリスト作成】同じURLに別のpost処理をルーティングしたい
やりたいこと
下記を同時に行いたい
- 指定したIDカラムのステータスの更新
- 指定したIDカラムの削除 完成イメージ
発生している問題
以下のことから削除処理が優先して行われてしまう
- 削除処理も更新処理もidを渡して行っている
- 同じ画面にリダイレクトさせている
該当のソースコード
web.php
<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Route::get('/login', 'LoginController@index'); Route::get('/register', 'RegisterController@index'); Route::post('/register', 'RegisterController@post'); Route::get('todos','TodosController@index'); Route::post('todos','TodosController@store'); Route::post('/todos/{id}','TodosController@update'); Route::post('/todos/{id}','TodosController@remove');index.blade.php
@extends('layouts.parents') @section('title', 'Todoリスト') @section('content') @if(count($errors) > 0) <div> <ul> @foreach($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <h1>Todoリスト</h1> <label><input type="radio" checked>すべて</label> <label><input type="radio">作業中</label> <label><input type="radio">完了</label> <table> <tr> <th>ID</th> <th>コメント</th> <th>状態</th> </tr> @foreach($todos as $todo) <tr> <!-- findで検索されたIDを取得 --> <input type="hidden" name="id" value="{{$todo->id}}"> <td>{{$loop->iteration}}</td> <td>{{$todo->comment}}</td> @if($todo->state > 0) <form action="{{url('/todos', $todo->id)}}" method="POST"> @csrf <!-- 作業中ボタン --> <td><input type="submit" value="作業中"></td> <input type="hidden" name="state" value="0"> </form> @else <form action="{{url('/todos', $todo->id)}}" method="POST"> @csrf <!-- 完了ボタン --> <td><input type="submit" value="完了"></td> <input type="hidden" name="state" value="1"> </form> @endif <form action="{{url('/todos', $todo->id)}}" method="POST"> @csrf <!-- 削除ボタン --> <td><button type="submit">削除</button></td> </form> </tr> @endforeach </table> <h1>新規タスクの追加</h1> <form action="todos" method="POST"> @csrf <input type="text" name="comment" value="{{old('comment')}}"> <!-- 追加ボタン --> <input type="submit" value="追加"> </form> @endsectionTodosController.php
<?php namespace App\Http\Controllers; use App\Todo; use Illuminate\Http\Request; use App\Http\Requests\TodoRequest; class TodosController extends Controller { //DBにあるレコードを表示する public function index(Request $request) { $todos =Todo::all(); return view('todos.index', ['todos' => $todos]); } //レコードを追加する public function store(TodoRequest $request) { // $this->validate($request, Todo::$rules); $todo = new Todo; $form = $request->all(); unset($form['_token']); $todo->fill($form)->save(); return redirect('todos'); } //state変換 // public function edit(Request $request) // { // $todos =Todo::all(); // return view('todos.boolean', ['todos' => $todos]); // } public function update(Request $request) { Todo::find($request->id) ->update(['state' => $request->state]); return redirect('todos'); } // レコードを削除する public function remove(Request $request) { Todo::find($request->id)->delete(); return redirect('todos'); } }解決
ルート定義メソッドを修正して解決?
一応意図した挙動にはなった参照
使用可能なルート定義メソッドの部分
https://readouble.com/laravel/5.8/ja/routing.html
FormからPUT、PATCH、DELETEリクエストを送る方法(Laravel)
https://qiita.com/kambe0331/items/ff49b175ea9d8edec8c9
POSTとPUTとPATCHの使い分けについて
https://teratail.com/questions/193410<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Route::get('/login', 'LoginController@index'); Route::get('/register', 'RegisterController@index'); Route::post('/register', 'RegisterController@post'); Route::get('todos','TodosController@index'); Route::post('todos','TodosController@store'); Route::patch('/todos/{id}','TodosController@update'); Route::delete('/todos/{id}','TodosController@remove');@extends('layouts.parents') @section('title', 'Todoリスト') @section('content') @if(count($errors) > 0) <div> <ul> @foreach($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <h1>Todoリスト</h1> <label><input type="radio" checked>すべて</label> <label><input type="radio">作業中</label> <label><input type="radio">完了</label> <table> <tr> <th>ID</th> <th>コメント</th> <th>状態</th> </tr> @foreach($todos as $todo) <tr> <!-- findで検索されたIDを取得 --> <input type="hidden" name="id" value="{{$todo->id}}"> <td>{{$loop->iteration}}</td> <td>{{$todo->comment}}</td> @if($todo->state > 0) <form action="{{url('/todos', $todo->id)}}" method="POST"> @method('PATCH') @csrf <!-- 作業中ボタン --> <td><input type="submit" value="作業中"></td> <input type="hidden" name="state" value="0"> </form> @else <form action="{{url('/todos', $todo->id)}}" method="POST"> @method('PATCH') @csrf <!-- 完了ボタン --> <td><input type="submit" value="完了"></td> <input type="hidden" name="state" value="1"> </form> @endif <form action="{{url('/todos', $todo->id)}}" method="POST"> @method('DELETE') @csrf <!-- 削除ボタン --> <td><button type="submit">削除</button></td> </form> </tr> @endforeach </table> <h1>新規タスクの追加</h1> <form action="todos" method="POST"> @csrf <input type="text" name="comment" value="{{old('comment')}}"> <!-- 追加ボタン --> <input type="submit" value="追加"> </form> @endsection<?php namespace App\Http\Controllers; use App\Todo; use Illuminate\Http\Request; use App\Http\Requests\TodoRequest; class TodosController extends Controller { //DBにあるレコードを表示する public function index(Request $request) { $todos =Todo::all(); return view('todos.index', ['todos' => $todos]); } //レコードを追加する public function store(TodoRequest $request) { $todo = new Todo; $form = $request->all(); unset($form['_token']); $todo->fill($form)->save(); return redirect('todos'); } //stateを更新する public function update(Request $request) { Todo::find($request->id) ->update(['state' => $request->state]); return redirect('todos'); } // レコードを削除する public function remove(Request $request) { Todo::find($request->id) ->delete(); return redirect('todos'); } }
- 投稿日:2021-01-14T01:38:38+09:00
PHP から SageMaker の推論を実行したい
TL;DR
- Python から SageMaker の推論を行うことが多いと思いますが、フロントエンドが PHP の場合もあると思います。EC2 で実行される PHP から SageMaker の推論を実行する方法を書いてみました。
- PHP だと numpy を扱えないので、画像だと Base64 とかにエンコードして送ります。
EC2 と SageMaker 間のデータ形式を決める
これは PHP に限らず必要な作業です。フロントエンドの方とML Engineer の方で決めましょう。
SageMaker は基本 Python なので便利な numpy を使いたくなるかもしれません。numpy だと TensorFlow, PyTorch, MXNet など各種モデルに簡単にデータを渡すことができます。一方、送る側が PHP だと、Python のライブラリである numpy でデータを送るのは難しいです。そこで、numpy ではなくて、PHP でも扱える形式、例えば json や画像の場合は Base64 などを使ってみましょう。ここでは EC2 にある画像を Base64 にエンコードして SageMaker に送ることを考えます。
SageMaker のエンドポイントを作る
ここでは試しに GluonCV の Semantic segmentation の pretrained モデルである DeepLab のモデルをホストします。
デプロイまでの流れは Gist に上げました。
https://gist.github.com/harusametime/ac3050d25c47b23534784ce8fb09da6bデプロイには推論用のスクリプトが必要で、ここにBase64の画像をどう扱うかも実装します。まずモデル読み込みの model_fn 関数の実装です。これは gluoncv の関数一発で呼び出せます。GPU が使えるインスタンスを想定しているので、CPU のみの場合は、mx.cpu() を ctx にしてくださいね。
def model_fn(model_dir): net = gluoncv.model_zoo.get_deeplab_resnet50_ade(pretrained=True, ctx=mx.gpu()) return netさて次は、上記のモデルにデータを渡して予測する transform_fn の実装です。最初に base64 のデコードをしたら、data には画像ファイルが入りますので、PIL Image で開いて、numpy変換、mxnet array へ変換します。DeepLab の前処理の test_transformを実行してから、predict で予測する流れです。Semantic Segmentation なので、ピクセルごとにラベルの候補を複数出してきます。ここでは一番尤もらしいラベルを出すとして、argmax で確率が最大のものを出します。最後に json 形式でレスポンスを返します。
def transform_fn(net, data, input_content_type, output_content_type): data = base64.b64decode(data) data = Image.open(BytesIO(data)) data = np.array(data) data = mx.nd.array(data,ctx=mx.gpu()) data = test_transform(data,ctx=mx.gpu()) output = net.predict(data) predict = mx.nd.squeeze(mx.nd.argmax(output, 1)).asnumpy().tolist() response_body = json.dumps(predict) return response_body, output_content_typeEC2側の PHP 実装
PHPを触るのは15年ぶりくらいな気がしますがやっていきましょう。SageMaker の推論を Python から実行する際に Python SDK (Boto3) が必要なように、PHPには AWS SDK for PHP が必要です。
EC2 から SageMaker の推論ができるよう IAM Role を付与
IAM Role の新規作成画面から EC2 を選んで、EC2 で使えるロールを作ります。
そのロールから SageMaker を操作できるよう AmazonSageMakerFullAccess のポリシーを付与します。あとは名前を付けて保存しましょう。
AWS SDK for PHP のインストール
公式のインストールガイドを参考にしましょう。
まず、PHPのバージョンが 5.5 以降が推奨です。私は使っている環境が PHP 5.3 だったのでバージョンを上げました。php をアンインストールして再インストールするだけです。もっとスマートなやり方があるかもしれません。php-xml はAWS SDK に必要だったのでいれました。yum -y remove php-* yum -y remove httpd-tools yum clean all yum -y install php yum -y install php-xmlphp が入ったら次は Composer のインストールです。インストールガイド に従えば良いですが、私はガイドからリンクされているHow do I install Composer programmatically? のファイルを作ってシェルから実行しました。
Composer が入ったら、いよいよ AWS SDK のインストールです。
composer require aws/aws-sdk-phpこれを実行するとインストール完了です。実行したディレクトリに
vendor
というフォルダができて、そこにはautoload.php
というファイルができています。このファイルを PHP のスクリプトから呼べば、AWS SDK を全部読んでくれるようです。PHPのスクリプトを実行
推論を実行する際は InvokeEndpoint を実行しますので、AWS SDK for PHP のドキュメントから使い方を探します。
https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-runtime.sagemaker-2017-05-13.html
$result = $client->invokeEndpoint([ 'Accept' => '<string>', 'Body' => <string || resource || Psr\Http\Message\StreamInterface>, // REQUIRED 'ContentType' => '<string>', 'CustomAttributes' => '<string>', 'EndpointName' => '<string>', // REQUIRED 'InferenceId' => '<string>', 'TargetModel' => '<string>', 'TargetVariant' => '<string>', ]);REQUIRED なのは
Body
とEndpointName
ですね。Body には Base64画像を入れて、EndpointName には上の方で作成したエンドポイントの名前を入れます。わからない方は、SageMaker のコンソールから確認できます。
では、このあたりをいれて php のコードを書いてみるとこんな感じです。
<?php require 'vendor/autoload.php'; use Aws\SageMakerRuntime\SageMakerRuntimeClient; $im = file_get_contents("./image.jpg"); $base64img = base64_encode($im); $client = new SageMakerRuntimeClient([ 'region' => 'us-west-2', 'version' => '2017-05-13' ]); $result = $client->invokeEndpoint([ 'Body' => $base64img, // REQUIRED 'EndpointName' => 'エンドポイントの名前', // REQUIRED ]); print($result['Body']); ?>実行してみるとこんな感じです。
[ec2-user@ip-10-0-0-144 ~]$ php predict.php [[5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ...(途中省略) 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]2次元配列なので画像の縦・横に対応していて、値としてラベル 5 とか 3 とか 0 が入っていますね。
ADE20K データセットでは、5 は天井、3 は床、0 は壁なので、与えた画像は室内の画像で、最初(上の方)に天井が写っていることが予測されますね。