投稿日:

Hashはいいぞ Hash::map編

気が付いたら前の記事から1年半以上空いてしまいました。それだけみんな忙しかったといえばそうなのですが、ようやっと時間ができたので、何を書こうかなと思ったとき、そういえば近くの人にHash::map教えてないなって思い出したんです。

これ、最初のうちはかなり読みにくい印象あると思うんです。ラムダ関数慣れが要るという感じで…

ここまでくるとforeachと大差ないっちゃ大差ないところもあります。
Hash::reduceともなるとさらに差がなくて、正直どっちで書けばいいか自分でも迷います。

それでも、配列を「ループで一つ一つ確認する」というforeachの見え方と、
配列を「各要素にまとめて適用する」というmapの見え方ってやっぱり違うと思うんです。

// Model->find('all') したときありがちな配列
$data = [
    o => [
        'Color' => [
            'id'=>'1',
            'name'=>ゴールド',
            'red'=>'255',
            'green'=>'215',
            'blue'=>'0',
        ]
    ],
    1 => [
        'Color'=>[
            'id'=>'2',
            'name'=>'シルバー',
            'red'=>'192',
            'green'=>'192',
            'blue'=>'192',
        ]
    ],
    2 => [
        'Color'=>[
            'id'=>'3',
            'name'=>'ブロンズ',
            'red'=>'154',
            'green'=>'98',
            'blue'=>'41',
        ]
    ]
];

//色名と16進コードの配列を作る
$result = Hash::map($data,'{n}.Color',function($item){
    return [ 
        'name' => $item['name'],
        'hex_code' => sprintf("#%02x%02x%02x",$item['red'],$item['green'],$item['blue'])
    ];
});

/**

$result = [
    0 => [
        'name' => 'ゴールド',
        'hex_code' => '#ffd700',
    ],
    1 => [
       'name' => 'シルバー',
       'hex_code' => '#c0c0c0',
    ],
    2 => [
       'name' => 'ブロンズ',
       'hex_code' => '#9a6229',
    ],
];

*/

//やってることは以下のforeachとあんまり差はない
$result_foreach = [];
foreach($data as $color){
    $result_foreach[] = [
        'name' => $color['Color']['name'],
        'hex_code' =>  sprintf("#%02x%02x%02x",$item['Color']['red'],$item['Color']['green'],$item['Color']['blue'])
    ];
}

foreachとあんまり変わらないなら、なぜこういう書き方をするかと考察したとき、いくつか強みっぽいところはあります

深い階層からもパスで引っ張ってきてくれる
深い深い配列の奥にあっても、Hashのパス記法を使えば、何重foreachを使わずとも。
引っ張ってくる個数と結果の配列は要素個数が同じで、キーは0始まり連番
foreachだとソースを読まないとそういう結果の予想は付かない
ラムダ関数の中は独自スコープ
外側と変数名がかぶることを気にしなくていい。外側の変数を使いたいときはfunction($item)use($outside_var)みたいな感じで

なんだかんだ、使ってると手になじんでくるものです。

投稿日:

Hashはいいぞ Hash::combine編

去年からだいぶHash::なんとかを使い続けて、だいぶ手に馴染んできました。foreachのほうが分かりやすい場合は、素直にforeachを回したらいいんですが、select用のリストをfind結果の複数のカラムから作る、みたいなことにHash::combineを使えると非常に見通しが良いです。なんてったって「配列のこれをキーとして、これを値とする配列を作る」ですもの。

// Model->find('all') したときありがちな配列
$data = [
    o => [
        'Color'=>[
            'id'=>'960018',
            'name'=>'カーマイン',
            'red'=>'150',
            'green'=>'2',
            'blue'=>'24',
        ]
    ],
    1 => [
        'Color'=>[
            'id'=>'00896B',
            'name'=>'ビリジアン',
            'red'=>'0',
            'green'=>'136',
            'blue'=>'53',
        ]
    ],
    2 => [
        'Color'=>[
            'id'=>'434DA2',
            'name'=>'ウルトラマリン',
            'red'=>'67',
            'green'=>'77',
            'blue'=>'162',
        ]
    ]
];

// [id => 色名]
$result_1 = Hash::combine($data,'{n}.Color.id','{n}.Color.name');
/**
$result_1 = [
    '960018'=>'カーマイン',
    '00896B'=>'ビリジアン',
    '434DA2'=>'ウルトラマリン',
];
*/

// [id => 'Rスペース詰め3桁,Gスペース詰め3桁,Bスペース詰め3桁']
$result_2 = Hash::combine($data,'{n}.Color.id',['%3d,%3d,%3d','{n}.Color.red','{n}.Color.green','{n}.Color.blue']);
/**
$result_2 = [
    '960018'=>'150,  2, 24',
    '00896B'=>'  0,136, 53',
    '434DA2'=>' 67, 77,162',
];
※ 配列のキー側引数も配列でsprintf記法指定できる
*/

どっちも、フォームの選択肢を作るとき凄い役に立ちます。
パス記法に慣れると、いくつも組み合わせてprintfの指定をすれば配列のキーも値も自在に組めます。
もうforeachでぐるぐる検索結果を回しては文字列をこねくり回して…みたいなことはしなくていい!

単純に「これとこれとこれを取り出して配列を作りたい!」とき、使ってください。

投稿日:

「Effective JavaScript」かいつまみ -基礎編-

翔泳社の「Effective JavaScript」(David Herman著 / 吉川邦夫訳)の一章から、
重要そうな部分をピックアップして箇条書きで要約しました。
太字の部分は要約者の勝手な感想(?)です。
普段開発で使用しているPHPに関して主に追記しています。

第一章 JavaScriptに慣れ親しむ

項目2 JavaScriptの浮動小数点数を理解しよう

・JavaScriptでは、すべての数が倍精度浮動小数点数(いわゆるdouble)である。
・-9,007,199,254,740,992~9,007,199,254,740,992までの整数は全てdoubleで表現する事が可能。
・当然のように、浮動小数点数演算特有の問題が存在。

console.log(0.1 + 0.2); //0.30000000000000004

・少数を扱う際は一度整数にしてから演算して、結果を調整するほうがよさそう。

console.log((0.1 * 10 + 0.2 * 10)/10); //0.3

項目3 暗黙の型変換に注意しよう

・JavaScriptはほとんどの型エラーをスルーして実行してしまう。
・数値演算地の-、*、/、%は、計算を行う前に引数を数値に変換しようとする。
・ただ+だけは例外的な動きをする。
・以下例

console.log(3 + true); //4
console.log("2" + 3); //"23"

・真偽値の型強制。
・ifや||や&&のような演算子は、受け取った値を真偽値に型変換して解釈する。
・JavaScriptでは偽となる値は、false 0 -0 “” NaN null undefined の7つだけ。
・PHPの場合は、”0″ や [] もfalseになるので要注意。

項目5 型が異なるときに==を使わない

・==で値を比較する場合、項目3で既出の暗黙の強制によって両者が数値に変換されて比較される。
・===は型を考慮して比較。
・==のほうが便利な場合もあるのでついつい使ってしまう事もありますが、基本的には===で統一したほうがよさそう。
特にPHPの場合 var_dump(“hoge” == 0)  //結果:true などと直感に反するケースがある(最初が数字でない文字列は0に変換されるため)ので要注意。

項目7 文字列は16ビットの符号単位を並べたシーケンスとして考えよう

・Unicodeが16ビット内でおさまると考えられていた時代にJavaScriptは生まれたため、JavaScriptの文字列の要素は16ビットの符号単位である。(UTF-16形式)
・実際にはUnicodeには16ビットを超える文字も存在し(例えば辰吉じょう一郎のじょう・丈の右上に点)、それらは16ビットの要素2つで表現される。
・そのためUnicodeで16ビットを超える文字の場合は、length や charAt などで文字数と結果が一致しないことになる。

投稿日:

Imagickを利用するにあたって

PDFファイルの1ページ目をイメージファイルjpgに変換するサンプルです。
imagickのサーバーへのインストールは、このブログからは除きます。
当初

$im = new Imagick();
//画像を生成したいPDFを読み込む
$im->readImage('hoge.pdf');
//特定のPDFのページ 0が表紙
$im->setImageIndex(0);
//サムネイルサイズ 640pxに収める
$im->thumbnailImage(640, 640, true);
//シャープ
$im->sharpenImage(0, 1);
//生成
$im->writeImage('out.jpg');

$im->destroy();

を元に関数を作成したのですが、
この記述では、階層が深いPDFを指定した場合は、エラーになるという致命的な問題が
潜んでいます。
そこで、ソースを

$im = new Imagick();
//画像を生成したいPDFを読み込む
$image = file_get_contents('/var/www/xxxx/xxxx/hoge.pdf');
$im->readImageBlob($image);
//特定のPDFのページ 0が表紙
$im->setImageIndex(0);
//サムネイルサイズ 640pxに収める
$im->thumbnailImage(640, 640, true);
//シャープ
$im->sharpenImage(0, 1);
//生成
$im->writeImage('/var/www/xxxx/xxxx/out.jpg');

$im->destroy();

で動くようになりました。
しかし、透明の箇所が真っ黒になるという不具合がありなおかつ
複数ページあるPDFと1ページのみのPDFでは動きが違うという問題にも直面
この難題を解決するのに、3時間程度要しました。
解決済みのサンプルがこちらです。

        $im = new Imagick();
        //画像を生成したいPDFを読み込む
        $image = file_get_contents('/var/www/xxxx/xxxx/hoge.pdf');
        $im->readImageBlob($image);
        $totalPage = $im->getImageScene();

        //サムネイルサイズ 640pxに収める
        $im->thumbnailImage(640, 640, true);
        //シャープ
        $im->sharpenImage(0, 1);
        // バックグラウンドを白にする。
        $im->setImageBackgroundColor('white');
        //特定のPDFのページ 0が表紙
        if ($totalPage != 0) {  // 複数ページの場合
            $im->flattenImages()->setImageIndex(0);
        } else {    // 単一ページの場合
            $im = $im->flattenImages();
        }
        //生成
        $im->writeImage('/var/www/xxxx/xxxx/out.jpg');
        $im->destroy();

以上です。

投稿日:

公開フォルダーにないPDFファイルを表示する方法

PDFファイルアップロードの機能を作成する場合、公開フォルダでない場所にアップロードして、
そのPDFファイルをaタグで別タブに表示したい場合があります。
そこで、その場合のサンプルを記載します。

ファイル名やIPアドレスはご使用の環境にあわせてください。
main.php(表示したいファイルをフルパスでGETで渡す。)




download

表示したいフォルダのファイルをオープンしてPDFとして表示させるためのphpファイル
pdfdisp.php


以上です。試してみてください。

投稿日:

アウトオブ眼中

ボタンをクリックしたら隠しdivの中のフォームをコピーして、
通し番号を発行して表に追加する、みたいな処理をよく書きます。
通し番号の入る場所は仮にダミー文字列(今回はQQQQ)を置いておいて、ボタンが押されたときの処理でまとめて書き換える感じです

何度か書いているうちにdata-属性に行番号を持たせることで、:lastから現在の最大行番号を把握し、
次の行番号を探る、なんてことをやるようになりました。
隠しフィールドよりずいぶんすっきり見えます。attrで持たせると文字列で保存されるので、取得時に0を引いて数値に解釈しなおすことをお忘れなく。 …【1】
追加するときだけじゃなくて削除にも役立ちます。$(“tr[data-no=’行番号’]”,”#fueruTable”).remove() で行ごと削れます。

ところで、これはこれで面倒なことが一つあって、これを実際submitしたとき。

QQQQはダミーフィールドなので使わないはずだし、何よりdataに関してforeachとか回すなんてことになると邪魔。
サーバー側にunset($_POST[‘data’][‘QQQQ’])を噛ませて長いことやってました。

ある時、formの閉じタグ位置を間違えて、隠しフィールドをformの外に追いやってしまったんです。
それでsubmitしたらどうなったかというと

QQQQがいない。
これはこれで何か問題がある気もするけれど、ただテンプレートとして置いておきたいだけで、そもそもsubmitする気のないフィールドは
そもそもform内に置かなければよかった!

考えてみれば当たり前のことなのに、長いこと気づかなかったのでついブログに書き出してしまいました。
submitしないフォームはformタグの外、アウトオブ眼中!他の事例でも使えたら使うつもりでいます。

投稿日:

phpexcelのEXCEL列表記のテーブル作成

phpExcelにて列を指定して値を表示する際に、
EXCEL列の配列になったものをインデックスで指定して表示できれば、
横に長い表を作成する際に役に立つと思いこのテーブルを作成しました。


// EXCELの横座標を設定する。
$col1 = array("","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z");
$col2 = array("","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z");
$col3 = array("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z");
$col4 = array("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z");
$colArr = array();
$idx = 0;
foreach ($col1 as $val1) {
    if ($val1 == "") {
        foreach ($col2 as $val2) {
            foreach ($col3 as $val3) {
                $colArr[$idx] = $val1 . $val2 . $val3;
                $idx++;
            }
        }
    } else {
        foreach ($col4 as $val2) {
            foreach ($col3 as $val3) {
                $colArr[$idx] = $val1 . $val2 . $val3;
                $idx++;
            }
        }
    }
}
$col = 0;
$row = 1;
$sheet->setCellValue("{$colArr[$col]}{$row}", "日付");  // A1に「日付」表示
$col = 26;
$sheet->setCellValue("{$colArr[$col]}{$row}", "日付");  // AA1に「日付」表示
$col = 702;
$sheet->setCellValue("{$colArr[$col]}{$row}", "日付");  // AAA1に「日付」表示
$col = 1378;
$sheet->setCellValue("{$colArr[$col]}{$row}", "日付");  // BAA1に「日付」表示
$col = 16383;
$sheet->setCellValue("{$colArr[$col]}{$row}", "日付");  // XFD1に「日付」表示 ここがEXCEL横の最大値です。

PHPEXCELにて、列表記をインデックスで扱いたい時の配列です。

投稿日:

JSONで画像データを送信する方法

JSONで単純にファイル名を渡しても、当然ファイルのデータは送ることができません。
そのような場合は、base64_encodeを使って送りましょう。
このサンプルでは、JAVAScriptの時点でPHPのbase64_encodeを使用してデータ化した文字列を送り
受けて側で、base64_decodeしてファイルへ保存で渡すことが可能です。
続きを読む JSONで画像データを送信する方法

投稿日:

Conohaのオブジェクトストレージを試す

システムによって写真を大量に上げられる事があり他のシステムへの影響が出てきたのもあり今回プログラムから上げる画像やPDF等のファイルをシステムが動いているサーバーからオブジェクトストレージサーバーに置くことになりました。
Conohaのオブジェクトストレージを試した際につまずいた事等を書いていこうと思います。
続きを読む Conohaのオブジェクトストレージを試す

投稿日:

array_filterの邪道?

まだまだ修行中です。最近は二度手間をできる限り抑えたいので
PHPのリファレンスを読み込んでみて、こんな関数既にあるんだなっていうのを見つけているところです。
しっかり覚えていなくても、頭の片隅に引っかかっていればいずれ使えるよな…という魂胆です。

そんな中でarray_filterの使い方がどうしても気になりました
http://php.net/manual/ja/function.array-filter.php

配列のキーや値を関数に通して、trueが返ってくるものだけを配列に詰め込んで返す、
使う関数は関数名を指定しても無名関数を渡してもいい、と。

ここで、手元のリファレンス書籍には「一次元配列のみに対応」と、
こちらのオンラインマニュアルには書いていない記述があったのがどうにも引っかかったんです。

「なぜ渡したらダメなのか、あるいはダメだったのか」

とりあえず、php5.4環境で渡してみてどうか調べてみました。

$order=array( 'Alice'=>array(
		'coffee'=>array(
			'milk'=>false,
			'sugar'=>true,
		),
	),
	'Bob'=>array(
		'coffee'=>array(
			'milk'=>true,
			'sugar'=>true,
		),
	),
	'Chalie'=>array(
		'coffee'=>array(
			'milk'=>true,
			'sugar'=>false,
		),
		'cake'=>array('chocolate')
	),
	'Deborah'=>array(
		'coffee'=>array(
			'milk'=>false,
			'sugar'=>false
		),
	),
	'Eve'=>array(
		'doughnut'=>array(
			'plain','strawberry'
		),
	),
	'Fred'=>null,
	'George'=>array(
		'coffee'=>array(
			'milk'=>true,
			'sugar'=>false
		)
	),
);

function order_filter($v){
	/* TODO:ここに検索条件を書く */
}

var_dump(array_filter($order,'order_filter'));

ここのorder_filter($v)をいくつか変えて出力を調べてみました。

//コーヒー注文
return isset($v['coffee']);

/* Output:
array (size=5)
  'Alice' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean false
          'sugar' => boolean true
  'Bob' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean true
          'sugar' => boolean true
  'Chalie' => 
    array (size=2)
      'coffee' => 
        array (size=2)
          'milk' => boolean true
          'sugar' => boolean false
      'cake' => 
        array (size=1)
          0 => string 'chocolate' (length=9)
  'Deborah' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean false
          'sugar' => boolean false
  'George' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean true
          'sugar' => boolean false
*/
//砂糖有りコーヒーまたはコーヒー注文無し
return (isset($v['coffee']['sugar'])&&$v['coffee']['sugar'])||empty($v['coffee']);

/* Output:
Notice:Undefined index: coffee in (file) on line 43 //43行目=上の条件を書いた行

array (size=4)
  'Alice' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean false
          'sugar' => boolean true
  'Bob' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean true
          'sugar' => boolean true
  'Eve' => 
    array (size=1)
      'doughnut' => 
        array (size=2)
          0 => string 'plain' (length=5)
          1 => string 'strawberry' (length=10)
  'Fred' => null
*/
//砂糖有りコーヒーまたはコーヒー注文無し 改
return (isset($v['coffee']['sugar'])&&$v['coffee']['sugar'])||empty($v['coffee']);

/* Output:
array (size=4)
  'Alice' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean false
          'sugar' => boolean true
  'Bob' => 
    array (size=1)
      'coffee' => 
        array (size=2)
          'milk' => boolean true
          'sugar' => boolean true
  'Eve' => 
    array (size=1)
      'doughnut' => 
        array (size=2)
          0 => string 'plain' (length=5)
          1 => string 'strawberry' (length=10)
  'Fred' => null
*/

結論としては、「キーの存在」を追加して調べる必要がある(ない場合noticeが出る)…みたいなところです。
この試行だけでは結局「なぜ渡したらダメなのか」という理由はわかりませんでした。
もっと未定義みたいな挙動が出てくるかと思ったら、そういうわけでもなく。
あるいは、バージョンが上がったことで渡せるようになったとか、そういうことなのかもしれないです。
(手元の書籍は5.1ぐらいに対応するよう書いていた9年以上前のものだからあり得る話)

可読性で言えば使わずにforeachループ回すのと五分五分、ってところかなと思います。
array_filterが何をしている関数かピンとこないと読みづらい、みたいな。

他の関数についても、気になったら実験を進めます。
それがリファレンスを頭の片隅に置くために役立つ気がしています。