配列とハッシュの基礎サンプル
最近、Perlのプログラムに触れる機会ができたのだが、配列とかハッシュとか、記号が多すぎて理解が追い付かない。 「$」とか「@」とか「%」とか「\」とか。
なので、以下のページを参考にしながら、軽くサンプルを作ってみた。
#! /usr/bin/perl # 別の変数に「コピー」を渡す my @ary1 = (1,2,3); my @ary2 = @ary1; # 件数を取るときは、一旦スカラー変数に代入 my $ct = @ary1; print $ct."\n"; # 「コピー」なので、元を変えても影響を受けない push @ary1, 4; for my $elem (@ary1) { printf "ary1 = $elem\n"; } for my $elem (@ary2) { printf "ary2 = $elem\n"; } # 別の変数に「参照(リファレンス)」を渡す my $ary3 = [1,2,3]; my $ary4 = $ary3; # 「参照(リファレンス)」なので、元を変えると先も変わる push @$ary3, 4; for my $elem (@{$ary3}) { printf "ary3 = $elem\n"; } for my $elem (@{$ary4}) { printf "ary4 = $elem\n"; } # 別の変数に「コピー」を渡す my %hash1 = (e1 => 1, e2 => 2, e3 => 3); my %hash2 = %hash1; # 「コピー」なので、元を変えても影響を受けない %hash1->{e4} = 4; for my $key (sort keys %hash1) { printf "hash1 = %s = %s\n", $key, %hash1->{$key}; } for my $key (sort keys %hash2) { printf "hash2 = %s = %s\n", $key, %hash2->{$key}; } # 別の変数に「参照(リファレンス)」を渡す my $hash3 = {e1 => 1, e2 => 2, e3 => 3}; my $hash4 = $hash3; # 「参照(リファレンス)」なので、元を変えると先も変わる $hash3->{e4} = 4; for my $key (sort keys %{$hash3}) { printf "hash3 = %s = %s\n", $key, %{$hash3}->{$key}; } for my $key (sort keys %{$hash4}) { printf "hash4 = %s = %s\n", $key, %{$hash4}->{$key}; }
出力は以下のような感じ。
Using a hash as a reference is deprecated at Main.pl line 38. Using a hash as a reference is deprecated at Main.pl line 40. Using a hash as a reference is deprecated at Main.pl line 43. Using a hash as a reference is deprecated at Main.pl line 53. Using a hash as a reference is deprecated at Main.pl line 56. 3 ary1 = 1 ary1 = 2 ary1 = 3 ary1 = 4 ary2 = 1 ary2 = 2 ary2 = 3 ary3 = 1 ary3 = 2 ary3 = 3 ary3 = 4 ary4 = 1 ary4 = 2 ary4 = 3 ary4 = 4 hash1 = e1 = 1 hash1 = e2 = 2 hash1 = e3 = 3 hash1 = e4 = 4 hash2 = e1 = 1 hash2 = e2 = 2 hash2 = e3 = 3 hash3 = e1 = 1 hash3 = e2 = 2 hash3 = e3 = 3 hash3 = e4 = 4 hash4 = e1 = 1 hash4 = e2 = 2 hash4 = e3 = 3 hash4 = e4 = 4
「Using a hash as a reference is deprecated」というのが気になるが、また後で調べよう。
指定するキーワードを全て含むファイルを探すスクリプト
指定するキーワードのいずれかを含むファイルを探す場合には、単純に
egrep -lr "(key1|key2|key3|key4|key5|key6)" data
としてやれば良くて、結果は以下のようになる。
data/1_2_3_4_5.txt data/1_2_3_4_5_6.txt data/1_2_3_5_6.txt
ただ、その逆、「指定するキーワードの全て」を含むファイルを探すとなるとなかなか簡単にはいかない。
一案として、
grep -lr key6 $(grep -lr key5 $(grep -lr key4 $(grep -lr key3 $(grep -lr key2 $(grep -lr key1 data)))))
なんていうのも一つの手だが、キーワードが増えるごとにネストの段数が増えるのが煩わしいし、なんか気持ち悪い。
なので、ちょっとしたシェルスクリプトを書いてみた。 なお、【パスに空白が含まれたりする】ケースは知らないなので、その辺りはご容赦を。
#! /bin/bash #debug=1 dir=$1 if [ ! -d "${dir}" ]; then printf "%s: No such directory.\n" "${dir}" exit 1 fi shift keys=($@) if [ ${#keys[@]} -eq 0 ]; then printf "Usage: %s dir keyword1 [keyword2 ...]\n" "${0}" exit 2 fi if [ ${debug} ]; then echo "Count of keys is ${#keys[@]}" fi for f in $(find "${dir}" -type f) ; do ct=0 for k in ${keys[@]} ; do if [ ${debug} ]; then echo "Checking ${f} for keyword '${k}'..." grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$' fi tmpCt=$(grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$' | wc -l) if [ ${tmpCt} -eq 0 ]; then break fi ct=$((ct+tmpCt)) done if [ ${ct} -eq ${#keys[@]} ]; then if [ ${debug} ]; then echo "OK: ${f}" else echo "${f}" fi else if [ ${debug} ]; then echo "NG: ${f}" fi fi done
これを
$ debug=1 ./grep-all.sh data key1 key2 key3 key4 Count of keys is 4 Checking data/1_2_3_4_5.txt for keyword 'key1'... data/1_2_3_4_5.txt:1 Checking data/1_2_3_4_5.txt for keyword 'key2'... data/1_2_3_4_5.txt:1 Checking data/1_2_3_4_5.txt for keyword 'key3'... data/1_2_3_4_5.txt:1 Checking data/1_2_3_4_5.txt for keyword 'key4'... data/1_2_3_4_5.txt:1 OK: data/1_2_3_4_5.txt Checking data/1_2_3_4_5_6.txt for keyword 'key1'... data/1_2_3_4_5_6.txt:1 Checking data/1_2_3_4_5_6.txt for keyword 'key2'... data/1_2_3_4_5_6.txt:1 Checking data/1_2_3_4_5_6.txt for keyword 'key3'... data/1_2_3_4_5_6.txt:1 Checking data/1_2_3_4_5_6.txt for keyword 'key4'... data/1_2_3_4_5_6.txt:1 OK: data/1_2_3_4_5_6.txt Checking data/1_2_3_5_6.txt for keyword 'key1'... data/1_2_3_5_6.txt:1 Checking data/1_2_3_5_6.txt for keyword 'key2'... data/1_2_3_5_6.txt:1 Checking data/1_2_3_5_6.txt for keyword 'key3'... data/1_2_3_5_6.txt:1 Checking data/1_2_3_5_6.txt for keyword 'key4'... NG: data/1_2_3_5_6.txt Checking data/2_3_4_5_6.txt for keyword 'key1'... NG: data/2_3_4_5_6.txt
としたり、
$ ./grep-all.sh data key1 key2 key3 key4 key5 key6 data/1_2_3_4_5_6.txt
としたり、という感じで。
【2023/06/11改版】 オリジナルのスクリプトは、key1にヒットしなくてもkey2~key6をチェックしてしまい、非常に効率が悪いと気付いたので改善。
オリジナルも残しておく。
#! /bin/bash #debug=1 dir=$1 if [ ! -d "${dir}" ]; then printf "%s: No such directory.\n" "${dir}" exit 1 fi shift keys=($@) if [ ${#keys[@]} -eq 0 ]; then printf "Usage: %s dir keyword1 [keyword2 ...]\n" "${0}" exit 2 fi if [ ${debug} ]; then echo "Count of keys is ${#keys[@]}" fi for f in $(find "${dir}" -type f) ; do ct=0 for k in ${keys[@]} ; do ct=$((ct+$(grep -m 1 -c "${k}" "${f}" /dev/null | grep -v ':0$' | wc -l))) done if [ ${ct} -eq ${#keys[@]} ]; then if [ ${debug} ]; then echo "OK: ${f}" else echo "${f}" fi else if [ ${debug} ]; then echo "NG: ${f}" fi fi done
短縮URLの展開処理(PHP)の改版
古き良き時代は終わりを告げ、Webサイトへの接続はHTTP over SSL/TLSが当たり前になった。
そんなわけで、久々に引っ張り出した短縮URL展開プログラムのPHP版が、このままじゃダメだ!という状況になっていたので改版。
<?php class ShortURL { private static $services = null; private static function initServices() { if (!is_null(self::$services)) { return; } self::$services = array(); self::$services[] = parse_url("http://t.co/"); self::$services[] = parse_url("https://t.co/"); self::$services[] = parse_url("http://bit.ly/"); self::$services[] = parse_url("https://bit.ly/"); self::$services[] = parse_url("http://goo.gl/"); self::$services[] = parse_url("https://goo.gl/"); self::$services[] = parse_url("http://tinyurl.com/"); self::$services[] = parse_url("https://tinyurl.com/"); self::$services[] = parse_url("http://tiny.one/"); self::$services[] = parse_url("https://tiny.one/"); self::$services[] = parse_url("http://htn.to/"); self::$services[] = parse_url("https://htn.to/"); } public static function expand($shortUrl) { self::initServices(); $parsed = parse_url($shortUrl); if (!$parsed) { return $shortUrl; } $isTarget = false; foreach (self::$services as $service) { if ($service['scheme'] === $parsed['scheme'] && $service['host'] === $parsed['host']) { $isTarget = true; break; } } if (!$isTarget) { return $shortUrl; } $defaultPort = $service['scheme'] === 'https' ? 443 : 80; $host = ($service['scheme'] === 'https' ? 'ssl://' : '') . $parsed['host']; $port = isset($parsed['port']) && $parsed['port'] > 0 ? $parsed['port'] : $defaultPort; $sock = fsockopen($host, $port); if (!$sock) { throw new Exception($shortUrl . ': Connection failed.'); } fwrite($sock, "GET {$parsed['path']} HTTP/1.1\r\n"); fwrite($sock, "Host: {$parsed['host']}\r\n"); fwrite($sock, "Accept: */*\r\n"); fwrite($sock, "\r\n"); $returnUrl = null; while (($header = fgets($sock))) { $header = trim($header); if (!$header) { break; } if (preg_match("/^Location: /i", $header)) { $returnUrl = preg_replace("/^Location: /i", "", $header); } } fclose($sock); if ($returnUrl) { return $returnUrl; } else { return $shortUrl; } } }
ポイントは、HTTP over SSL/TLSの際には「デフォルトポート番号が443」ということと、「fsockopenを使うときは "ssl://" を前に付ける必要がある」ということかしら。 後は、Locationヘッダを全て小文字で返してくる輩もいたので、その対応も追加(preg_match/preg_replaceの "i" オプションの指定)。
呼び出しのサンプル。 これに適当にHTMLコードを付け加えてあげると、最大100段まで展開・表示してくれます。
<pre><?php if (isset($_POST['url'])) { include('./ShortURL.php'); $url = $_POST['url']; try { printf("%s\r\n", htmlspecialchars($url)); $indent = "┗"; for ($i = 0; $i < 100 && ($newUrl = ShortURL::expand($url)) !== $url; ++$i) { printf("%s\r\n", $indent . htmlspecialchars($newUrl)); $indent = " " . $indent; $url = $newUrl; } } catch (Exception $e) { echo htmlspecialchars($e->getMessage()); } } ?></pre>
実際に動くものをサンプルコードと合わせて提示するサイトを作りたいと思いながら幾星霜・・・
Net::SMTPSを使い始めたところからの悪夢⇒DLLがロードされない問題の調査方法
苦しんだ3日間の奮闘メモ。
事の始まり
PerlのNet::SMTPSモジュールを使って、GmailのSMTPサーバー経由でメールを送ろうという試みを始めたところから悪夢は始まった。
プログラムのコアな部分は大まかに以下のような感じ。
sub send { my $self = shift; my ($args) = @_; my $from = $args->{from}; my $to = $args->{to}; my $header = $args->{header}; my $body = $args->{body}; my $config = MyConfig->new; $config->load(); my $smtp = $config->getSmtpAddress(); my $port = $config->getSmtpPort(); my $user = $config->getSmtpUser(); my $password = $config->getSmtpPassword(); my $mail = Net::SMTPS->new($smtp, Hello => $smtp, Port => $port, doSSL => 'starttls', Debug => 0 ); # $mail->starttls(); $mail->auth($user, $password); $mail->mail($from); $mail->to($to); $mail->data(); $mail->datasend(join("\r\n", $header, $body)); $mail->dataend(); $mail->quit(); }
とか。
こうすればいいのか、とか。
まぁいろいろとあったが、何とかCentOS 7のVM上でNet::SMTPSを使ってGmailのSMTPサーバー経由でメールを送れるようになった。
問題はここから。
悪夢の始まり
Windows環境に持ってきて、XAMPPに同梱されているPerl.exeで同じプログラムを実行すると、なぜか
Failed to open SMTPS connection: Bad file descriptor at・・
というエラーメッセージが出てメールが送れない。 printデバッグで、
require IO::Socket::SSL;
しているところで失敗しているっぽいことを突き止めたり、
SET PERL_DL_DEBUG=1
とすると、以下のようにデバッグログが出ることを知ったりする。
DynaLoader.pm loaded (. C:/xampp/perl/site/lib C:/xampp/perl/vendor/lib C:/xampp/perl/lib, C:\xampp\c\lib \xampp\c\x86_64-w64-mingw32\lib \xampp\c\lib\gcc\x86_64-w64-mingw32\8.3.0) DynaLoader::bootstrap for Net::SSLeay (auto/Net/SSLeay/SSLeay.xs.dll) DynaLoader::bootstrap for Digest::MD5 (auto/Digest/MD5/MD5.xs.dll) Failed to open SMTPS connection: Bad file descriptor at ・・・
そこで、DLLのロードをしているDynaLoader.pmにprintデバッグして、
MD5.xs.dll
ではなく、やはり SSLeay.xs.dll
のロードに失敗していることを確認。
SSLeayと言えば libeay32.dll
と ssleay32.dll
という頭もあったのだろう。
また、以下のサイトを見てしまったのも悪夢を助長させる要因となった。
(・・・2日後・・・)
ふと、「そもそも依存しているのはlibeay32.dll
と ssleay32.dll
じゃなかったりする?」と疑問を持ったところから急展開を見せる。
「DLL 依存性」などのキーワードで探すと、あるDLLが依存しているDLLの一覧を出力する方法を知る。
最初、コマンドプロンプトで
C:\Users\hhelibex>dumpbin /dependents C:\xampp\perl\vendor\lib\auto\Net\SSLeay\SSLeay.xs.dll 'dumpbin' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。
と言われて、あれ?と思って後者のサイトを見つけ、Visual Studio 2019をインストール&Windows OSの再起動をした。
サイトの記述に従い、Visual Studio 2019を起動し、[ツール]-[コマンドライン]-[開発者用 PowerShell]でPowerShellを起動し、コマンドを叩く。
すると、
********************************************************************** ** Visual Studio 2019 Developer PowerShell v16.11.26 ** Copyright (c) 2021 Microsoft Corporation ********************************************************************** PS C:\Users\hhelibex\source\repos> dumpbin /dependents C:\xampp\perl\vendor\lib\auto\Net\SSLeay\SSLeay.xs.dll Microsoft (R) COFF/PE Dumper Version 14.29.30148.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file C:\xampp\perl\vendor\lib\auto\Net\SSLeay\SSLeay.xs.dll File Type: DLL Image has the following dependencies: msvcrt.dll KERNEL32.dll libcrypto-1_1-x64__.dll libssl-1_1-x64__.dll perl532.dll Summary 1000 .CRT 2000 .bss 1000 .data 1000 .edata 8000 .idata 3000 .pdata D000 .rdata 1000 .reloc 5E000 .text 1000 .tls 4000 .xdata PS C:\Users\hhelibex\source\repos>
libcrypto-1_1-x64__.dll
と libssl-1_1-x64__.dll
!?初めて見たぞ。
とりあえずググる。そして、いろいろとダウンロードして名前を変更して(アンダースコア2つ「__」が入っていないので)、順番に試す。 ダメ。
途方に暮れながら、何気なくPC内を探す。 ・・ なんかある!?
C:\Users\hhelibex> dir C:\xampp\apache\bin ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は 4006-07DE です C:\Users\hhelibex\xampp\apache\bin のディレクトリ 2023/05/23 20:33 <DIR> . 2023/05/23 20:33 <DIR> .. 2023/03/07 22:22 98,816 ab.exe 2023/03/07 22:22 110,592 abs.exe 2023/03/07 22:22 43,008 ApacheMonitor.exe 2023/03/07 22:22 19,456 apr_crypto_openssl-1.dll 2023/03/07 22:22 31,232 apr_dbd_odbc-1.dll 2023/03/07 22:22 14,848 apr_ldap-1.dll 2022/05/30 19:58 215,352 curl-ca-bundle.crt 2019/02/06 15:58 4,110,456 curl.exe 2023/03/07 22:25 9,192 dbmmanage.pl 2023/03/07 22:22 101,888 htcacheclean.exe 2023/03/07 22:22 124,416 htdbm.exe 2023/03/07 22:22 86,016 htdigest.exe 2023/03/07 22:22 118,784 htpasswd.exe 2023/03/07 22:24 30,720 httpd.exe 2023/03/07 22:22 65,536 httxt2dbm.exe 2023/05/23 20:33 <DIR> iconv 2023/03/15 03:30 30,422,016 icudt71.dll 2023/03/15 03:30 3,031,552 icuin71.dll 2023/03/15 03:30 60,928 icuio71.dll 2023/03/15 03:30 2,253,312 icuuc71.dll 2021/09/12 18:59 55,808 jansson.dll 2023/03/07 22:20 215,552 libapr-1.dll 2023/03/07 22:20 36,864 libapriconv-1.dll 2023/03/07 22:20 294,912 libaprutil-1.dll 2023/02/09 21:49 3,445,248 libcrypto-1_1-x64.dll ⇐☆☆☆ 2019/02/06 15:58 1,020,536 libcurl.dll 2023/03/07 22:21 459,776 libhttpd.dll 2023/03/15 03:30 209,920 libsasl.dll 2023/03/15 03:30 380,928 libssh2.dll 2023/02/09 21:50 689,664 libssl-1_1-x64.dll ⇐☆☆☆ 2022/11/02 01:03 1,373,696 libxml2.dll 2023/03/07 22:22 58,368 logresolve.exe 2019/04/05 23:28 184,320 lua52.dll 2023/03/07 22:16 156,160 nghttp2.dll 2023/02/09 21:51 550,912 openssl.exe 2021/08/23 23:32 400,896 pcre.dll 2023/01/12 18:44 564,224 pcre2-8.dll 2012/04/17 02:30 61,440 pv.exe 2023/03/07 22:22 78,848 rotatelogs.exe 2023/03/07 22:22 18,432 wintty.exe 2022/11/02 01:06 89,600 zlib1.dll 40 個のファイル 51,294,224 バイト 3 個のディレクトリ 415,936,548,864 バイトの空き領域 C:\Users\hhelibex>
ファイル名はちょっと違うけど、それっぽい! 早速PATHを通して実行してみる。。。やっぱりダメ。じゃあ、コピーして名前を変更して実行。 ・・・ 動いた!!!
じゃあ、PATHを通さずに、libcrypto-1_1-x64__.dll
と libssl-1_1-x64__.dll
だけコピーして・・ダメ。
まぁ、とりあえず動く方法が見つかって、ようやく一安心したところで、詳細を調べてみた。
先ほどのDLLの依存関係の続き。
PS C:\Users\hhelibex\source\repos> dumpbin /dependents C:\Users\hhelibex\SMTPServer\libcrypto-1_1-x64__.dll Microsoft (R) COFF/PE Dumper Version 14.29.30148.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file C:\Users\hhelibex\SMTPServer\libcrypto-1_1-x64__.dll File Type: DLL Image has the following dependencies: WS2_32.dll ADVAPI32.dll USER32.dll bcrypt.dll KERNEL32.dll VCRUNTIME140.dll api-ms-win-crt-stdio-l1-1-0.dll api-ms-win-crt-convert-l1-1-0.dll api-ms-win-crt-string-l1-1-0.dll api-ms-win-crt-time-l1-1-0.dll api-ms-win-crt-utility-l1-1-0.dll api-ms-win-crt-runtime-l1-1-0.dll api-ms-win-crt-filesystem-l1-1-0.dll api-ms-win-crt-heap-l1-1-0.dll api-ms-win-crt-environment-l1-1-0.dll Summary 1000 .00cfg 8000 .data 3000 .idata 1C000 .pdata CF000 .rdata 8000 .reloc 1000 .rsrc 252000 .text PS C:\Users\hhelibex\source\repos> dumpbin /dependents C:\Users\hhelibex\SMTPServer\libssl-1_1-x64__.dll Microsoft (R) COFF/PE Dumper Version 14.29.30148.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file C:\Users\hhelibex\SMTPServer\libssl-1_1-x64__.dll File Type: DLL Image has the following dependencies: libcrypto-1_1-x64.dll ⇐☆☆☆ KERNEL32.dll VCRUNTIME140.dll api-ms-win-crt-time-l1-1-0.dll api-ms-win-crt-string-l1-1-0.dll api-ms-win-crt-utility-l1-1-0.dll api-ms-win-crt-runtime-l1-1-0.dll api-ms-win-crt-convert-l1-1-0.dll api-ms-win-crt-stdio-l1-1-0.dll Summary 1000 .00cfg 5000 .data 6000 .idata 6000 .pdata 23000 .rdata 2000 .reloc 1000 .rsrc 74000 .text PS C:\Users\hhelibex\source\repos>
・・おまえか! libssl-1_1-x64__.dll
が libcrypto-1_1-x64.dll
に依存している。
だからlibcrypto-1_1-x64__.dll
と libssl-1_1-x64__.dll
だけあってもダメで、同じ内容でファイル名だけ違う libcrypto-1_1-x64.dll
がいないとダメなのか。
案の定、ファイルを3つ用意したら、PATHを通す必要もなく動いた。 あとは、これをWindowsサービス登録する形になるから、その場合にどうなるかだが、それはこれから調査。
反省
「SSLeay」と言えば「libeay32.dllとssleay32.dll」という思い込みから悪夢が始まり、3日間くらい浪費したので、思い込みは良くない。 でも、もう少しエラーメッセージを改善してほしい。依存しているけど見つからないライブラリをエラーメッセージに載せてくれればここまで悩まずに済んだんだけどな。
fopenに存在しないファイル名を指定したときの挙動
PHP 5.4.16からPHP 8.1.7にバージョンアップをしようとしたときのこと。
<input type="file" name="file" ~
に値が指定されていないときに
fopen($_FILES['file']['tmp_name'], 'r');
したとき、例外が発生するようになってしまったので調査。同じPHP 5.x系ということで、php56を使い、php56とphp81での比較とする。
そもそものコードは以下のような感じ。
Main1.php
<?php $filename = $argv[1]; $fp = fopen($filename, "r"); if (!$fp) { print "{$filename}: Cannot open file.\n"; exit(1); } print "{$filename}: OK\n"; // ・・・ fclose($fp);
php56での実行結果。
$ php56 Main1.php Main1.php Main1.php: OK $ php56 Main1.php a.txt PHP Warning: fopen(a.txt): failed to open stream: No such file or directory in /home/hhelibex/blog/2022-0705-01/Main1.php on line 5 a.txt: Cannot open file. $ php56 Main1.php "" PHP Warning: fopen(): Filename cannot be empty in /home/hhelibex/blog/2022-0705-01/Main1.php on line 5 : Cannot open file. $
php81での実行結果。
$ php81 Main1.php Main1.php Main1.php: OK $ php81 Main1.php a.txt PHP Warning: fopen(a.txt): Failed to open stream: No such file or directory in /home/hhelibex/blog/2022-0705-01/Main1.php on line 5 a.txt: Cannot open file. $ php81 Main1.php "" PHP Fatal error: Uncaught ValueError: Path cannot be empty in /home/hhelibex/blog/2022-0705-01/Main1.php:5 Stack trace: #0 /home/hhelibex/blog/2022-0705-01/Main1.php(5): fopen() #1 {main} thrown in /home/hhelibex/blog/2022-0705-01/Main1.php on line 5 $
ファイル名が空でなく存在しないファイルの場合の挙動はWarningのまま変わらないが、ファイル名が空文字列の場合に、PHP 8.1ではValueErrorが発生するようになってしまった。
対症療法をするなら、以下のような感じだろうか。
Main2.php
<?php $filename = $argv[1]; if (empty($filename)) { print "Filename cannot be empty.\n"; exit(1); } $fp = fopen($filename, "r"); if (!$fp) { print "{$filename}: Cannot open file.\n"; exit(1); } print "{$filename}: OK\n"; // ・・・ fclose($fp);
しかし、ファイルが存在しない場合のWarningが気持ち悪い。
これは、C言語でプログラムを書いていた頃の癖でfopen()の実行結果しかチェックしないのが問題なのだろう。
ということで、PHPでの最適解は以下になるのではないか。
Main3.php
<?php $filename = $argv[1]; if (!file_exists($filename) || !($fp = fopen($filename, "r"))) { print "{$filename}: Cannot open file.\n"; exit(1); } print "{$filename}: OK\n"; // ・・・ fclose($fp);
php81での実行結果。
$ php81 Main3.php Main3.php Main3.php: OK $ php81 Main3.php a.txt a.txt: Cannot open file. $ php81 Main3.php "" : Cannot open file. $
厳密には、file_exists()とfopen()の間にファイルが存在しなくなった場合のエラーを考えなければならないが、それはイレギュラーケースとしてログに記録されてもいいのではないだろうか。
型宣言~float~
型宣言のfloat編。どこまで許容されるのか検証。
Main.php
<?php function to_float($val):float { print "=== val=" . var_export($val, true) . " ===" . PHP_EOL; try { return $val; } catch (TypeError $e) { print $e->getMessage() . PHP_EOL; print $e->getTraceAsString() . PHP_EOL; return -1; } } class Hoge { private int $val; public function __construct($val) { $this->val = $val; } } class MyInteger { private int $val; public function __construct($val) { $this->val = $val; } public function __toString():string { return $this->val; } } var_dump(to_float(0)); var_dump(to_float(00)); var_dump(to_float(123)); var_dump(to_float(0123)); var_dump(to_float(0x123)); var_dump(to_float(0.1)); var_dump(to_float(1.23)); var_dump(to_float(false)); var_dump(to_float(true)); var_dump(to_float("0")); var_dump(to_float("00")); var_dump(to_float("123")); var_dump(to_float("0123")); var_dump(to_float("0x123")); var_dump(to_float("0.1")); var_dump(to_float("1.23")); var_dump(to_float("hello")); var_dump(to_float(null)); var_dump(to_float(new Hoge("123456789"))); var_dump(to_float(new MyInteger("123456789")));
実行結果。
$ php81 Main.php === val=0 === float(0) === val=0 === float(0) === val=123 === float(123) === val=83 === float(83) === val=291 === float(291) === val=0.1 === float(0.1) === val=1.23 === float(1.23) === val=false === float(0) === val=true === float(1) === val='0' === float(0) === val='00' === float(0) === val='123' === float(123) === val='0123' === float(123) === val='0x123' === to_float(): Return value must be of type float, string returned #0 /home/hhelibex/blog/2022-0704-01/Main.php(43): to_float() #1 {main} float(-1) === val='0.1' === float(0.1) === val='1.23' === float(1.23) === val='hello' === to_float(): Return value must be of type float, string returned #0 /home/hhelibex/blog/2022-0704-01/Main.php(46): to_float() #1 {main} float(-1) === val=NULL === to_float(): Return value must be of type float, null returned #0 /home/hhelibex/blog/2022-0704-01/Main.php(47): to_float() #1 {main} float(-1) === val=Hoge::__set_state(array( 'val' => 123456789, )) === to_float(): Return value must be of type float, Hoge returned #0 /home/hhelibex/blog/2022-0704-01/Main.php(48): to_float() #1 {main} float(-1) === val=MyInteger::__set_state(array( 'val' => 123456789, )) === to_float(): Return value must be of type float, MyInteger returned #0 /home/hhelibex/blog/2022-0704-01/Main.php(49): to_float() #1 {main} float(-1) $
まぁ、floatへのキャストと挙動は同じですな。取り立てて面白いところがない。
型宣言~bool~
型宣言のbool編。どこまで許容されるのか検証。
Main.php
<?php function to_bool($val):bool { print "=== val=" . var_export($val, true) . " ===" . PHP_EOL; try { return $val; } catch (TypeError $e) { print $e->getMessage() . PHP_EOL; print $e->getTraceAsString() . PHP_EOL; return false; } } class Hoge { private int $val; public function __construct($val) { $this->val = $val; } } class MyInteger { private int $val; public function __construct($val) { $this->val = $val; } public function __toString():string { return $this->val; } } var_dump(to_bool(0)); var_dump(to_bool(00)); var_dump(to_bool(123)); var_dump(to_bool(0123)); var_dump(to_bool(0x123)); var_dump(to_bool(0.1)); var_dump(to_bool(1.23)); var_dump(to_bool(false)); var_dump(to_bool(true)); var_dump(to_bool("0")); var_dump(to_bool("00")); var_dump(to_bool("123")); var_dump(to_bool("0123")); var_dump(to_bool("0x123")); var_dump(to_bool("hello")); var_dump(to_bool("false")); var_dump(to_bool("true")); var_dump(to_bool(null)); var_dump(to_bool(new Hoge("123456789"))); var_dump(to_bool(new MyInteger("123456789")));
実行結果。
$ php81 Main.php === val=0 === bool(false) === val=0 === bool(false) === val=123 === bool(true) === val=83 === bool(true) === val=291 === bool(true) === val=0.1 === bool(true) === val=1.23 === bool(true) === val=false === bool(false) === val=true === bool(true) === val='0' === bool(false) === val='00' === bool(true) === val='123' === bool(true) === val='0123' === bool(true) === val='0x123' === bool(true) === val='hello' === bool(true) === val='false' === bool(true) === val='true' === bool(true) === val=NULL === to_bool(): Return value must be of type bool, null returned #0 /home/hhelibex/blog/2022-0703-01/Main.php(47): to_bool() #1 {main} bool(false) === val=Hoge::__set_state(array( 'val' => 123456789, )) === to_bool(): Return value must be of type bool, Hoge returned #0 /home/hhelibex/blog/2022-0703-01/Main.php(48): to_bool() #1 {main} bool(false) === val=MyInteger::__set_state(array( 'val' => 123456789, )) === to_bool(): Return value must be of type bool, MyInteger returned #0 /home/hhelibex/blog/2022-0703-01/Main.php(49): to_bool() #1 {main} bool(false) $
当然ながら、文字列 "false"、"true" はいずれも true となる。
ここで注意すべきなのは、あくまでも「:bool」であり「:boolean」とは書けないということ。 マニュアルに以下の記述がある。
警告 上記のスカラー型のエイリアスはサポートされていません。 つまり、これらはクラスやインターフェイスの名前として扱われているということです。 たとえば、型の宣言に boolean を使った場合、 値が boolean クラスまたはインターフェイスのインスタンスであることが要求されます。 bool 型ではありません。
もし「:boolean」と書いた場合、実行結果は以下のようになる。
PHP Warning: "boolean" will be interpreted as a class name. Did you mean "bool"? Write "\boolean" to suppress this warning in /home/hhelibex/blog/2022-0703-01/Main.php on line 3 === val=0 === to_bool(): Return value must be of type boolean, int returned #0 /home/hhelibex/blog/2022-0703-01/Main.php(30): to_bool() #1 {main} PHP Fatal error: Uncaught TypeError: to_bool(): Return value must be of type boolean, bool returned in /home/hhelibex/blog/2022-0703-01/Main.php:10 Stack trace: #0 /home/hhelibex/blog/2022-0703-01/Main.php(30): to_bool() #1 {main} thrown in /home/hhelibex/blog/2022-0703-01/Main.php on line 10