ImageMagickでのPNGファイル生成の高速化

 
こんにちは岡野です。
弊社サービスではImageMagickでPNGファイルを生成することが多いのですが、高速化するための設定を調査したので共有します。
 

環境

ImageMagick 6.9.12-84 Q8
Ubuntu 22.04
 

調査内容

PNGファイル出力時の圧縮設定を変更した際の出力時間を調査しました。なおこの解説記事を参考にしています。

圧縮方法を設定する-quality引数を0~99で変えながら実行した際の時間を数回計測しました。

使用したコマンドは
$ convert logo: -resize 1024×1024! logo.png
$ rm log ; for q in `seq 0 1 99` ; do /usr/bin/time -f %e:$q -a -o log convert logo.png -quality $q png32:logo-$q.png ; done
$ sort log

で、結果は以下です。
11,12,13,14,21,22,23,24,31,32,33,34は0.05秒
41,42,43,44,51,52,54は0.06秒
以降、省略

ファイルサイズは以下の通りでした。
$ ls -l logo-[123][1234].png logo-75.png
384337  3月 24 17:31 logo-11.png
384337  3月 24 17:31 logo-12.png
384337  3月 24 17:31 logo-13.png
384337  3月 24 17:31 logo-14.png
378112  3月 24 17:31 logo-21.png
378112  3月 24 17:31 logo-22.png
378112  3月 24 17:31 logo-23.png
378112  3月 24 17:31 logo-24.png
374669  3月 24 17:31 logo-31.png
374669  3月 24 17:31 logo-32.png
374669  3月 24 17:31 logo-33.png
374669  3月 24 17:31 logo-34.png
295753  3月 24 21:00 logo-75.png (デフォルト設定)

11,12,13,14,21,22,23,24,31,32,33,34のいずれかの設定が良さそうでした。ただしpng:compression-filterの仕様を見ると画像内容によって上記の結果も結構変わりそうです。
 

結論

今後も検討を続けますが一旦は-quality 11を使用することにしました。ただしファイルサイズがデフォルト設定より増えるため(295,753->384,337)、ユーザアクセス内容によっては弊害も大きく注意が必要です。
 

データベースインデックスの作成に関する感想

あけましておめでとうございます。陳です。
今日は、データベースのインデックスについてお話ししたいと思います。ご存じの通り、SQLインデックスはクエリの検索パフォーマンスを向上させ、データベースの効率を高めるためのものです。


インデックス作成

例:CREATE INDEX idx_name ON Student (name);

複合カラムインデックスもよく使われています。


例:CREATE INDEX idx_name_class ON Student (name, class);


インデックス削除

例:DROP INDEX idx_name ON Student; Indexを作成したら、EXPLAIN でindexの使用状況を確認できます。
実行したら、以下のような結果が得られます。
EXPLAIN SELECT * FROM Student WHERE name = 'John';

+----+-------------+---------+------------+------+---------------+----------+-----+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | Student | NULL | ref | idx_name | idx_name | 767 | const| 1 | 100.00 | Using index|
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------+


説明:

- id: クエリの選択部分の識別子。外部のクエリの場合は1です。
- select_type: SELECTのタイプ。この場合はSIMPLEで、サブクエリやUNIONのない単純なSELECT。
- table: アクセスされているテーブル。
- type: 使用されているアクセス方法のタイプ(例:フルテーブルスキャンの場合はALL)。
- possible_keys: 使用可能なインデックス。
- key: オプティマイザによって実際に選択されたインデックス。
- key_len: 使用されたキーの長さ。
- ref: インデックス名で指定された列または定数。
- rows: クエリを実行するためにMySQLが調査する行の推定数。
- filtered: テーブル条件によってフィルタリングされた行の割合。
- Extra: クエリの実行に関する追加情報。
 
初めてのindexなら、私はまずpossible_keysにこのindexが考えられるかどうかを確認します。次に、実際に選択されたindexとクエリを実行する情報を確認します。
 
最も重要なのは rows 列です。なぜなら、これはMySQLがクエリに回答するために処理する必要がある行の数を示しています。extra の情報もクエリの実行方法をより理解しやすくします。例えば、extra に ‘using index condition; using where;‘ と書かれている場合、インデックスを使用した後に結果をフィルタリングする必要があったことを示します。
 
複数の複合カラムインデックスを作成して、運用する時、考え通りに動かないことがありますね。explainを実行すると、possible_keysにはあるのに、使われていませんといったケースが発生することがあります。また、使われていても、クエリの効率が上がらないこともあります。
 
それは、複合カラムインデックスに設定されたカラムの順序のためかもしれません。
 
この順序と実行されたクエリ条件の順番が異なる場合、使われてもらっていない状況があります。そのため、複合カラムインデックスを作成する際は、順序が重要です。頻繁にクエリの条件として使用される列が左側にあるほど良いです。
 
また、列の一意性が高いほど、より多くの値をフィルタリングでき、検索の速度が向上します。列の分散性も考慮する必要があります。分散性は、各値の数量の差異を示します。
 
迷った時、Index Hints というものを使ってクエリにするINDEXを指定して違うIndexを使用してexplainで結果を考えることも便利と思います。
 
USE INDEX はindexを使用することを提案しますが、必ずしも指定通りには使用されません。FORCE INDEX は指定したINDEXのみを使用します。
例:
SELECT *
FROM Student USE INDEX (idx_age)
WHERE age > 20;
 
今回の感想は以上です。EXPLAIN に興味がある方は、以下の記事での説明が詳しく、理解しやすいので、お勧めさせていただきます。

Using EXPLAIN in MySQL to analyze and improve query performance:
https://medium.com/datadenys/using-explain-in-mysql-to-analyze-and-improve-query-performance-f58357deb2aa

PHPのstdClassの動きが環境によって違った件

スズキです。PHPのstdClassのプロパティにアクセスしようとしたときの挙動が、環境によって異なる現象に遭遇したので、共有です。

やりたかったこと

  • json文字列の中から「orderDate」というプロパティから日付データを取り出したい
  • 「orderDate」というプロパティが存在しない場合は、代わりに今日の日付を使いたい
json文字列のイメージはこんな感じ。
{
    "orderId": 1001,
    "orderDate": "2023-11-30",
    "itemName": "UVコーティングシール"
}

修正前コード(問題あり)

PHPのjson_decodeでjson文字列からstdClassを作って、jsonの中身を使うプログラムです。
// $jsonObjectは「stdClass」クラス
$jsonObject = json_decode($jsonString, false);
try {
    $prop = $jsonObject->orderDate;  // ①orderDateがないときはExceptionが発生する…はず?
    $orderDate = new DateTime($prop);  // ②$propがnullだとTypeErrorが発生
} catch (Exception $e) {
    $orderDate = new DateTime();  // ③今日の日付を使う
}
①の行にて「orderDate」というプロパティがないとき、Exceptionが発生してくれれば、catchブロックの③にジャンプして今日の日付を$orderDateにセットできる、ということを目論んだコードなのですけど、これがダメでした。 自分の開発環境では①にてErrorExceptionが発生したので、期待通りcatchブロックへジャンプしたのですが、別のサーバに乗せたら①にてErrorExceptionは発生せず、nullが取得されたのです。nullが取得されたことで、その後の②でTypeErrorが発生したのですが、TypeErrorはExceptionを継承していないのでcatchできず、結局処理としてはエラーになってしまったというわけです。 つまり、json_decodeで取得した「stdClass」の挙動が、環境によって異なったのですね。個人的には、 $jsonObject->orderDate; のところで、プロパティが無ければnullが取れるのが直感的で好きです。

修正後コード(問題解消)

「stdClass」クラスの挙動の違いはさておき、そもそもjsonのプロパティがないときの処理を例外処理を利用して実現しようとしているのが圧倒的によくないので、普通にif文で書き直しました。なんで例外処理でやろうと思ったのだろう、当時の私…
// $jsonObjectは「stdClass」クラス
$jsonObject = json_decode($jsonString, false);
if (property_exists($jsonObject, 'orderDate') && !empty($jsonObject->orderDate)) {
    $prop = $jsonObject->orderDate;
    $orderDate = new DateTime($prop);
} else {
    $orderDate = new DateTime();
}

まとめ

ということで、「stdClass」クラスに存在しないプロパティにアロー演算子(->)でアクセスしたときの挙動は環境によって異なり、ErrorExceptionが発生したり、nullが取得できたりする、というメモでした。PHPのバージョン違いかも?と思いきや、確認したら同じバージョン番号だったので謎は深まるばかり。 どういう環境だと挙動が異なるのか、についての調査は難航しそうだったので、今回はこのへんで…

JsonSchemaについて

こんにちは、黒羽です。 ここしばらく開発しているアプリケーションでは、GUI画面の入力項目やブラウザからWebSocketのメッセージとして渡されたパラメータをJsonSchemaファイルに定義した条件でバリデーションするという実装を行ったので定義ファイルを作成するときに詰まったポイントなどを共有します。   そもそもJsonSchemaってなんやねんってときはググればだいたい解決できる世の中ですが、ものすごくざっくりというとプロパティの検証に必要な条件をJson形式で記述したスキーマ言語です。 以下のJsonはかの有名な「見た目は子供、頭脳は大人」な某小学生のプロフィールを基に書いてみました。
{
    "name": "江戸川コナン",
    "age": 6,
    "birthdayMonth": "May",
    "birthdayDay": 4,
    "sex": "male",
    "job": ["student",
        "detective"
    ]
}
この情報をベースに某小学生の通う小学校に入ることのできる人物を定義してみました。
{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "default": {},
    "required": [
        "name",
        "age",
        "birthdayMonth",
        "birthdayDay",
        "sex",
        "job"
    ],
    "properties": {
        "name": {
            "type": "string",
            "minLength": 1,
        },
        "age": {
            "type": "integer",
            "default": 0,
            "minValue": 5
        },
        "birthdayMonth": {
            "type": "string",
            "enum": [ "Jan", "Feb", "Mar", "Apr", "May","Jun", "Jul", "Aug"
            , "Sep", "Oct", "Nov", "Dec"
            ]
        },
        "birthdayDay": {
            "type": "integer",
            "default": 1,
            "maxValue": 31,
"minValue": 1 }, "sex": { "type": "string", "default": "", "enum": [ "male","female" ] }, "job": { "type": "array", "default": [], "items": { "type": "string", "enum": [ "student", "detective", "parent" ] } } } }
定義した条件としては
  • 名前を1文字以上持っていること
  • 5歳以上であること
  • 誕生月の略称がenumで定義した項目に含まれること
  • 誕生日が1~31の範囲であること
  • 性別が男女のどちらかであること
  • 職業が学生、探偵、保護者のいずれかであること
となります。 主題から脱線しそうなのでこれ以上の深掘りはしませんが某黒ずくめの組織の長髪の兄貴だとか警察関係者は立ち入り禁止、眠りの名探偵は職業が探偵、保護者なので立ち入り許可が出ます。 基本的なデータ型のチェック(type)、数字型の値の範囲の判定(minValue, maxValue)、リスト内の項目合致(enum)と基本的なものであれば上記の定義だけで十分です。 if分岐なども可能なので条件によって子プロパティが動的に変わるプロパティなども定義はできますがかなり記述量が増えます。 ここからは不便な点、詰まった点を共有します。

判定の結果がエラーだった場合において、エラー理由を取得すると日本語以外のメッセージしか取得できない

現状、JsonSchemaのエラーメッセージは日本語に対応しておらず、英語含めて3か国語程度しか選択できないようです。 日本国内での使用が一番想定されている開発中のアプリではユーザーに英語のエラーメッセージを解読してもらうのは忍びないのでJsonSchemaに用意されている予約語以外のフィールド名を使用するとAnnotationsというフィールドにオブジェクトとしてまとめられる仕様を利用して日本語メッセージを返すようにしています。

動的なプロパティを作ろうとすると記述量が膨大になる

if分岐があると上で書きましたがif-thenをJson形式で書くため、素直に書くと記述量がとんでもないことになります。 今回のアプリではcase文のような分岐が必要だったため以下のような雰囲気で記述しました。
"allOf": [
        {
            "if": {
                "properties": {
                    "job": {"const": "student"}
                }
            },
            "then": {
                "properties": {
                    "details": { "$ref": "#/definitions/student" }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "job": { "const": "detective" }
                }
            },
            "then": {
                "properties": {
                   "details": { "$ref": "#/definitions/detective" }
                }
            }
        }
    ]
見慣れないプロパティはドキュメント等を参照してもらうとして、条件としては「jobが”student”だった場合はdefinitionsプロパティに定義したstudentのdetailsプロパティを参照し、jobが”detective”だった場合はdetectiveのdetailsプロパティを参照する、それ以外はdetailsを持たない」となっています。 allOfのおかげでここは比較的に簡潔に書けました。(それでもdefinitions項目に別途色々定義したりする必要があるのでJson自体はカオス。)

Formatのチェックがあるがかなり甘い

文字列が特定の形式にのっとっているかを判定してくれるFormatフィールドがあり、URIやIPアドレスの形式チェックができます。これは便利!!!と思って使っていますが細かいところでNGパターンがチェックからすり抜けます。止む無し。 試した過程であったのは
  • IPアドレス→第4オクテットがなくても正常と判断される
  • URI→「:/」でも正常と判断される
といったものです。他のサイトを調べても「Formatだけじゃ不十分だからPatternできちんと正規表現で潰してね!」みたいな感じだったのでFormatとPatternの併用で潰している箇所があります。(ポート番号はさすがにないけど最大値最小値で判定するのは微妙だったのでここも正規表現を使って潰していたり。)  

終わりに

今回はJsonSchemaについてでした。 入力値の簡単なチェック処理をソースコードにだらっと書くのが嫌い、美しくない!と思う方は試してみると面白いかもしれないです。

CSSカスタムプロパティ色々便利なようです

ほぼ書き終わった記事を下書きで保存したら最初と最後だけになりました。
真ん中はどこへ行ったのでしょう?

株式会社イメージ・マジックの技術ブログ前回の担当のsoenoです。

最近CSSカスタムプロパティを触ることがあり、そのうちの機能で使わなかったところが気になったので コードと合わせて紹介します。


CSSカスタムプロパティとは?

CSSカスタムプロパティ(またはCSS変数)は、CSSで再利用可能なスタイル情報を格納し、管理するための仕組みです。
つまりscss環境を作らなくてもcssで変数が使えます。
また、scssの変数と合わせて使うこともできます。


CSSカスタムプロパティの使い方

通常のCSSプロパティと異なり、カスタムプロパティは--(ハイフン2つ)で指定します。 記載場所はcssで:root{}のカッコ内に指定すると(グローバルなスコープになるので) そのcssの読み込まれていれば別のcssからでも使えるようになります。 使うときはvar();で囲います。

指定して使用する例を以下に挙げます。
:root {
  --main-bg-color: blue;
}

#main{
  background:var(--main-bg-color);
}
上の例の#main{}の中でもCSSカスタムプロパティーを指定することができますが、その場合は#main{}の外では使えません。


javascriptからCSSカスタムプロパティの値を変更する

さて、本題です。 CSSカスタムプロパティで指定した値はjavascriptから変更することができます。 以下に例を挙げます。
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <title>カスタムプロパティデモ</title>
    <style type="text/css">
        :root {
            --h1Color: #e70d9e;
            --h2Color: #9e0de7;
            --h3Color: #350de7;
            --h4Color: #0d4be7;
            --h5Color: #0da2e7;
            --h6Color: #0de77a;
        }

        h1 {
            color: var(--h1Color);
        }

        h2 {
            color: var(--h2Color);
        }

        h3 {
            color: var(--h3Color);
        }

        h4 {
            color: var(--h4Color);
        }

        h5 {
            color: var(--h5Color);
        }

        h6 {
            color: var(--h6Color);
        }

        #stage{
            display: flex;
        }
        #inputArea {
            margin: 30px;
            width: 350px;
            flex:1;
        }
        #inputArea label{
            display: inline-block;
            width: 225px;
        }
        #inputTargetArea {
            display: flex;
        }
    </style>
</head>

<body id="stage">
    <div id="targetArea">
        <h1>h1 hello world</h1>
        <h2>h2 hello world</h2>
        <h3>h3 hello world</h3>
        <h4>h4 hello world</h4>
        <h5>h5 hello world</h5>
        <h6>h6 hello world</h6>
    </div>
    <div id="inputArea">
        <div id="inputTargetArea">
            <label for="targetChanger" name="targetChanger">何を変更するか選べます。</label>
            <select id="targetChanger">
                <option value="h1">h1</option>
                <option value="h2">h2</option>
                <option value="h3">h3</option>
                <option value="h4">h4</option>
                <option value="h5">h5</option>
                <option value="h6">h6</option>
            </select>
        </div>

        <div id="inputColorArea">
            <label for="colorChanger">何色に変更するかを選べます。</label>
            <input type="color" id="colorChanger" name="colorChanger" value="#cbcbcb" />
        </div>
    </div>

    <script language="javascript">
        document.getElementById("colorChanger").addEventListener("input", changeColor, false);
        function changeColor() {
            var targetTagName = document.getElementById("targetChanger").value;
            var newColor = this.value;
            document.documentElement.style.setProperty('--' + targetTagName + 'Color', newColor);
        }
    </script>
</body>
</html>
例は次のようになっています。

 ・cssカスタムプロパティでh1~h6の要素に文字色を指定。  ・セレクトボックスで各要素を指定できるように。  ・カラーピッカーで指定した色をCSSのカスタムプロパティーの変更で指定の要素に反映させる。

例では一対一なので良さがわかりにくいですが、変数で指定した個所すべてが変わります。 ですので指定したcssを引き継げばページをまたいだ指定や、そのユーザーのみのページテーマといった指定もできそうです。


結論

scss等使わないとできなかった変数での管理などがCSSカスタムプロパティでできるようになってます。 今回紹介はしていませんが、cssでなくjsで値を指定するなんてこともできます。 CSSカスタムプロパティ色々便利です。

Google Spreadsheet APIを使ってみる その1

はじめに

こんにちは、イメージ・マジックのもあいです。
GoogleのSpreadsheet APIを仕事で使用したので備忘録として残しておきます。

プロジェクトの作成

Googleアカウントでログインしておいて、Google Cloud コンソール の APIライブラリページ にアクセスし、プロジェクトの選択をクリックします


新しいプロジェクトをクリックします

プロジェクト名を入力して作成をクリック

しばらくするとプロジェクトが作成されます。

APIを有効にする

プロジェクトを作成した後、プロジェクトを選択すると下記の様になるので、Drive APIとSpreadsheet APIを有効にします。

Google Drive APIを選択して、有効にするをクリックします

Google Spreadsheet APIを選択して、有効にするをクリックします


認証情報の作成

Google Drive APIとGoogle Spreadsheet APIを有効にしたら認証情報を作成をクリックします

認証情報の作成は下記の様に選択をして次へボタンをクリックします

サービスアカウントIDを入力して作成して続行ボタンをクリック。サービスアカウントが作成できましたと表示されるので、完了ボタンをクリックします。

これでサービスアカウントが作成できました。
左ペインの認証情報をクリックして、サービスアカウント欄のサービスアカウントをクリックします。

キーをクリックします

鍵を追加をクリックし、新しい鍵を作成をクリックします

JSONを選択して作成をクリックします

jsonファイルがダウンロードされました。今後このファイルと画面に表示されているサービスアカウントのメールアドレス(testsample@testsample-398107.iam.gserviceaccount.com)を保存しておいてください。Driveでのファイルオーナーになります。

終わりに

今回はプロジェクト作成から認証情報の作成までとなります。認証情報作成で取得したjsonファイルの取扱には注意してください。間違ってもGitHub等のソースコード管理にコミットしてはいけません。

サーバ上のエラー発生行をブラウザからPhpStormで開く方法

こんにちは岡野です。
今回はPHPでの開発が楽になる方法を紹介します。最初の設定は面倒ですがメリットは大きいため、PhpStorm(もしくはIntelliJのPHPプラグイン)を使っている方はぜひ試してください。
   
  • 環境
    Windows
    IntelliJ IDEA 2023.2 + PHP Plugin
    Symfony(おそらく他のフレームワークでも対応可能)
    PhpStorm Protocol (最終編集2023-08-17)
  • インストール
  1. https://github.com/aik099/PhpStormProtocol のCodeボタンからzipをダウンロードする。  
  2. zip内の「PhpStorm Protocol (Win)」 を C:\Program Files\ へコピーする。  
  3. C:\Program Files\PhpStorm Protocol (Win)\run_editor.reg をダブルクリック。OK押下。  
  4. run_editor.jsを編集。上記PhpStormProtocol説明ページに載っている方法でうまくいかない場合は、editor変数を直接変更する。  
     
    editor = ‘”C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\IntelliJ IDEA Ultimate\\bin\\idea64.exe”‘;
     
  5. Symfonyの設定を行う。 例は、サーバ:/opt/app/、ローカル:c:\git\app\symfony/というマッピングの場合。
(Symfony6.1以上の場合)
.env.localに以下を記述。
SYMFONY_IDE=phpstorm://open?file=%f&line=%l&/opt/app/>c:/git/app/symfony/
 
(Symfony6.0以下の場合)
framework.yaml内のframeworkにide要素を追加。
framework:
  ide: 'phpstorm://open?file=%%f&line=%%l&/opt/app/>c:/git/app/symfony/'
 
・実行
エラー発生時にファイルパスのリンクをクリックすると、該当箇所がIntelliJで開く。

Twigでも文字列連結

初めまして。石川です。
今日は、最近業務内でした新しい発見について共有させていただきます。

Twigで文字列連結するには

大したことではないのですが今までTwigでは文字列連結ができないと思って業務に取り組んでいました。
なぜなら、Twig触り始めたばかりの時に{{str1.str2}}という風に書いてエラーが出力されたからです。
その時に、きちんと調べなかったことが原因で、conntroller側で文字列連携した変数を作成してTwig側に渡すという風に切り抜けてきました。
しかし、今回きちんと調べてみると、{{str1~str2}}という風にすることで文字列連結できることを発見しました。

Twigで参照するには

ちなみに、ドットだと文字列連結ではなく参照が可能みたいです。
{% set array = {‘x’: 11, ‘y’:12} %}
{{ array.x }}
上記記載のようにすると参照できます。

Twigで変数展開するには

変数展開は{{ “aaa #{1 + 10} bbb”}}という風に、#{}で囲むことによってできます。 上記の結果は”aaa11bbb”となります。

最後に

Twig側で処理する方が、簡潔に書ける場合も多いのではないかと思うので、状況を見てTwig側に処理を書くのも良さそうです。 以上石川から共有させていただきました。

“0”はTRUEかFALSEか

 初めまして、陳です。今日は、数ヶ月前に文字列処理を行った際のミスと感想を少しご紹介したいと思います。

 文字列の値を判定する際には、有効な文字列であるかどうかや空でないかどうかを気にします。通常、渡された値がNULLか空の文字列であるかどうかをチェックします。

 そのとき、最初に思い浮かぶのはIF条件文ですよね。

 IF条件式は、プログラミングの中で最もよく使われる便利なものだと思います。条件を設定し、結果がTRUEかFALSEかによって処理を分岐させることができます。パラメータの値を基本的にチェックする際には、ほとんどの場合、欲しい値に合致しているかどうかを判断するためにIF条件式を使用します。

 FALSEかの判定に対して、最も簡単で便利な方法は、プログラミング言語におけるデータ値のTRUEかFALSEの判定を利用することです。よく知られているJavaScriptとして、FALSEと判定されるデータ値は以下の通りです:
  • JavaScript:
    – undefined
    – null
    – ブール値の false
    – 数値のゼロ: 0
    – 浮動小数点数のゼロ: 0.0
    – NaN (Not a Number)
    – 空の文字列: “”
 その時、私はそのような認識を持ち、PHPで空の文字列の判定をif(!$string)で処理していました。そして、私は間違えました。

 JavaScriptでは、文字列に対して、””(空文字列)はFALSEと判定されることを知っています。パラメータの値が空文字列でない場合、TRUEと判定されます。したがって、”a”や”0″のいずれもTRUEとなります。

 しかし、PHPの世界では少し異なります。FALSEと判定されるデータ値は以下の通りです:
  • PHP:
    – null
    – ブール値のfalse
    – 数値のゼロ: 0
    – 浮動小数点のゼロ: 0.0
    – 空の配列: []
    – 空の文字列: “”
    – 文字列”0″
 PHPでは、””(空文字列)以外にも、”0″という文字列もFALSEと判定されます。そのため、if(!$string)という条件だけでチェックすると、判定が誤ってしまうことがあります。

 文字列が空値でないのに、FALSEと判定されることに違和感を覚えたので、原因を調べてみました:

 PHPは、ウェブリクエストでの使用を前提として設計されており、頻繁に文字列の入力(URLパラメータやブラウザのフォームからのPOSTリクエストなど)を扱います。そのため、PHPは文字列を他の型に自動的にキャストします。

 これの簡単な例として、’1′ + ‘2’は3を返し、エラーや’12’などの他の解釈は行いません。同じロジックで、文字列’0’は数値0として使用することができます。

 ’0’は数値の0として使用できると言っていますが、数値の0は「空」です。PHPでは、「ゼロである」という特性を「文字列としての特性」よりも重視するため、’0’は「偽」と見なされます。

 要するに‘0’ == 0 == false もしくは (bool)’0′ === (bool)(int)’0′

(資料参考URL

 つまり、’0’という文字列は実は数値0として扱われ、そのためにFALSEと判定されるのです。
そのため、PHPで空文字列かどうかを判定したい場合には、次のような条件式を直接記述することが最善です。
  • $string === “”と空文字列を比較する
  • empty($string) というPHPの組み込み関数を使用して空値かどうかを判定する
  • strlen( $string ) === 0 と文字列の長さが0かどうかをチェックする

 以上、今回、皆さんとシェアしたい内容でした。

ファイルアップロードを伴うWebAPIの設計パターンを比べる

こんにちは、スズキです。 ファイルアップロードを伴うWebAPIを開発する機会がぼちぼちあるのですが、ファイルアップロード周りの方式をどんな設計にするかを迷いがちなので、考えを整理してみました。

方式1:multipart/form-dataを使う

ファイルアップロードの基本、multipart/form-dataをAPIリクエストに使う方式です。
  • 単純なAPIならコマンドでも比較的ポンと気軽に送信できるのが魅力
  • JSONベースのシステムから送信するときにリクエストを別途組み立てないといけないのが面倒

こういうときに採用すると良さそう

  • テキストデータが少なくファイルのやり取りがメインのAPIをつくりたいとき
  • コマンドベースで使えるようなシンプルなAPIをつくりたいとき

方式2:JSON+Base64エンコードを使う

application/jsonをAPIリクエストに使う方式です。ファイル自体のデータもBase64エンコードしてJSONデータの中に含めてしまいます。
  • JSONベースのシステムからAPIを使いやすいのが魅力
  • ファイルのエンコード・デコードの処理時間が余分にかかるのと、Base64エンコードすることでファイルサイズが増えるので通信時間が増える

こういうときに採用すると良さそう

  • ファイルサイズが小さくテキストデータのやり取りがメインのAPIをつくりたいとき
  • リクエストもレスポンスもすべてJSONでやり取りできるAPIをつくりたいとき

方式3:multipart/form-dataとJSONを使い分ける

開発するAPI群のうち、ファイルアップロード用APIだけmultipart/form-dataを使い、その他のAPIにはapplication/jsonを使う方式です。ファイルアップロード用APIでファイルを特定するためのキーを発行し、その他のAPIではそのキーを使ってファイルを指定します。
  • JSONベースのシステムから使いやすく、アップロードしたファイルを他のAPIで何度も使いまわしやすいのが魅力
  • ファイルアップロードが個別のAPIなため、APIのリクエスト回数が増える

こういうときに採用すると良さそう

  • サイズの大きいファイルと複雑なテキストデータのどちらも取り扱うAPIをつくりたいとき
  • アップロードしたファイルを何度も使いまわせるAPIをつくりたいとき

以上、どちらかというと私の備忘録な投稿でした。