20201113のlaravelに関する記事は3件です。

Laravelのemailバリデーション

Laravelデフォルトのバリデーションルール「email」を使用したWEBサービスを作成したところ、バリデーション不足を指摘された。

環境
Laravel 5.8.38

問題点

ユーザー登録画面にて、test@example.comあいうえおといった、@以降にひらがなを含むメールアドレスを登録できてしまう。

RegisterController.php
protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:100'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', new AlphaNumHalf, 'min:8', 'confirmed'],
    ]);
}

スクリーンショット 2020-11-11 5.43.58.png

実際に登録されたメールアドレス
test@example.xn--com-m63bkmoq

まじか・・・

結論

'email'
 ↓
'email:strict,dns,spoof'
とりあえずこれ。dnsによりドメインが存在するアドレスのみバリデーションを通るので、意味不明な日本語を入れたらまず通らないようにはできると思う。
が、本当にこれが正しいのか分からないので、色々調べてみる。

何が起きているのか

Laravelの公式ドキュメントによれば、

email
フィールドがメールアドレスとして正しいことをバリデートします。内部でこのバリデーションルールはメールアドレスの検証にegulias/email-validatorパッケージを使用しています。デフォルトではRFCValidationバリデータが適用されますが、他のバリデーションスタイルも適用可能です。
適用可能なバリデーションスタイルは、次の通りです。
・rfc: RFCValidation
・strict: NoRFCWarningsValidation
・dns: DNSCheckValidation
・spoof: SpoofCheckValidation
・filter: FilterEmailValidation

emailバリデーションにも種類があるらしい。今更知った。デフォルトではRFCValidationが適用されるとのこと。
それぞれの詳細は下記サイトが参考になった。
【Laravel 5.8.33】メールアドレスの新バリデーション・実例全5件!

では、他のバリデーションスタイルでtest@example.comあいうえおを登録しようとするとどうなるか。結果はこちら。

バリデーションスタイル 結果
rfc test@example.xn--com-m63bkmoq
strict test@example.xn--com-m63bkmoq
dns 登録できない
spoof test@example.xn--com-m63bkmoq
filter test@example.xn--com-m63bkmoq

それぞれのバリデーションの詳細をみるとそりゃそうだよな、という感じ。

xn--com-m63bkmoqとはなんなのか

調べてみると、Punycodeという符号化方式で、comあいうえおの部分がアルファベット、数字、ハイフンのみの文字列xn--com-m63bkmoqに変換されているようだ。

ドメイン名として Punycode を使用する際は、ピリオド(.)で区切られたドメイン名の階層レベルごとにプレフィックスとして「xn--」を使用し、エンコードされた文字列を続ける。大文字と小文字は区別されない。

可読なドメイン名 Punycodeでのドメイン名
ドメイン名例.jp xn--eckwd4c7cu47r2wf.jp
ウィキペディア.ドメイン名例.jp xn--cckbak0byl6e.xn--eckwd4c7cu47r2wf.jp
可愛いね.そうでもないよ xn--n8j5d625jn9k.xn--n8jd2ewbp7lub

引用:Punycode

可愛いね.そうでもないよは笑う。

また、試しに日本語JPドメイン名のPunycode変換・逆変換にてcomあいうえおと入力してみると、やはりxn--com-m63bkmoqに変換された。

スクリーンショット 2020-11-11 13.02.43.png

カスタムバリデーションで正規表現を使ってみる

正規表現を使ったカスタムバリデーションを試そうともしたが、Punycodeでアルファベット、数字、ハイフンのみの文字列に変換されてしまうのならば、RFCValidation等と同じ結果になってしまうのではないだろうか?

念のため試してみる。
emailの正規表現は難しいらしく、どれを使えば良いのか・・・という感じだが、こちらの記事を参考にさせていただいた。
メールアドレスを表す現実的な正規表現

自作のバリデーションルール

MyEmailRegex.php
public function passes($attribute, $value)
{
    return preg_match("/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/", $value);
}
RegisterController.php
use App\Rules\AlphaNumHalf;

protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:100'],
        'email' => ['required', 'string', new MyEmailRegex, 'max:255', 'unique:users'],
        'password' => ['required', new AlphaNumHalf, 'min:8', 'confirmed'],
    ]);
}

この正規表現の意味は、「何かしらの文字@英数字か"-".英数字か"-"」となる。
全角文字がPunycodeでアルファベット、数字、ハイフンのみの文字列に変換されてからバリデーションを通っていそうだが、そうであればやっぱり正規表現で引っ掛けるのは難しいのではないか。

test@example.comあいうえおを登録してみると、やっぱりバリデーションに引っかからずtest@example.xn--com-m63bkmoqと登録された。

このようなバリデーションは無難であると先の記事には書いてあるが、それなら僕の受けた指摘は一体なんなのだと釈然としない。

strict,dns,spoofを併用する

Laravelが用意してくれているバリデーション

  • strict: NoRFCWarningsValidation
  • dns: DNSCheckValidation
  • spoof: SpoofCheckValidation

は、併用が可能。

RegisterController.php
protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:100'],
        'email' => ['required', 'string', 'email:strict,dns,spoof', 'max:255', 'unique:users'],
        'password' => ['required', new AlphaNumHalf, 'min:8', 'confirmed'],
    ]);
}

なぜ併用するかというと、
strictだけだと先に述べたようにtest@example.comあいうえおが通ってしまう。
dnsだけだと.test..@gmail.com等が通ってしまう。
※RFCでは@の前に.をつけること等が禁止されている。

strictでRFCに違反するアドレスがはじかれ、
dnsでドメインが有効でないアドレスがはじかれる。
ついでにspoofでなりすましメールもはじかれる。

ドメイン部分に日本語を入力し、Punycodeで変換されたものが実際に有効なドメインでなければ(そんなドメインがあるのか分からないが)、日本語を入力してもしっかりバリデーションしてくれるようになった。

結論

Laravelのemailのバリデーションについて、デフォルトのままemail(email:rfc)では不十分な場合がある。その場合は、email:dns等、他のバリデーションも併用すると良さそう。

ただ、バリデーションの種類云々と同じくらい大事なのが、「どの程度のバリデーションでサービスの依頼主は満足するか?」を、質問なり打ち合わせでしっかり確認しておくことだと感じた。(僕は何も確認せずに納品してしまったので)

記事の内容等に間違いがありましたら、申し訳ございません。

おまけ

vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
public function validateEmail($attribute, $value, $parameters)
{
    if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
        return false;
    }

    $validations = collect($parameters)
        ->unique()
        ->map(function ($validation) {
            if ($validation === 'rfc') {
                return new RFCValidation();
            } elseif ($validation === 'strict') {
                return new NoRFCWarningsValidation();
            } elseif ($validation === 'dns') {
                return new DNSCheckValidation();
            } elseif ($validation === 'spoof') {
                return new SpoofCheckValidation();
            } elseif ($validation === 'filter') {
                return new FilterEmailValidation();
            }
        })
        ->values()
        ->all() ?: [new RFCValidation()];

    return (new EmailValidator)->isValid($value, new MultipleValidationWithAnd($validations));
}
vendor/laravel/framework/src/Illuminate/Validation/Concerns/FilterEmailValidation.php
namespace Illuminate\Validation\Concerns;

use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Warning\Warning;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Validation\EmailValidation;

class FilterEmailValidation implements EmailValidation
{
    /**
     * Returns true if the given email is valid.
     *
     * @param  string  $email
     * @param  EmailLexer
     * @return bool
     */
    public function isValid($email, EmailLexer $emailLexer)
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    /**
     * Returns the validation error.
     *
     * @return InvalidEmail|null
     */
    public function getError()
    {
        //
    }

    /**
     * Returns the validation warnings.
     *
     * @return Warning[]
     */
    public function getWarnings()
    {
        return [];
    }
}

参考サイト

・emailのバリデーションについて
PHPで各種バリデーション
Gmailが日本語など非アルファベット文字を含むメールアドレスとの送受信に対応
RFC 5322 & 5321に沿ったメールアドレス(local-part)に使える文字まとめ
メールアドレスを表す現実的な正規表現
バリデーションで使用する正規表現の読み方

・ドメインについて
ドメイン名のしくみ

・Laravelバリデーションについて
Laravel 5.8 バリデーション
【Laravel 5.8.33】メールアドレスの新バリデーション・実例全5件!
バリデーションエラー/POST送信時のLaravelの挙動を追う

・Punycodeについて
Punycode
日本語JPドメイン名のPunycode変換・逆変換

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

PHPのキャスト演算子

キャスト演算子とは

キャスト演算子は右辺オペランドのデータ型を指定した型に変換する。
(ここで登場するオペランドとは演算子の対象となる値のこと。)

以下の例ではシングルクォートで囲んでいる '10' は文字列型として扱われる。
文字列型をキャスト演算子 (int) で整数型へ変換する。

<?php
$a = '10';
$b = (int) $a;
echo gettype($a)."<br/>\n";
echo gettype($b)."<br/>\n";
?>
結果は以下のようになります。

$a = 文字列
$b = 整数

キャスト演算子の種類

image.png

サンプルコード

<?php
$var = 3.14;

$var = (string)$var;
echo gettype($var)."<br/>\n";

$var = (int)$var;
echo gettype($var)."<br/>\n";

$var = (bool)$var;
echo gettype($var)."<br/>\n";

$var = (float)$var;
echo gettype($var)."<br/>\n";

$var = (array)$var;
echo gettype($var)."<br/>\n";

$var = (object)$var;
echo gettype($var)."<br/>\n";

$var = (unset)$var;
echo gettype($var)."<br/>\n";
?>
結果はこのようになります。

string
integer
boolean
double
array
object
NULL

引用元
こちらの記事を参考に作成しました

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

docker 上の composer 経由で指定位置に laravel プロジェクトを用意するメモ

結論: どうやって実現するか

※ docker は事前にインストール・起動済みとします。(コダワリがなければ Docker Desktop https://www.docker.com/products/docker-desktop や WSL2 上に Docker をインストールして使えるようにしとけば OK です。)

例えば、 ~/Documents/tmp/laravel-project-via-composer という laravel プロジェクトを作るなら以下。

% cd ~/Documents/tmp
% docker run --rm -i -t -v $PWD:/app composer create-project --prefer-dist laravel/laravel laravel-project-via-composer
  1. laravel プロジェクトを配置したいディレクトリに移動して
  2. そのディレクトリで composer のオフィシャルイメージを使って、 laravel プロジェクトを準備する

これだけ。

(なんなら、 -i -t も不要だが、出力が見やすくなるので入れてる。)

これで ~/Documents/tmp/laravel-project-via-composer に用意される。

$PWD を任意のディレクトリすれば、コマンドの発行元ディレクトリはどこでも良いので、 % cd ~/Documents/tmp も要らない。(が、うっかりミスとか怖いから、個人的には cd してからコマンド実行したい派。)

念の為に書くと $PWD:/app の右辺が /app となっているのは https://github.com/composer/docker/blob/fbef2df32932859094b4cd3fb072f1e93a7fa1f8/2.0/Dockerfile#L60 にて、 WORKDIR /app となっているから。

※つまり、もし、何らかの理由で将来 composer の Docker image の WORKDIR の位置が変わったら同様にコマンド発行時のパスも変更することになる。(使う image の Dockerfile を読むの大切。)

なぜこの方法を使うのか

  • ローカルの環境の PHP や Composer のバージョンとか気にしたくない
  • anyenv とかでも管理できるけど、 OpenSSL のバージョン齟齬とかで面倒…
  • build するより docker image 使ったほうが早い
  • 多くの場合、本来的目的は、 build をしたいのではなく、 laravel プロジェクトを用意したいだけ
  • laravel プロジェクト用意した後に、 WEB App 実行用の Dockerfile や docker-compose 用ファイルは別途用意したら良い

たぶん、他のある程度有名な FW やパッケージマネージャーがあるなら同様の考え方で WEB App の下準備ができるはず。(ハードウェアとの通信が必要とか、そういうのは別のお話。)

ハードウェアトラブルや、ハードウェアの乗り換えとかで、いちいちローカル環境を整備するコストを考えると馬鹿らしいので、使えるものは使うと便利かも知れませんよ?というお話でした。

ちなみに laravel/installer をインストールして laravel new hoge という選択肢も当然にあるのですが、 docker 使って開発環境用意するだけなら composer の image 使って実行した方が楽やん?って思ってます。知らんけど。

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