PHP Advent Calendar 2021 15日目
前日は@hanhan1978さんの複雑度の上がったLaravelをミドルウェアから眺める方法でした
早速本題というか軽い説明と実装に入ります
【実装】イミュータブル
イミュータブルとは不変である事を意味しますが、今回は再束縛・再定義・再代入全ての意味で不可という意味で使います
簡単に言うとjavascriptのconstです
phpによる実装の前にnodejs
とdeno
におけるconst
の確認から始めます
nodejsのconst
Welcome to Node.js v17.2.0.
Type ".help" for more information.
> const varx =123;
undefined
> const varx =123;
Uncaught SyntaxError: Identifier 'varx' has already been declared
イミュータブルです
denoのconst
Deno 1.16.4
exit using ctrl+d or close()
> const verdeno = 123;
undefined
> const verx = verdeno;
undefined
> const verdeno = 345;
undefined
> verdeno
345
> verx
123
イミュータブルです
ですが最初に書いた通り今回はnodejs的な方でいきます
phpによる再現
1. イミュータブル再現の為のクラスを作る
phpにはconstによる変数定義が無いのでclassを使う事で再現します
<?php
class Vars{
private $vars = [];
public function __set(string $name, $var){
isset($this->vars[$name]) ? throw new Exception("\${$name} Already defined") : $this->vars[$name] = $var;
}
public function __get(string $name) {
return $this->vars[$name];
}
}
2. Varsを使う側
Interactive shell
// $varsの定義
php > require_once('immutable.php');
php > $vars = new Vars();
// 通常の変数定義の代わりに、$varsのプロパティを定義する
php > $vars->input_text='onamax';
php > echo $vars->input_text;
onamax
// 再定義をしようとするとエラー
php > $vars->input_text='uwagaki';
Warning: Uncaught Exception: $input_text Already defined in /code/immutable.php:5
Stack trace:
#0 php shell code(1): Vars->__set('input_text', 'uwagaki')
#1 {main}
thrown in /code/immutable.php on line 5
// 再定義が出来ないだけで、使用は可能
php > echo $vars->input_text;
onamax
// 別のプロパティ名なら当然定義可能
php > $vars->input_phone='09000000000';
php > echo $vars->input_phone;
09000000000
php > $vars->input_phone='09012341234';
Warning: Uncaught Exception: $input_phone Already defined in /code/immutable.php:5
Stack trace:
#0 php shell code(1): Vars->__set('input_phone', '09012341234')
#1 {main}
thrown in /code/immutable.php on line 5
php > echo $vars->input_phone;
09000000000
- ソース中の変数は必ずVarsのプロパティとして持つ
- 一度しかVarsをnewしない
- Varsを保持してる変数($vars)を上書きしてはいけない
と言う制約はあるけどjavascript
のconst
的な感じになりました
###では少し加工をしてから変数を持ちたい時はどうするか?
普通っぽい感じで書くと
php > $_GET['birth'] = 2019; //例なのでこれはワザと
php > $vars = new Vars();
php > $vars->birth = $_GET['birth'];
php > if(20 <= 2021 - $vars->birth){
php { $vars->over20 = true;
php { }else{
php { $vars->over20 = false;
php { }
php > var_dump($vars->over20);
bool(false)
美しく書くと
php > $_GET['birth'] = 2019;
php > $vars = new Vars();
php > $vars->birth = $_GET['birth'];
php > $vars->over20 = (function($birth) {
php ( if(20 <= 2021 - $birth){
php ( return true;
php ( }else{
php ( return false;
php ( }
php ( })($vars->birth);
php > var_dump($vars->over20);
bool(false)
何を言いたいかと言うと$vars->over20
に値入れる部分が醜いよねという話です(主観なので醜いと感じるかは人それぞれです
醜い例の方でif
の外でプロパティ定義しようと思っても無理です
例にしたコードが簡単過ぎて三項演算子で良いじゃんとかmatchで良いじゃんって思うかもしれませんが仕方ありません。短くて分かりやすい例が思い浮かばなかったのです。
javascript
の場合だとこの為のだけにconst
ではなくlet
にする人もいるかもしれません
typescript
では、型が一発で定義出来ない場合にテンポラリー代わりの変数を無駄にスコープに追加しなきゃっていう場合もあるかもしれませんが、美しい書き方をすると美しい
const nu ="100";
const age: string = ((input)=> typeof input == "string" ? input: "cast:"+input.toString())(nu);
【実装】ムーブセマンティクス風
手抜き足抜きで言うとrustっぽい動きに見せるだけです
雑に分かりやすい言葉で言うと変数に入った値は一回しか使えないよっていうルールです
変数に値を入れる → 変数を使う → その変数は消えてなくなるという感じのやつです
なんで風かと言うと、ムーブしないからです
なので飽くまでムーブセマンティクス風な動きに見せるだけのものとなります
1. ムーブセマンティクス風な動きの再現の為のクラスを作る
イミュータブルの例同様に、classを用意しnew Vars()
を使う事で何とかします
<?php
class Vars{
private $vars = [];
public function __set(string $name, $var){
$this->vars[$name] = $var;
}
public function __get(string $name) {
if(isset($this->vars[$name])) {
$_ = $this->vars[$name];
unset($this->vars[$name]);
return $_;
} else {
throw new Exception("Property \${$name} was moved(or undefined)");
}
}
public function copy(string $name) {
if(isset($this->vars[$name])) {
return $this->vars[$name];
} else {
throw new Exception("Property \${$name} was moved(or undefined)");
}
}
}
2. Varsを使う側
php > require_once('move.php');
php > $vars = new Vars();
php > $vars->name='oppai'; //プロパティをセット
php > echo($vars->name); //プロパティを使う
oppai
php > echo($vars->name); //プロパティを使う
Warning: Uncaught Exception: Property $name was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('name')
#1 {main}
thrown in /code/move.php on line 14
php > $vars->name='oppai'; //プロパティを再度セット
php > echo($vars->name); //プロパティを使う
oppai
php > echo($vars->name); //プロパティを使う
Warning: Uncaught Exception: Property $name was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('name')
#1 {main}
thrown in /code/move.php on line 14
- ソース中の変数は必ずVarsのプロパティとして持つ
- 一度しかVarsをnewしない
- Varsを保持してる変数($vars)を上書きしてはいけない
とイミュータブルの例同様の制約があります
関数に渡す場合も普通に使うだけだと一度しか使えない
php > function addAt($_) {
php { echo "$_@";
php { }
php > $vars->lang = 'php';
php > addAt($vars->lang); //一回目は使えるが
php@
php > addAt($vars->lang); //二回目は使えない
Warning: Uncaught Exception: Property $lang was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('lang')
#1 {main}
thrown in /code/move.php on line 14
なので値のコピーを渡して何度も使う
php > function addAt($_) {
php { echo "$_@";
php { }
php > $vars->lang = 'php';
php > addAt($vars->copy('lang')); //一回目使えて
php@
php > addAt($vars->copy('lang')); //二回目も使えて
php@
php > addAt($vars->copy('lang')); //二回目も使える
php@
以上です
どうでしたか?これを用いる事でバグ軽減の可能性を感じませんか?
自己紹介など
- 職業技術者歴十数年のphper、必要に応じてサーバーネットワーク触る、AWSに翻弄される
- 最近まで技術に興味が無く好きでもないためこれまで一切勉強をした事が無かった、勉強を始めたのはここ1~2年
- 現在フリーランス
- 本日バースデイである
- 今回初めてQiitaを書く
- teratailでちらりちらりと回答をしている
- nodejs必須のtypescriptは大嫌いですが、denoで動かすならtypescript好きです