🛠️

小細工.php

2022/09/27に公開

https://zenn.dev/lfz/articles/df8449097dd250
のようなものを雰囲気で書いていく


php8.2

1.Struct

構造体

<?php
declare(strict_types=1);
readonly final class Favorite {
    public function __construct(
        public string $name,
        public string $detail
    )
    {
    }
}
readonly final class StructProfile {
    public function __construct(
        public string $name, 
        public int $age, 
        public array $favorites, //array<Favorite>
    )
    {
        array_walk($favorites, fn($elm) => $elm instanceof Favorite ?: throw new InvalidArgumentException('型エラー'));
    }
}

$profile = new StructProfile(
    'ワタル', 
    10, 
    [
        new Favorite('登山', 'とある事情で某山に登る事。仲間が出来てうれしい。'),
        new Favorite('ロボット操作', '池の神さま?が乗り移ってると思われるロボットに乗って悪と戦う'),
    ]
);
print_r($profile->name);
print_r($profile->age);
print_r($profile->favorites);
try{
    $profile->name = 'ABC';
}catch(Throwable $e){
    print_r($e->getMessage());
    echo "\n";
}

$profile = new StructProfile(
    'ワタル', 
    10, 
    [
        ['登山', 'とある事情で某山に登る事。仲間が出来てうれしい。'],
        ['ロボット操作', '池の神さま?が乗り移ってると思われるロボットに乗って悪と戦う'],
    ]
);
print_r($profile->name);
print_r($profile->age);
print_r($profile->favorites);
結果
ワタル10Array
(
    [0] => Favorite Object
        (
            [name] => 登山
            [detail] => とある事情で某山に登る事。仲間が出来てうれしい。
        )

    [1] => Favorite Object
        (
            [name] => ロボット操作
            [detail] => 池の神さま?が乗り移ってると思われるロボットに乗って悪と戦う
        )

)
Cannot modify readonly property StructProfile::$name
PHP Fatal error:  Uncaught InvalidArgumentException: 型エラー in /home/username/src/ideas/struct.php:17
Stack trace:
#0 [internal function]: StructProfile->{closure}()
#1 /home/username/src/ideas/struct.php(17): array_walk()
#2 /home/username/src/ideas/struct.php(39): StructProfile->__construct()
#3 {main}
  thrown in /home/username/src/ideas/struct.php on line 17

php8.1

1.Partial Application(引数の部分適用)

関数の一部にパラメーターを当て込んだ状態の関数を用意

$arr=[1,3,4,5,7,9,11,12,13];

$map = fn(callable $fn) => array_map($fn, $arr);
$filter= fn(callable $fn) => array_filter($arr, $fn);

$evenFlag = $map(fn($x)=> $x%2===0);
print_r($evenFlag);

$filterd = $filter(fn($x)=> $x%2===0);
print_r($filterd);

$filterd = $filter(fn($x)=> $x%2!==0);
print_r($filterd);
結果
Array
(
    [0] =>
    [1] =>
    [2] => 1
    [3] =>
    [4] =>
    [5] =>
    [6] =>
    [7] => 1
    [8] =>
)
Array
(
    [2] => 4
    [7] => 12
)
Array
(
    [0] => 1
    [1] => 3
    [3] => 5
    [4] => 7
    [5] => 9
    [6] => 11
    [8] => 13
)

個人的な好みで即時アロー関数にしているけれどfunction useで書いてもOK

function useの例
$map = function(callable $fn) use ($arr){
    return array_map($fn, $arr);
};
print_r($map(fn($x)=> $x%2===0));

2.scope

本筋の関係の無い処理はクロージャーを使い閉じたスコープへ

$weekday = [
    'sunday',
    'monday',
    'tuesday',
    'wednesday',
    'thursday',
    'friday',
    'saturday'];
$current = (new DateTimeImmutable())->setTime(23,59,59);
$yearEnd = $current->setDate($current->format('Y'), 12, 31);

//金曜日以外をフィルタリングする:スコープ閉じる
$fridays = (function($start, $last){
    $ret=[];
    while($start <= $last) {
        if((int)$start->format('w') === 5) {
            $ret[] = $start;
        }
        $start = $start->modify('+1 day');
    }
    return $ret;
})($current, $yearEnd);

//本筋の処理なのでスコープ閉じない
foreach($fridays as $day){
    //わざとらしく$dayを元に曜日を表示する
    echo $day->format('Y-m-d'), $weekday[(int)$day->format('w')], "\n";
}

3.match guard

matchを工夫して条件付きmatch(elixir:ガード)や、タプルを使ったマッチ(rust:マッチ)のようにする

enum Level:string {
    case Critical='ヤバイ';
    case Caution='緊張';
    case Safe='セーフ';
}

$data = [
    [100, Level::Critical],
    [100, Level::Caution],
    [100, Level::Safe]
];

foreach($data as $input){
    $nextAction = match ($input) {
        (fn($num, $level) => $num===100 && $level===Level::Critical ? $input : null)(...$input) => '現実逃避',
        (fn($num, $level) => $num===100 && $level===Level::Caution ? $input : null)(...$input) => '試行錯誤',
        default => '続行'
    };

    echo $input[0], ' | ' ,$input[1]->value, ' | ' , $nextAction, "\n";

}

4.Socket

これは小細工ではなくソケットプログラミングの紹介となる
2022年時点で20代の前半中盤のフレッシュなタイプに向けての紹介なので、自分のような30代のオールドタイプな人達は大抵知ってるだろうから読み飛ばし推奨、ソケットプログラミングが出来る人は読む必要無し

予備知識

curlwgetでWEBページが取得出来るのはクライアント/サーバー双方がHypertext Transfer Protocol(つまりhttp)の仕様に従った実装をしている為だが、このソケットプログラミングを用いる事で独自のプロトコルを作る事が出来る

UDPサーバー

以下のサンプルは送った文字をそのままクライアントに返し、空を二度送るとサーバーが終了するプログラムとなる
せっかくなのでパイプライン風で作ったクラスを用いて書いた

サンプルソース
pipe_udpserver.php
$recv = Pipe::resource(socket_create(AF_INET, SOCK_DGRAM, SOL_UDP))
    ->then(function($socket){
        if(!$socket){
            exit(socket_strerror(socket_last_error()));
        }
        return $socket;
    })->then(function($socket){
        socket_bind($socket, '127.0.0.1', 8080);
        return $socket;
    })->then(function($socket){
        $res='';
        $from = '';
        $port = 0;
        $prev = 0;
        while($byte = socket_recvfrom($socket, $buf, 1024, MSG_WAITALL, $from, $port)){
            if(1024 < $byte) {
                socket_sendto($socket, '1024 byte over', 1024, 0, $from, $port);
            }
            if($byte===1 && $prev===$byte){
                socket_sendto($socket, '[Connection closed]', 1024, 0, $from, $port);
                break;
            }
            socket_sendto($socket, '[Received]'.$buf, 1024, 0, $from, $port);
            $prev = $byte;
            $res .= trim($buf);
        }
        socket_close($socket);
        return $res;
    })->fin();
echo "[Received message]\n";
var_dump($recv);

:::

これを実行し

php pipe_udpserver.php

ncなどのudpで通信出来るクライアントを使って適当に文字を打つと下記のように表示される事を確認できる

$ nc -u localhost 8080[Received]あ
い
[Received]い
空でエンター二回
[Received]空空でエンター二回

[Received]

[Connection closed]

TCPサーバー

phpマニュアル丸っとコード書いてあるのでそちらを見た方が良いかもしれない

気まぐれで追記していく予定

5.メモ

キャッシュの事だが、memcacheredisの事では無く、そのプロセスの実行中に限りメモリに残す事を言う。
phpは基本的にweb開発にしか使われないので利用個所はあまりないが、バッチ処理などで活躍する。
今回はサンプルとしてDBの結果をキャッシュするが、他にも計算結果を残したりなど用途は様々。
注意事項としてspiral frameworkのようにプロセスが生き続けるようなphpの使い方をしている場合にはメモリが残り続ける事に気を付ける必要があるが、大抵のphpユーザーはapache/nginx/caddyなどのWEBサーバーを通して使うと思うので考慮する必要はないだろう。
Laravel Octaneを使った場合にどうなるかは知らないのでメモする人は検証してから使うこと。

下準備

サンプルでsqliteを使うのでその準備

$ asdf plugin-add sqlite
$ asdf install sqlite 3.39.4
$ asdf global sqlite 3.39.4
$ sqlite3 db_memo.db
SQLite version 3.39.4 2022-09-29 15:55:41
Enter ".help" for usage hints.
sqlite> create table users('id', 'name', 'age');
sqlite> insert into users ('id', 'name', 'age') values (1, 'l-freeze', 23);
sqlite> insert into users ('id', 'name', 'age') values (2, 'm-freeze', 24);
sqlite> insert into users ('id', 'name', 'age') values (3, 'n-freeze', 25);
sqlite> select * from users;
1|l-freeze|23
2|m-freeze|24
3|n-freeze|25

php

declare(strict_types=1);
$usersTable = new Users();

$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),$usersTable->getAll());
print_r(array_map(fn ($d)=> implode('|', $d), $data));
$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),$usersTable->getAll());
print_r(array_map(fn ($d)=> implode('|', $d), $data));
$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),$usersTable->getAll());
print_r(array_map(fn ($d)=> implode('|', $d), $data));

final class Users {
  private $dbh = null;
  private $result = null;
  public function __construct(){
    try {
      $this->dbh = new PDO('sqlite:./db_memo.db');
    } catch (PDOException $e) {
      echo $e->getMessage();
    }
  }

  public function getAll(){
    if(!is_null($this->result)) {
      echo "[From cache]";
      return $this->result;
    }else{
      echo "[From db]";
    }

    try {
      $st = $this->dbh->prepare('select * from users');
      $st->execute();
      $this->result = $st->fetchall();
      return $this->result;
    } catch (PDOException $e) {
      echo $e->getMessage();
    }
  }

  public function __destruct(){
    $this->dbg=null;
  }

}
実行結果
[From db]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)
[From cache]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)
[From cache]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)

6.Static Variables

文字通り静的な変数の事。staticを書ける箇所はたくさんあるが、紹介するのはfunctionスコープのstatic。
自分の業務経験からするとstatic $変数の存在自体を知らない人がほとんどだったので紹介する。便利なのでphpを使う技術屋さんはぜひ活用して欲しいと思う。

php

メモの例として作ったサンプルソースをfunction+staticで書き換える。

<?php

$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),getDbData());
print_r(array_map(fn ($d)=> implode('|', $d), $data));
$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),getDbData());
print_r(array_map(fn ($d)=> implode('|', $d), $data));
$data = array_map(fn ($d)=> array_filter($d, fn($k)=> !is_int($k), ARRAY_FILTER_USE_KEY),getDbData());
print_r(array_map(fn ($d)=> implode('|', $d), $data));


function getDbData(){
  static $result = null;
  if(!is_null($result)){
    echo "[From cache]";
    return $result;
  }
  try {
    $dbh = new PDO('sqlite:./db_memo.db');
    $st = $dbh->prepare('select * from users');
    $st->execute();
    $result = $st->fetchall();
    $dbh = null;
  } catch (PDOException $e) {
    echo $e->getMessage();
  } finally {
    $dbh = null;
    echo "[From db]";
  }
  return $result;
}

結果
[From db]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)
[From cache]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)
[From cache]Array
(
    [0] => 1|l-freeze|23
    [1] => 2|m-freeze|24
    [2] => 3|n-freeze|25
)

Discussion