エンジニアのひよこ_level10

【毎日更新!】新卒2年目エンジニアブログです! プログラムだけじゃなく、マネジメントとかも書いていきたい!

【php】イミュータブルなクラスのフィードバック対応をしてみる【498日目】

昨日こんな記事書きました

www.nyamucoro.com

これで、イミュータブルっぽいクラスを作りました。

で、最初にフィードバックをもらった方に、あの記事を見てもらったら、こんな記事をおすすめしていただきました。

参考記事

qiita.com

qiita.com

あれー?昨日の記事よりわかりやすくて、内容が入ってるよー\(^o^)/

さすがsuinさんですね……!

実装書き換えてみる

class User {
    private $name;
    private $status;

    public function __construct(string $name, Status $status) {
        $this->name = $name;
        $this->status = clone $status;
    }

    public function setStatus(Status $status) {
        $new_instance = clone $this;
        $new_instance->status = clone $status;
        return $new_instance;
    }

    public function setName(string $name) {
        $new_instance = clone $this;
        $new_instance->name = $name;
        return $new_instance;
    }
}

疑問

privateなフィールドなのに、なんでcloneで別インスタンス作ったのに、フィールド値が書き換えられるの……?

PHP: オブジェクトのクローン作成 - Manual

PHP 7.0.0 以降では、新しくクローンしたオブジェクトのメンバーに、作成したその式の中でもアクセスできるようになりました。

し、しらなかったー!!!

ってことで、無事にいい感じのイミュータブルっぽいクラスが出来上がりましたねたぶん!やったー!

【PHP】イミュータブルなクラスを作ってみる【497日目】

始まりは突然に。

昨日のブログにツッコミが入りました(‘ω‘ )

なるほどたしかに!
changeStatusというフィールド値をUserに持たせたほうが、Userのフィールド値とか状態に触れる知識を外部に持たせないって出来ますね。
状態に対する関心も、あちこちに分散しない……!

ってことで、頂いたアドバイスを自分なりに解釈して実装してみる。
(で、出来たらちょっとレビューをお願いしてみる・・・!)

頂いたアドバイス

実装

class User {
    private $name;
    private $status;

    public function __construct(string $name, int $status) {
        $this->name = $name;
        $this->status = $status;
    }

    public function __set($name, $value) {
        throw new Exception('This object can't be set field value.');
    }

    public function setStatus(int $status) {
        return new User($this->name, $status);
    }

    public function setName(string $name) {
        return new User($name, $this->status);
    }
}

応用

フィールド値にインスタンスを持つ場合。
前に勉強したやつ実装してみる。

【デザパタ自作】イミュータブルパターン(インスタンス内包対応)【266日目】 - エンジニアのひよこ_level10

class User {
    private $name;
    private $status;

    public function __construct(string $name, Status $status) {
        $this->name = $name;
        $this->status = clone $status;
    }

    public function __set($name, $value) {
        throw new Exception('This object can't be set field value.');
    }

    public function setStatus(Status $status) {
        return new User($this->name, clone $status);
    }

    public function setName(string $name) {
        return new User($name, clone $this->status);
    }
}

感想?

こんな感じ・・・?
これで、たぶん値の変更は不可能になって、データは新しいオブジェクトを返すようになっているはず・・・?

使用感としても、ユーザーは内部に何を持ってるかを意識せず、関数だけ意識できる気がする。

// 全部違うインスタンスになる。たぶん変更出来ないはず
$user = new User('youkan', new Status(false));
$user = $user->setName('Uiro');
$user = $user->setStatus(new Status(true));

Tell Don't Askの観点とか知らない言葉が出てきてるので、いつか勉強したいというか、
早くぺちおぶ行きたいのに、Laravel JP Conferenceとかイベントに被るから行けなかった・・・(登壇者だからどうあがいても行けなかった)

また今度こそ、行きたいね・・・!

【PHP】油断すると破壊的な関数を書いちゃうよねって話【496日目】

こんなコードがありました

PHPに限った話ではないですが。

こんなコードがありました。

function changeUserStatus($user) {
    $user->status = 2
    return $user;
}

echo $user->status; // 1

$changed_user = $this->changeUserStatus($user);

echo $user->status;  //1. ??
echo $changed_user->status; //2. ??

さて。このとき1,2の値はどうなるでしょうか。

正解は

両方共2になりますね。

なぜか。インスタンスの参照先を渡して、インスタンスの値をそのまま書き換えてしまったから。

オブジェクトのインスタンスを扱うときは、結構やりがち。

どうしたらよかった?

もし、元の値を変更したくなかったのなら以下

function changeUserStatus($user) {
    //ここを書き換える
    $changed_user = clone($user);
    $changed_user->status = 2
    return $changed_user;
}

echo $user->status; // 1

$changed_user = $this->changeUserStatus($user);

echo $user->status;  // 1
echo $changed_user->status; // 2

clone()関数で全く同じものを生成して、それを書き換えてreturnする。

【Faker】boolean関数でTrueの確率を操作+元コード読む【489日目】

fakerって?

テストとかでよく使われるFaker。

ダミーの値を作るときによく使われていて、ランダム文字列、それっぽい電話番号、それっぽい住所などをランダム生成してくれます。

booleanの確率操作

trueやfalseを出す確率を実は操作できます。テスト内容によっては重要かも。

書き方は単純。引数に『True』が出る確率を書く。

$faker = new Faker\Generator();

$faker->boolean(50);

さて、元コードどうなってるんでしょう?

元コード

たぶん、これ。(ちょっと自信ないのが、Generatorのコメントから飛んだから)

vendor/fzaninotto/faker/src/Faker/Provider/Miscellaneous.php

    /**
     * Return a boolean, true or false.
     *
     * @param int $chanceOfGettingTrue Between 0 (always get false) and 100 (always get true)
     *
     * @return bool
     *
     * @example true
     */
    public static function boolean($chanceOfGettingTrue = 50)
    {
        return mt_rand(1, 100) <= $chanceOfGettingTrue;
    }

あーなるほどわかりやすい・・・

ココらへん読むと結構ニヤニヤできますよ(‘ω‘ )この機会に是非

【PHP】sprintfで同じ値を使いたいときに、コピペしない方法【479日目】

sprintfで同じ値が使いたい

sprintf('1. %s, 2. %s, 3. %d', 'apple', 'grape', 'apple');

こんなふうに、appleが被っている。

%dとかに数値を入れる

sprintf('1. %1$s, 2. %2$s, 3. %1$s', 'apple', 'grape');

これで、 %1$sには、appleが入り、 %2$sには grapeが入ります!

同じ値を使いたいってときは、とにかく値を並べるより、こっちの方がいいかもです!

【テスト】dataProviderで分岐で読み込む関数名をチェックする【439日目】

概要

XxxService

public function func($flag)
{
    if($flag) {
        return $this->user_repository->xxx();
    }

    return $this->user_repository->yyy();
}

こんなとき、xxxとyyyが読み込まれる場合をチェックしたい

コード

public testFunc($flag, $function_name)
    $mock = Mockery::mock('Tests\Unit\App\Stubs\StubUserRepository')->makePartial();

    // 該当関数が一度だけ呼ばれるのを期待する
    $mock->shouldReceive($function_name)
        ->once();

    (new XxxService($mock))->func($flag);
}

public function FuncDataProvider()
{
    return [
        'flagがtrueの場合' => [
            'flag' => true,
            'function_name' => 'xxx',
        ],
        'flagがfalseの場合' => [
            'flag' => false,
            'function_name' => 'yyy',
        ],
    ];
}

shouldReceiveの関数名の部分をdataProviderで分岐させてチェックする。

【PHP】配列で共通している値だけを抜き出す関数【437日目】

概要

1.以下のときに"apple","grape"の値だけを返したい

["apple", "orange", "grape"]
["apple", "grape", "banana"]

2.array_intersect()を使う

配列で、共通の値だけを抜き出したい

$array_1 = ["apple", "orange", "grape"];
$array_2 = ["apple", "grape", "banana"];

こんなときに、"apple","grape"を取り出したい。

ユーザーの共通項を取り出したり、
条件を配列で用意しておいて、条件一致しているものを確認するなどに使えますね。

array_intersect

$array_1 = ["apple", "orange", "grape"];
$array_2 = ["apple", "grape", "banana"];

return array_intersect($array_1, $array_2);

これでよし。

公式ドキュメント

PHP: array_intersect - Manual