20191021のPHPに関する記事は8件です。

メモ

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 .

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 foreachCreating default object from empty valueあたりはよく見かけるのではないかと思います。
これらはPHP8では例外になって完全に動かなくなるので注意しましょう。

それ以外でも、ゆるふわぺちぱーに対する締め付けは年々厳しくなる一方で、彼らの肩身はどんどん狭まりつつあります。
かつてはPHP以上にアバウトで破壊と慈悲の混沌だったJavaScript界も、最近は型に嵌まっていないゆるふわJavaScripterを完全排除する流れができあがっています。
やがて彼らの居場所が完全に失われてしまったとき、難民たちはいったいどこに行くのでしょうね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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内に Userrole_code 属性のEnum値を取得するアクセサを定義します(LaravelのEloquentでは getHogeHogeAttribute などのメソッドを定義することで、 $user->hoge_hoge のように取得することができるようになります)。

まず、 User に先程作成したEnum Role を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 にも対応する区分値が代入されるようにします。

setRoleAttribute
public 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を取得できます。

getRoleLabelAttribute
public function getRoleLabelAttribute()
{
    return $this->role->getLabel();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分で立てる、オープンソースのテストケース管理システムをまとめてみた

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月にリリースされている中、今なお対応していないことを鑑みると、開発リソースはあまりなさそうです。

Testopia - Mozilla | MDN

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系)では利用できないかも知れません。

Redcase - Plugins - Redmine

まとめ

テストケース管理は多数あるのですが、2008年くらいをピークとして様々なソフトウェアが作られ、その内幾つか(TestLinkやKiwi TCMSなど)が継続的に開発を続けているといった雰囲気があります。技術領域としては変革的なものが出てきていないのかも知れません。

しかし、テスト管理を今なおExcelで行っているチームも多いと言います。操作性が悪かったり、共有もしづらく、お勧めはしません。遠隔地とデータを共有したりすることを考えればWebブラウザベースの方が良いでしょう。クラウドベースですぐにテスト管理をはじめたい場合はQualityForwardをぜひお試しください!

QualityForward

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.yml
userdb:
  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.yml
version: '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.php
class 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

ここまでのソース

こちらでどうぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PocketMine-MPをConohaVPSで立てる(2019/10)

突如としてマインクラフトをしたくなる

突然にマインクラフトをしたくなった。理由はいらないのさ。
ハマり始めた人と一緒にやることになったのだが、ワールドを共有しかつ同時にオンラインするには以下の方法がある。

  1. Realmsにサブスクライブして400or900円/月(適当)で遊ぶ
    • あんま金払いたくない。解約めんどくさそう。あんま回線良くなさそう。
  2. 同じWifiネットワークに接続して遊ぶ
    • 使っているワールドをコピーしてローカルでごちゃごちゃすればまぁ仮想的に同時にワールドを編集することになるけどめんどくさい。ワールドで遊んでたらバッタリ会うみたいなワクワクがないのでナシ。
  3. 自分でサーバを立てる
    • 一番めんどくさそう。でもマイクラもインフラにも触れるから楽しいことになりそう。

自分でサーバを立てることにする

そろそろ個人で真面目にサービスを作りたかったので、インフラの勉強がてら自分で立てることにした。
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";

ミドルウェアが足りてないと↓
スクリーンショット 2019-10-21 13.09.41.png

コンパイルが終わったら、カレントディレクトリが以下のようになる。
スクリーンショット 2019-10-21 13.28.43.png

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にするわ!ってなりました。
誰かのお役に立てたら幸いです。じゃ、ちょっくら木こりしてきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

正規表現を使って特定の文字列から前を削除したい

$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文字がまったくないか、連続するかという意味
[|] |だけだとメタ文字になってしまうので[]で文字列化

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

contact form 7 でクッキー情報を送信する

contact form 7 でクッキー情報を送信する方法を試行錯誤しながら書いたので備忘録的な感じで書いていきます。

なぜやろうと思ったか

元々の機能としては投稿をブックマークしてそのブックマークをしたものをformで送信するというものを作成するつもりでした。

しかし、ブックマークするプラグインは溢れているのにも関わらず、それをformで送信する機能のついたプラグインは確認できませんでした。

それならいっそ作ってしまおうと、そういうことです。

functions.php
add_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.js
jQuery(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>');
    }
  }
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む