Open4

phpの新し目の構文たち

モッモッ

認証スニペット的な例

すんごいシンプルな例

一応typehintは最初から付けている

cat test.php
<?php

class UserService
{
    private $users = [
        1 => [
            'name' => 'John',
            'password' => '123456',
        ],
        2 => [
            'name' => 'Alice',
            'password' => '234567',
        ],
    ];

    public function authenticate($userId, $password): bool
    {
        if (isset($this->users[$userId]) && $this->users[$userId]['password'] === $password) {
            return true;
        }

        return false;
    }
}
$userService = new UserService;
var_dump($userService->authenticate(1, '123456')); // true
モッモッ

キーのチェック系は大抵isset()を廃止できる

まあ、長いしね

     public function authenticate(int $userId, string $password): bool
     {
-        if (isset($this->users[$userId]) && $this->users[$userId]['password'] === $password) {
-            return true;
-        }
-
-        return false;
+        return ($this->users[$userId]['password'] ?? null) === $password;
     }
 }
 $userService = new UserService;
モッモッ

ユーザーをオブジェクトで返却してみる

まずは古いスタイル

<?php

class User
{
    public int $id;
    public string $name;
    public string $password;
    public ?string $email;

    public function __construct(int $id, string $name, string $password, ?string $email = null)
    {
        $this->id = $id;
        $this->name = $name;
        $this->password = $password;
        $this->email = $email;
    }
}

class UserService
{
    private $users = [
        1 => ['name' => 'John', 'password' => '123456'],
        2 => ['name' => 'Alice', 'password' => '234567', 'email' => 'test@example.com'],
    ];

    public function authenticate(int $userId, string $password)
    {
        $userData = isset($this->users[$userId]) ? $this->users[$userId] : null;

        if ($userData && $userData['password'] === $password) {
            return new User(
                $userId,
                $userData['name'],
                $userData['password'],
                isset($userData['email']) ? $userData['email'] : null
            );
        }

        return null;
    }
}

$userService = new UserService();
$user = $userService->authenticate(1, '123456');

var_dump($user);
object(User)#2 (4) {
  ["id"]=>
  int(1)
  ["name"]=>
  string(4) "John"
  ["password"]=>
  string(6) "123456"
  ["email"]=>
  NULL
}

コンストラクタからプロパティに押し上げる的な代入は最早不要

    public function __construct(int $id, string $name, string $password, ?string $email = null)
    {
        $this->id = $id;
        $this->name = $name;
        $this->password = $password;
        $this->email = $email;
    }

これ面倒くさいので(まあcopilotならざくっと書いてくれるかもだけど)php8からは省略した定義を行う事ができる

<?php

class User
{
    public function __construct(
        public int $id,
        public string $name,
        public string $password,
        public ?string $email = null
    ) {}
}

これを コンストラクタプロパティプロモーション とか言うそーな

コンストラクターのtypehintを正しくセットするのとissetの廃止

-    public function authenticate(int $userId, string $password)
+    public function authenticate(int $userId, strIng $password): ?User
     {
-        $userData = isset($this->users[$userId]) ? $this->users[$userId] : null;
+        $userData = $this->users[$userId] ?? null;

この辺はphp7.1くらいで完全対応となる

名前付き引数

         if ($userData && $userData['password'] === $password) {
             return new User(
-                $userId,
-                $userData['name'],
-                $userData['password'],
-                isset($userData['email']) ? $userData['email'] : null
+                id: $userId,
+                name: $userData['name'],
+                password: $userData['password'],
+                email: $userData['email'] ?? null
             );
         }

順番を気にしなくてもいいのがあるけどやはりコンテキストがはっきりするのでよいと思う。なおemailのところは何度も出てきている null合体演算子 である

モッモッ

callbackの受け渡し(古いやつ)

たとえば以下のように定義し直してみる

# id1: password:123456で認証成功したら`$user`オブジェクトが返却される
$user = $userService->authenticate(1, '123456');
var_dump($user);

から

# id1: password:123456で認証成功したら`$user`オブジェクトが返却される、失敗したときはその情報でユーザーを作成してしまう
$newUser = $userService->authenticate(3, 'newpass', $createUserCallback);
var_dump($newUser);

古い記法

<?php

class User
{
    public int $id;
    public string $name;
    public string $password;
    public ?string $email;

    public function __construct(int $id, string $name, string $password, ?string $email = null)
    {
        $this->id = $id;
        $this->name = $name;
        $this->password = $password;
        $this->email = $email;
    }

    public static function create(array $data): self
    {
        return new self(
            $data['id'],
            $data['name'],
            $data['password'],
            isset($data['email']) ? $data['email'] : null
        );
    }
}

class UserService
{
    private $users = [
        1 => ['name' => 'John', 'password' => '123456'],
        2 => ['name' => 'Alice', 'password' => '234567', 'email' => 'test@example.com'],
    ];

    public function authenticate(int $userId, string $password, ?callable $createUsing = null): ?User
    {
        $userData = isset($this->users[$userId]) ? $this->users[$userId] : null;

        if ($userData && $userData['password'] === $password) {
            return User::create(array_merge(['id' => $userId], $userData));
        }

        if ($createUsing) {
            $newUserData = $createUsing(['id' => $userId, 'password' => $password]);

            if (is_array($newUserData)) {
                $this->users[$userId] = $newUserData;
                return User::create($newUserData);
            }
        }

        return null;
    }
}

$userService = new UserService();

// コールバック関数(PHP 7.4 互換)
$createUserCallback = function ($user) {
    return [
        'id' => $user['id'],
        'name' => 'NewUser' . $user['id'],
        'password' => $user['password'],
        'email' => 'newuser' . $user['id'] . '@example.com',
    ];
};

// 認証成功時
$user = $userService->authenticate(1, '123456');
var_dump($user);

// 認証失敗時、新規ユーザー作成
$newUser = $userService->authenticate(3, 'newpass', $createUserCallback);
var_dump($newUser);

前回の古い記法からのdiff

         $this->password = $password;
         $this->email = $email;
     }
+
+    public static function create(array $data): self
+    {
+        return new self(
+            $data['id'],
+            $data['name'],
+            $data['password'],
+            isset($data['email']) ? $data['email'] : null
+        );
+    }
 }

 class UserService
@@ -23,17 +33,21 @@ class UserService
         2 => ['name' => 'Alice', 'password' => '234567', 'email' => 'test@example.com'],
     ];

-    public function authenticate(int $userId, string $password)
+    public function authenticate(int $userId, string $password, ?callable $createUsing = null): ?User
     {
         $userData = isset($this->users[$userId]) ? $this->users[$userId] : null;

         if ($userData && $userData['password'] === $password) {
-            return new User(
-                $userId,
-                $userData['name'],
-                $userData['password'],
-                isset($userData['email']) ? $userData['email'] : null
-            );
+            return User::create(array_merge(['id' => $userId], $userData));
+        }
+
+        if ($createUsing) {
+            $newUserData = $createUsing(['id' => $userId, 'password' => $password]);
+
+            if (is_array($newUserData)) {
+                $this->users[$userId] = $newUserData;
+                return User::create($newUserData);
+            }
         }

         return null;
@@ -44,3 +58,17 @@ public function authenticate(int $userId, string $password)
 $user = $userService->authenticate(1, '123456');

 var_dump($user);
+
+// コールバック関数(PHP 7.4 互換)
+$createUserCallback = function ($user) {
+    return [
+        'id' => $user['id'],
+        'name' => 'NewUser' . $user['id'],
+        'password' => $user['password'],
+        'email' => 'newuser' . $user['id'] . '@example.com',
+    ];
+};
+
+// 認証失敗時、新規ユーザー作成
+$newUser = $userService->authenticate(3, 'newpass', $createUserCallback);
+var_dump($newUser);

とりあえずソースの解説

    public function authenticate(int $userId, string $password, ?callable $createUsing = null): ?User
    {
        $userData = isset($this->users[$userId]) ? $this->users[$userId] : null;

        if ($userData && $userData['password'] === $password) {
            // 認証成功
            return User::create(array_merge(['id' => $userId], $userData));
        }

ここまでは従来と変わらない。このifブロックは認証成功した場合Userオブジェクトを返却して終わっている。失敗したとき

          if ($createUsing) {
              $newUserData = $createUsing(['id' => $userId, 'password' => $password]);

              if (is_array($newUserData)) {
                  $this->users[$userId] = $newUserData;
                  return User::create($newUserData);
              }
          }

          return null;

こっちの方のブロックに入ってくる。$createUsingの中に関数がある場合のみ機能している

$createUsing(['id' => $userId, 'password' => $password]

こんな感じで定義した関数の中にidとpasswordをつめた配列をつっこんで

function ($user) {
      return [
          'id' => $user['id'],
          'name' => 'NewUser' . $user['id'],
          'password' => $user['password'],
          'email' => 'newuser' . $user['id'] . '@example.com',
];

これをcallしているというわけだ

さらに

        if ($userData && $userData['password'] === $password) {
            return User::create(array_merge(['id' => $userId], $userData));
        }

この箇所は、いちいちプロパティーを全部してい してるわけでもなくidだけ追加して作成するようにしている