- 投稿日:2019-10-21T23:16:46+09:00
メモ
Laradock を使って Laravel, PHP, MySQL, phpMyAdmin のdocker環境を構築
- ドキュメント 2.2 Installation A.2 の条件に書いてあるコマンドを実行してもmysqlのコンテナが落ちて動かない...
- MySQLのバージョンが8以上だと MySQLサーバー側で使われる認証方式がLaravel側でサポートされていないため認証に失敗する
- ~/laradock/.env の MYSQL_VERSION を latest から 5.7.28 に変更
- パスの指定がデフォルトの状態だとファイルが生成されてない
- ~/laradock/.env の DATA_PATH_HOST を ~/.laradock/data から .laradock/data に変更
- ~/laradock/.env の設定内容
- APP_CODE_PATH_HOST=../laravel/
- DATA_PATH_HOST=.laradock/data
- MYSQL_VERSION=5.7.28
- MYSQL_DATABASE=laravel
- MYSQL_USER=laravel
- MYSQL_PASSWORD=laravel
- MYSQL_PORT=3306
- MYSQL_ROOT_PASSWORD=root
ディレクトリ
project
├── laradock/ (<- git clone https://github.com/laradock/laradock.git)
└── laravel/ターミナルからlaradockディレクトリに移動して docker-compose up -d nginx mysql phpmyadmin workspace のコマンド(20分位かかる)を実行したあと docker-compose exec --user=laradock workspace bash コマンドで workspace に入って laravelをインストール composer config -g repositories.packagist composer https://packagist.jp && composer global require hirak/prestissimo && composer create-project laravel/laravel .
- 投稿日:2019-10-21T20:15:11+09:00
【PHP8.0】PHP8で警告のエラーレベルが軒並み厳しくなる
多くの警告について、PHP8.0でエラーレベルが変更されます。
これはReclassifying engine warningsというRFCで受理されたものです。
提案者はいつものNikita。
影響の大きい未定義変数アクセスについては個別に紹介しましたが、ここではそこで紹介しなかった細かい警告について見ていきます。これまでE_NOTICEだった警告の一部がE_WARNINGに、これまでE_WARNINGだった警告の一部が例外になります。
E_WARNINGを抑制するような書き方をしている場合、PHP8では動かなくなる可能性が高いので気をつけましょう。
現在E_NOTICE以下であればいきなり動かなくなることはありませんが、そもそも抑制する書き方がよくないので、なるべく修正した方がよいでしょう。エラーレベルの変更がない警告も並んでいるので、もしかしたら全警告が列挙されてるのか?と思ったのですが、
expected to be a %s, %s given
とか色々無いものもあるので、全てを出しているわけではないようです。
どういう基準なんだろうか?Reclassifying engine warnings
Attempt to increment/decrement property '%s' of non-object
E_WARNING → Error exception
オブジェクトではない変数のプロパティをインクリメント/デクリメントすると発生する。
$a = 1; $a->b++;Attempt to modify property '%s' of non-object
E_WARNING → Error exception
オブジェクトではない変数のプロパティを変更すると発生する。
$a = 1; $a->b['c'] = 1;Attempt to assign property '%s' of non-object
E_WARNING → Error exception
オブジェクトではない変数にプロパティを追加すると発生する。
$a = 1; $a->b = 1;このへん全部同じでいいんじゃないか?
Creating default object from empty value
E_WARNING → Error exception
未定義の変数にプロパティを追加すると発生する。
$a->b = 1;PHP7.3では
$a
が定義されるのだが、PHP8では何も定義されなくなると思われる。ところで未定義の変数のプロパティを変更しようとすると何の警告もなくオブジェクトが生成されるのだが、こっちは今後もいいのだろうか。
$a->b['c'] = 1; // エラー出ない var_dump($a); // object(stdClass)#1 (1) { ["b"]=> array(1) { ["c"]=> int(1) } }Trying to get property '%s' of non-object
E_NOTICE → E_WARNING
オブジェクトではない変数のプロパティを参照すると発生する。
$a = 1; $a->b;Undefined property: %s::$%s
E_NOTICE → E_WARNING
オブジェクトの未定義プロパティを参照すると発生する。
$a = new stdClass(); $a->b;PHPの場合、入力として外部引数やらAPIやらを使うことが多いため、読み取りの失敗については書き込みより寛容気味。
Cannot add element to the array as the next element is already occupied
E_WARNING → Error exception
配列の自動挿入による整数キーがPHP_INT_MAXを超えたときに発生する。
$a = [ PHP_INT_MAX => 1, ]; $a[] = 2;ちなみに計算値で指定すれば、PHP_INT_MAXを超えていてもいける。
$a = [ PHP_INT_MAX => 1, ]; $a[PHP_INT_MAX+1] = 2; // -2147483648とかになるCannot unset offset in a non-array variable
E_WARNING → Error exception
エラーの出し方がわからない。
Cannot use a scalar value as an array
E_WARNING → Error exception
文字列型ではないスカラー型の変数に配列値を追加すると発生する。
$a = true; $a[] = 1;文字列型の場合は文字単位アクセスという正しい文法。
ちなみにnullで初期化した場合は問題なく動く。
$a = null; $a[] = 1; // [ 0 => 1]なぜかfalseでも動く。
$a = false; $a[] = 1; // [ 0 => 1]Trying to access array offset on value of type %s
E_NOTICE → E_WARNING
文字列型ではないスカラー型の変数を配列形式で読み込もうとすると発生する。
$a = true; $a[1];このE_NOTICE自体PHP7.4で追加されたもので、それ以前は何も出さずにnullを返していた。
Only arrays and Traversables can be unpacked
E_WARNING → TypeError exception
関数呼び出し時の引数展開にiterableでない値を渡すと発生する。
var_dump(...1);unpackとは特に関係ない。
Invalid argument supplied for foreach()
E_WARNING → TypeError exception
iterableでない値をforeachすると発生する。
$a = 1; foreach($a as $loop){}Illegal offset type
E_WARNING → TypeError exception
配列のキーに配列やオブジェクトを指定すると発生する。
$a = [ new stdClass() => 1, [] => 2, ];Illegal offset type in isset or empty
E_WARNING → TypeError exception
isset
およびempty
でチェックする配列のキーに配列やオブジェクトを指定すると発生する。$a = []; isset($a[new stdClass()]);ちなみに
$a
が未定義やスカラー型の場合は何のエラーも起こらない。isset($a[new stdClass()]); // エラー出ない $a = 1; isset($a[new stdClass()]); // エラー出ない未定義やint型等であれば配列形式アクセスした時点でfalseだから中身を見る必要もないというのはわかるが、文字列型でもエラーが出ない理由はよくわからない。
$a = 'a'; isset($a[1]); // true isset($a[new stdClass()]); // false エラー出ないIllegal offset type in unset
E_WARNING → TypeError exception
unset
する配列のキーに配列やオブジェクトを指定すると発生する。$a = []; unset($a[new stdClass()]);
$a
が未定義の場合Illegal offset type
は発生しないが、かわりにUndefined variable
のE_NOTICEが出る。
文字列以外のスカラー型には何のエラーも出さず、文字列型やオブジェクトにはFatal errorが発生する。unset($a[new stdClass()]); // E_NOTICE: Undefined variable $a = 1; unset($a[new stdClass()]); // エラー出ない $a = 'a'; unset($a[new stdClass()]); // Fatal error: Cannot unset string offsets $a = new stdClass(); unset($a[new stdClass()]); // Fatal error: Cannot use object of type stdClass as arrayこのあたりの法則はさっぱりわからない。
Indirect modification of overloaded element of %s has no effect
E_NOTICEのまま
SplFixedArray
に突っ込んだ配列の値を直接変更すると発生する。$a = new SplFixedArray(1); $a[0] = [1]; $a[0][0] = 2;値を変更しているつもりだが、実際には変更されていないという注意。
SplFixedArray
に限らず、ArrayAccess
をimplementsしたクラスに一般的に発生する症状のようだ。Indirect modification of overloaded property %s::$%s has no effect
E_NOTICEのまま
マジックメソッド
__get
が配列を返す場合、その返り値を直接変更すると発生する。class A{ private $value = ['a' => 1, 'b' => 2]; public function __get($k){ return $this->value; } } $a = new A; $a->value['a'] = 3;こちらも値を変更したつもりだが、実際には変更されていない。
なお配列ではなくオブジェクトであれば、エラーも出ないし値を直接変更できてしまう。
class A{ private $obj; public function __construct(){ $this->obj = new stdClass(); } public function __get($k){ return $this->obj; } } $a = new A; $a->obj->b = 1; var_dump($a); // { 'obj' => stdClass{ 'b'=>1 } }Object of class %s could not be converted to int/float/number
E_NOTICEのまま
オブジェクトをスカラー型にキャストすると発生する。
(int)new stdClass();緩い比較が内部的にこのキャストを使用しているため、オブジェクトとスカラー型を緩く比較するとE_NOTICEが発生する。
$a = new stdClass(); var_dump($a == 1); // E_NOTICE var_dump($a === 1); // エラー出ない比較ではエラーが出るべきではないので、こちらの問題がどうにかなるまでエラーレベルを変更しない。
A non-numeric value encountered
E_WARNINGのまま
次項で一緒に解説する。
A non well formed numeric value encountered
E_NOTICEのまま
非数値文字列を数値演算すると発生する。
1 + '1'; // エラー出ない 1 + '1a'; // E_NOTICE: A non well formed numeric value encountered 1 + 'a'; // E_WARNING: A non-numeric value encountered完全に数値形式の文字列ではエラーは出ず、一部だけ数値として評価できるときは
non well formed numeric value
、完全に数値でない場合はnon-numeric value
になる。
今回はエラーレベルが変わらないが、数値形式文字列の計算は安全のためキャストしておいた方がよいだろう。1 + (int)'a'; // エラー出ないAccessing static property %s::$%s as non static
E_NOTICEのまま
staticプロパティにインスタンスからアクセスすると発生する。
class A{ public static $property = 1; } $a = new A(); $a->property;正しくは
$a::$property
、もしくはA::$property
。
インスタンス内部からであればself::$property
もいける。Array to string conversion
E_NOTICE → E_WARNING
配列を文字列型にキャストすると発生する。
(string)[];変換前の配列の中身がどうなっていたとしても変換後の文字列は
Array
になるので、実質的に機能していない状態なのでExceptionでもいい気がする。Resource ID#%d used as offset, casting to integer (%d)
E_NOTICE → E_WARNING
リソースIDを配列のキーとして使用すると発生する。
$fp = fopen('hoge', 'w+'); $array = [$fp => $fp]; var_dump($array); // []リソースIDは整数っぽい値であり、かつプログラム中ではユニークなので、このような使い方ができそうではあるが実際は動いていない。
明示的にキャストするとint型になるため警告は発生せず、正しく動作する。$fp = fopen('./hoge', 'w+'); $array = [(int) $fp => $fp]; var_dump($array); // [1=>resource]そもそも動いてないので、これもいきなりExceptionでいい気がしないでもない。
String offset cast occurred
E_NOTICE → E_WARNING
文字列への角括弧オフセットアクセスのキーに整数ではない数値を使ったときに発生する。
'string'[1.5]; 'string'[true];下の項目と同じような内容なのでエラーレベルを揃えたという話のようだ。
Illegal string offset '%s'
E_WARNINGのまま
文字列への角括弧オフセットアクセスのキーに数値ではない値を使ったときに発生する。
'string'['a'];Uninitialized string offset: %d
E_NOTICE → E_WARNING
文字列への角括弧オフセットアクセスで範囲外の値を読み込もうとしたときに発生する。
'string'[10];Illegal string offset: %d
E_WARNINGのまま
文字列への角括弧オフセットアクセスでマイナスの範囲外の値を変更したときに発生する。
$str = 'string'; $str[-10] = 'a';正の範囲外を変更したときは単に文字列が伸びるだけでエラーは発生しない。
$str = 'string'; $str[10] = 'a'; // 'string a'Cannot assign an empty string to a string offset
E_WARNING → Error exception
文字列への角括弧オフセットアクセスで値を空文字に変更しようとしたときに発生する。
$str = 'string'; $str[1] = '';2文字以上与えた場合は2文字目以降が無視されるだけでエラーは発生しない。
$str = 'string'; $str[1] = 'abcde'; // 'saring'Only variables should be passed by reference
E_NOTICEのまま
リファレンス関数に値を直接渡すと発生する。
sort([2, 1]);Only variable references should be returned by reference
E_NOTICEのまま
リファレンス返しで値を直接返すと発生する。
function &ref(){ return 1; } ref();Only variable references should be yielded by reference
E_NOTICEのまま
リファレンス返しで値を直接yieldすると発生する。
function &ref(){ yield 1; } foreach(ref() as $v);リファレンス返しは百害しかないので使用してはならない。
Only variables should be assigned by reference
E_NOTICEのまま
リファレンスではない関数をリファレンスで受け取ろうとすると発生する。
function ref(){ return 1; } $x = &ref();Attempting to set reference to non referenceable value
E_NOTICEのまま
出し方がわからないどころか、事例すら一切出てこない謎の警告。
Cannot pass by-reference argument %d of %s%s%s() by unpacking a Traversable, passing by-value instead
E_WARNINGのまま
参照渡し関数にTraversableな値を引数アンパックして渡すと発生する。
function ref(&$var){} ref(...new ArrayIterator([1]));いみがわからない。
Division by zero
E_WARNING → DivisionByZeroError exception
数値を0で割ると発生する。
1 / 0;PHP7.4までは計算結果が
float(INF)
になる。Undefined variable
E_NOTICE → E_WARNING
未定義変数を参照すると発生する。
echo $a;詳細は個別記事を参照のこと。
Undefined array index
E_NOTICEのまま
配列の未定義キーを参照すると発生する。
$a = []; echo $a[1];詳細は個別記事を参照のこと。
感想
そもそもどうすれば出せるのかすらわからないエラーがあった。
警告に寛容なプログラミングをしている場合、
Invalid argument supplied for foreach
やCreating default object from empty value
あたりはよく見かけるのではないかと思います。
これらはPHP8では例外になって完全に動かなくなるので注意しましょう。それ以外でも、ゆるふわぺちぱーに対する締め付けは年々厳しくなる一方で、彼らの肩身はどんどん狭まりつつあります。
かつてはPHP以上にアバウトで破壊と慈悲の混沌だったJavaScript界も、最近は型に嵌まっていないゆるふわJavaScripterを完全排除する流れができあがっています。
やがて彼らの居場所が完全に失われてしまったとき、難民たちはいったいどこに行くのでしょうね。
- 投稿日:2019-10-21T20:09:12+09:00
LaravelでDBの区分値をEnum化していい感じに扱う
ソフトウェアでテーブル内の区分値を扱う方法は様々あるかと思いますが、個人的に扱いやすくできたので備忘録です。
本稿における区分値とは以下のような各種区分をDB上で扱いやすくするために、数値化などをおこなった値を指しています。性別区分[ 'gender' => [ 0 => 'not known', 1 => 'male', 2 => 'female', 9 => 'not applicable' ] ]権限区分[ 'roles' => [ 1 => 'developer', 2 => 'administrator', 3 => 'staff' ] ]TL;DR
myclabs/php-enum を使用して、区分値をEnum化し、Model内でよしなにする。
Enumって何
Enumとは
Enumerate
の略で列挙
を意味します。列挙型は、その名の通り定数を列挙したリストのようなものを作成することができ、区分値を扱う際によく利用されます。実装にもよりますが、値が同じでも異なるオブジェクトとして扱うことができるというメリットがあります。例えば、前述の性別と権限の区分値を定数として実装した場合
MALE === DEVELOPER
はどちらも値が1
であるためtrue
となり、バグを生み出してしまう可能性があります。Enumは、言語によっては組み込まれている場合もありますが、PHPには組み込まれていない型であるため、PHPで使用したい場合は、自作するか外部ライブラリを使用する必要があります。
一般的に定数を扱うよりも処理が遅くなるので、定数はすべて列挙型というわけではなく、区分値などの適した場面で使用するのがよいかと思います。
区分値をEnum化する
Enumライブラリのインストール
PHPのEnumライブラリは複数ありますが、本稿ではGitHubのStar数が多い & 汎用的に扱えることから、myclabs/php-enumを使用します。
以下のコマンドでインストールします。
$ composer require myclabs/php-enum
Enumの作成
ファイルを作成する場所はお好みですが、今回は
app
以下にEnums
ディレクトリを作成し、そこに定義します。権限区分を例にEnumを作っていきます。Role.php<?php namespace App\Enums; use MyCLabs\Enum\Enum; class Role extends Enum { const DEVELOPER = 1; const ADMINISTRATOR = 2; const STAFF = 3; }Enumの作成はこれだけです。
モデルからRoleのEnum値を取得できるようにする
User
のModel内にUser
のrole_code
属性のEnum値を取得するアクセサを定義します(LaravelのEloquentではgetHogeHogeAttribute
などのメソッドを定義することで、$user->hoge_hoge
のように取得することができるようになります)。まず、
User
に先程作成したEnumRole
をuseします。今回採用したEnumライブラリはコンストラクタにEnumに設定した値を渡すことで、定義した値を持つEnum値を得ることができます。以下のように記述し、
$user->role
で$user->role_code
に対応するEnum値が取得できるようします。
(モデルのソースはuseなど色々省略しています)User.php<?php namespace App\Models; use App\Enums\Role; class User { public function getRoleAttribute() { return new Role($this->role_code); } }Enumを使ってモデルに区分値を設定できるようにする
Userにミューテターを定義し、Userモデルの
role
属性にRole
のEnum値を代入することで、同時にrole_code
にも対応する区分値が代入されるようにします。setRoleAttributepublic function setRoleAttribute(Role $role) { $this->role_code = $role->getValue(); }Modelから権限を判定できるようにする
せっかくなので、
php-enum
に備わっているequals
を利用してModel内で権限も判定できるようにしておきます。equalsに渡す引数はメソッド呼び出しの形式であることに注意します。
()
をつけないとクラス変数という扱いになり、正しい比較ができません。 メソッド呼び出しの形式にすることで、 Enum型を返却してくれます。User.php<?php namespace App\Models; use App\Enums\Role; class User { public function getRoleAttribute() { return new Role($this->role_code); } public function setRoleAttribute(Role $role) { $this->role_code = $role->getValue(); } public function getIsDeveloperAttribute() { return $this->role->equals(Role::Developer()); } public function getIsAdministratorAttribute() { return $this->role->equals(Role::Administrator()); } public function getIsStaffAttribute() { return $this->role->equals(Role::STAFF()); } }これで権限を判定する場面で扱いやすくなりました。
Labelが欲しい
それぞれの権限名称をフロント側に表示したい場合などがあるかと思います。Laravelの機能を使ってスマートに取得できるようにします。
langファイルを作成する
Laravelには
app/resources/lang
以下に配置したディレクトリから、渡されたキーに対応する文字列を返却してくれる__
というヘルパーメソッドがいます。app/resources/lang
以下には locale名を関したディレクトリが存在し、configで設定されたlocaleと同じ名前を持つディレクトリから取得される仕組みになっています。今回はlocaleを
ja
に設定していますので、app/resources/lang/ja
以下にmy_app.php
を作成し、各区分の名称を記述します(ファイル名はなんでもいいです)。my_app.php<?php return [ 'developer' => '開発者', 'administrator' => '管理者', 'staff' => '担当者' ];これでコード内で
__('myapp.developer')
といったようにコールすることで、'開発者'
が取得できます。また、言語別にファイルを用意すればそれぞれの言語で取得できます。Enum内に
getLabel
メソッドを作成するメソッド名は何でもいいです。マジックメソッド
__toString
をオーバーライドしても良いのですが、デフォルトの挙動を破壊するのがなんとなく怖いので、今回は新たにメソッドを定義します。
getLabel
では lang に定義した、Enumに対応する文字列を取得できるようなロジックを記述します。
今回はEnumのKeyを小文字にするだけで、langに定義したKeyになることと、今後Role
が追加されたとしても同じように対応できるだろうという考えで、EnumインスタンスのKeyをstrtolower
で小文字にすることでlangのキーを生成しています。単純に小文字にするだけでは対応できない場合はEnumのキー => langのキー
のような連想配列を作成して取得するのがよさそうですね。Role.php<?php namespace App\Enums; use MyCLabs\Enum\Enum; class Role extends Enum { const DEVELOPER = 1; const ADMINISTRATOR = 2; const STAFF = 3; public function getLabel() { return __('my_app.'.strtolower($this->getKey())); } }Model内に
getRoleLabelAttribute
メソッドを作成する最後に以下のようなメソッドをモデル内に定義すれば、
$user->role_label
のような形でLabelを取得できます。getRoleLabelAttributepublic function getRoleLabelAttribute() { return $this->role->getLabel(); }
- 投稿日:2019-10-21T17:55:33+09:00
自分で立てる、オープンソースのテストケース管理システムをまとめてみた
QualityForwardというクラウドベースのテストケース管理システムをサポートしています。その関係もあって、オープンソースのテストケース管理システムを調べてみました。無償で、自分で立てるという選択を選ぶ場合の参考にしてください。
利用できる、利用できそうなソフトウェア
少なくとも3年より前に更新されているソフトウェアです。また、公式サイトがちゃんと立ち上がっていて更新されているものに限定しています。
TestLink
オープンソースのテスト管理と言えばTestLinkを思い浮かべる方が多いのではないでしょうか。開発言語はPHPとなっています。現在はGitHubにコードを移しているようです。今年リリースされた1.9系が最新版です。
TestLink download | SourceForge.net
TestManagerForTracPlugin – Trac Hacks
Tracのプラグインとしてテストケース管理を提供します。最終更新は2017年3月となっています。Wikiページを拡張する形でテストケース用のモデルを追加しています。
TestManagerForTracPlugin – Trac Hacks - Plugins Macros etc.
Kiwi TCMS
10年以上も開発が行われているテストケース管理システムです。Python/Djangoで開発されています。ここにデモ版のサイトが立ち上がっています。一部のラベルは日本語化されているようです。
Kiwi TCMS - the leading open source test case management system
Next Generation Testing Management Tool
グラフの種類も豊富で開発もまだまだ活発に行われています。残念なのは言語が中国語のみで、記述内容がはっきりと分からないことでしょう。Javaで作られています。
aaronchen2k/ngtesting-platform: Next Generation Testing Management Tool
Nitrate
Python/Django製のテストケース管理システムです。Python3.6/3.7をサポートしています。Dockerでも利用できますので、試用は簡単にできそうです。
Nitrate/Nitrate: Django based full-featured test case management system
利用できなさそうなソフトウェア
調べている中で見つかったソフトウェアです。多くが2013年あたりで更新を停止しています。フレームワークやプログラミング言語が古かったり、UIがこなれていないものが多いです。開発自体が活発ではないので、セキュリティリスクがあるかと思います。
Testopia
Mozilla製のテスト管理システムです。Bugzillaの機能拡張として開発されていますが、Bugzilla 5系との互換性がありません。Bugzilla 5は2015年07月にリリースされている中、今なお対応していないことを鑑みると、開発リソースはあまりなさそうです。
MozTrap
MozTrapも同様にMozillaが開発しているテスト管理システムですが、4年前で開発は停止しています。デモサイトもなくなっているので開発は行われていないようです。
mozilla/moztrap: MozTrap test case management system
qaManager
プロジェクトトラッキング、リソース管理機能もあるテスト管理システムです。ソースコードの最終更新が2008年なので、現在のニーズにはマッチしていない可能性がありそうです。
qaManager download | SourceForge.net
QaTraq
QATraqはGPL v2以下で公開されているテスト管理システムです。こちらもqaManagerと同様2008年のソースコード更新が最終となっています。
QaTraq download | SourceForge.net
Radi
Radiは2013年で最終更新が止まっています。スクリーンショットを見るとIEとなっており、フレームが活用されたデザインなのでかなり古い印象を受けます。
Radi a light weight test director tool download | SourceForge.net
RTH
テスト要件を管理するためのシステムとなっています。ソースコードの更新は2009年までです。
RTH - Requirements and Testing Hub download | SourceForge.net
Tarantula
Ruby 1.9、Rails 3.2で開発されたアジャイルテスト管理です。現在は開発が停止しています。元々商用として提供していたようですが、会社がなくなるタイミングでオープンソース・ソフトウェアになったようです。
prove/tarantula: Tarantula Test Management Tool
Redcase
Redmineプラグインとして動作するテストケース管理です。サポートするRedmineが3.x系なので、現在の最新版(4系)では利用できないかも知れません。
まとめ
テストケース管理は多数あるのですが、2008年くらいをピークとして様々なソフトウェアが作られ、その内幾つか(TestLinkやKiwi TCMSなど)が継続的に開発を続けているといった雰囲気があります。技術領域としては変革的なものが出てきていないのかも知れません。
しかし、テスト管理を今なおExcelで行っているチームも多いと言います。操作性が悪かったり、共有もしづらく、お勧めはしません。遠隔地とデータを共有したりすることを考えればWebブラウザベースの方が良いでしょう。クラウドベースですぐにテスト管理をはじめたい場合はQualityForwardをぜひお試しください!
- 投稿日:2019-10-21T15:34:22+09:00
PHP Slim 環境構築(13) AWSサービスの利用
PHP Slim 環境構築(13) AWSサービスの利用
Introduction
前回は、DockerコンテナにNginxとPHP-FPMの両方を格納した構成をEC2に構築し、同一サーバ上で動く別のDockerコンテナ(RDB, DynamoDB, S3)とアクセスを行いました。
今回は、RDB、DynamoDB、S3などをAWS上のサービスを利用するように変更します。この一連のシリーズは、自分への備忘録が第一目的のため、だいぶ不親切です。
すみません・・・AWS
SystemManager
DB接続情報などを格納するのにSystemManagerのパラメータストアを使用するようにします。
SecretsManagerによるシークレットローテーションなども試したいのですが、有償になってしまうため今回は断念しています(無償期間は過ぎた)。今回使用したパラメータは以下の通りです。
キー名 意味 DYNAMODB_ACCESS_KEY DynamoDBにアクセスするIAMユーザーのAccessKey DYNAMODB_SECRET DynamoDBにアクセスするIAMユーザーのSecret LOG_DB_PASSWORD ログ用DBに想定しているRedshiftへの接続パスワード LOG_DB_USER ログ用DBに想定しているRedshiftへの接続ユーザ STORAGE_ACCESS_KEY S3にアクセスするIAMユーザのAccessKey STORAGE_BUCKET アクセスするS3のバケット名 STORAGE_SECRET S3にアクセスするIAMユーザのSecret USER_DB_PASSWORD RDBへの接続パスワード USER_DB_USER RDBへの接続ユーザ RDS(MariaDB)
今回は、テスト用に使用するRDBとして、RDS for MariaDBを使用しました。
Auroraを使用しないのは、もちろん無償だからです。
Dockerコンテナを起動しているEC2からアクセスできるように、セキュリティグループを設定する必要があります。
また、このDBへのアクセスユーザとパスワードをSSMに登録します(USER_DB_USER, USER_DB_PASSWORD)。RDS(PostgreSQL)
今回は、ログ用DBとしてRedshiftを想定していますが、テスト用にはRDS for PostgreSQLを使用しました。
理由は、もちろん無償・・・。
セキュリティグループに注意してください。
くた、このDBへのアクセスユーザとパスワードをSSMに登録します (LOG_DB_USER, LOG_DB_PASSWORD)。DynamoDB
DynamoDBとDynamoDBにアクセスすることができる(AmazonDynamoDBFullAccess)IAMユーザを作成しました。
このユーザのアクセスキーとシークレットを、SSMに登録します (DYNAMODB_ACCESS_KEY, DYNAMODB_SECRET)。ElastiCache
ElastiCache(Redis)を作成しました。
S3
S3にテストに用いるバケットを作成し、そのバケットにアクセスできるIAMユーザを作成しました。
このユーザのアクセスキーとシークレット、および、作成したバケット名をSSMに登録します (STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_BUCKET)。変更点
前回まではローカルPCで稼働するlocal環境、単一のEC2上にall-in-oneで動作するdevelopment環境を作成しました。
今回は、一部awsを利用するdevaws環境を新設しました。次回は、EC2をECSに移行する予定ですので、今回の環境は過渡期的なものです。ソースツリー
今回の変更箇所です。
$(PROJECTROOT) /compose /web_front devaws.conf (新規) /web_hoge Dockerfile settings-devaws.yml (新規) docker-compose-devaws.yml (新規) /credentials .empty (新規) ssm /src /hoge /lib /Controller DynamodbController.php StorageController.php ApplicationSetting.php (新規) MyContainerBuilder.php composer.json composer.lock .gitigore/compose/web_front/devaws.conf (新規)
/compose/web_front/development.confと同じです。
/compose/web_hoge/Dockerfile
設定ファイルのコピー先を変更しました。
これは/var/www直下だと、通常ありそうな場所では無いため、分かりにくかったからです。/compose/web_hoge/Dockerfile...(略)... #COPY settings-${environment}.yml /var/www/settings.yml COPY settings-${environment}.yml /usr/local/etc/myapp/settings.yml ...(略).../compose/web_hoge/settings-devaws.yml (新規)
前回までの設定ファイルsettings-development.ymlでは、接続情報を直接書いていたのですが、セキュリティ上、AWSサービスのアクセスキーなどを直接書くわけにはいかないので、SSMのパラメータストアの情報を見に行く仕組みを作成し、それに書き換えています。
また、SSMへの接続情報が追加されています ($$で囲まれた部分。ただし、SSMへアクセスできるIAMユーザのアクセスキーとシークレットは別ファイルで定義することにする)。/compose/web_hoge/settings-devaws.ymluserdb: host: mydbinstance.csvkmjl9r9hs.ap-northeast-1.rds.amazonaws.com dbname: userdb user: $$USER_DB_USER$$ password: $$USER_DB_PASSWORD$$ logdb: host: logdbinstance.csvkmjl9r9hs.ap-northeast-1.rds.amazonaws.com dbname: logdb user: $$LOG_DB_USER$$ password: $$LOG_DB_PASSWORD$$ redis: host: myredis.g3vevh.ng.0001.apne1.cache.amazonaws.com dynamodb: endpoint: https://dynamodb.ap-northeast-1.amazonaws.com region: ap-northeast-1 key: $$DYNAMODB_ACCESS_KEY$$ secret: $$DYNAMODB_SECRET$$ table: mydynamo storage: endpoint: https://s3.ap-northeast-1.amazonaws.com region: ap-northeast-1 key: $$STORAGE_ACCESS_KEY$$ secret: $$STORAGE_SECRET$$ bucket: $$STORAGE_BUCKET$$ ssm: endpoint: https://ssm.ap-northeast-1.amazonaws.com region: ap-northeast-1/compose/docker-compose-devaws.yml (新規)
devaws環境用、docker-composeファイル。local, development環境は、docker-compose-common + 環境用と二つに分けていましたが、devaws環境は一つです。ややこしいので、docker-compose-commonは不要だったかもしれないですね。
web_hogeのvolumesに、ssm接続設定ファイルを含むディレクトリの定義が追加されています(../credentials:/usr/local/etc/myapp/credentials)。
/compose/docker-compose/devaws.ymlversion: '3' services: web_front: build: context: ./web_front args: - environment=devaws ports: - "80:3128" - "443:8443" volumes: - ../certs:/etc/certs:ro container_name: web_front networks: - private_net web_hoge: build: context: ./web_hoge args: - environment=devaws volumes: - ../src/hoge:/var/www/hoge - ../src/vendor:/var/www/vendor - ../credentials:/usr/local/etc/myapp/credentials container_name: web_hoge networks: - private_net networks: private_net: driver: bridge ipam: driver: default config: - subnet: 172.28.0.0/24/credentials/.empty
gitで空ディレクトリを管理するためのファイル。.gitkeepと同じです。
/credentials/ssm (新規、GitHubリポジトリには含まれていません)
SSM接続用のパラメータファイルです。
/credentials/ssm[default] aws_access_key_id = AXXXXXXXXXXXXXXXXXXX aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/src/hoge/lib/Controller/DynamodbController.php
DynamoDBの接続テーブルの設定の取得を、直接設定ファイルを参照するような形から、ApplicationSettingクラスに移譲するように変更しました。
/src/hoge/lib/Controller/DynamodbController.phpclass DynamodbController { /** * @var string */ private $targetTable = null; ..(略).. private function getTable(): string { if (is_null($this->targetTable)) { $setting = ApplicationSetting::getInstance(); $this->targetTable = $setting->getSettingValue('dynamodb', 'table'); } return $this->targetTable; }/src/hoge/lib/Controller/StorageController.php
DynamoDBControllerと同様に、設定ファイルを参照するのではなく、ApplicationSettingクラスに移譲するように変更しました。
/src/hoge/lib/ApplicationSettings.php (新規)
設定ファイルの内容を取得し、$$で囲まれた文字列があれば、実際の値をSSMに問い合わせて値を返します。
なお、値はローカル、apcuでキャッシュされます。ソースは、前回にはMyContainerBuilderに含まれていた部分を抽出し、ssm関連処理を追加したものです。
/src/hoge/lib/ApplicationSettings.php<?php namespace Hoge; use Aws\Credentials\CredentialProvider; use Aws\Exception\AwsException; use Aws\Sdk; /** * Class ApplicationSetting * @package Hoge */ class ApplicationSetting { /** * 設定ファイルのパス */ const SETTINGS_YAML = '/usr/local/etc/myapp/settings.yml'; /** * 設定ファイルのパス */ const SSM_CREDENTIALS_PROFILE = 'default'; const SSM_CREDENTIALS_PATH = '/usr/local/etc/myapp/credentials/ssm'; /** * localにcacheするparameterのTTL */ const PARAMETER_TTL = 60; /** * @var ApplicationSetting */ private static $self = null; /** * @var array * 取得済みの値 */ private $replacedText = []; /** * @var array * settings.yamlの内容 */ private $settings = []; /** * @return ApplicationSetting インスタンスを取得 */ public static function getInstance(): ApplicationSetting { if (is_null(static::$self)) { static::$self = new static(); } return static::$self; } /** * ApplicationSetting constructor. */ protected function __construct() { $this->settings = yaml_parse_file(static::SETTINGS_YAML); } /** * 指定されたカテゴリ内のキーの値を取得する * @param string $category * @param string $key * @return mixed */ public function getSettingValue(string $category, string $key) { if (!isset($this->settings[$category]) || !isset($this->settings[$category][$key])) { return false; // TODO: 本当はException } $value = $this->settings[$category][$key]; if (strpos($value, '$$') === false) { return $value; // 置換の必要が無ければその値のまま返す } if (!preg_match('/^(.*)\$\$(.*)\$\$(.*)$/', $value, $matches)) { // {{KEY_NAME}}か? return $value; // 置換の必要が無ければその値のまま返す } return $matches[1] . $this->replaceValue($matches[2]) . $matches[3]; } /** * @param string $original * @return mixed */ private function replaceValue(string $original) { // このリクエストで置換済みの値か? if (isset($this->replacedText[$original])) { return $this->replacedText[$original]; } // apcuに保存されている値か? $fetchedValue = apcu_fetch($original); if ($fetchedValue) { $this->replacedText[$original] = $fetchedValue; return $fetchedValue; } // SSMサービスへの認証 $provider = CredentialProvider::ini( static::SSM_CREDENTIALS_PROFILE, static::SSM_CREDENTIALS_PATH ); $memoizedProvider = CredentialProvider::memoize($provider); // SSM Clientの取得 $ssmSetting = $this->settings['ssm']; $sdk = new Sdk([ 'endpoint' => $ssmSetting['endpoint'], 'region' => $ssmSetting['region'], 'version' => '2014-11-06', 'credentials' => $memoizedProvider ]); $ssm = $sdk->createSsm(); // settingsファイル内のすべての$$キーをリストアップ $keys = []; foreach ($this->settings as $settingArray) { foreach ($settingArray as $key => $value) { if (preg_match('/\$\$(.*)\$\$/', $value, $matches)) { // {{KEY_NAME}}か? $keys[] = $matches[1]; } } } // SystemManagerのパラメータストアから値を一斉に取得 try { $result = $ssm->getParameters([ 'Names' => $keys ]); } catch (AwsException $e) { return false; // TODO: 本当はException } foreach ($result['Parameters'] as $parameter) { $name = $parameter['Name']; $value = $parameter['Value']; if (!apcu_exists($name)) { apcu_store($name, $value, static::PARAMETER_TTL); } $this->replacedText[$name] = $value; } if (!isset($this->replacedText[$original])) { return false; // TODO: 本当はException } return $this->replacedText[$original]; } }/src/hoge/lib/MyContainerBuilder.php
前回のソースから、ApplicationSettingで処理しているYamlファイル関連処理などを削除したものです。
/src/composer.json, composer.lock
composer require ext-apcuにより、APCuモジュールを有効にしました。
composer.json{ .. (略).. "require": { "ext-apcu": "^5.1" .. (略).. }/.gitignore
credentialsディレクトリ処理を追加。
.gitignore# Created by .ignore support plugin (hsz.mobi) /.idea/ *.iml /credentials/* !/credentials/.emptyここまでのソース
こちらでどうぞ。
- 投稿日:2019-10-21T13:50:54+09:00
PocketMine-MPをConohaVPSで立てる(2019/10)
突如としてマインクラフトをしたくなる
突然にマインクラフトをしたくなった。理由はいらないのさ。
ハマり始めた人と一緒にやることになったのだが、ワールドを共有しかつ同時にオンラインするには以下の方法がある。
- Realmsにサブスクライブして400or900円/月(適当)で遊ぶ
- あんま金払いたくない。解約めんどくさそう。あんま回線良くなさそう。
- 同じWifiネットワークに接続して遊ぶ
- 使っているワールドをコピーしてローカルでごちゃごちゃすればまぁ仮想的に同時にワールドを編集することになるけどめんどくさい。ワールドで遊んでたらバッタリ会うみたいなワクワクがないのでナシ。
- 自分でサーバを立てる
- 一番めんどくさそう。でもマイクラもインフラにも触れるから楽しいことになりそう。
自分でサーバを立てることにする
そろそろ個人で真面目にサービスを作りたかったので、インフラの勉強がてら自分で立てることにした。
Conohaは使ったことがなかったが、ネットを見回した感じ一番マイクラのサーバになってそうで情報量が多そうだと判断したのでConohaをマイクラサーバとして選択した。
とりあえず以下の記事でサーバを立てる。自分はメモリ1GB SSD50GBのプラン(最大900円?)でイメージはOS/CentOsを選んだ。
ConoHaでサクっとWebサーバーを作ろう
ここからが長く、必要なミドルウェアを落として、PMMPのコードをコンパイルして。。などを繰り返すことになるが、なかなか上手くいかない。なんせ4,5年前の記事がほとんどで、前までは上手くいっていたんだろうなーみたいな記事がたくさんある。しかも自分は所詮アプリケーションエンジニアなのでデバッグしようにもキツイ。。
とりあえず、公式の手順も上手くいかないがこの手順に絞ってエラーを解決していこうと考えた。(この時点でだんだんとRealmsが選択肢に。。しかも俺結局900円払うことになってるじゃん。。)
このドキュメントにそって作るんだ!他は信頼しすぎるな!
公式手順通りに実行
1. 一般ユーザを作成
とりあえずrootユーザはよろしくないし、今後の手順でもrootユーザは怒られるので一般ユーザを作成してログイン。
ユーザを追加
adduser takeru
takeru
ユーザのパスワード変更
passwd takeru
takeru
を全権限をもつGroup(Wheel)に追加する。
sudo gpasswd -a takeru wheel
ユーザの切り替え
su takeru
rootディレクトリに権限がないのでホームディレクトリに移動。
cd
2.
wget -q -O - https://get.pmmp.io | bash -s -
を実行この時
Please Install ***
と出たら大人しくインストールする。
以下のように流せばおそらく必要なものは揃う。sudo yum install -y make; sudo yum install -y autoconf; sudo yum install -y automake; sudo yum install -y m4; sudo yum install -y bison; sudo yum install -y gcc-c++ sudo yum install -y git; sudo yum install -y cmake; sudo yum install -y libtool";コンパイルが終わったら、カレントディレクトリが以下のようになる。
3.php7をインストール
tarをダウンロードする
wget https://jenkins.pmmp.io/job/PHP-7.3-Aggregate/lastSuccessfulBuild/artifact/PHP-7.3-Linux-x86_64.tar.gz
tarを展開
tar xfz PHP-7.3-Linux-x86_64.tar.gz
成功すると
~/bin/php7/bin
にphp関連のファイルができているので確認する。4.実行してみる
~/
に戻る。./start.sh
を実行する。 すると./bin/php7/bin/php: error while loading shared libraries: libbz2.so.1.0: cannot open shared object file: No such file or directory
というメッセージが出て実行できない。 いろいろ検索してみるとこんなblogが見つかった。 くそ!ライブラリに不具合があったトラップか!と素直にsudo yum install -y bzip2-devel
する。 そのあとにsudo ln -s find /usr/lib64/ -type f -name"libbz2.so.1*" /usr/lib64/libbz2.so.1.0
する。5. 改めて実行してみる
ようやく
./start.sh
で実行できるようになり初期設定のフローが始まる。総括
10/21現在。Realmsで生活しています。
どうやらserver.propertiesもロクに読み込まれていない(spwanMobsとか,シード値とかのプロパティが効いていない)っぽいし、何より情報が少なすぎてこんなんならRealmsにするわ!ってなりました。
誰かのお役に立てたら幸いです。じゃ、ちょっくら木こりしてきます。
- 投稿日:2019-10-21T11:52:10+09:00
正規表現を使って特定の文字列から前を削除したい
$array = Array( 0 => 'ユーザー1|一郎', 1 => 'ユーザー2|次郎', 2 => 'ユーザー3|三郎', 3 => 'ユーザー4|四郎', );文字列「|」 から前を削除して
$array = Array( 0 => '一郎', 1 => '次郎', 2 => '三郎', 3 => '四郎', );のようにしたい。
やったこと
$array = preg_replace('/.*[|])/', '', $array);解説
preg_replace関数 preg_replace( $正規表現パターン , $置換後の文字列 , $置換対象の文字列 ) 正規表現 // 正規表現パターンの範囲を明示する(デリミタ)。 .* とにかくなんでもいい1文字がまったくないか、連続するかという意味 [|] |だけだとメタ文字になってしまうので[]で文字列化
- 投稿日:2019-10-21T09:57:55+09:00
contact form 7 でクッキー情報を送信する
contact form 7 でクッキー情報を送信する方法を試行錯誤しながら書いたので備忘録的な感じで書いていきます。
なぜやろうと思ったか
元々の機能としては投稿をブックマークしてそのブックマークをしたものをformで送信するというものを作成するつもりでした。
しかし、ブックマークするプラグインは溢れているのにも関わらず、それをformで送信する機能のついたプラグインは確認できませんでした。
それならいっそ作ってしまおうと、そういうことです。
functions.phpadd_action( 'get_header', 'fovorites_form'); function fovorites_form() { if(!is_NULL($_COOKIE["simplefavorites"])){ preg_match('/(?<=^.{27}).+/', $_COOKIE["simplefavorites"], $str); $splited = str_split($str[0]); $i = 0; $j = 0; $nos[0] = ''; while($splited[$j] != ']'){ if($splited[$j] == ','){ $i++; $nos[$i] = ''; } else{ $nos[$i] .= $splited[$j]; } $j++; } global $wpdb; $sql = ''; foreach ($str as $value) { $sql .= $value; $sql .= ','; } $sql = substr($sql, 0, -1); $result = $wpdb->get_results("SELECT post_title, guid FROM wp_posts WHERE ID IN ($sql)", ARRAY_A ); $i = 0; foreach ($result as $value) { $json[$i]['post_title'] = $value['post_title']; $json[$i]['post_guid'] = $value['guid']; $i++; } wp_enqueue_script('some_handle', 'jsファイルのパス', array( 'jquery' ), false, true); wp_localize_script('some_handle', 'object_name', $js_variable); } }全体像はこんな感じです。
jsファイルはmain.jsjQuery(function(){ if(jQuery('#documents').length != 0){ if(object_name){ var checkbox = jQuery('#documents input').html(); var input; for (var i = 0; i < object_name.length; i++) { input = '<input type="checkbox" name="' + 'checkbox-455[]' + '" value="' + object_name[i]['post_title'] + '" checked="checked"><a href="' + object_name[i]['post_guid'] + '">' + object_name[i]['post_title'] + '</a><br>'; jQuery('#documents').append(input); } } else { jQuery('#documents').append('<p>...</p>'); } } });