20210114のPHPに関する記事は15件です。

[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);

まとめ

やりたいことに対応するタグ要素さえ見つけられれば、紹介したコードで応用が利くと思います。

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

【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 が生成される。

image.png

リソースコントローラの内容

メソッドの概略
(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.php
Route::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操作用のアクションの叩き台が記述されたコントローラー(リソースコントローラーと呼ぶ)を登録するためのメソッド。

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

【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 クラス名のようにインスタンスを作成する手間を省ける。

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

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);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.php
            if(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外でファイルを別にしてリプライさせたいときに有用では… ないでしょうか。

完。

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

Laravel 【php artisan db:seed】時のエラーに関して

Laravel 【php artisan db:seed】を実行した時のエラー処理に関して

【目標】
Laravelで掲示板を作成中に、ダミーデータを生成し、一覧に表示したい

【手順】
seederファイルを下記のコードで生成する

Php artisan make:seeder ファイル名

生成したseederファイルを編集後に、

Php artisan db:seed

で実行したものの、下記のようなエラーが発生した・・・

image.png

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:seed

Once you stop learning, you start dying

誰かの学習の一助になりますように

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

【Laravel】Routingとは?getメソッドを使ったルーティング方法の実例まとめ。

Routingを理解する。getメソッドを使った主要なルーティング方法の実例まとめ。

参考:Larave公式 routing

目次

  1. Routingとは?
  2. Routingファイルの場所
  3. Routerの主なメソッド一覧
  4. Route::getメソッド
    1. 文字列を指定
    2. viewを指定
    3. コントローラーとメソッドを指定
    4. パラメータを渡す
    5. パラメータを複数渡す
    6. パラメータの有無で条件分岐させる
    7. コントローラーにパラメータを渡す
    8. whereメソッドでパラメータを指定する
    9. パラメータをグローバルに指定する
  5. 名前付きルート
    1. nameメソッド
    2. asでルート名を指定する
  6. middlewareメソッドでフィルターをかける
  7. groupメソッドで複数のルートをまとめる
  8. redirectメソッドで転送する
  9. ルートとルート名の一覧を表示する


Routingとは?

入力されたURLに対し、表示するファイル(またはテキストなど)を指定する処理。


Routingファイルの場所

routes/web.phpに記述する。

image.png

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によって自動的に読み込まれる。

image.png


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');

よく使うのはgetgroupprefixあたり。残りは使う頻度が低い。

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;
});



▼ブラウザの表示

image.png


5. パラメータを複数渡す

パラメータは複数渡すことも可能。

Route::get('user/{name}/{id}', function($username, $number){
    return 'こんにちは。ユーザーID:'.$number.'の'.$username.'さん';
});



▼ブラウザの表示

image.png


6. パラメータの有無で条件分岐させる

パラメータ名に?をつけると、パラメータ名がない場合に変数に代入する値を指定できる。

Route::get('/user/{name?}', function ($name = 'John') {
    return $name;
});

/user/にアクセスすると、指定したJhonを返す。
/user/$名前/にアクセスすると、入力された名前を返す。

image.png
image.png


7. コントローラーにパラメータを渡す

第1引数にパラメータのあるURIを指定し、第2引数にコントローラーのメソッドを指定した場合、パラメータがコントローラーのメソッドの引数に渡される。


web.php
Route::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.'さん';
    }
}



▼ブラウザの表示

image.png


8. whereメソッドでパラメータを指定する

whereメソッドを使うことでパラメータのフォーマットを指定できる。

where('パラメータ名', '正規表現')

Route::get('/user/{name?}', function ($name = 'John') {
    return $name;
})->where('name', '[A-z]+');

※注意点
・whereメソッドで指定するパラメータ名は、getのURIで指定しているパラメータ名と合わせる。functionの引数の変数名ではない。



▼指定したフォーマットに合わなかった場合
該当するページがないと判断され404 | Not Foundとなる。

image.png


パラメータを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メソッドを使って指定すると、グローバルにパラメータのフォーマットを制限できる。

image.png

Route:pattern('パラメータ名', '正規表現')

RouteServiceProvider.php
public 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');
});

image.png

コントローラーアクションにもルート名を付けられる。

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をクライアントに返す前に、設定した条件のフィルターを介すことができる。

middlewareメソッドとは?


groupメソッドで複数のルートをまとめる

groupメソッドを使うと、共通のURI接頭語やルート名接頭語を省略表記できる。

また、middlewareメソッドにgroupメソッドをチェーンでつなぐこともできる。

groupメソッドとは?


redirectメソッドで転送する

301や302転送を指定して転送することができる。

redirectメソッドとは?


ルートとルート名の一覧を表示する

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毎の、ルート名、アクション(関数かコントローラーか)、適用されているミドルウェアを確認することができる。

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

【Laravel】リダイレクト処理。Routeのredirectメソッドについて

Laravelのルーティングでリダイレクトを設定する方法について。

redirectメソッド

Routeファサードのredirectメソッドを使う。

デフォルトの設定(302)

Route::redirect('here', 'there');
 ┗ here: requestされたパス
 ┗ there: リダイレクト先のパス

デフォルトでは302リダイレクト(一時的なリダイレクト)になる。

▼実例

Route::redirect('xxx', '/');

image.png

xxxにアクセスすると、/が表示される。


恒久的なリダイレクト(301)

Route::redirect('here', 'there', 301);

▼実例

Route::redirect('xxx', '/', 301);

image.png


恒久的なリダイレクト (permanentRedirectメソッド)

permanentRedirectメソッドを使っても301転送ができる。

Route::permanentRedirect('here', 'there');

▼実例

Route::permanentRedirect('xxx', '/');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】ルーティングのミドルウェアとは?作成手順と実例 (Route::midlewareの意味など)

ルーティングで使われるmidlewareとは何かについて。

▼こういうの

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
    //省略
});
Route::group(['middleware' => 'auth.very_basic'], function () {
    Route::get('/', function () {
    //省略
});


目次

  1. ミドルウェアとは?
  2. フローで見るMiddleware
  3. Middlewareを実際に使うまでの流れ
  4. ミドルウェアの作成(条件ファイルの作成)
  5. Middlewareの中身
  6. ミドルウェアの条件作成
  7. ミドルウェアの登録
  8. ミドルウェアをルートに適用


ミドルウェアとは?

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:レスポンスをチェックする機能

ミドルウェアは複数設置可能。

image.png

参考: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

image.png

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つある。

  1. グローバル登録
  2. ルート登録
  3. グループ登録

ルート全体に通すか、個別にルートを指定するか、複数のミドルウェアをまとめて登録するかの違い。

いずれもApp\Http\Kernelクラスの中に記述する。(デフォルトでミドルウェアが登録されている。)

image.png

1. グローバル登録

すべてのルートにミドルウェアを適用するには、app/Http/Kernel.phpの$middlewareプロパティに追加する。

protected $middleware = [ ];

Kernel.php
protected $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.php
protected $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.php
protected $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');
});

groupメソッドとは?


(補足)auth.very_basic

Laravel標準のauth.basicとは違い、実際のデータベースの情報を使うことなくBasic認証を追加するミドルウェア。

image.png
https://github.com/olssonm/l5-very-basic-auth/blob/master/README.jp.md

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

【Laravel】ルートのグループ化。Route::groupメソッドの役割について。(prefixとnameメソッドも))

Laravelのルートファイルで頻繁に登場するRoute::groupの意味について。

▼こういうの

web.php
Route::group(['prefix' => 'topics', 'as' => 'topics.'], function() {
        Route::get('/', 'TopicController@index')->name('index');
        Route::get('{filename}.html', 'TopicController@show')->name('show');
    });


目次

  1. Route::groupの意味
  2. 実例
  3. コントローラアクションの場合
  4. ミドルウェアを適用する場合
  5. prefixメソッド
  6. nameメソッド


Route::groupの意味

Routeファサードのgroupメソッドを呼び出している。
このメソッドで、URIの接頭部名前空間の接頭部を共通化できる。

類似したルートを複数記述する場合に便利なメソッド

groupメソッドの前に別のメソッドがきて、チェーンでつなぐパターンもある。

・例:Route::middleware()->group();


groupメソッドの構文

group(['prefix' => '共通するURI', 'as' => '共通するルート名' ]{ Route::get(); Route::get();,,,  })

prefixasはどちらか一方、あるいはどちらも無しでも作動する。


実例

view/topicsディレクトリに、index, show, detailの3つのビューがある場合

image.png

通常のルーティング

それぞれのルートを作成する場合は以下のようになる。

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');
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】コメントアウトの@paramや@returnとは何か?読み方や書き方を理解する。

artisanコマンドでファイルを自動生成すると、コメントアウトで@param@returnが記載されていることがある。この意味について。


目次

  1. 実例
  2. PHPDoc
  3. 冒頭のアットマークの意味
  4. タグの書き方
  5. タグの種類

実例

例えば、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ブロックの書き方
/**
  * 説明の要約
  *
  * 説明の詳細(複数行)
  *
  * 要素(タグ)の説明
  *  
  */

PHPDocリファレンス

冒頭のアットマークの意味

@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>

タグ一覧

image.png

かなりの量のタグが用意されている。
PHPDocタグ一覧

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

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;

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

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の環境構築とメールキャッチャーのサーバを立てて検証を行っていた。
  • メールキャッチャーは下記の方法でサーバを立てた。

エラーまでの経緯

  1. laravel6のアプリでメール送信機能を作成した。
  2. アプリからメールを送信した。

エラーの内容

  • 下記のエラーがブラウザ上に表示された。

    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と表示されているはず。

    🧨_Connection_could_not_be_established_with_host_127_0_0_1__stream_socket_client____unable_to_connect_to_127_0_0_1_1026__Connection_refused_.png

解決までの経緯

  • 下記コマンドを実行してメールキャッチャーをデフォルト設定で起動し直した。

    $ mailcathcer
    
  • laravelのアプリ名ディレクトリで下記コマンドを実行して設定のキャッシュをリセットした。

    $ 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を実行して設定のキャッシュをクリアしてみよう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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>
@endsection

TodosController.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');
    }

}

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

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_type

EC2側の PHP 実装

PHPを触るのは15年ぶりくらいな気がしますがやっていきましょう。SageMaker の推論を Python から実行する際に Python SDK (Boto3) が必要なように、PHPには AWS SDK for PHP が必要です。

EC2 から SageMaker の推論ができるよう IAM Role を付与

IAM Role の新規作成画面から EC2 を選んで、EC2 で使えるロールを作ります。

そのロールから SageMaker を操作できるよう AmazonSageMakerFullAccess のポリシーを付与します。あとは名前を付けて保存しましょう。

保存したロールはEC2の画面でインスタンスに付与できます。
pic3.png

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-xml

php が入ったら次は 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 なのは BodyEndpointName ですね。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 は壁なので、与えた画像は室内の画像で、最初(上の方)に天井が写っていることが予測されますね。

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