がるの健忘録

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

privateやprotectdのメソッドのテストをどうやる? って話の備忘録

PHPでprivateなメソッドを外から叩く時ってどうやるんだっけ?」で、リフレクションって単語をしょっちゅう忘れるから「それの備忘録用」って見方をしないように*1
さっきもまぁ「リダイレクト……リファラー……り……り……」で、そこそこ悩んでたのは秘密だ。

おいといて。

protectedだと割と本当に色々あって、一番雑なのが
・テスト用に継承クラス作る
ってやりかた。

<?php
declare(strict_types=1);

// テスト対象のクラス
class TestTarget {
    protected function t()
    {
        return mt_rand(1, 10);
    }
}

// テスト用クラス
class Test extends TestTarget
{
    public function h() {
        return $this->t();
    }
}

//
$obj = new Test();
var_dump( $obj->h() );

PHP7の無名クラス使うなら

<?php
declare(strict_types=1);

// テスト対象のクラス
class TestTarget {
    protected function t()
    {
        return mt_rand(1, 10);
    }
}

// テスト用クラス
$obj = new class extends TestTarget {
    public function h() {
        return $this->t();
    }
};

var_dump( $obj->h() );

これでもOK。

この辺りだと、staticなメソッドでも自由自在。
継承クラス書いてもよいし、無名クラス使うんなら

<?php
declare(strict_types=1);

// テスト対象のクラス
class TestTarget {
    protected static function t()
    {
        return mt_rand(1, 10);
    }
}

// テスト用クラス
$obj = new class extends TestTarget {
    public function h() {
        return static::t();
    }
};

var_dump( $obj->h() );

とも書ける。

……で片付けたいので、正直「メソッドはprivateにすんな面倒だ」って、色々な角度から思う。
んだけど、まぁ「時すでに遅し」なケースもあろうかと思うので、少しだけ考察。

普通のメソッドなら、ReflectionMethodのsetAccessible()が手っ取り早いっつか綺麗だとは思う。

<?php
declare(strict_types=1);

// テスト対象のクラス
class TestTarget {
    private function t()
    {
        return mt_rand(1, 10);
    }
}

//
$m = new ReflectionMethod('TestTarget', 't');
$m->setAccessible(true);

var_dump( $m->invoke(new TestTarget) );

なお実はstaticも普通にいける。

<?php
declare(strict_types=1);

// テスト対象のクラス
class TestTarget {
    private static function t()
    {
        return mt_rand(1, 10);
    }
}

//
$m = new ReflectionMethod('TestTarget', 't');
$m->setAccessible(true);

var_dump( $m->invoke(new TestTarget) );

プロパティをダイレクトにやりとりする事は基本やらないので、メソッドベースだとこの辺があれば「とりあえず」かなぁ、的な。

*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()
なのでまぁ、思案するほどでもないかなぁ、と。

Slim4のRequestインスタンスの作られ方

ほぼメモ。

Slim\Factory\ServerRequestCreatorFactory::create();
	Slim\Factory\Psr17\ServerRequestCreator ->createServerRequestFromGlobals()
		Slim\Psr7\Factory\ServerRequestFactory ->createFromGlobals
			$request = new Request($method, $uri, $headers, $cookies, $_SERVER, $body, $uploadedFiles);

大体、こんな流れでRequestが作られるっぽい。

………せめて new Request のクラス名が変数かなんかで外に出ててくれれば orz
ちょっと時間がないから、追々、なんか考えていきませう(getParamメソッドが復活してほしい orz)

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のほうがよいんだろうなぁ……

アクアリウム覚え書き

貰った水草

ミクロソリウム プテロプス
エキノドルス オパクス(子株)
オレンジミリフィラム

ミクロソリウム プテロプスは溶岩石に巻き付いて、活着を試みる。
エキノドルス オパクスは前景に植える。「肥料結構喰う」らしいので、固形含めて肥料多めに。
オレンジミリフィラムは「重しで沈めてもいいし溶岩砂に植えてもいいし」との事なので、両方を試みてみる。

その後のアクアリウム

20cmキューブの立ち上げから大体40日ほど、かな。
………気付いたら30cmキューブが一つ増えてますよ(笑

書いてなかった諸々を時系列(一部、思い出した順)でまとめていくと

外掛けフィルターをラクラクフィルターMに変更
→ 少し細工をして「バイオビーズがグルグルまわる」生態濾過特化に修正:拡散型吐出口を外して直にしたらすげぇいい感じでビーズがブン回ってます
→ 軽くエアレーションで外から空気をぶち込んでます

ハイグロフィラ ポリスペルマはほぼ枯れました orz
5本中、1本だけ残ってるのが、最近元気に葉っぱを広げてきているので……頑張ったら、盛り返す?
あと「小さすぎてとりあえず水面に浮かしていた」やつが2つくらい、これもなにげにけなげに生きています。

アナカリスとマツモが、すげぇ繁殖しています。
時々カットするとそこから2本になって成長して……を繰り返して、すごい量になってます(笑
これは後に「30cmキューブ」につながります(笑

アマゾンフロッグピットが、アナカリス&マツモにも増して「すげぇ勢いで繁殖しまくってます」。
2回くらい間引いたのですが、それでもなお「水面を全部、覆い尽くさんがばかり」の勢いで、実際、割と覆われまくってます(これも30cmキューブに)。

アカヒレさんが一匹、★に orz
多分、フィルター掃除の時に間違えてどこかに挟んじゃったっぽい……気をつけないと。

ミナミヌマエビ、2匹が★に orz
1匹は「フィルターの中に吸い込まれてた」。外掛けフィルターの掃除の時に、ストレーナーを外したスキに入られたっぽ orz
一匹は不明。ある日、いきなり「真っ赤になって倒れてた」。

一方で、1匹(か2匹)、抱卵しました!!
その前後、ミナミヌマエビさんがすげぇワシワシしてたので、多分いわゆる「抱卵の舞」とかってやつなのかなぁ? と。
なお「いきなり真っ赤なミナミヌマエビさん」は、そのワシワシの翌日でした。

レッドラムズホーンさんは、すげぇ勢いで繁殖してます。……多分、稚貝はそこそこ亡くなってるんだろうなぁ……数が多すぎて把握できない(笑
でっかい主的なのが数匹いるので、多分、こいつらが産卵していると思われます。

で、どうも水温が高い、のと、メインのミナミヌマエビさんが「23度がベスト」とかいうお話だったので。
ヒーターを「メダカ」系の、23度固定に変更。

一時期「亜硝酸塩が高め」「硝酸塩が結構高め」だったのですが。
PIXY カリウムグロウリキッドを入れたのがよかったのかなんなのか、一時期以降、両方とも「めっきり数値が低い」。水草の量がスゴイからなぁ……頑張ってるのかしらん?


で、まぁ、30cmキューブ(笑
濾過は「GEXマルチベースフィルターL + 簡単ラクラクパワーフィルターL」で、8枚だかつかって「最大サイズ」に。底面濾過、吸い込み式にチャレンジ。
砂は溶岩砂(No.31 Volcano)。……洗うのがすげぇ大変。3Lを2つ買って1個半使い、大体5cmくらいの厚さにしてみました。
ライトは「アクロ TRIANGLE LED BRIGHT 300 1950lm Aqullo Series 30cm水槽用照明」を選択。1950もあればとりあえず色々となんとかなるんじゃなかろうか? 的な。
エアレーションは水作の2S。パワー持て余してます(笑

その水槽に
・月曜日夜:セッティングして水いれて回しはじめ
・火曜日:水草レッドラムズホーン(4匹+水槽に付いてた稚貝)を入れる
 → ウィローモスを買い求めて流木とかにまきつけ
 → ゼニゴケ(でいいのかな? ウィローモスの仲間と聞いた)を買い求めて流木とかにまきつけ
 → アナカリスを20cm水槽&ボトルから持ち込んで重りつけて沈める
 → マツモを20cm水槽&ボトルから持ち込んで重りつけて沈める
 → アマゾンフロッグピットを20cm水槽&ボトルから持ち込んで重りつけて沈める
で、あとはBICOMのバクテリア少しいれてバクターボールも入れて、ゆっくりと立ち上げ。
多分、まっさらな立ち上げよりは少し早いんだろうなぁ……「水槽の白濁」がすでに出てるから、体感、2倍(もしくは1日くらい前倒し)。
パイロットフィッシュより手前にレッドラムズホーンさんがいるので、少しバクテリアの増え方もよくなることを期待。

予定では
・一週間後を目処に、パイロットフィッシュとしてプラティを4匹ほどぶち込み
・さらに二~三週間後を目処に、本命のミナミヌマエビさんを20匹ほど(!!)ぶち込み
の予定。

相変わらず「本命はミナミヌマエビさん」なので、ミナミヌマエビさんの過ごしやすい環境にしていきたいなぁ。

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だとは思わないけど、「よくわからずに使う」と色々と面倒そうなので、あんまりよい匂いを感じない