奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

PHPのコーディングにおけるベストプラクティスを考える:in_array関数

PHPのコーディングにおけるベストプラクティスを考える準備記事 - 奇特なブログ

しばらく間隔が空きましたが、第三段です。
これは、以下の記事とかもあるので、結構有名な気もしますが、
でも、ちょいちょい第三引数未指定に起因したバグを見聞きするので、まだまだ存在するのではないかと。

qiita.com

qiita.com

もう、↑に書いてあるので、以上終了・・・いや、他にもあるんですよね。

gallu.hatenadiary.jp

in_arrayだと線形探索になるので、他の関数で行った方が良いのでは?と。

そこで、以下のコードを書いて、検証してみました。

other/in_array.php at master · kitoku-magic/other · GitHub

一つ注意点として、上記のGitHubの速度調査では、
in_array()の「第一引数が配列」のケースが無い(配列を指定できるのを知らなかったし、見たこともない)ので、
今後、修正が入る可能性がありますので、そこはご注意いただければと。

あと、見ての通り色々検証したので結構長くなってますが、要約としては以下の様な感じですね。

1.配列で値の存在チェックをしたい場合、チェックしたい値を事前に出来るだけキーに書いておく方が良い
2.PHPの仕様や関数の戻り値の仕様的に、チェックしたい値がキーに出来ない場合、array_flipでキーの変更をするぐらいなら、in_arrayを使った方が速い
3.ソースが読みにくいと思われる(存在"しない"値を返すので)が、array_diffとarray_diff_keyが結構速い
4.上記ソースのfinallyの所で色々検証しているが、in_arrayに限らず、どの関数も取扱注意な点はある

で、上記1と2に該当するのですが、

(1)PHPにおいて、HTMLのチェックボックスでPOSTされた時の配列の場合、どうするか
(2)DBからSELECTした時の配列の場合、どうするか
(3)CSVをパースした時の配列の場合、どうするか
(4)外部のAPIからデータ取得した時の配列の場合、どうするか

この辺の時にどうするかが、あると思いますね。

まず(1)は、以下は多次元ですが、1次元でも「配列のキーに任意の値を指定出来る」みたいですね。

blog.felt-life.info

普通に知らなかったですが(苦笑)

あと、AjaxでPOSTする場合なら、POSTする配列データを作る際に、

その時の配列のキーに値を設定しておくというのはあると思いますね。

で、この形式でPOST出来れば、配列内の存在チェックでキーでチェックする事が出来るかと。

また(2)ですが、どうやらこっちも、以下のやり方があるみたいですね。

qiita.com

今回の話とは少し逸れますが、「任意のキーで値をグルーピングする方法」も、以下にありましたね。

kinocolog.com

・・・これは、この企画やって良かったですね、普通に知らなかったので(笑)

また(3)は、パース後にループで回せば当然設定は出来ますけど、

パース「直後」に任意の項目の値をキーにねぇ・・・今後、CSV周りも予定があるので、その時にでも考えてみましょう。

(4)も、これは外部APIの仕様次第でしょうから、ということで。


ということで、今回のin_arrayがまさにそうですが、

今回のテーマである「値ではなく、キーを検索する」については、

結構イケそうですね。

2021年上半期の振り返りと2021年下半期の目標

さて、例年ですと年に一回の振り返り記事なんですけど、

「半年前と今で、考えている事が結構違う」と、毎年思ったりしてまして、

で、今年もそんな感じなので、ここで書こうかと思いました(笑)

まずは、以下を基に振り返りから。

kitoku-magic.hatenablog.com


1.本業

順調というか、むしろ好調?という感じですかね。

あまりそうは見えないと思われたかもしれないですが、

基本的に「理想形があって、それと比較してどうか」という話なら、

どうしても不満が出るって、当たり前の事ですが。

なので、「不満を言っている = 微妙な仕事」とは、必ずしもならないかなと。

先までは勿論分かりませんが、少なくとも上半期についてはそんな感触でした。

おかげで、去年のマイナス分も取り返せて良かったですね。


2.PHPのコーディングにおけるベストプラクティスを考える

こちらは、上半期に何度か記事を書きましたけど、

これなんかは、上記の昨年末の振り返り記事に「まったく」出てきてませんね(笑)

で、こういうのが結構起きるんですよね。

そして、現在は「配列周りの関数レビュー」の為の検証を色々していますが、

こちらは、思ったよりも腰を据えて丁寧に考えた方が良いかと思っています。


要は、「コーディングにおける"標準化"」の話だと思ってるんですが、

フレームワークを導入すると、みんな同じ様なコードを書くようになる」とか、

もう都市伝説レベルの話だろうと思いますし(苦笑)

他にも、リーダブルコードが出てしばらく経っているのに、

命名」や「読みやすさ」に関しては、あまり変わっていない様なとか。

ここは来年・再来年とか長期戦になる気がしていますが、

自分の勉強も勿論含めて、ちょっと頑張ってみたい所だなと。


3.旅行と移動手段(鉄道など)

御殿場のアウトレットに行けてない~(3月がチャンスだったか・・・)は置いておいて。

旅行に関しては、去年以前から興味はありましたけど、

その旅行にあたっての「移動手段」に関しては、全然意識してなかった(遠方じゃないのもあるけど)なと。

で、移動手段には「鉄道・バス・車・飛行機・フェリー」などがありますが、

鉄道に関しては、「発着メロディ」とか音楽があるなと(笑)

いい日旅立ち」なんかもそうですね。

・・・飛行機というか空港も、ブライアン・イーノの「Music for Airports」がありますが、まあいいや(苦笑)

で、鉄道については「グリーン車」「寝台列車」「北海道新幹線」「グランクラス」に乗った事がないとか、

「ただの通勤手段」としか考えてなかったなとか、

あと、僕の場合は、地元と鉄道の結び付きが、どうやら強かったとかもありますね。

今年は、この情勢ですし、あまり動きは無いと思いますけど、今後はちょっと意識していこうかなと。

で、これも、昨年末には「まったく」書いてないですね(笑)


4.ファッション

これは、上述の通り、若干停滞気味ですが、

「近場にだって、店はある」とか、

最近は、「洗剤」の方に意識を払っているので、もう少しの辛抱といった所かなと。


5.音楽

ここは、去年でピークアウトで、そこからペースは落ちてますが、

YOASOBIとglobeの総括が控えているかなといった感じですね。

YOASOBIは、まだ早い気もするので、globeの中で「ここ20年ぐらいの電子音楽の流れ」として触れる気もしますけど。


6.ギャンブル

これは、昨年末には一旦中止としたんですけど、

でも、時々やっぱり本を読んだ方が・・・と思うんですよね(苦笑)

というより、これはギャンブルというより「戦略」とか「立ち回り」の話で、

例えば、パチンコで、「自分が出ていなくて、出ている台が空いたら、すぐに移動する」のを、

「ハイエナ(厳密には違うかも)」と言って、印象の悪い行為と言われたりしますが、

でも、勝つ為には、そういう行動も必要だったり。

勿論、出る台がすぐに出るとは限らないので、

ある程度は腰を据えて移動「しない」事も必要だったりと。

こういうのは、立ち回りのセンスというか、他の分野にも使えると思いますのでね。


で、ここまでが振り返りで、

下半期については、上記以外には、以下を考えてはいます。


1.YouTubeとラジオ

まだ、何にもしてないので、下半期に動きがあるかは不明なんですが、

YouTubeへの動画投稿をしていこうかなと。

ただ、ラジオとあるので、

例えば以下の様な形式はあるかなと。

米津玄師 Pale Blue Radio - YouTube


まあ、上記もヒントになってはいたんですけどね。

あとは、これはYouTubeじゃないですけど、インターネットラジオ番組ですね。

Web R@dio Station“くりらじ”


勿論、radicoも、プレミアム会員なので、通常のラジオ番組もありますけど。


なんでラジオなのかについて、「ラジオ的な聞き方」というのが、仕事でも稀に聞くんですけど、

要は、BGMっぽく、ただ流しておいて、特に真剣に聞くわけでもないという聞き方。

だから、動画を観るのとは違って、他の事をしながらでも聞けると。

これなら、「寝る時に、目を瞑っていて」も出来ますし。

どう考えたって「楽」だと思うんですよね。

あと、職種にも依りますけど、

「耳をあまり使わない」というのも、特にリモートワークだと顕著なんではとかも。


あと、去年までブログに書いていた「ダイジェスト記事」が無くなったので、

特に「時事系ネタ」については、ここで喋った方が良いかとも思いましたし。

なんか、無くなったら無くなったで、手持ち無沙汰感がやっぱりあるみたいですね。


あとは、これはラジオじゃなくて動画なんですけど、

一部では「ライブコーディング」と言われている、

「プログラミングをしているプロセスの動画化」と言いますか、

「どうやって、コーディング(設計もあるでしょうけど)をしているんだ?」とかは、

これは動画じゃないとダメだろうなと。

ただ、僕はコーディングが早いタイプでも無い(どっちかというと遅いと思う)ので、

スピードに関しては、あまり参考にはならないとは思いますけど。

あと、そういうのじゃなくても、「そんなに難しくない、技術解説」なんかも、

動画で喋りながら行う方が、もしかしたら分かりやすいか?とも思いますね。


という感じで、結構ネタが豊富っぽいので、ちょっと考えてみようかなと。



下半期は、こんな感じでしょうかね。

今後も、どうぞよろしくお願いします。

洗濯と洗剤について考える:初級

前回:
kitoku-magic.hatenablog.com

ファッション関係記事の第二弾ですが、
今回は、タイトルの件について考えてみたいと思います。
大体、服を買って洗濯をしないというのは、
一部のジーンズ辺りを除けば、あり得ないと思いますので、
当然必須だろうと。

「服が着れなくなったら、買い替えれば良いだろう?」と言ってしまえば終わりという話もあるかとは思いますけど、
まあでも、そこに「金と時間」もかかると思いますし、あまり一般的ではないだろうと。

また、今回の記事の「ターゲット層」は「初級」ですし、
以下に幾つかでも該当している方になります。

1.洗剤は、どれを使っても同じと思っているので、何も考えずに洗剤を購入している
2.洗濯機の説明書を、まともに読んだことが無い
3.服の種別に関わらず、全て分別せずに洗濯機に入れている
4.洗濯表記というものを知らない
5.家庭用洗濯機では洗ってはいけない服があることを知らない

こんな人いるの?・・・去年までの僕がこれですね(苦笑)
しかも、全て該当の役満状態(笑)
まあでも、少なくとも1つ2つぐらいは該当している方もいらっしゃるのではと思いましたけどね、洗濯・洗剤の掲示板なんかを見ると。
あと、服に興味無ければ、当然洗濯もどうでも良い・・・とはなったりならなかったりみたいですが、
それなりには、「相関」はあるんじゃないかと。
なので、かなりの無知っぷりかと思いますので、
内容的には、初級というか超初級レベルぐらいかもしれないですね。
なので、以下の内容をもし試す場合には、自己責任でくれぐれもご注意ください。

逆に、上記に全く該当していない方は、
おそらくここで読むのを止めた方が良いと思います。
それか、「誤りの指摘」や「アドバイス」を頂けるとありがたいですね。

それでは本題ですが、
「洗濯表記→洗濯機→洗剤→まとめ→今後」
という順番に書いていきたいと思います。


■洗濯表記

まず、この用語がなんだ?って感じなわけですよ。
で、調べてみると、消費者庁にもページがあるという。

洗濯表示(平成 28年12月1日以降) | 消費者庁

また、↑の方が細かいですが、
以下のページの方が、分かりやすい感じはありますかね。

洗濯表示

で、上記をまとめると、どうやら以下の8つがあると。

(1)洗濯処理記号
(2)漂白処理記号
(3)乾燥処理記号
(4)自然乾燥記号
(5)アイロン仕上げ処理記号
(6)商業クリーニング処理記号
(7)ウエットクリーニング処理記号
(8)付記用語

で、「付記用語」だけは「言葉」で、
他は「記号」ですね。

で、これらが、「大半?の服」に「記載されている」と。
ただ、少し厄介なのが、
「ポケットの奥」とか、分かりにくい場所に記載がある服もありまして、
ちょっと、探すのに苦戦した服もありましたね。
なので、「根気よく?」探してみると良さそうです。

ただ、本当に記載されて「いない」服もあるようで、
現時点で分かっているのは、
以下の服には、記載がないですね、多分。

・「靴下(記載があるのを、見た事が無い)」
・「運動用のショートパンツ(記載があったり、なかったり)」
・「防寒用のタイツ(記載があったり、なかったり)」

おそらく、まだ記載がない服も、探せばあるとは思いますけど。

で、記号の詳細についてはリンク先に書かれていますが、
とりあえずポイントなのは、
「服によって異なる点が結構多い」って所じゃないでしょうか。

この点だけで、結構びっくりしましたね。

あと、↑に「平成28年(2016年)」と書かれていましたが、
服の「販売(製造?)時期」によって、洗濯表記の内容が変わった様ですので、
自分の持っている服が、いつ買った服かを知るのにも、役立ちますね(笑)

で、洗濯表記については、以下でもまた出てきますが、
一旦ここまでにして、次にいってみましょう。


■洗濯機

といっても、メーカーやら種類やら色々ありますが、
ただ「説明書」が、おそらく確実にありまして、
で、昨今の洗濯機なら「色々なコース」があるだろうと。
例えば、「標準」「ガンコ汚れ」「おしゃれ着」「毛布」とか。

そして、上記の洗濯表記の、特に「洗濯記号」によって、
コースを「変える」必要があると。

で、これは「一例」ですが、
例えば、洗濯記号に「非常に弱い(大事にしたい服なら、「弱い」でも)」「手洗い」と書かれている場合には、
「おしゃれ着コース」が適しているのでは?とか。

あるいは、洗濯記号が「洗濯禁止」だったり、
ドライクリーニングが「石油系溶剤でドライクリーニング可能」となっている場合には、
そもそも、家庭用洗濯機では洗濯が「出来ない(クリーニング屋行き)」とか。

上記は、僕が現在所持している洗濯機に書かれている(少しだけ変えていますけど)内容ですが、
とにかく、「洗濯機の説明書を見るのが大切」ですね。

いや、そもそもなんですけど、
洗濯機に限らず、説明書って「あんまり読まない」というのがあるので、
むしろ、そこに注意ですね(苦笑)

あと、洗濯表記の「付記用語」に「洗濯ネット使用」とか書かれている場合には、
服を洗濯ネットに入れて洗うというのもありますね。
「洗濯ネット」って何?と思ったら、普通に100円ショップにも売ってましたけど(笑)

洗濯機は、とりあえずこんな所でしょうか。
では、一番面倒な「洗剤」にいってみましょう。


■洗剤

コンビニやスーパー程度だと、そんなに種類は無いですが、
ドラッグストアやホームセンターに行くと・・・どれにしたら良いやらって話ですね(苦笑)

ということで、まずは「洗剤の種類」を整理してみましょう。

・種別
(1)液体(現在最も主流で、粉末より使いやすい)
(2)粉末(昔は主流だった。液体より汚れが落ちる?)
(3)ジェルボール(比較的新しい。詰め替えも含めて一番楽?)

・液性
(1)弱酸性(あまり見かけないが、一応ある。ただ、使い道があまり無い?)
(2)中性(まあまあ見かける。弱アルカリ性と比べると、汚れがあまり落ちない?)
(3)弱アルカリ性(最もよく見かける。一番良く使うと思われる)

・機能
(1)部屋干し可能か否か
(2)おしゃれ着用洗剤か否か
(3)漂白剤の有無
(4)柔軟剤の有無
(5)蛍光増白剤の有無
(6)界面活性剤の含有量

・その他の洗剤
(1)しみ抜き洗剤(シャツの襟の汚れを取ったりとか)
(2)漂白剤(塩素系・酵素系・還元系)(基本、白物衣料限定。また、塩素系と還元系は、余程の事がない限り、色々な意味で危険なので使用しない方が良い)

機能と、その他の洗剤は、他にもまだあるのですが、
よく見かけるものだけ書きました。

・・・さて、色々な要素が増えて、だいぶん混乱してきましたね。
実際ここからが面倒なんですよね、「どの服を、どの洗剤で洗えば良いんだ?」と。
そこで、僕の現在の洗濯の実施状況のまとめを、以下で整理してみました。


■まとめ

1.何も考えずにクリーニング屋に持っていく服

上記にあった、そもそも家庭用洗濯機で洗えない服(洗濯記号が、洗濯禁止になっている服)


2.中性のおしゃれ着用洗剤(エマールやアクロン。現在はエマールを使用)を使い、洗濯機のおしゃれ着コースで洗う服

以下のどれかに該当する服。

(1)洗濯表記に関係なく大事にしたい服
(2)洗濯表記の洗濯記号が「非常に弱い」「手洗い」になっている服
(3)ドライクリーニング「可能」となっている服
(4)洗濯表記に「中性洗剤使用」と書かれている服
(5)生地が薄くて破れそうな服

ここは、上記で「石油系溶剤でドライクリーニング可能」となっている場合、そもそも洗濯禁止のはずですが、
ただ「洗濯記号」の方が「禁止じゃない」場合には、
結局、「洗ってしまってます」ね。
なので、ちょっと「ギャンブル入ってます」が、今の所多分ですが問題無さそうに見えますね。
厳密にやるんなら、クリーニング屋行きなのかもしれないですけどね。

あと、靴下は、洗濯表記が「無い」ので、ちょっと難しいんですが、
生地の厚さが「薄くて破れそう」な靴下は、
ここに入れてますね。
あと、洗濯表記の無いタイツとかもそうですね。
表記が「無い」から、よく分からないというのもありますけど。

あと、もっと細かくするなら、
「白物」と「それ以外」で、分けて洗った方が良いと思いますけど、
現状では、そこまではしていないですね。
あ、あとここに該当する服は、表記が無くても洗濯ネットを使って洗ってますね。

ただ、この洗剤はやっぱり「欠点」もあるようで、
汚れは、「あまり落ちない」様に見えますね。
なので、以下の「しみ抜き洗剤」を使ったりもしていますが、
それでも「落ちない」場合が、先日ありましたね。

トップ ナノックス 部分洗い剤 エリそで用 蛍光剤無配合 洗濯洗剤 液体 本体 250g
https://www.amazon.co.jp/dp/B00YLXYHJC

ザウトマン シミ取り用 液体洗剤 PRO 240ml
https://www.amazon.co.jp/dp/B000FQQ42W

なので、ここは「つけ置き洗い」や「酵素系漂白剤使用」ですかね。
まだ試してませんけどね。


3.弱アルカリ性で且つ、漂白剤も蛍光増白剤も含まれていない洗剤を使い、洗濯機の標準コースで洗う服

上記1・2にも、下記4にも当てはまらない服(白物以外の多くの服)

ここが、最も洗う服が多いですが、
漂白剤も蛍光増白剤も含まれて「いない」というのがポイントですね。
どうやら、漂白剤ほどではないでしょうけど、
蛍光増白剤も、色を白くする(というより、白を際立たせるというニュアンスか?)特徴がある様なので。
なので、ここの服は全て、白物「以外」にしています。

具体的な洗剤は、
実験中なので、これからも試しますが、
現状では、以下の2つですね。

【大容量】フレグランスニュービーズ 洗濯洗剤 粉末 フラワーリュクスの香り 1.41kg
https://www.amazon.co.jp/dp/B07BDMV525

ボールド 洗濯洗剤 液体 洗濯水をデトックス グリーンガーデン&ミュゲ 本体 850g
https://www.amazon.co.jp/dp/B08R45CGS1

現在は、ボールドの方を使ってますけど、
でも、フレグランスニュービーズも、特に悪い印象はない感じでしたね。
どちらも、蛍光増白剤も漂白剤も入っていない(はず)です。


4.弱アルカリ性で且つ、蛍光増白剤も漂白剤も含まれている洗剤を使い、洗濯機の標準コースで洗う服

上記1~3に該当しない、白物の服

とにかく白い服で、上記に含まれない服がココですね。
「オフホワイト」の様な服(ズボンにある)が非常に迷うんですけど、
それは、上記3にしています。
でも、どっちが良いんでしょうね。

あと、具体的な洗剤は、現状以下にしていますが、これからも要検証ですね。
でも、ここはあんまり種類無い気もしますけど。

部屋干しトップ 除菌EX 洗濯洗剤 粉末 部屋干し 洗剤 0.9kg
https://www.amazon.co.jp/dp/B06WCZ9RFS

あと、ここの服は時々ですが、
しつこい汚れ用に、以下の様な単独の漂白剤も、
つけ置き洗いをして使ってますね。

オキシクリーン 1500g
https://www.amazon.co.jp/dp/B001Q7FU9M

ワイドハイター クリアヒーロー 粉末タイプ 本体 530g
https://www.amazon.co.jp/dp/B01MR6LG3R

で、僕の使い方もあるかもなんですが、
今の所、ワイドハイターの方が、効果が高い様に思いますね。
オキシの方が、多用途?で評判が良い様にも見えるんですけどね。

原因として、ここは一つ「仮説」があるんですけど、
「用途が特化」している方が、効果が「高い」んじゃないかと。
なので、「オキシ漬け」とかも聞く使い勝手の良いオキシよりは、
ワイドハイターの方が良い?かもと。

洗剤でも、「掃除用」の方は、
その傾向が見えると思うんですよね。
だから、洗濯用もそうじゃないかなと。

なので、今後は、その辺りもチェックしていきたいですね。


■今後

では、初級はここで終わり(思ったより、長くなりましたね)で、
以下には、中級以降の課題を、一部書いておきます。

しばらくは、色々「実験」をするので、
次に書くのは、早くても1年後にはなるとは思いますし、
あと、以下で全てではないでしょうけど。

・海外の服だと、洗濯表記が違う?
・洗濯表記の、細かい意味は?
・洗濯ネット1つに、沢山の服を入れても大丈夫?
・洗わないジーンズは、どうするの?
・弱酸性の洗剤の使い道は?
・おしゃれ用の服の、しつこい汚れはどうする?
・界面活性剤の細かな成分は、どういう意味?
・どういう汚れだと、酸性洗剤の方が良いの?
・全般的に、どの洗剤が最も良いの?
クリーニング屋は、どの店が良いの?
ナチュラルクリーニングって何?

あと、内容的に「化学」知識が必要な箇所が多い印象なんですよね。
でも、僕は化学の成績は「最悪」という最大の問題もありまして(苦笑)
理科「全般」が苦手なのですが、特に化学が酷かった記憶がありますね。

ということで、結構苦労するかもしれませんが、
頑張っていきたいと思います。

何より、お気に入りの服を大切にする為にもね。
というより、これが無かったら書いてなかったですし、
興味を持つ事も無かったと思いますので。

良きファッションライフを!ということで。

PHPのコーディングにおけるベストプラクティスを考える:is_numeric関数

PHPのコーディングにおけるベストプラクティスを考える準備記事 - 奇特なブログ

さて、今回は上記の記事の「第二弾」ですね。
以下の、前回のempty()に続き、結構使用頻度の多い関数だと思います。
そして、emptyと同じぐらいかそれ以上に、取り扱い注意な関数かなとも。

PHPのコーディングにおけるベストプラクティスを考える:empty関数 - 奇特なブログ

では、まずは関数のマニュアルを見てみましょう。

PHP: is_numeric - Manual


指定した変数が数値または 数値形式の文字列であるかどうかを調べます。

で、この「数値形式の文字列」というのが、
どういったものかが、以下に書かれていると。

PHP: 数値形式の文字列 - Manual


PHP 8.0.0 より前のバージョンでは、 先頭に ホワイトスペースがある場合にだけ、 文字列は数値と見なされていました。 数値の後に ホワイトスペースがある場合は、 その文字列は 先頭から始まる 数値形式の文字列とみなされていました。

上記の他の部分も見て、「思ったより複雑だな」と思ったわけですが、
特に、個人的に気になったのは上記の引用箇所ですね。

以下が、今回の検証用のソースで、そこにも書かれているんですが、
「 1」が、上記のルール(だと思われる)に則って、is_numericが「true」になると。

https://github.com/kitoku-magic/other/blob/master/php_coding_best_practice/is_numeric.php

ただ、上記に8.0.0と書いているので、バージョン8からは変わったのかもしれませんが、
それは別にしても、なんという仕様でしょうか(苦笑)
どういう理由で、こうしているんでしょうね?
そこが一番気になるんですが。


この関数の問題点として、上記の値もそうなんですが、
「trueになる値が多過ぎる」というのと「どうして、この値がtrueになる?」というのが多いと。
ちょっと、この辺はemptyに似ていますね。


ただ、emptyも含めて思う事なんですけど、
「一つの関数にしては、多機能過ぎる」と。
ちょっと細分化し過ぎと思いますが、
「0以上の整数をチェックする関数」、
「負の整数をチェックする関数」、
「0以上の浮動小数をチェックする関数」、
「負の浮動小数をチェックする関数」、
みたく分けても良いと思っているんですよね。


以下は、お気に入りの本の抜粋だと思いますが、
特に2の「1つのプログラムには1つのことをうまくやらせる」なんかが、
emptyにもis_numericにも当てはまっていない感がしますね。

UNIXという考え方 The UNIX philosophy · GitHub

あと、emptyの時にも思いましたけど、
単純に、「trueになる値」覚えていられないでしょ?(苦笑)
is_numericは、以下(探せば、まだあると思うけど)ですよ(苦笑)


// is_numeric()に渡した結果、結果がtrueになるのは以下の値
// int型とfloat型の値は全て(INFとNANも)true
// '-1'
// '0'
// '1'
// '+0'
// '+1'
// '-1.1'
// '-1.0'
// '-0.1'
// '0.0'
// '0.1'
// '1.0'
// '1.1'
// '+0.0'
// '+0.1'
// '+1.0'
// '+1.1'
// '00'
// '01'
// '.0'
// '.1'
// '0.'
// '1.'
// '-9223372036854775809'
// '-9223372036854775808'
// '9223372036854775807'
// '9223372036854775808'
// (-1 * PHP_FLOAT_MAX) - 1の文字列
// (-1 * PHP_FLOAT_MAX)の文字列
// PHP_FLOAT_MAXの文字列
// PHP_FLOAT_MAX + 1の文字列
// ' 1'
// ' 0'

あと、前回のemptyの記事を読み直しましたけど、
ソースを「書いている時」と「読んでいる時(特に、他人の)」の話は、
今回のis_numericも同じだなと思いましたね。


で、じゃあどうすれば良いのかについて、
若干、前回のempty()の前言撤回になるんですけど、
「仕様(やりたい事)による」んだと思いますね。
それと、再掲しますが以下の検証コードの結果を踏まえて考えると。

https://github.com/kitoku-magic/other/blob/master/php_coding_best_practice/is_numeric.php

いや、例えば「if (empty($value))」みたいなコードっていうのは、
当たり前ですが「条件分岐」で、
で、その「条件」と、empty()がtrueになる「条件」が、
「完全に一致」していれば、上記のコードがベストじゃないですかって。
ただそこで、「そんなケース有るんですか?」って、僕も思うんですが、
ただ、有った場合には、そうじゃない?と。
あと、「empty()でtrueになる値の中で、0だけfalseにして欲しいけど、他はtrueで良い」みたいな、
完全ではないけど一致している場合なんかも、AND条件を追加して書けば良いでしょうし。
だから、is_numericというか、条件分岐の判定条件というのが、
そういうものなんだろうと思いましたね。
まず、これが一つ。


あと、前回のempty()も改めて実施したんですが、
速度は良好なんですよね、empty()もis_numeric()も(検証コード内に記載)。
「速いっていっても、そのぐらいならねえ」って話で、
パフォーマンス目的で、他に改善の余地が「本当に」無いならアリなんじゃと思いますが。
ほら、パフォーマンスの大会とかあるじゃないですか、ああいうのだったらアリなんじゃと思いますね。
ここまでが、empty()とis_numeric()を使う理由としてはあるかなと。


逆に使えない点は、もう散々書いてきましたが、
別の切り口として、「関数の引数のmixed」を取り上げてみたいかなと。
empty()もis_numeric()も、どちらもmixedでしたし。

どうやら、PHP8からはunion型というmixed型を実現した様なものが登場した様ですが。

PHP: 型宣言 - Manual

ただ、ここでいつも思うのは、

1.データ型として、mixedという型があるわけじゃない。あくまでも、関数の引数として、どんな型でも受け付けるというだけ
2.関数の引数がmixedで宣言されていたとしても、関数に引数を「渡す時点」では、必ずintやstringなどの何らかのデータ型になっているので型は指定できる

と。
まあ上記2は、レアケース?として、
「関数に渡す変数が、条件によって型が変わる」ので、
何の型になっているか分からないというのはあるかと思いますが、
ただ、あまり良い設計ではないと思うんですけどね。
引数としては受け付けたとしても、
最悪関数内で、先に型チェックをしてから処理を行いたいかなと。


というのがあるので、前回のempty()では書き忘れましたが、
検証コードでは、まず「is_int()」などをしてからチェックしています。
単純に、チェックの書き方が型によって違うからもありますけどね。


で、is_numeric()の代わりにどれが良いのかは、
「受け付けるのは整数なのか小数なのか両方なのか」「負の値も受け付けるのか」「00の様な値も受け付けるのか」によるので、
「仕様次第」かなと。
だから、is_numeric()も場合によってはアリでは?と思ったんですけどね。


ただ、型がintとfloatの場合は特に悩まないと思いましたが、
stringはねえ、速度はやっぱり遅かったですが、
仕様次第では「正規表現もアリ」じゃないかと思いますね。
「許容したい値を、厳密に定義可能」なのが、正規表現の良い所じゃないかなと。
他の代替案の、ctype_digitやfilter_varは、それが出来ないので。
速度については、超爆速システムじゃなくて且つ、ループ内で書かなければとは思いましたね。
他も含めて、決定打になる書き方が無かったからというのもあるんですけど。
いやあ、数値チェックは結構難しいじゃないかというのが、今回の最大の収穫ですね。


あと、もっと良い書き方がある等ありましたら、教えて頂けるとありがたいですね。
特に、今回の検証コードだと、コメントアウトしてますが、
bccompやgmp_cmpの小数の比較は気になってますね。
この辺、なんか上手い方法ないかなあと。


あと、最後に以下以外もありますが、
大変参考になりました、ありがとうございます。

PHP:文字列が整数かの検証にはis_numeric, ctype_digit, filter_varどれが適切か? - Qiita

PHPのis_numeric関数は使うべきでないという話 - hnwの日記

楽に検証!値をチェックしてくれるfilter関数

便利なFilter関数を使いこなそう その3 - ZDNet Japan

PHPのコーディングにおけるベストプラクティスを考える:empty関数

PHPのコーディングにおけるベストプラクティスを考える準備記事 - 奇特なブログ


さて、今回は上記の記事の「第一弾」です。
しばらくは、「微妙な関数シリーズ」になると思います。
で、やっぱり最初は、この企画を考えるきっかけになった「empty関数(厳密には関数ではないとの事ですが)」かなと。
これまでも、「散々苦しめられました」しね。


まずは、関数のマニュアルを見てみましょう。

PHP: empty - Manual


つまり、empty() は本質的に !isset($var) || $var == false と同じことを簡潔に記述しているだけです。

この辺が引っかかるんですよね、「 == 」と2個になっている所が。
だから、動きにも納得はいくんですけど、そこがどうなんだ?と。


例2 文字列のオフセットに対する empty()
PHP 5.4 以降では、文字列内でのオフセット指定を渡したときの empty() の挙動が変わりました。

長い(原文は短いですが)ので引用はしませんが、
上記の例2のソースと、5.4からの実行結果を見てみて下さいよ。
分からんでもないですが、この法則覚えていられますか?っていう(苦笑)

基本的に、「trueになる値が多過ぎて、覚えていられない」というのが、
問題としてあると思っているんですよね。
だから、issetなんかは、まあアレも例外的な値としてnull値がありますけど、
それだけではあるので、まだマシかと。


あと、業務であるあると思われるのが、
ソースを「書いている時」と「読んでいる時(特に、他人の)」に、
それぞれあるかなと思いまして。

書いている時なら、
上記の「trueになる値」が多く、
ケアレスミスをちょくちょくしてしまったり、
あるいは、テストミスで「想定していない値が、分岐を通ってしまった」とか。
後者の方がヤバいでしょうけど。
「 ==(イコール2個) 」でも、似た様な話はちょいちょいありますが、その辺も似てますね。
「なんとなく、値が入ってなくて空なら処理してほしい」って時に、empty()を使う気もしますが、
その「なんとなく」っていうのが、危ないんじゃないですかねっていう。

逆に、読んでいる時なら、
その変数が型指定されていれば、若干マシですが、
「どんな型の、どんな値が入っているか」が分かりにくいと思いますね。
「empty($value)」って見た時に、その辺が分かんないじゃないですか。
だから、部分抜粋ですけど、以下の様なコードなら、
「あ、int型だな」って分かるから、少し読みやすくなるんじゃないかと。
でも、こういうコード見たことないですし、
あと、それでもわざわざemptyを使う必然性は、感じない様にも思いますし。
大体、関数定義から近いから分かるだけって気もしますし。

function test(int $value)
{
  if (empty($value))
  {
  }
}


この関数に限らず基本的な話として思うのは、
「出来るだけ、読みやすく(リーダブルに)、簡単にシンプルに済ませた方が良い」と。
でも、emptyは、「書くのが楽」っていうのが使われる原因じゃないかと思っていますが、
その場は楽でも、その後の動作確認やら、他人がそのソースを読んだ時の読みやすさやら考えるとっていう。
後者は、「コメントを書く」なんかにも、言えると思うんですけどね。


じゃあ、どうすれば良いのかということで、
以下に改善案(検証用も含んでいます)のソースを書いてみました。
一部(文字列だけは、少々面倒か?)除くと、別に面倒でもないと思うんですけどね。


あと、GitHub
https://github.com/kitoku-magic/other/blob/master/php_coding_best_practice/empty.php
にも上がってますが、同じソースです。

<?php

// PHP7.4.15にて検証

class foo
{
}

try
{
  // empty()に渡した結果、結果がtrueになるのは以下の値
  // false
  // 0
  // +0
  // 00
  // 0.0
  // +0.0
  // 00.0
  // array()
  // null
  // '0'
  // ''
  // 未定義の変数

  // 検証用の値を全て含んだ配列
  $empty_values = [
    // bool
    true,
    false,

    // int
    PHP_INT_MIN - 1,
    PHP_INT_MIN,
    -1,
    0,
    1,
    +0,
    +1,
    PHP_INT_MAX,
    PHP_INT_MAX + 1,
    00,
    01,

    // float
    // PHP_FLOAT_MINは、正の値の中での最小値なので0になる
    PHP_FLOAT_MIN - 0.1,
    PHP_FLOAT_MIN,
    (-1 * PHP_FLOAT_MAX) - 0.1,
    (-1 * PHP_FLOAT_MAX),
    -1.1,
    -1.0,
    -0.1,
    0.0,
    0.1,
    1.0,
    1.1,
    +0.0,
    +0.1,
    +1.0,
    +1.1,
    PHP_FLOAT_MAX,
    PHP_FLOAT_MAX + 0.1,
    00.0,
    01.0,
    INF,
    NAN,

    // array
    [],
    array(),
    array(null),

    // object
    new foo(),

    // null
    null,
    NULL,

    // string
    'true',
    'false',
    '-1',
    '0',
    '1',
    '0',
    '1',
    '+0',
    '+1',
    '-1.1',
    '-1.0',
    '-0.1',
    '0.0',
    '0.1',
    '1.0',
    '1.1',
    '+0.0',
    '+0.1',
    '+1.0',
    '+1.1',
    '[]',
    'array()',
    'array(null)',
    'new foo()',
    "fopen('/tmp/test', 'x')",
    'null',
    'NULL',
    '',
    '00',
    '01',
    '.0',
    '.1',
    '0.',
    '1.',
    // PHP_INT_MIN - 1
    '-9223372036854775809',
    // PHP_INT_MIN
    '-9223372036854775808',
    // PHP_INT_MAX
    '9223372036854775807',
    // PHP_INT_MAX + 1
    '9223372036854775808',
    // (-1 * PHP_FLOAT_MAX) - 1
    '-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858369',
    // (-1 * PHP_FLOAT_MAX)
    '-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368',
    // PHP_FLOAT_MAX
    '179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368',
    // PHP_FLOAT_MAX + 1
    '179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858369',
    '1 ',
    ' 1',
    ' 1 ',
    '1 ',
    "1\t",
    '0 ',
    ' 0',
    ' 0 ',
    '0 ',
    "0\t",
  ];

  echo "--------------------改善前--------------------\n";

  // empty関数での判定結果を出力
  foreach ($empty_values as $empty_value)
  {
    echo var_export($empty_value, true) . ' is ' . var_export(empty($empty_value), true) . "\n";
  }

  // 以下2つは、var_exportで出力出来ないので別枠
  echo 'リソース型 is ' . var_export(empty(fopen('/tmp/test', 'x')), true) . "\n";
  unlink('/tmp/test');

  echo '存在しない変数 is ' . var_export(empty($not_exist), true) . "\n";

  echo "--------------------改善後--------------------\n";

  // 以下からが改善案(結果は、上記と全く同じ)
  foreach ($empty_values as $empty_value)
  {
    // bool
    if (is_bool($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === false, true) . "\n";
    }
    // int
    else if (is_int($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === 0, true) . "\n";
    }
    // float
    else if (is_float($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === 0.0, true) . "\n";
    }
    // array
    else if (is_array($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export(count($empty_value) === 0, true) . "\n";
    }
    // object
    else if (is_object($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === null, true) . "\n";
    }
    // null
    else if (is_null($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === null, true) . "\n";
    }
    // string
    else if (is_string($empty_value) === true)
    {
      echo var_export($empty_value, true) . ' is ' . var_export($empty_value === '' || $empty_value === '0', true) . "\n";
    }
  }

  // fopenの失敗時の戻り値がfalseの為(なので、リソース型を扱う関数によって違う)
  echo 'リソース型 is ' . var_export(fopen('/tmp/test', 'x') === false, true) . "\n";

  echo '存在しない変数 is ' . var_export(isset($not_exist) === false, true) . "\n";

  echo "--------------------「is_null」の速度調査--------------------\n";

  // おまけ:「is_null」と「=== null」での比較調査(一千万件で以下なので、正直誤差レベルかと)
  ini_set('memory_limit', -1);
  $null_values = array_fill(0, 10000000, null);
  $start = microtime(true);
  foreach ($null_values as $null_value)
  {
    if (is_null($null_value) === true)
    {
    }
  }
  // time: 0.24362707138062(上記の、「 === true」を省略すると、time: 0.17196798324585)
  echo 'time: ' . (microtime(true) - $start) . "\n";

  echo "--------------------「 === null」の速度調査--------------------\n";

  $start = microtime(true);
  foreach ($null_values as $null_value)
  {
    if ($null_value === null)
    {
    }
  }
  // time: 0.19953608512878
  echo 'time: ' . (microtime(true) - $start) . "\n";

  while (count($empty_values) <= 10000000)
  {
    $empty_values = array_merge($empty_values, $empty_values);
  }

  echo "--------------------「empty」の速度調査--------------------\n";

  // empty関数での判定時間
  $start = microtime(true);
  foreach ($empty_values as $empty_value)
  {
    if (empty($empty_value) === true)
    {
    }
  }
  // time: 0.382817029953(速度は優位の模様)
  // 百万件だと、0.048418045043945
  echo 'time: ' . (microtime(true) - $start) . "\n";

  echo "--------------------「改善案」の速度調査--------------------\n";

  $start = microtime(true);
  foreach ($empty_values as $empty_value)
  {
    // bool
    if (is_bool($empty_value) === true)
    {
      if ($empty_value === false)
      {
      }
    }
    // int
    else if (is_int($empty_value) === true)
    {
      if ($empty_value === 0)
      {
      }
    }
    // float
    else if (is_float($empty_value) === true)
    {
      if ($empty_value === 0.0)
      {
      }
    }
    // array
    else if (is_array($empty_value) === true)
    {
      if (count($empty_value) === 0)
      {
      }
    }
    // object
    else if (is_object($empty_value) === true)
    {
      if ($empty_value === null)
      {
      }
    }
    // null
    else if (is_null($empty_value) === true)
    {
      if ($empty_value === null)
      {
      }
    }
    // string
    else if (is_string($empty_value) === true)
    {
      if ($empty_value === '' || $empty_value === '0')
      {
      }
    }
  }
  // time: 1.4359290599823(型チェックもしているから当然か)
  // 百万件だと、0.18049216270447
  echo 'time: ' . (microtime(true) - $start) . "\n";

  echo "--------------------「改善案(1回のif文)」の速度調査--------------------\n";

  $start = microtime(true);
  foreach ($empty_values as $empty_value)
  {
    if (
      ($empty_value === false) ||
      ($empty_value === 0) ||
      ($empty_value === 0.0) ||
      (is_array($empty_value) === true && count($empty_value) === 0) ||
      ($empty_value === null) ||
      ($empty_value === '') ||
      ($empty_value === '0')
    )
    {
    }
  }
  // time: 2.0437140464783(判定回数が多くなるので当然か)
  // 百万件だと、0.24031591415405
  echo 'time: ' . (microtime(true) - $start) . "\n";
}
finally
{
  unlink('/tmp/test');
}

// 参考:上記の全ての結果
/*
true is false
false is true
-9.223372036854776E+18 is false
-9223372036854775807-1 is false
-1 is false
0 is true
1 is false
0 is true
1 is false
9223372036854775807 is false
9.223372036854776E+18 is false
0 is true
1 is false
-0.1 is false
2.2250738585072014E-308 is false
-1.7976931348623157E+308 is false
-1.7976931348623157E+308 is false
-1.1 is false
-1.0 is false
-0.1 is false
0.0 is true
0.1 is false
1.0 is false
1.1 is false
0.0 is true
0.1 is false
1.0 is false
1.1 is false
1.7976931348623157E+308 is false
1.7976931348623157E+308 is false
0.0 is true
1.0 is false
INF is false
NAN is false
array (
) is true
array (
) is true
array (
  0 => NULL,
) is false
foo::__set_state(array(
)) is false
NULL is true
NULL is true
'true' is false
'false' is false
'-1' is false
'0' is true
'1' is false
'0' is false
'1' is false
'+0' is false
'+1' is false
'-1.1' is false
'-1.0' is false
'-0.1' is false
'0.0' is false
'0.1' is false
'1.0' is false
'1.1' is false
'+0.0' is false
'+0.1' is false
'+1.0' is false
'+1.1' is false
'[]' is false
'array()' is false
'array(null)' is false
'new foo()' is false
'fopen(\'/tmp/test\', \'x\')' is false
'null' is false
'NULL' is false
'' is true
'00' is false
'01' is false
'.0' is false
'.1' is false
'0.' is false
'1.' is false
'-9223372036854775809' is false
'-9223372036854775808' is false
'9223372036854775807' is false
'9223372036854775808' is false
'-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858369' is false
'-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368' is false
'179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368' is false
'179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858369' is false
'1 ' is false
' 1' is false
' 1 ' is false
'1 ' is false
'1	' is false
'0 ' is false
' 0' is false
' 0 ' is false
'0 ' is false
'0	' is false
リソース型 is false
存在しない変数 is true
*/


ちょっとビックリしたのが、
「empty(0.0)」はtrueで、
「empty('0.0')」はfalseなんですね(苦笑)
上記もマニュアルの方の内容も含めて、覚えていられます?(苦笑)
こういうのは、ガ〜っとコードを書いている時に、
混乱の元にしかならないと思うんですけどね。


で、改善案について、書くのって別に大変じゃないと思うんですけど、どうなんですかね?
さっきも書いたように、文字列だけは若干面倒と思いますけど、
ユーティリティ的に、別に関数を作って(str_empty()とか?)、それを呼べば良いんじゃないかとも思いますし。


「サッと書けて、楽だから(推測ですが)」って理由で、使われているのかもしれないですけど、
上記に書いた様な問題があると思いますので、今回取り上げました。


あと、もっと良い書き方がある等ありましたら、教えて頂けるとありがたいですね。