がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

お仕事コードの書き方(の一部)

おいちゃん、経験的に、割合とPHPが長くなってきております。
んで、PHPでコード書くとき、割と色々と「丁寧に」書く癖があって。
特に昔は割と嫌がられたりしてたもんだけど、おかげでバージョンアップしても「平気の平左で」受け入れられるんだよなぁ、とかふと思い出しまして。
……なんて事を考えるとお仕事コードの書き方で「おいちゃんが気にしている事」があって、その辺を書いておくとなんか役に立つ事もあるのかなぁ? と思い、雑文を散らかしてみようか、と。

端的には
・キワを攻めない
に尽きるのですが……かみ砕いて。

例えばif文の評価式は最終的にbooleanが欲しいものでございます。が、関数やらメソッドやらの戻り値が、空文字だったり数値0だったりNULLだったりする事もございます。
とはいえその辺は暗黙の変換があるので

if (null !== 関数())

とか書かなくても

if (! 関数())

と書けば「nullがreturnされてきたからbooleanだとfalseだからそれの否定だから」で簡単に処理を書く事ができます。

複数の条件式があっても、括弧で囲わなくても演算子の優先順位があるから、優先順位をちゃんと理解していれば括弧なんて不要です。

型とか気にしなくても、例えば「文字が欲しい」ところに数値0書いたってよしなに解釈してくれます。

その他諸々。
言語仕様を「熟知していれば」こその、「一見不思議なんだけど、言語仕様を熟知していれば理解できる」書き方ってのは、色々とございます。

おいちゃん、上述を一通り「大変にお好まない」んですねぇ。
関数の戻り値云々で、多分これはそこそこ引っかかっている人もいたんだろうなぁと思われるstrpos()。
正直、あの問題おいちゃんは「1度も引っかかった事がない」んですが、マニュアルにも書かれているくらいなんで「結構引っかかった人もいるんだろうなぁ」とか、しみじみ。

っつかif文とかwhile文とかの条件式を書く所はちゃんと「条件式」で。
かつ、==ではなくて===で「型を意識して厳密に」。
かつ昨今なら「declare(strict_types=1);」付けて。
この辺を死守しておくと、少々ナニカがあってもあんまり困りません。

括弧はちゃんと付ける。
「これくらいは演算子の優先順位があるから」と思っても、異なる演算子があるんなら「このくくりと」「このくくりと」がわかるように、しっかりと丁寧に書く。
省略しない。

……若干悩むのが「インクリメントとかの前置と後置」ですが。
まぁPHPの界隈だと「そのテクニック使うな」って言われる事が多いですかねぇ。なので使わないようにしてます。

「ンなこと言ったってその辺の挙動、そうそう変わらないよ」ってよく言われるんですが。
【PHP8.0】非厳密な比較演算子`==`の挙動が今さら変更になる https://qiita.com/rana_kualu/items/82cc8295d2102d14b88a
【PHP8.0】演算子.と+の優先順位が変わる https://qiita.com/rana_kualu/items/db7ae541016bd0b02122
諸行無常。一切のものは流転し変化し、変わるものでございます。

まぁ関数やメソッドの戻り値は「falseかしら? NULLかしら?」って調べるのが若干面倒かもしれませんが。
括弧付けるくらい、そんなに手間ですかしらん?
って思ってるです正直。

こーゆー思想で作ってるので。
(最近、Slimにどっぷりハマって放置してますが)自前のフレームワーク、PHP5からPHP7に対応させるとき、ほぼ修正いらずでした(一桁行くらいだったかし、修正したの)。
逆にこの辺が雑なコードをPHP5からPHP7に移植させるとき、驚くほど手間がかかりました。

いやまぁ「いつどこにコストをかけるのか?」って話なんだろうとは思うのですが。
どうせなら「その言語にちょっとくらい不慣れな人」が読んでもすんなり読めるような、丁寧なコード書いてたほうが色々とメリットも多いんじゃ無かろうか? と思うんですが、どうなんですかねぇ?

駄目なUUIDv4実装

「実装によってはUUID4が重複しうる」って話を聞いて「じゃぁ駄目な実装を書いてみよう」と思いました(笑
珍しく「徹頭徹尾、非実用」です(笑

<?php
mt_srand(time());
printf("%08x-%04x-4%03x-%04x-%012x\n"
    , mt_rand(0, 0xffffffff)
    , mt_rand(0, 0xffff)
    , mt_rand(0, 0xfff)
    , mt_rand(0, 0x3fff) + 0x8000
    , mt_rand(0, 0xffffffffffff)
    );

これだと、同じ秒で(別マシンだろうがなんだろうが)同タイミングで作成すると、ちゃんと(?)重複します(笑
いやまぁ「普通に考えて、最低限 random_bytes() だろ」とか思うのですが、そこはほら、実験という名のお遊びなのでwww

なお

mt_srand(time());

がキモになりますので、絶対に省略しないでください。省略されると、ちゃんと(?)重複しません(笑

うんなんか書いたら満足したwww

ちょっとした小技……の背景

典型的には「(第一種)ホワイトリスト*1」の実装でありがちなのですが。

declare(strict_types=1);

$white_list = [
    'hoge',
    'foo',
    'bar',
    'baz',
    'qux',
];
//
$input = 'bar';
//
if (true === in_array($input, $white_list, true)) {
    echo "{$input} is in \n";
} else {
    echo "{$input} is NOT in \n";
}

こんな風に書く事が多いと思うんですよね、多分。

ただ、おいちゃんはこんな風に書きます。

declare(strict_types=1);

$white_list = [
    'hoge' => 1,
    'foo' => 1,
    'bar' => 1,
    'baz' => 1,
    'qux' => 1,
];
//
$input = 'bar';
//
if (true === isset($white_list[$input])) {
    echo "{$input} is in \n";
} else {
    echo "{$input} is NOT in \n";
}

valueは別に1でも''(空文字)でもtrueでもなんでもよいのですが(nullだけは嫌)。
あと、issetじゃなくてarray_key_existsでも、と思うのですがなんでだかarray_key_existsの綴りを指が覚えてくれないので、「値がnullじゃない事が確定している」状態だとisset使う事が多いです。

先に、テストコード。

declare(strict_types=1);

$s = 'a';
$awk = [];
for($i = 0; $i < 100000; ++$i) {
    $awk[] = $s;
    $s ++;
}
$t = microtime(true);
$hash_awk = array_flip($awk);
$t_end = microtime(true);
echo 'array_flip is ' , $t_end - $t , "\n";

//
$needle = $awk[ count($awk) - 1 ];

//
$t = microtime(true);
in_array($needle, $awk, true);
$t_end = microtime(true);
echo "in_\tis " , $t_end - $t , "\n";

//
$t = microtime(true);
isset($hash_awk[$needle]);
$t_end = microtime(true);
printf("isset\tis %.28f\n", $t_end - $t);

//
$t = microtime(true);
array_key_exists($needle, $hash_awk);
$t_end = microtime(true);
printf("array_\tis %.28f\n", $t_end - $t);

何回かお試し。

[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0030989646911621
in_ is 0.00078010559082031
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0043699741363525
in_ is 0.00089287757873535
isset is 0.0000011920928955078125000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0034210681915283
in_ is 0.00078892707824707
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0035109519958496
in_ is 0.00085806846618652
isset is 0.0000009536743164062500000000
array_ is 0.0000000000000000000000000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0038630962371826
in_ is 0.0010230541229248
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0031359195709229
in_ is 0.00078296661376953
isset is 0.0000009536743164062500000000
array_ is 0.0000011920928955078125000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0031070709228516
in_ is 0.00080204010009766
isset is 0.0000009536743164062500000000
array_ is 0.0000000000000000000000000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0037379264831543
in_ is 0.0010411739349365
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000

issetとarray_key_existsは、なんかもはや「誤差レベル」のような雰囲気が(笑
in_arrayとは露骨に数値が違う、ってのだけ覚えておいていただければ。

ここで書きたいのは「検索するんなら、in_arrayよりisset(かarray_key_exists)のほうがよりよいのではないかしらん?」というお話でふ。

端的には「O(n)かO(1)か」の違いなのですが……厳密には「O(1)かどうかはkeyのコリジョンの具合にもよるんだけど、大分と1に近い」くらい。
in_arrayは線形探索だと思われるので、基本的に「配列を頭から舐めていって見つかったらそこで終了」となります。だからまぁ、テストでは最悪の形である「一番最後の要素」にしたのですが。
issetとarray_key_existsは「hash検索」なので。 https://gallu.hatenadiary.jp/entries/2006/09/30 とか https://gallu.hatenadiary.jp/entry/20060930/p2 とか見ていただくと(多少)細かく書いてありますが、元々が「速やかに検索するための方法」という側面でのhashでもあるので、要素がn個あっても、比較的高速に検索をする事が出来るようになるです。

これが「どっかから配列を取ってきて検索」の場合、「array_flipのコスト」と「検索のコスト」とのせめぎ合いになるので微妙な所ではあるのですが。
(第一種)ホワイトリストとかの場合は大体「コードのどこか(定数とか)」にあらかじめ配列を書いている、と思われるので。
そーゆー時は、普通の配列をin_arrayするよりもhash配列をissetなりarray_key_existsなりするほうが、速度的にも早いし、「検索」という理屈からもより「適切」なんじゃないかなぁ、とか思うわけなんですね。

なんていう小技を、なにげにちょいちょいと説明するケースがあったので、どこかで文章に残せたらなぁ、と思ったので残しておきます。

*1:許可されたモノが列挙されたリスト

「文字コードを変換しつつCSVファイルを読み込む」時の書き方

なんか、毎回
・「書ける」のは覚えてる
・書式を忘れる
のループに陥ってるので、備忘録(苦笑

// CSVの読み込み
$file_name = ファイル名;
$file_name = 'php://filter/read=convert.iconv.SJIS-win%2FUTF-8/resource=' . $file_name;
$file_obj = new SplFileObject($file_name);
$file_obj->setFlags(SplFileObject::DROP_NEW_LINE); // 最後の改行は消す
//
while(false === $file_obj->eof()) {
    $row = $file_obj->fgetcsv();
    // 以下、処理
}

大雑把に、こんな書き方をすると
SJISCSVUTF-8に変換しながら読み込み
が出来るので、CSV処理の時には重宝するんじゃないかなぁ、と思われます。

CSVを「作る(書き出す)」時は
・一気にCSV文字列を作る
・mb_convert_encoding()
なのでまぁ、思案するほどでもないかなぁ、と。

PHP7.4系のコンパイル

いやまぁ

sh ./configure -h

でチェックしろ、って話しではあるのですが。

PHP7.3系とPHP7.4系で、configure のオプションがちょいと変わっております。……ちょろっとべっくらこいたよおいちゃん。
おいちゃん、PHPは自力コンパイルする事が多くて、もうちょっと正確には
・周辺のライブラリは、差し障りがないかぎり yum*1で入れる:勿論、 -devel でね(はぁと)
PHP自体は、php.netのダウンロードのページからwgetしてきてコンパイル
って方法をとる事が多いです。

んで。まぁ

 --enable-pcntl \
 --enable-sysvsem \
 --enable-sysvshm \
 --enable-sysvmsg \

とかいう「普段書くコードの趣味丸わかり」なオプションはともかくとして。ちょいと引っかかった2つがあって、微妙に手間取ったので、備忘録兼ねて。

いち。
PHP7.4を入れる時に、どうも「oniguruma」「sqlite」が必須のようでございまっする。
なので、とりあえず

sudo yum install oniguruma-devel sqlite-devel 

で、コンパイル前にinstallしておきましょう。 -y オプションは、なんとなし、割と使わない事が多いです。いやまぁどうせYesと答えはするのですが、えぇ。

に。
ここがハマった。ちと案件でGDを使ってるのがあったのですが、PHP7.3までの

 --with-gd \
 --with-jpeg-dir=/usr/lib \
 --with-zlib-dir=/usr/lib \

が、通らない orz

調べたところ、上述は

 --enable-gd \
 --with-jpeg \
 --with-zlib \

に変わっている模様。
あと、地味にハマったのが
・一端「 --enable-gd」だけでコンパイルしたらjpegが有効になってない
・ので「--with-jpeg」を加えたらライブラリが足らん

sudo yum install libjpeg-devel

・で、「--with-jpeg」付けて再度コンパイルしたんだけど有効にならん orz

答えは簡単で「まず先に make clean してから」。
おそらく「一端gdのエクステンション自体はコンパイルされちゃった」から、そいつ消さないと有効にならない orz
コンパイル2回分くらいハマりました orz

なので、configureするまえに「make clean」するとよかとかと思われます。

なんか、英語でも「jpegが有効にならねぇ」的なのがちらほらと散見されたので、ナレッジ残しておくとよいかなぁ、とか思いつつ、めも。

*1:もうdnfのほうがよいんだろうなぁ……

Slim4-Skeleton解析のためのSlim4解析のためのPHP-DIの解析

タイトルが長い(笑

いやまぁ色々と調べていてそれはそれで後で記事にするのですが。
Slim4で使われているContainerについてちょいと調べたので、備忘録かてがて。

Slim3では "pimple/pimple" が使われていたのですが、Slim4では "php-di/php-di" が使われています。
……いや「plainなinstallすると色々とアレがナニ」な感じで色々と色々なのですが、その辺はまた別に記載いたします*1

似たようなもんか……と思いきや色々と差異があったりするので、その辺を調べてみました。

一端、ざっくりとSlim4をインストールしてある、とか思ってくださいませ。
ちと別件もあるので少し余計なものが入ってますが、composer.json

{
    "require": {
        "slim/slim": "^4.4",
        "twig/twig": "^3.0",
        "php-di/php-di": "^6.0",
        "slim/psr7": "^1.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
}

大体こんな感じ。

で、まずは準備。

<?php
declare(strict_types=1);

// 基準になるディレクトリ(最後の / はない形式で)
define('BASEPATH', realpath(__DIR__ . '/..'));

// オートローダ
require(BASEPATH . '/vendor/autoload.php');

/*
 *
 */
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;

// Builderインスタンスの生成
$containerBuilder = new ContainerBuilder();
$container = $containerBuilder->build();
var_dump( get_class($container) );

どうもお作法として
・まずcontainerBuilderを作る
・containerBuilderからcontainerを作る
って感じらしいので、上述のように。

string(12) "DI\Container"

ってな感じで、OK。

以前の、Slim3(というかpimple)のようなやり方を一応試してみる。

//
$container['test'] = function ($c) {
    return \stdClass();
};
var_dump($container->get('test'));
Fatal error: Uncaught Error: Cannot use object of type DI\Container as array in 

一発でアウト。
ほむ。配列系のやつ*2、持ってないんだなぁ。

で、色々と調査。
どうも
・設定は「ContainerBuilderに対して行う」
・メソッド「addDefinitions()」を使う
ようなので、ちょいとコードを書き換え。

大まかには

<?php
declare(strict_types=1);

// 基準になるディレクトリ(最後の / はない形式で)
define('BASEPATH', realpath(__DIR__ . '/..'));

// オートローダ
require(BASEPATH . '/vendor/autoload.php');

/*
 *
 */
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;

// Builderインスタンスの生成
$containerBuilder = new ContainerBuilder();

// Builderに対して設定
XXXXXXXXXXXXXXXXX

// Containerインスタンスの生成
$container = $containerBuilder->build();

// 以下、getつかって色々
XXXXXXXXXXXXXXXXX

って感じになるぽ。
最後に全ソース書くので、一端、局所で説明していきます。

まず、Slimだとお馴染みsetting。

// 配列の設定
$containerBuilder->addDefinitions([
    'setting' => [
        'setting_test' => 'OK',
    ],
]);

ってやると

var_dump($container->get('setting'));

で取れます。

array(1) {
  ["setting_test"]=>
  string(2) "OK"
}

うんまぁこの辺はよし。
次。

古くからある「あらかじめDIに書いておくとどこからでも同一インスタンスが取得できる」のは、今回「Definitions」って呼称するようです。
設置の仕方は上述と一緒。

// 「Classを明示的に設定」のテスト: Definitions
class Foo {
}
//
$containerBuilder->addDefinitions([
    'testFoo' => function(ContainerInterface $c) {
        $obj = new \Foo();
        $obj->s = $c->get('setting')['setting_test'];
        return $obj;
    },
]);

ってやると

//
var_dump($container->get('testFoo'));
var_dump($container->get('testFoo'));

object(Foo)#28 (1) {
  ["s"]=>
  string(2) "OK"
}
object(Foo)#28 (1) {
  ["s"]=>
  string(2) "OK"
}

って取れまして、同じインスタンスNo持ってるので「同一のものが取れている」事がわかります。
だからまぁ「addDefinitionsで括ってやれば大体Slim3の頃と同じ事が出来る」で、基本は終了でございます。
とりあえず最低限としては「Slim3で"配列への代入"で書いていた所を、addDefinitionsメソッドで包んでやればOK」って感じぽい。


ここから、追加調査。
少し気になる記述があったので試してみる……曰く「Autowiring」。
http://php-di.org/doc/autowiring.html

Autowiring is an exotic word that represents something very simple: the ability of the container to automatically create and inject dependencies.

ぐぐる先生翻訳

自動配線は、非常に単純なものを表すエキゾチックな単語です。依存関係を自動的に作成および注入するコンテナの機能です。

自動的? automatically create? ふぁ?

実験。

// 「解決出来るクラス名渡したら設定しなくてもDIしてくれるよ」テスト: Autowiring
class Test {
}
var_dump($container->get('Test'));
var_dump($container->get('Test'));
object(Test)#37 (0) {
}
object(Test)#37 (0) {
}

………取れてる………しかも(ある意味)ちゃんと「シングルトン(同一インスタンス)」で。
……いいのかどうか、悩む挙動だなぁ。

なお試してませんが

$containerBuilder->useAutowiring(false);

で、この挙動はoffに出来るぽい。
……幾分色々と思案される所ではあるような気がする……なんとなく、おいちゃん的には(=業務的には)offっておいたほうが安全な気がせんでもない。

お次。
……どうも「コンストラクタの時、型指定をすると"その型のインスタンスをgetして取ってきてくれる"」とかいう、絶妙に微妙な機能があるっぽい*3

とりあえず、まずは「無指定の時」の実験。

class Test2 {
    public function __construct($o) {
        $this->o = $o;
        var_dump( get_clas($o) );
    }
}
var_dump($container->get('Test2'));
var_dump($container->get('Test2'));
echo "\n";
Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test2" cannot be resolved: Parameter $o of __construct() has no value defined or guessable

をや?
「has no value defined or guessable」?
あぁそうかここ別に「なにかが必ず渡ってくる」って訳でもないから、か。

では改めて「型を指定して」実験。

class Test3 {
    public function __construct(Test $o) {
        $this->o = $o;
    }
}
var_dump($container->get('Test3'));
var_dump($container->get('Test3'));
echo "\n";
object(Test3)#36 (1) {
  ["o"]=>
  object(Test)#37 (0) {
  }
}
object(Test3)#36 (1) {
  ["o"]=>
  object(Test)#37 (0) {
  }
}

確かに入ってるなぁ。しかもちゃんと「Container経由」のインスタンスが。

……あれ? ってことは?

//
$containerBuilder->addDefinitions([
    'testFooTest' => function(Test $c) {
        $obj = new \Foo();
        $obj->s = $c;
        return $obj;
    },
]);

してから

var_dump($container->get('testFooTest'));
var_dump($container->get('testFooTest'));
echo "\n";
object(Foo)#43 (1) {
  ["s"]=>
  object(Test)#40 (0) {
  }
}
object(Foo)#43 (1) {
  ["s"]=>
  object(Test)#40 (0) {
  }
}

予想通りではある……便利とも言えるけど、気をつけないと割と諸刃っぽいなぁ。

でもまぁせっかくなんでもうちょっと突っ込み。
暗黙のクラスは「自作のクラスじゃなくて、PHPが元々持っているクラス」でも行けるのか?

class Test4 {
    public function __construct(\ArrayObject $o) {
        $this->o = $o;
    }
}
var_dump($container->get('Test4'));
echo "\n";
Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test4" cannot be resolved: Entry "ArrayObject" cannot be resolved: The parameter "input" of __construct() has no type defined or guessable. It has a default value, but the default value can't be read through Reflection because it is a PHP internal class.

ほむ。
「it is a PHP internal class.」
なのか。んじゃこれは駄目だな。

……「クラス名じゃないけど、Containerに指定しているkey名」だと、どうだろう?

class Test5 {
    public function __construct(testFoo $o) {
        $this->o = $o;
    }
}
var_dump($container->get('Test5')); // Fatal error: Uncaught ReflectionException: Class testFoo does not exist in /
echo "\n";
Fatal error: Uncaught ReflectionException: Class testFoo does not exist in ...

うんまぁそうかPHPのparseでエラーになるかそりゃそうだ。


というわけで
・とりあえず「Slim3の頃と同じような感じで使いたい」なら「ContainerBuilderのインスタンスにaddDefinitions()してからbuild()メソッドで$containerインスタンスを作る」
・containerのget、或いはaddDefinitions()に渡すfunction()の引数、またはコンストラクタに「自作クラスのクラス名」を渡すと、それを自動的に取ってきてくれる
ってのがわかった感じ。

一応「Slim3からの移植」にはさほどの差し障りがないなぁ、というのと「ちょっと便利が過ぎて少し微妙」な機能があるので、使いどころかなぁ、的な印象でございます。

最後に、今回のコードの大体の全景を。

<?php
declare(strict_types=1);

// 基準になるディレクトリ(最後の / はない形式で)
define('BASEPATH', realpath(__DIR__ . '/..'));

// オートローダ
require(BASEPATH . '/vendor/autoload.php');

/*
 *
 */
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;

// Builderインスタンスの生成
$containerBuilder = new ContainerBuilder();

// 配列の設定
$containerBuilder->addDefinitions([
    'setting' => [
        'setting_test' => 'OK',
    ],
]);

// 「Classを明示的に設定」のテスト: Definitions
class Foo {
}
//
$containerBuilder->addDefinitions([
    'testFoo' => function(ContainerInterface $c) {
        $obj = new \Foo();
        $obj->s = $c->get('setting')['setting_test'];
        return $obj;
    },
    // タイプヒンティングで「別のクラス」を指定したら、(解決できるなら)それを解決する
    'testFooHoge' => function(Test $t) {
        $obj = new \Foo();
        $obj->o = $t;
        return $obj;
    },
]);
//
$containerBuilder->addDefinitions([
    'testFooTest' => function(Test $c) {
        $obj = new \Foo();
        $obj->s = $c;
        return $obj;
    },
]);

// Containerインスタンスの生成
$container = $containerBuilder->build();

//
var_dump($container->get('setting'));
echo "\n";

//
var_dump($container->get('testFoo'));
var_dump($container->get('testFoo'));
echo "\n";

var_dump($container->get('testFooHoge'));
var_dump($container->get('testFooHoge'));
echo "\n";


// 「解決出来るクラス名渡したら設定しなくてもDIしてくれるよ」テスト: Autowiring
class Test {
}
var_dump($container->get('Test'));
var_dump($container->get('Test'));
echo "\n";

/*
// Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test2" cannot be resolved: Parameter $o of __construct() has no value defined or guessable
class Test2 {
    public function __construct($o) {
        $this->o = $o;
        var_dump( get_clas($o) );
    }
}
var_dump($container->get('Test2'));
var_dump($container->get('Test2'));
echo "\n";

*/

class Test3 {
    public function __construct(Test $o) {
        $this->o = $o;
    }
}
var_dump($container->get('Test3'));
var_dump($container->get('Test3'));
echo "\n";


//
var_dump($container->get('testFooTest'));
var_dump($container->get('testFooTest'));
echo "\n";

// これは駄目なのか:It has a default value, but the default value can't be read through Reflection because it is a PHP internal class.
class Test4 {
    public function __construct(\ArrayObject $o) {
        $this->o = $o;
    }
}
//var_dump($container->get('Test4')); // Fatal error: Uncaught DI\Definition\Exception\InvalidDefinition: Entry "Test4" cannot be resolved: Entry "ArrayObject" cannot be resolved: The parameter "input" of __construct() has no type defined or guessable. It has a default value, but the default value can't be read through Reflection because it is a PHP internal class.
//echo "\n";

// 流石にこれは駄目か
/*
class Test5 {
    public function __construct(testFoo $o) {
        $this->o = $o;
    }
}
var_dump($container->get('Test5')); // Fatal error: Uncaught ReflectionException: Class testFoo does not exist in /
echo "\n";
*/

*1:使うんだから composer.json に書いておいて欲しい orz

*2:ArrayAccess インタフェース

*3:「暗黙」ではないからそこまでNGだとは思わないけど、「よくわからずに使う」と色々と面倒そうなので、あんまりよい匂いを感じない

PHPで「型宣言された引数の型の名前」を知る方法

元々は、Laravelで興味深い機能*1を見かけたのが初手で。
その後、(近々公開しますが)Slim4のContainerでも似たような事をやっていたので、ちぃと思い切って調査をしてみました。

端的には
・関数(メソッド)宣言で引数に型宣言をしている時に、「なんのクラスを宣言しているのか?」を知る方法
です。

んと……色々と手間を省くためにざっくりしたコードで恐縮ですが。

class Hoge {
    public function t(\Exception $e, \ArrayObject $ar, string $s, int $i) {
    }
}

こんなクラスがあった時に
・t()の、第一引数には\Exception、第二引数には\ArrayObject、第三引数にはstring、第四引数にはintが型指定されている
事を知りたい時があるわけですよ……多分、フレームワーク作ってるとかじゃないとあんまりなさそうなシチュエーションですが、フレームワークとかの「汎用系ルーチン」作ってると、こーゆーの、稀にあります。

で、調べてみたのですが……わかれば割と簡単。
Reflectionを使ってたんですねぇ(少なくとも Slim4のContainerであるPHP-DIでは。Laravelが使ってるのか「別の方法なのか」は知らにゃい)。

ざっくり、コードを書いてみました。

<?php

// 調査用クラス
class Hoge {
    public function t(\Exception $e, \ArrayObject $ar, string $s, int $i) {
    }
}
// 調査用関数
function t(\Exception $e, \ArrayObject $ar, string $s, int $i) {
}

/*
//
$ref = new \ReflectionFunction('tt');
//$ref = new \ReflectionMethod('Hoge', 't');

//
$params = $ref->getParameters();
//
foreach($params as $p) {
    echo $p->getType() , "\n";
}
*/

// 一行にまとめてみるとこんなん
foreach(( new \ReflectionMethod('Hoge', 't'))->getParameters() as $p) {
    echo $p->getType() , "\n";
}

説明は省略(笑
質問があったら、コメントにでも書いて貰えれば返答できるかと思われます。

いやわかってしまえば「あぁそりゃそうか」なんですが。
Reflection、ざっくり斜めには見てるけど、あんまり「がっぷり四つに組んで」までは見てないからなぁ。
今度、腰を据えて丁寧にじっくりとガッツリと見てみるのも面白いかもしんない。

以上、ちと興味深くて「割とず~っとうっすらと気になってた」機能の調査でした。

*1:調べたら、FormRequestと言うそうです