N_OR’s diary

無職が何か言ってる

感想文:広告業界の動向とカラクリがよくわかる本

業界研究本。
業界のパワーバランスより、世の中にはどういう種類の広告商品があり、何を意図して商品が作られたか、といったことを知れたのが面白かった。

ネットの広告、複雑過ぎない?

広告を打ちたいときに、どういうやり方があるのか、勉強になりました。(理解したとは言ってない)

一番印象に残ったのは「シズル感」の解説。

広告マンはよく「この写真、シズル感サイコー」などといったりする。シズル感は、人間の五感を刺激して気持ちよくさせる感じを指す。 なお、「この写真、何かシズル感が足りないよね」のように、人の作品を根拠無で否定する場合にもよく使う。

なんかここだけ文体が違うが、なにか筆者の思いがあるのだろうか。。。

感想文:リーダブルコード

感想文と言うか紹介。
プログラマでも非プログラマでも、プログラムを書く人は全員読むべき。

プログラムの作り方なんてユーザは気にしていないので、プログラムくらいは自分たちのために書けばいいんじゃないと常々思う。
そのためにの一つが「読みやすく書く」です。
プログラムは書くより読む機会の方が多いからね。

以前、読みやすくコードを書くことを考えていたら生産性が落ちると主張する人達に出会ったことがあるが、 考えなくても読みやすいコードを書けるようになってほしい。
それはプログラマの技術だと思う。

Laravelでphpunitを利用したテスト

phpunitでテストを書いてみる。

テストの自動化というか、DevOpsというか、システム作りもシステム化していくべきで、こういう対応は大事だと思う。

ただ、テストを自動化することの是非について、仕事としての判断が入ってくると喧嘩する人たちが出てくるので注意が必要。

個人的にはテストコードを書かなくて良いのは、一回だけ動けばいいようなプログラムくらいだと思う。(ただし、テスト自体は必要)
そうではないプログラムのテストを書かない方が良いと言う人は、真面目にテストしてなくてテストの大変さを知らないんじゃないかと思う。

あと、テストコードを必須にすれば、コードを書き逃げするプログラマに対しての牽制にもなりそうかな。

phpunitの確認

Laravelには標準でphpunitが入っているらしいので、とりあえずバージョン確認。

phpunit --version
PHPUnit 9.5.6 by Sebastian Bergmann and contributors.

9.5.6だった。

とりあえず動かしてみる。 実行は

phpunit 

で。

結果は

PHPUnit 9.5.6 by Sebastian Bergmann and contributors. 
 
Warning:       Your XML configuration validates against a deprecated schema. 
Suggestion:    Migrate your XML configuration using "--migrate-configuration"! 

..                                                                  2 / 2 (100%) 

Time: 00:00.155, Memory: 18.00 MB 

OK (2 tests, 2 assertions) 

何かOKでた。

デフォルトで何が動いている?

ここら辺が動いている模様。

app/tests/Feature/ExampleTest.php 
app/tests/Unit/ExampleTest.php 

これらはlaravelにデフォルトでおかれているファイル。
動作させるフォルダやディレクトリの設定は

app/phpunit.xml 

にある。
クラス名にTestついてなきゃだめ、とかそんなルールっぽいのが書いてる。

テストの作成

phpunitで動作させるクラスを作成する。 laravelでのコマンド

php artisan make:test HomeTest

上記コマンドではデフォルトでFeatureディレクトリにファイルを作成する。 --unitオプションでUnitディレクトリに作成。 FeatureとUnitの使い分けは、

  • Featureは機能のテスト
  • Unitはメソッドのテスト

のイメージかしら。 ここら辺の使い方は開発時にルールを決めた方がよさげ。

なお、クラス名は最後「Test」(ファイル名が◯◯Test.php)で作る必要あり。

テストクラスの実装

ログイン後にhomeへのリクエストが機能しているかを確認するためのテスト。 下記は、テストユーザを作って、そのユーザでログインし、認証されたユーザで正常にhomeのコンテンツが返されるか、を確認している。

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use App;

/**
 * テストデータ
 */
class TestData
{
    public const email = 'user1@example.com';
    public const password = 'Test1234';
}

/**
 * ホームコントロールテスト
 */
class HomeTest extends TestCase
{
    // データベースの初期化にトランザクションを使う
    use DatabaseTransactions;

    // ログインユーザの保持用
    public $TestUser;

    /**
     * 初期設定
     *
     * @return void
     */
    public function setUp(): void
    {
        parent::setUp();

        // `users` テーブルにデータを作成 
        $this->TestUser = factory(App\User::class)->create([
            'id' => '999',
            'name' => 'test太郎',
            'password' => bcrypt(TestData::password),
            'email' => TestData::email,
        ]);
    }

    /**
     * テストユーザでのログイン
     *
     * @return void
     */
    public function Login()
    {
        // ログインページへの単純なアクセス
        $response = $this->get(route('login'));
        $response->assertStatus(200);

        // ログイン実施
        $response = $this->post(route('login'), [
            'email' => TestData::email,
            'password' => TestData::password,
        ]);
        // リダイレクトでページ遷移するのでstatusは302
        $response->assertStatus(302);
        // リダイレクト先のパス
        $response->assertRedirect('/home');

        // ユーザーがログイン認証されているか
        $this->assertAuthenticatedAs($this->TestUser);
    }

    /**
     * ログインのテスト
     *
     * @return void
     */
    public function testIndex()
    {
        // ログイン
        $this->Login();

        // ログイン後のhome
        $response = $this->get(route('home'));
        $response->assertStatus(200);
    }   
}

assertについて

望んだ値かをチェックするためメソッド。色んな種類があるので、状況に応じて使い分ける。

factoryについて

テストデータを作るもの。 ファイルは以下に置かれる。

database/factories

ちなみに、

php artisan ui vue –auth  

上記のartisanコマンドでユーザ認証機能などを作成している場合、UserのFactoryはデフォルトで作成されている。

テスト実施時にpostのレスポンスコードが419

419エラーコードはCSRFでのエラーらしい。
テストの時は無視するように設定。

以下のファイルを開き、

App/Middleware/VerifyCsrfToken.php 

以下のコードを追加。

public function handle($request, \Closure $next) 
{ 
    if (env('APP_ENV') !== 'testing') { 
        return parent::handle($request, $next); 
    } 
    return $next($request); 
} 

testingとかその辺の理由は

app/phpunit.xml 

あたりを覗くとわかりそうな気がしないでもない

laravelでのvalidation処理4(ルールの自作)

色々なValidationの実装を試してみる。
今回は自分でRuleクラスを作成してValidationの内容を実装する。

例としてユーザIDがユニークであるかをチェックするルールを作成。

ルールクラスを作成

ターミナルからルールクラスを作成するコマンド。

php artisan make:rule UserIdUniqueRule 

ruleクラスの実装

上記で作成されたファイル(クラス)の実装。
passesにチェックを通すための処理を記述。
modelを呼び出してDBから値を取得。取得した値とチェックする値を比較して判定する。
エラー時の文言はmessageに実装。

<?php

namespace App\Rules\;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Auth;
use App\Model\User;

class UserIdUniqueRule implements Rule
{
    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value(userId)
     * @return bool
     */
    public function passes($attribute, $value)
    {
        // IDがなし(新規)もしくは自分自身はチェック不要
        if ($value == null || $value == Auth::id()) {
            return true;
        }

        // ユーザが登録済みかのチェック
        $mUser = new User();
        $user = $mUser->getUserInfo($value);

        if ($user != null) {
            return false;
        }

        return true;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return '既に登録済みのIDです';
    }
}

Request側への実装

作成後、リクエストクラスのrulesに上記で作成したクラスを追加。 リクエストクラスは以前やりかた3で作成したものをイメージ。

    use App\Rules\UserIdUniqueRule; 

    public function rules() 
    { 
        return 
            // チェック内容 
            [ 
                'id' => ['required', new UserIdUniqueRule()], 
                'name' => 'required', 
                'email' => 'required', 
            ]; 
    } 

以上。

余談1

Ruleクラスを作成するやり方以外にサービスプロバイダを使用する方法もあるが、個人的にしっくりこないので、そういったやり方が存在することだけ覚えて内容は割愛。

余談2

Rule専用のディレクトリが用意されることになるが、自作しなきゃならないようなチェックはなんらかの業務的な要件がほとんどであり、そういったチェックは業務処理の近くにあった方がわかりやすい気がする。
そんなことない?
自分が業務処理は共通化しない方針なのでこういった感想になるのかしら。
とりあえず作られたままに利用します。

余談3

ここまで何となくlaravelを触っていての感想。
基本的にModel本体ではエラーチェックはしないので、Modelを使う場合は今回の様にどこかしらでチェック済された、正しい値が入っていることを期待することになる。

ちなみに、今回の作りだと、参照は
 Control→Request(Validator)→Rule→Model
となりModelのためのチェックはされていない。
もし更新処理する場合は気をつけたほうが良さそう。するシチュエーションがあるかは知らない。

laravelでのvalidation処理3

laravelで色んなvalidationを試す。 リクエストクラスを自作してvalidationを設定する方法。

これまでの処理1処理2に比べると一番現実的な気がする。

今後、絶対にlaravelで用意されている方法以外のエラーチェック(ruleの自作)が必要になるので、Validationとして独立して記述されていた方が拡張する際にもごちゃごちゃしなくなると思うし。

リクエストクラスの作成

php artisan make:request ファイル名

作成したファイルを以下になるように修正・メソッド追加。

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;

class ファイル名 extends FormRequest
{
    /** true:エラーあり */
    private $has_error;
    public function getHasError(): bool
    {
        return $this->has_error;
    }

    public function authorize()
    {
        // サンプルのため承認はすべてOK
        return true;
    }

    public function rules()
    {
        return
            // チェック内容
            [
                'id' => 'required',
                'name' => 'required',
                'email' => 'required',
            ];
    }

    public function messages()
    {
        return
            // エラー文言
            [
                'id.required' => ':attributeは必須です。',
                'name.required' => ':attributeは必須です。',
                'email.required' => ':attributeは必須です。',
            ];
    }

    public function attributes()
    {
        return
            // attribute
            [
                'id' => 'ID',
                'name' => '名前',
                'email' => 'email'
            ];
    }

    protected function failedValidation(Validator $validator)
    {
        // エラーの発生を検知
        $this->has_error = true;

        // Validationでエラー検知時の処理。
        // requestで設定されたチェックはControllerに到達する前にValidationエラー判定される
        //(詳しいタイミングは追ってない)
        // ここでexceptionが発生したらリダイレクトされる動作になる。
        // 今回、エラー時の動作はコントローラ側に任せたいのでValidationは発生させない。
    }

    public function getValidatedMessage()
    {
        // エラー時にメッセージを取り出すため
        return $this->validator->messages();
    }
}

この例ではgetHasErrorとgetValidatedMessageは筆者の感性で作ったメソッド。あとはオーバーライド。

コントローラ側で自作リクエストを利用する。

コントローラで以下の様に修正。 もともとRequest型だったものを自作のリクエストに置き換えれば良い。

testMethod到達時にはエラーチェックが完了しているため、エラーチェックの結果を取り出してエラーがあれば相応の処理をする。

use App\Http\Requests\自作したリクエスト

// 引数のRequest型を作成したカスタマイズしたRequestに置き換える
public function testMethod(自作リクエストクラス $request)
{
    // エラーチェック
    if ($request->getHasError()) 
    {
        $res = new class
        {
            public $has_error = true;
            public $message = null;
        };

        $res->message = $request->getValidatedMessage();

        Log::debug('エラーあり' . json_encode($res));
        
        return json_encode($res);
    }
    ・・・略
}

laravelでのvalidation処理2

laravelのvalidationの色んなやり方を試す。
今回はコントローラ側でValidatorを作ってチェックする。
エラー後の動作は自分でハンドリングする。

use Illuminate\Support\Facades\Validator;

/**
* ユーザ情報更新
*/
public function postEditUser(Request $request)
{
    $validate = Validator::make(
        $request->all(),
        // チェック内容
        [
            'id' => 'required',
            'name' => 'required',
            'email' => 'required',
        ],
        // エラー文言
        [
            'id.required' => ':attributeは必須です。',
            'name.required' => ':attributeは必須です。',
            'email.required' => ':attributeは必須です。',
        ],
        // attribute
        [
            'id' => 'ID',
            'name' => '名前',
            'age' => '年齢'
        ]
    );

    if ($validate->fails())
    {
        // エラーあり
        Log::debug('エラーあり' . $validate->messages());

        // TODO:responseのjsonを作りたい
        $res = new class
        {
            public $has_error = true;
            public $message = null;
        };

        $res->message = $validate->messages();
        return json_encode($res);
    }

    // TODO:正常時の処理
}

ちなみに、nameの項目が未入力だった場合の$validate->messages()はこんな感じ

{
    "name": ["名前は必須です。"]
}

自分でエラー時のレスポンス作るなら、システム的に統一したフォーマットを作った方が良いので個別のコントローラでレスポンスを作ることはないんじゃないかしらと想定。
この書き方はちょいプロ用かな。

laravelでのvalidation処理1

いろいろやり方はあるみたい。 以下はコントローラ中に処理を書くやり方。 Requestクラスで用意されているvalidateメソッドを使う。 参考

/** 
* ユーザ情報更新 
*/ 
public function postEditUser(Request $request) 
{ 
    $request->validate( 
    // チェック内容 
    [ 
        'id' => 'required', 
        'name' => 'required', 
        'email' => 'required', 
    ], 
    // エラー文言 
    [ 
        'id.required' => ':attributeは必須です。', 
        'name.required' => ':attributeは必須です。', 
        'email.required' => ':attributeは必須です。', 
    ], 
    // attribute 
    [ 
        'id' => 'ID', 
        'name' => '名前', 
        'age' => '年齢' 
    ] 
    );
   
 // 以下正常時の処理 
} 

validateメソッドの結果、リクエスト不正が起こったときは例外(exception)が発生する。 このやり方の場合、例外発生時はエラーの値とともに現在のページにリダイレクトされるらしいので、ここではキャッチしないのがおそらく正解。

例外でリダイレクトされるとblade側で@errorディレクティブが有効になるようなのでエラー処理はblade側に記載できていれば良さそう。

このやり方、ajaxでエラーチェックする場合は出番無いと思う。

なお、例外時、exceptionには以下のような値が入っている。 httpのステータスコードとしては422を返そうとしているのが分かる。

{ 
    "validator": { 
        "customMessages": { 
            "id.required": ":attributeは必須です。", 
            "name.required": ":attributeは必須です。", 
            "email.required": ":attributeは必須です。" 
        }, 
        "fallbackMessages": [], 
        "customAttributes": { 
            "id": "ID", 
            "name": "名前", 
            "age": "年齢" 
        }, 
        "customValues": [], 
        "extensions": [], 
        "replacers": [] 
    }, 
    "response": null, 
    "status": 422, 
    "errorBag": "default", 
    "redirectTo": null 
}