読者です 読者をやめる 読者になる 読者になる

おじゃまぷよ系エンジニアメモ

主にスマホネイティブ関連Tips。たまにWebも書きたい。お腹はぷよぷよ

phpのsession_decodeはarray_mergeされる

あるときphpでscript書いてるときにハマった。 session_decode()したら$_SESSIONに配列がまるっと置き換えられると思ってたらどうも違ったらしい

<?php
session_start();
$_SESSION = ['a' => 'b'];
$data1 = session_encode();
$_SESSION = ['c' => 'd'];
$data2 = session_encode();

$_SESSION = [];
session_decode($data1);
session_decode($data2);
print_r($_SESSION);

session_decodeをした結果

(  
    [c] => d  
)  

こうなると思ってたら実際は

(  
    [a] => b  
    [c] => d  
)  

こうなった。

<?php
session_start();
$_SESSION = ['a' => 'b'];
$data1 = session_encode();
$_SESSION = ['a' => 'd'];
$data2 = session_encode();

$_SESSION = [];
session_decode($data1);
session_decode($data2);
print_r($_SESSION);

同じキーを入れると

(  
    [a] => d  
)  

こうなって見事にキーの中身がbからdに上書きされた。
これは騙される。完全にまるっと置き換えられると思ってたけど、そうじゃなかった。

今年一年のエンジニアライフを振り返ってみる

歳を取るたびに1年が短くなっていくというのどうやら本当のようで、振り返れば1年があっという間にすぎさってしまっています。
そんな短い1年で自分が一体エンジニアとしてどういうことができたのかちょっと振り返ってみたくなったので何をやったのかバーっと思い出してみます。

初のiOSアプリのリリース

masahide318.hatenablog.jp

Icros

Icros

  • Masahide Takahata
  • Social Networking
  • Free

今年一番まともなアウトプットってこれだけかもしれないです……
まだこれをやっていたころはswift2なのでswift3で何かiOSアプリを改めて作りたい。
またクラス構造も最近流行りのMVVMの構造とかは無関係にViewControllerにすべてのロジックを詰め込んでいるのでもはや何書いてるのかわからないレベル。

kotlinでAndroidアプリ作る

github.com

play.google.com

retrofitとRxJavaを使いたかったから無理やり作った。
tokenのリフレッシュ周りがクソ実装のまま放置してしまった。時間があったら直すは結局直さない。仕事でも同じで確実に技術的負債を積んでいく。ただこれは負債を返すより早くリリースすることを目標にしてたので正しい判断だったのだ……。
kotlinでAndroid開発するのは個人的にアリだとは思うが、Instant Runやkapt周りが怪しいので変にハマったときに解決する、もしくは便利なライブラリを諦める覚悟がない場合は素直にJavaでやるほうがいいかな。 個人の開発であってもクラス設計などしっかり考えて技術的負債を残さず開発するクセつけないと、実務でついえいやで実装してしまうので注意する。

kotlinでDIライブラリ書く

github.com phpのDIライブラリのpimpleの模倣版。AndroidのApplicationクラスで必要なサービスクラスの生成をコンテナにつめて使ってみる妄想をしたが、まだ実際に使ってない。Dagger2あるし…
ただkotlinで何か書いてみたかった欲を発散させたなにかになった。

Webエンジニアしてphpフレームワークさわる

具体的なアウトプットはないが、仕事上ネイティブアプリとしての仕事がまわってこなくなりWebの開発がメインになったので真面目にWebの基礎からやろうと勉強した。 もともとあるソースにつきたしするのは出来たが一からフレームワークの動きを追うっていう機会があまりなかったのでLaravelとSlim3とdietcakeをVagrant上に構築して軽く動かすことだけした。 Slim3は実務でも使うことがあり、軽量でなかなか使いやすい。
そもそもnginxの設定やmysqlやRedisのインストールも怪しかったのでAnsibleを使ってそれらもろもろ一発で入るようにした
deploy方法にcapistrano入れて実際にさくらサーバーにdeployしてみたりと、Webアプリケーションエンジニアとしての基礎を改めて学んだ。
これらの基礎を改めて学んで何か実務でも障害が起こったときに問題の切り分けなど判断がスムーズにできるようになった気がする。
ネイティブアプリのAPIを作るときもさくっとサンプル作ったりできるようになったので本当に色々と立ち回りやすくなった。

品質や設計、テストについて考えるようになった

このあたりについてはまた別の機会に書いてみようかな。
今まで品質がいいとはバグが少ないことではない。テストコードはただ書けばいいというものではないと改めて考えされた。
テストで何を保証したいのかなどテストを設計しないと,、意味もなくただカバレッジ100%にするってだけではただ開発の足かせにしかならないなと学んだ。
最近はソフトウェアの品質やテスト計画など、ソフトウェアの品質はいかにして高めるのかという本ばかり読んでる。
このあたりはまだ自分も勉強中なのでしっかり身につけて、個人の開発というよりチームの開発につなげていきたい。

まとめ

広く浅く色んな技術に触れた1年だった。 ひっそりと個人でネイティブアプリをやりつつ、仕事ではWebアプリをやるという少し前に流行ったフルスタックエンジニア(笑)になりつつある。自分の軸となる技術を確立されせて、あの技術はこの人ってなれるぐらい成長したい。 とりあえずswift3で何かリリースするぞ!Vue.jsもさわりたいし!やりたいことが多すぎるっ!

後仕事でもネイティブアプリやりたいなぁ……

来年の抱負

  • swift3でiOSアプリ作る
  • Androidアプリ何か作る
  • 英語のドキュメントちゃんと読めるように英語もっと勉強する
  • 健康診断でオールAをとる

これら頑張っていきたい。エンジニアとして周りの人に負けないように研鑽を積んでいく一年にする。

phpでRedisのlRangeがPhakeで差し替えられない罠

ある日phpのRedisをPhakeでmockにしてテストしていたときにlRangeがなぜか差し替えられない状況に陥った。
ソースコードは以下のような感じ

<?php
class HogeTest extends \PHPUnit_Framework_TestCase{
    /** @var  \Redis */
    private $target;

    public function setUp(){
        $this->target = \Phake::mock(\Redis::class);
    }

    public function testHoge(){
        \Phake::when($this->target)->get("key")->thenReturn("value");
        $this->assertEquals("value", $this->target->get("key")); //OK
    }

    public function testLGet(){
        \Phake::when($this->target)->lRange("key", 0, -1)->thenReturn(["a","b"]);
        $this->assertEquals(["a","b"],$this->target->lRange("key", 0, -1) ); // NG
    }
}

functionのgetは期待通り差し替えられているのにlRangeだけはどうしてもnullが帰ってきてしまう。
phpredisを調べてみると、

github.com

このソースを見るとlRangeはlGetRangeのエイリアスとして存在しているらしい。
つまりlRangeは本来存在しないfunction…
ということでlGetRangeに置き換えてみると無事にfunctionを差し替えることが出来ました

<?php
class HogeTest extends \PHPUnit_Framework_TestCase{
    /** @var  \Redis */
    private $target;

    public function setUp(){
        $this->target = \Phake::mock(\Redis::class);
    }

    public function testLGetRange(){
        \Phake::when($this->target)->lGetRange("key", 0, -1)->thenReturn(["a","b"]);
        $this->assertEquals(["a","b"],$this->target->lGetRange("key", 0, -1) ); //OK
    }
}

phpのエクステンションでエイリアスとして存在するfunctionは置き換えられないということに気をつけて、次もしPhakeで差し替わらないfunctionあったときはエイリアスを疑おう

モジュール結合度について復習した

プリンシプルオブプログラミング読んでなんとなく思い立ってモジュール結合度について考え直した
昔、基本情報技術者試験受けたときに勉強したときはただ暗記しただけだったけど改めて結合度について学んでみると「なるほどな…」って思うことが多かったので実際にクラスとか書いてまとめてみる

モジュール結合度とは

モジュール同士の関係の密接さを表す尺度……
と書いてあるがちょっと意味が難しい。クラス同士の影響度と言ったほうがわかりやすい。クラスの処理や値を変更すると他のクラスに影響を及ぼすものが結合度が高いということだろう。 モジュール結合度には以下の6種類があり、レベルが高いほど結合度が低い。

  • レベル6データ結合
  • レベル5 スタンプ結合
  • レベル4 制御結合
  • レベル3 外部結合
  • レベル2 共通結合
  • レベル1 内容結合

レベル6データ結合

スカラー型のデータを引数として渡す結合。

<?php
class ModuleA {
    public function add($a,$b){
        return $a + $b;
    }
}

class ModuleB{
    public function doService(){
        $moduleA= new ModuleA();
        $result = $moduleA->add(1,2); //データ結合
        // doSomething
    }
}

ModuleAもModuleBもお互いに内部の実装に干渉しない(ブラックボックス化されている)ので、安心してプログラムの中を変更できる。
結合度が低いと言える。

ModuleAはModuleBとデータ結合してる状態。

レベル5 スタンプ結合

共通域にないデータ構造を受け渡しするような結合形態。
スカラー型ではなく、オブジェクトを引数として渡すと解釈して良さそう。

<?php
class User{

    private $id;
    private $name;

    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getName() {
        return $this->name;
    }

    public function setName($name) {
        $this->name = $name;
    }


}

class ModuleA {
    /**
     * @param $user User
     * @return string
     */
    public function doService($user){
        return " name : ".$user->getName();
    }
}

class ModuleB{
    public function doService(){
        $user = new User(1,"masahide");
        $moduleA = new ModuleA();
        $result = $moduleA->doService($user);
        // doSomething
    }
}

ModuleAもModuleBもお互いに内部の実装に干渉しない。
スタンプ結合の場合受け渡すデータの一部を使用しないことがある。
Userのnameは使うけどidは使わないというような状態。

レベル4 制御結合

呼び出し側のモジュールが、呼び出し先のモジュールの制御を指示するデータをパラメーターとして渡す方式。
よくある引数にflgとかtypeとかstatusを渡して呼び出し先の処理を制御するパターンと解釈してよさそう。

<?php
class ModuleA {
    public function doHoge($type){
        if($type === 1){
            //doSomething
        }elseif ($type === 2){
            //doSomething
        }elseif ($type === 3){
            //doSomething
        }else{
            //doSomething
        }
    }
}

class ModuleB{
    public function doService(){
        $moduleA = new ModuleA();
        $moduleA->doHoge(2);
        // doSomething
    }
}

ModuleBがModuleAを使用するときに、typeを渡すことによって内部の処理を制御してる。
データ結合や、スタンプ結合と違いモジュールの使用側が使用するモジュールの内部の実装を知っている必要があるので結合度が高くなると解釈した。

レベル3 外部結合

外部宣言したデータを共有したモジュール間の結合形式。
書籍や記事によってはグローバル変数のことであったり、publicな変数であったりと少し表現が違っている。
基本情報技術者試験に則って、グローバル変数の値を参照するパターンを外部結合としてみる。

<?php

$GLOBALS['hoge'] = "hoge";

class ModuleA {
    public function doService(){
        echo "moduleA : ".$GLOBALS['hoge'];
    }
}

class ModuleB{
    public function doService(){
        echo "moduleB : ".$GLOBALS['hoge'];
    }
}

こんな感じかな… グローバル変数なのでどこで書き換えれるかわからず、書き換えられるとModuleA,Bともに影響を受けるので結合度は高い。

レベル2 共通結合

これもグローバル変数をモジュール間で共有するパターンだが、外部結合との違いは、データ結合とスタンプ結合の違いと同じように
データ構造を共有して不必要な値までも共有しているということ

<?php

$GLOBALS['user'] = new User(1,"masahide");

class User{
    private $id;
    private $name;

    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function getId() {
        return $this->id;
    }
    public function setId($id) {
        $this->id = $id;
    }
    public function getName() {
        return $this->name;
    }
    public function setName($name) {
        $this->name = $name;
    }
}

class ModuleA {
    public function doService(){
        echo "moduleA : ".$GLOBALS['user']->getName();
    }
}

class ModuleB{
    public function doService(){
        echo "moduleB : ".$GLOBALS['user']->getName();
    }
}

これもグローバル変数を共有しているのでどこで書き換えれるかわからず、さらに本来不必要なデータまで共有している分外部結合よりさらに高くなる

レベル1 内容結合

モジュール同士の命令の一部を共有したり、外部宣言していないデータを直接参照したりしている状態
カプセル化できてないクラスや、リフレクション使って無理やり命令の一部を使っている状態と言ってよさそう

queryパラメーターをPHPで暗号化して渡してJavaで復元したい

とあるPHPで書かれたWebサービスAから、とあるJavaで書かれたWebサービスBにパラメーターを暗号化してqueryパラメーターで渡したいときがあったのでその時のメモ

PHP側の暗号・復号化を行うクラス

<?php
class MyCipher {
    const SECRET_KEY = "1234567890123456"; //Javaと共通の秘密鍵。ランダウな文字列16桁
    const IV_PARAMETER = "1234567890123456"; //Javaと共通のIV。ランダムな文字列16桁
    const METHOD = "AES-128-CBC"; //256のほうが安全だが、Java側がデフォルトで256使えないので128にしてる

    /**
     * 受け取った文字列を暗号化しBase64+URLエンコードして返す
     * @param $originalStr
     * @return string
     */
    public static function encrypt($originalStr) {
        return urlencode(openssl_encrypt($originalStr, self::METHOD, self::SECRET_KEY, false, self::IV_PARAMETER));
    }

    /**
     * Base64+URLEncodeされている暗号化文字列を元の文字列に復号する
     * @param $encryptStr
     * @return string
     */
    public static function decrypt($encryptStr){
        return openssl_decrypt(urldecode($encryptStr), self::METHOD, self::SECRET_KEY, false, self::IV_PARAMETER);
    }
}

$encrypt = MyCipher::encrypt("abcdef"); //JxdRLkrOq5SDY9YMcUSUhg%3D%3D
$decrypt = MyCipher::decrypt($encrypt); //abcdef
echo $decrypt === "abcdef"; //復号される

Javaの暗号・復号化を行うクラス

public final class MyCipher {

    private static final String SECRET_KEY = "1234567890123456";// 秘密鍵は16文字で。PHP側と同じものを指定する
    private static final byte[] IV_Parameter = "1234567890123456".getBytes();//IVの値。PHP側と同じものを指定する
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";// 暗号化方式

    /**
     * 文字列を16文字の秘密鍵でAES暗号化してBase64+URLEncodeした文字列で返す
     */
    public static String encrypt(String originalSource) throws Exception {
        Cipher cipher = initCipher(Cipher.ENCRYPT_MODE);
        //文字列をバイト列に変換
        byte[] originalBytes = originalSource.getBytes();
        //暗号化後のバイト列取得
        byte[] encryptBytes = cipher.doFinal(originalBytes);
        //バイト列を文字列にして返す
        return URLEncoder.encode(Base64.getEncoder().encodeToString(encryptBytes),"UTF-8");
    }

    /**
     * Base64+URLEncodeされたAES暗号化文字列を元の文字列に復元する
     */
    public static String decrypt(String encryptStr) throws Exception {

        //暗号化されたmd文字列からバイト列を取得
        //JavaのUrlDecoderは「+」を半角スペースに変えてしまうので、「%2B」に置換する
        byte[] encryptBytes = Base64.getDecoder().decode(URLDecoder.decode(encryptStr.replace("+", "%2B"),"UTF-8"));
        Cipher cipher = initCipher(Cipher.DECRYPT_MODE);
        //復号化後にバイト列取得
        byte[] originalBytes = cipher.doFinal(encryptBytes);
        //文字列にして返却
        return new String(originalBytes);
    }

    private static Cipher initCipher(int method) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(method,new SecretKeySpec(SECRET_KEY.getBytes(),"AES"),new IvParameterSpec(IV_Parameter));
        return cipher;
    }
}
public class Sample {
    public static void main(String[] args) throws Exception {
        String encrypt = MyCipher.encrypt("abcdef");
        System.out.println(encrypt); //JxdRLkrOq5SDY9YMcUSUhg%3D%3D
        System.out.println(MyCipher.decrypt(encrypt)); //abcdef
        //復号化されてる
    }
}

これでPHPJavaで相互に値を受け渡しできるようになりました。 秘密鍵と初期ベクトルが外部に漏れない限りは復号できないと思われるので、まぁ十分に安全かと思います。
ただ不安はつきものなのであまり重要なデータは別の方法で受け渡ししたほうが良いかもしれない。

参考URL : stackoverflow.com

http://itmemo.net-luck.com/java-aes/

日々是妄想 : 初期化ベクトルってなに?(解決編)

kotlinでワンソースのDIコンテナ試しに書いた

やっと仕事落ち着いてきたので軽く何かkotlin書きたいなあと思ってモヤモヤして、そういえば以前からDagger2も取り入れてAndroidアプリ開発したいなぁと思っていたが
なんかDagger2の使い方がややこしくて、逆に複雑度が増す感じがして個人で小規模な開発には正直メリットを見いだせなかった。純粋に勉強不足というものもあるがもっとゆるーいDIコンテナ欲しいなと考えたり他のDIコンテナってどんな感じなんだろうと思って色々調べてるうちに、PHPの軽量なDIコンテナのPimpleに出会った
なるほど……オブジェクトを生成する関数をDIコンテナに配列で保存しておいて必要になったらそのコンテナに保存しておいた関数でオブジェクトを生成するという感じか
これぐらいゆるい感じで軽量なら同じようなことがkotlinでもできそうだなと思い立ってとりあえず書いた

GitHub - masahide318/Kontainer

koRegister(String,()->Any)でオブジェクトの生成方法を保存しておき、koInject(String)でオブジェクトを生成して返す koInjectSingleton(String)で取得すれば、singletonとしてオブジェクトを取得できる 使い方はReadmeにも書いてある通り以下の様な感じ

class SampleObj(val id: Int, val name: String) {
}

koRegister("sample",{SampleObj(1,"hoge")})

//inject with new Object
val sampleObj1:SampleObj = koInject("sample")
val sampleObj2:SampleObj = koInject("sample")
//別のインタンスとして取得できる
println(sampleObj1 === sampleObj2) //false

//inject with singleton
val singleton1:SampleObj = koInjectSingleton("sample")
val singleton2:SampleObj = koInjectSingleton("sample")
//シングルトンとして同一オブジェクトが帰る
println(singleton1 === singleton2) // true

Androidの開発で果たして使えるのか、恐らくApplicationクラスのonCreateでサービスクラス系をこいつにぶち込む感じになるんだろうが
まぁ試しに使って見ながら余裕があれば色々改良してみよう

knockout.jsでajaxでAPI叩いてモーダル表示分け

とあるqueryパラメーターによって表示するモーダルの内容を変えるのをどうしようかと考えた結果
jQueryajaxAPIを叩いて、受け取ったレスポンスをKnockout.jsを使ってデータバインディングする形に落ち着いた。 モーダルを1つふやすのに、api,js,htmlの3つのファイル修正加えないといけないけど、基本的にif文なり加えて増やせばいいだけなので
そんなに複雑にならないであろうと思っている。 表示、非表示をもう少しうまく切り替えたいけど…

続きを読む