HTML – input – file

基本形

ファイルアップロードは、FORM要素とINPUT要素のそれぞれで属性の設定が必要。

FORM要素は以下のように記述する。

  • methodPOST
  • enctype属性に"multipart/form-data"を設定する

INPUT要素は以下のように記述する。

  • type属性を"file"に設定する
  • methodPOST
  • name属性を設定する
    • この属性値で言語やフレームワークによってファイルを扱う
    • 以下の例ではname属性値を"file"としている

この場合の動作は以下のとおり。

  • "ファイルを選択"ボタンを押すとダイアログがファイル選択ダイアログが表示される
  • 一度に1つのファイルを選択できる
  • ファイルを選択して"開く"ボタンを押すとファイルがアップロードされる

複数ファイルの選択

基本形ではダイアログで1つのファイルしか選択できない。複数ファイルを選択・アップロードできるようにするには、INPUT要素を以下のように記述する。FORM要素に変更はない。

  • type属性を"file"に設定する
  • methodPOST
  • name属性の値の末尾に[]を付けて配列にする
    • 以下の例ではname属性値を"files[]"としている
  • multiple属性を追加

これによってファイル選択ダイアログで複数のファイルが選択・送信できるようになる。

 

Laravel – ルーティングが効かない

現象

routes/web.phpにルーティングを追加したが、そのルーティングが機能せずブラウザの表示が真っ白になってしまった。

原因

以下のような順番でルーティングを書いたため。

理由

URLに.../products/select_typeが与えられると、まずリソースルーティングのところで.../products/{id}として解釈されてしまい、以降のルーティングに到達しないため。

解決

ルーティングの順番を再考する。基本的に個別ルーティングはリソースルーティングより前に置く。

 

 

PHP – new staticとnew self

概要

staticselfnewと組み合わせることでクラス内でインスタンスを生成する際に使えるが、以下のような違いがある。

  • new staticはそれが実行されるときのクラスのインスタンスを生成する
  • new selfはそれが定義されたときのクラスのインスタンを生成する

参照:static::とself::

準備

以下のようにParentClassとそれを継承したChildClassを準備する。

  • いずれも同じ名前のインスタンスメソッドmethod()を持つ
  • それぞれのクラスはスタティックメソッドparent_method()child_method()を持つ
  • 何れの内容も同じで、以下を実行する
    • (new static)->static_method()
    • (new self)->static_method()

new staticnew selfはクラスのインスタンスを生成する。この例では()->で生成されたインスタンスから直接メソッドを呼び出しているが、$instance = new staticのように一旦変数に参照させて$instance->method()としてもよい。

親クラスのメソッドで生成する場合

親クラスのメソッド内でstaticselfで生成されるインスタンスは親クラスのインスタンス。

子クラスのメソッドで生成する場合

子クラスのメソッド内でstaticselfで生成されるインスタンスは子クラスのインスタンス。

継承元の親クラスのメソッドで生成する場合

子クラスから継承元の親クラスのメソッドを呼んで、その中でstaticselfで生成されるインスタンスは以下の通り。

これらは以下のような動作による。

子クラスから呼ばれた親クラスのスタティックメソッドで以下が実行される。

new staticで生成されるのは、この文が実行されるときにスタティックメソッドを呼び出したChildClassなので、ChildClassのインスタンスが生成されて、そのmethod()が呼ばれる。

new selfで生成されるのは、この文が定義されたParentClassなので、ParentClassのインスタンスが生成されて、そのmethod()が呼ばれる。

 

Laravel – RegisterController

概要

RegisterControllerは、Laravelのプロジェクト開始時にapp/Http/Controllers/Authディレクトリー下に生成される。

ルーティングファイルにAuth:routes()を記述することで、このコントローラーの各アクションは以下のようにURLと結び付けられる。

method URL action name
GET /register showRegistrationForm register
POST /register register

ソースコード

内容

コントローラーの機能

ソースコード冒頭のコメントの要旨は以下の通り。

  • このコントローラーは、新規ユーザーの登録、入力バリデーション、データ作成とともに行う
  • デフォルトではこの機能をIlluminate\Foundation\Auth\RegistersUsersトレイトで実装し、追加のコードなしで上記の機能を提供

RegistersUsersトレイトの導入

登録後の遷移先

登録後の遷移先を設定している。

コントローラーの生成

コントローラー生成時のコンストラクター。

入力バリデーション

メールアドレスをユーザーIDとして使うため、入力バリデーションでemailuniqueの制約をかけている。

Userモデルインスタンスの作成

ユーザー登録時、パスワードにHash()メソッドを適用して暗号化している。

 

Laravel – $this->guard()

 

ログイン認証の例

たとえばログイン認証のLoginControllerに使われるIllumination\Foundation\Auth\AuthenticatesUsersトレイトlogin()メソッドは次のような内容。

この中で呼ばれているログイン認証処理のattemptLogin()メソッドは、同じトレイトで定義されている。

ここでguard()というメソッドが出てくる。これは同じAuthenticatesUsersトレイトの最後の方で以下のように定義されているが、そもそもどういうものかよくわからなかったので掘り下げてみたら、どんどん深みにはまってしまった。

guard()が見当たらない

AuthenticatesUsersトレイトでAuth::guard()が呼ばれているので、まずAuthクラスについて調べてみる。

AuthクラスはAuthenticatesUsersトレイトの最初で以下のようにuse定義されている。

そこでIlluminate\Foundation\Authクラスを見てみると、コメントにstaticメソッドらしきものが書かれているが、クラス定義の本体にはguard()メソッドが見当たらない。

Authクラスの継承元のIlluminate\Support\Facades\Facadeクラスも見てみたが、useしているトレイトも含めてguard()メソッドは定義されていない。

マジックメソッド__callStatic()を使っている

__callStatic()の内容

いくつかのサイトを参考にしたところ、__callStatic()というマジックメソッドを使っていることがわかってきた。このマジックメソッドは、クラスに定義されていないstaticメソッドが呼ばれた際に実行される。

Facadeクラスを見てみると、以下のように__callStatic()が定義されていた。

最初にあったAuth::guard()スタティックメソッドを呼ぶと、このマジックメソッドが呼ばれ、引数の$method'guard'$argsは空の配列となる。

以下で、この__callStatic()の内容を読んでいく。

getFacadeRoot()

メソッドの最初でgetFacadeRoot()メソッドが呼ばれている。最終的に$instanceにはAuthManagerのインスタンスが返されるが、その道筋を追ってみた。最後の部分だけがまだ理解できておらず、参考サイトの受け売りだが。

このメソッドは同じFacadeクラスで定義されていて、getFacadeAccessor()を引数に与えたresolveFacadeInstance()の戻り値を返している。

getFacadeAccessor()

ここでstaticメソッドgetFacaceAccessor()は、Facadeクラスとこれを継承したAuthクラスの両方で定義されている。

FacadeクラスでのgetFacadeAccessor()の定義は、単に「メソッドが定義されていない」という例外を返す。

一方、Facadeを継承したAuthクラスで定義されたgetFacadeAccessor()は文字列'auth'を返す。

getFacadeRoot()Facadeを継承したAuthクラスから呼ばれると、AuthクラスのstaticメソッドがFacadeクラスのstaticメソッドをオーバーライドして実行される。

この結果、resolveFacadeInstance()の引数には文字列'auth'が与えられる。

resolveFacadeInstance()

引数に'auth'を与えられたresolveFacadeInstance('auth')の挙動を見てみる。

流れは以下の通り。

  • 引数$nameの内容は文字列なので、1つ目のifブロックはスルー。
  • static::$resolvedInstanceFacadeで定義されていて、$nameに対応したインスタンスがあればそのインスタンスが得られる。
  • もしインスタンスが登録されていなければ、最後のifブロックでstatic::$app['auth']の内容が登録されて、そのインスタンスが返される。

この$app['auth']がわからなかった。こちらのサイトの内容をひとまず丸呑みする。

  • static::$appはヘルパーメソッドapp()と同じ動作をする
    • static::$app[‘auth]app()[‘auth’]と同じ意味
    • これはサービスコンテナからauthサービスのインスタンスが起動させる下記のコードと同じことを表す
  • app()->make(‘auth’)で作成されるサービスはIlluminate\Auth\AuthServiceProviderregisterメソッドによって登録されており、Immulminate\Auth\AuthMangaerがインスタンス化されている

__callStatic()の戻り値

$instanceが適正にセットされれば、_callStatic()は以下のように戻り値を返す。

__callStatic()が呼ばれたときの$methodguard()、引数は空だったので、戻り値のメソッドはAuthManager::guard()となる。

guard()はSessionGuard()になる

AuthManager::guard()

結局、attemptLogin()->guard()AuthManager::guard()となり、その内容は以下の通り。

引数なしで呼ばれるので、$nameAuthManager::getDefaultDriver()となる。その内容は以下の通り。

このメソッドは、configディレクトリーにあるauth.phpのdefaultsのキー'guard'に対する値を返す。デフォルトでその内容は'web'となっている。

そして、最後のreturn文にある$this->guards[$name]$this->guards['web']となるが、これが存在しない場合は??演算子の右側が評価されて、AuthManager::resolve('web')が内容としてセットされる。

AuthManager::resolve()

AuthManager::resolve()の内容は以下の通り。

最初の実行文で呼ばれるgetConfig('web')により、config/auth.phpguardsセクションの'web'キーに対応する値が$configとしてセットされる。

auth.phpの該当する部分は以下の通り。

そして、$driverMethodに文字列連結で生成された文字列'createSessionDriver'が格納される。

createSessionDriver()メソッドはAuthManagerに存在するので、ifブロック内の$this->createSessionDriver($name, $config)が実行される。

createSessionDriver()を見ると、$guardとして\Illuminate\Auth\SessionGuardクラスのインスタンスが格納される。

SessionGuardクラスはattempt()メソッドを持つので、最初のattemptLogin()メソッドは以下を実行していることになる。

参考サイト

 

Laravel – REST~リソースベース

概要

RESTはREpresentational State Transferの略。ここでは以下のような感じで理解しておく。

  • リソースを一意に特定できるURL
  • GET、POSTなどのメソッド
  • ステートレスで完結

Laravelでは、リソースベースでルーティングを記述し、リソースコントローラーを生成することで、RESTに沿ったコードが書ける。

以降、Itemというモデルを仮定する。Itemモデルのインスタンスをitem、その集合体をitemsとする。

リソースを一意に特定するURL

サーバーにモデルの集合体が保存されていて、これら全体あるいはその中の特定のデータや、データを表示するビューなどのリソースを指定するURLの書き方。

/items
itemのインスタンスの集合体あるいはこれらを表示するビューなどのリソース。
/items/create
1つのitemを作成するための、入力フォームを含むビューなどのリソース。
/items/{id}
itemsのうちidで特定される1つのitemやこれを表示するビューなどのリソース。POST、PATCH、DELETEといったメソッドによってitemに対する操作を指定する。
/items/{id}/edit
idで特定されるitemの内容を編集するフォームを含むビューなどのリソース。

メソッド

GET
URLで指定したビューのHTMLなどのリソースをサーバーに要求する。
POST
URLで指定したリソースの内容をサーバーに送信する。
PATCH
URLで指定したリソースの内容でサーバー上のリソースの内容を変更するよう要求する。
DELETE
URLで指定したリソースをサーバー上から削除するよう要求する。
PUT
指定した内容でサーバー上のリソースを置き換える。LaravelのRESTfuLの枠組みでは使われない。

RESTfuLなルーティング

メソッドとURLを組み合わせることで、サーバー上のリソースに対する操作が確定する。LaravelでのRESTfuLな操作は以下の7つで、それぞれに応じたコントローラーのアクションにルーティングするのが標準。

リソースベース

リソースベースの考え方

RESTfuLなルーティングと、これに対応したアクションを自動的に生成する方法。

  • リソースルーティングの書き方によって、7つのRESTfuLなルーティングを1行で記述
  • artisanのコントローラー作成コマンドの--resourceオプションで、RESTfuLなルーティングに対応した7つのアクションを持つコントローラーを生成

リソースルーティング・名前付きルート

ルーティング定義に以下の1行を書くだけで、RESTfuLな7つのルーティングを書いたのと同義になる。

routes/web.php

このとき各ルートに名前も付けられる

一覧で確認。

リソースの限定

以下のように書くことで、特定のリソースのみを選んだり除外したりできる。

リソースコントローラー

以下のartisanコマンドで、RESTfuLなルーティングに対応したメソッドを持つコントローラーが生成される。

php artisan make:controller ItemController --resource

生成されるコントローラーのアクションは以下のとおり。

  • index()
  • create()
  • store(Request $request)
  • show($id)
  • edit($id)
  • update(Request $request, $id)
  • destroy($id)

app/Http/Controllers/ItemController

 

Laravel – 例題 – Rock-Paper-Scissors

概要

LaravelのToy problemとして、簡単なじゃんけんのアプリケーションを作る。

  • 初期画面にRock/Paper/Scissorsの3つのラジオボタンと”Match!”ボタンを表示
  • ユーザーがラジオボタンのいずれかを選択して”Match!”ボタンを押すと、コンピューター側の手をランダムに発生させて勝敗比較
  • ユーザー/コンピューターの名前と手、結果を表示に加えて再表示

ユーザー、コンピューターのオブジェクトを生成するPlayerクラスを作成する。

また、手の値のバリデーションは行っていない。

Playerクラス

ユーザー/コンピューターに関わらず、プレーヤーの名前と現在の手(Rock/Paper/Scissors)を保持し、勝ち負け・あいこの判定機能を備えるクラス。

クラスファイルはpublic下のサブディレクトリ―に置く

  • クラスファイルの配置はpublic/rock_paper_scissors/Player.php
  • コントローラーなどからアクセスする場合、public_path()を使う

クラスの仕様は以下の通り。

  • 名前空間を含むフルパスのクラス名はrock_paper_scissors\Player
  • このクラスからユーザーとコンピューターそれぞれのインスタンスを生成
  • Playerのプロパティーは名前($name)とじゃんけんの手($hand_number)
  • $hand_numberは0: rock、1: paper、2: scissorsに対応
  • $hand_numberは外部からpublicメソッドで設定
  • $hand_numberに対応した手の名称をクラス定数の配列として準備し、現在の手の名前を返すメソッドを準備
  • 自身が他のPlayerに対して勝っているか(wins)、あいこか(draws)を判定するメソッドをそれぞれ準備

ルーティング

ルーティングはGETメソッドとPOSTメソッドに対応して2つ。

コントローラー

コントローラーのアクションは、GETに対する初期ページへの遷移と、POSTに対する処理・遷移の2つ。

ビュー

GETの場合はフォームだけ表示し、POSTでじゃんけんを実行したときには結果も表示する。

実行結果

初期表示

Rock-Paper-Scissors

  

 

結果表示

Rock-Paper-Scissors

  


User

  1. Name: USER
  2. Hand: Rock

Computer

  1. Name: COM
  2. Hand: Paper

Result

Computer won

 

PHP – SplFileObject::setFrags()

概要

PHPのSplFileObjectのフラグがわかりにくかったので整理した。確認したバージョンは以下の通り。

  • OS: Vagrant/VirtualBox/CentOS7
  • PHP: 7.3.28
  • Laravel: 6.20.29

フラグ詳細

\SplFileObject::READ_CSV

行読込の際、CSV列として解釈する。
\SplFileObject::READ_AHEAD
日本語・英語のどのサイトでもマニュアル記述そのままに”先読み/巻き戻しで読み込む”としか書いていない。SKIP_EMPTYなどの処理のためにバッファリングして、処理が行われた場合に巻き戻すという意味か。
\SplFileObject::SKIP_EMPTY
空行が無視される。改行コードのみの行は空行とは認識されず削除対象とならないが、DROP_NEW_LINEと組み合わせることで空行がスキップされる。
\SplFileObject::DROP_NEW_LINE
改行文字が無視される。ダブルクォートで囲んだ項目中に改行文字がある場合も、改行文字が削除される。たとえば以下の様にフィールドの途中に改行を入れたり、空行がある場合、DROP_NEW_LINEにより宮城県と仙台市の間の改行が除かれ、”宮城県仙台市”とつなげられる

テキストファイル読み込み時

概要

以下のようにテキストファイルを扱いたい場合。

  • 改行コードは取り除きたい
  • 空行は読み飛ばしたい

このような場合、SplFileObjectのインスタンスに対して以下のようにフラグ設定する。

改行コードは取り除きたいが空行は空行のままで残したい場合はDROP_NEW_LINEのみ設定し、READ_AHEADはあってもなくてもよい。ただし最後の行の次に空行が付いてきてしまう。

テスト用ファイル

以下のテキストファイルで確認した。エンコーディングはUTF-8で最後の行や空行も含めて各行末のコードはLF

テスト用コード

  • コントローラーでファイルを読み込み、行単位で配列に格納
  • dd()で配列の内容を確認(returnは実行されない)
  • setFlags()の引数を変化させて結果を比較

結果

フラグ設定なしの場合

setFlags()の行をコメントアウトし、フラグ設定なしで実行した場合。

  • 改行コードが残る
  • 最終行の後に空文字列("")が付いてくる

SKIP_EMPTYのみを設定した場合

  • 改行コードが残る
  • 最終行の後にbooleanfalseが付いてくる

DROP_NEW_LINEのみを設定した場合

  • 改行コードは取り除かれる
  • 改行のみの空行は空文字列になる
  • 最終行の後に空文字列が付いてくる

SKIP_EMPTY | DROP_NEW_LINE

  • 改行コードは取り除かれる
  • 空行は取り除かれる
  • 最終行の後にbooleanfalseが付いてくる

READ_AHEAD | SKIP_EMPTY

  • 改行コードは残る
  • 空行は残る
  • 最終行の後には何も付かない

READ_AHEAD | DROP_NEW_LINE

  • 改行コードは取り除かれる
  • 改行のみの空行は空文字列になる
  • 最終行の後に空文字列が付けられる

READ_AHEAD | SKIP_EMPTY | DROP_NEW_LINE

  • 改行コードは取り除かれる
  • 改行のみの空行は取り除かれる
  • 最終行の後には何も付かない

CSVファイル読み込み時

概要

以下のようにCSVファイルを読み込みたい場合。

  • 空行はスキップしたい
  • フィールド中の改行は取り除きたい

このような場合、SplFileObjectのインスタンスに対して以下のようにフラグ設定する。

空行はスキップするがフィールド中の改行は残したい場合、フラグの設定だけでそのような結果を得ることはできないようである。

READ_CSVのみ設定し、内容がnullのデータを登録せずに飛ばしていくことになる。

テスト用ファイル

テスト用コード

結果

READ_CSVのみの場合

  • 空行に対応してnullの要素が残る
  • フィールド中の改行は残る
  • 最終データの後にnullの要素が付く

SKIP_EMPTYを追加

  • 空行に対応してnullの要素が残る
  • フィールド中の改行は残る
  • 最終データの後にfalseの要素が付く

DROP_NEW_LINEを追加

  • 空行に対応してnullの要素が残る
  • フィールド中の改行は取り除かれる
  • 最終データの後にnullの要素が付く

SKIP_EMPTY | DROP_NEW_LINEを追加

  • 空行は取り除かれる
  • フィールド中の改行は取り除かれる
  • 最終データの後にfalseの要素が付く

READ_AHEAD | SKIP_EMPTYを追加

  • 空行に対応してnullの要素が残る
  • フィールド中の改行は残る
  • 最終データの後には何も付かない

READ_AHEAD | DROP_NEW_LINEを追加

  • 空行に対応してnullの要素が残る
  • フィールド中の改行は取り除かれる
  • 最終データの後にnullの要素が付く

READ_AHEAD | SKIP_EMPTY | DROP_NEW_LINEを追加

  • 空行は取り除かれる
  • フィールド中の改行は取り除かれる
  • 最終データの後には何も付かない

 

 

Laravel – コントローラー

コントローラーの場所

コントローラーは、アプリケーションディレクトリー以下の次のディレクトリーに配置される。

app/Http/Controllers/

一般のコントローラー

概要

  • 一般のコントローラーは複数のアクションを持つ
  • アクションはコントローラーのメソッドとしてpublic宣言される
  • アクションはルーティングで'コントローラー名@アクション名'として呼び出される。

コントローラーの生成

アプリケーションディレクトリー内で以下のコマンドを実行する。

php artisan make:controller コントローラー名

モデル作成時に-c/--controlleroptionを付けると、モデルと同時にコントローラーも作成される。この時のコントローラー名は単数形。

アクションの定義

コントローラークラスのメソッドとしてアクションを定義する。

ルーティング定義

ルーティングファイルで、メソッド・URLとコントローラーを結びつける。

シングルアクションコントローラー

概要

  • シングルアクションコントローラーは、アクションを1つだけ持つ
  • アクション名は__invoke()で定義する
  • ルーティングではコントローラー名のみ指定する

コントローラーの生成

生成は一般のコントローラーと同じ。ただしシングルアクションコントローラーの場合は、コントローラー名の末尾にControllerを付さない慣例あり。たとえばSingleActionなどのようにControllerを付けない。

php artisan make:controller コントローラー名

--invokableオプションを付けると、__invoke()メソッドが準備された状態でコントローラーが生成される。

php artisan make:controller コントローラー名 --invokable

なお、シングルアクションコントローラーを生成する場合は、ルーティングの定義より先に生成しないとエラーが発生する。

アクションの定義

シングルアクションコントローラーのアクション名は__invoke()と決まっている。

ルーティング定義

一般のコントローラー・アクションは'コントローラー名@アクション名'としたのに対して、シングルアクションコントローラーのルーティングではコントローラー名のみ指定する。

 

PHP – php.iniの設定

php.iniの場所

普通は/etc/php.ini

文字コード

もし必要なら変更。

mbstringをインストールしている場合、mbstring.internal_encodingは非推奨となっていて、internal_encodingdefault_charsetが使われるようだ。

internal_encodingの方も、指定していなければdefault_charsetが使われるとのこと。

タイムゾーン

コメントアウトを外し”Asia/Tokyo”を追記。

ブラウザーへのエラー表示

ブラウザーへのエラー表示を有効化のためoff→onに変更。ただし運用時はoffに。