[PHP]10進数を任意の文字を使って62進数などのn進数に相互変換する(基数変換)

短縮URL等を発行する際に、データベースなどのIDをURLと関連付けて使うことが多いですが、単純に数字だけのIDを使うと多くの桁数を消費してしまいます。
0-9 に a-f を加えて一桁で16個まで表現できる16進数を使えば少し桁数を節約することができます。

この場合 a が 10 を表し、f が 15 となり、10 が 16 を意味します。
PHP で16進数の変換を行うには hexdec() や dechex() を使います。

16進数ではアルファベットの「f」までしか使いませんでしたが、残りの「g-z」を加えればもっと多くの数値を表現できます。また、URL は大文字小文字を区別するので、「A-Z」を更に加えて62進数にすると一桁だけでもかなりの情報量を扱えるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
$char = array_merge(range('0','9'), range('a', 'z'), range('A', 'Z'));
 
$number = 1000000;
echo encode($number, $char) . ', ';
 
$str = "Test";
echo decode($str, $char);
 
function encode($number, $char){
    $result = "";
    $base = count($char);
     
    while($number > 0){
        $result = $char[ fmod($number, $base) ] . $result;
        $number = floor($number / $base);
    }
    return ($result == "" ) ? 0 : $result;
}
 
function decode($str, $char){
    $result = 0;
    $base = count($char);
    $table = array_flip($char);
    $digit = array_reverse(preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY));
 
    foreach($digit as $i => $value){
        if(!isset($table[$value])) return false;
        $result += pow($base, $i) * $table[$value];
    }
     
    return $result;
}

[実行結果]

4c92(1000000の62進数化)
13163621(「Test」の10進数化)

変数 char に代入された文字列を使って n 進数に変換します
基数は文字列の種類によって自動的に決められます。

ちなみにこの 62進数に更に2文字を加えたのが Base64 というもので、
PHP では base64encode() などの関数として利用できます。
Base64 は上記の英数字に加えて「+」と「/」が使われます。また、4文字ずつ区切って変換するので余った部分は「=」で埋められます。
Base64 で短縮URLを作る場合は「+」や「/」をURLとして問題の起きない「_」などの文字に置き換える必要があります。

[参考]
http://q.hatena.ne.jp/1222172924/186107/#i186107

[PHP, JS]jQueryのAjaxによるPHP、Javascript間の送受信(JSON)

Ajax(非同期通信)を用いれば画面遷移なしでデータのやり取りができるため、最近では定番の技術として頻繁に用いられています。

今回は Javascript を使ってホストにデータを送信し、処理結果を受け取るというプログラムを JSON、JSONP を使うケース、同一ドメイン、クロスドメイン環境のケースに分けて説明します。

同一ドメイン間で JSON を使ったサンプル

クライアント

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Ajax</title>
        <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
        <script>
        $(function(){
            $("#button1").on('click', function(event){
                event.preventDefault();
                var param = { "text": "Hello" };
 
                $.ajax({
                    type: "GET",
                    url: "get.php",
                    data: param,
                    crossDomain: false,
                    dataType : "json",
                    scriptCharset: 'utf-8'
                }).done(function(data){
                    alert(data.text);
                }).fail(function(XMLHttpRequest, textStatus, errorThrown){
                    alert(errorThrown);
                });
            });
        });
        </script>
    </head>
    <body>
        <button id="button1">submit</button>
    </body>
</html>

ホスト

1
2
3
4
5
6
7
8
9
<?php
// Ajax以外からのアクセスを遮断
$request = isset($_SERVER['HTTP_X_REQUESTED_WITH'])
     ? strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) : '';
if($request !== 'xmlhttprequest') exit;
 
$text = filter_input(INPUT_GET, 'text');
header('Content-type: application/json; charset=utf-8');
echo json_encode(['text' => $text . ', World!']);

出力結果

Hello, World!

「Hello」と書かれたボタンを押すと、JavaScript から PHP に「Hello」という文字列が送信されます。
PHP はそれに「, World!」という文字列を加えて返します。
Javascript は結果を受け取り「Hello, World!」と表示する流れです。

非クロスドメイン環境で ajax を使うと HTTP_X_REQUESTED_WITH というヘッダー情報が送信されます。これをもとに Ajax 以外の方法をブロックしています。

「type: "POST"」とした場合は POST メソッドで送信されるのでホスト側も「$text = filter_input(INPUT_POST, "text");」のようにして受け取ることになります。

JSONP を使ったクロスドメイン Ajax

JSONP(JSON with padding)を使うとクロスドメイン環境での Ajax が可能です。
クライアントの dataType に jsonp を指定し、type を GET に変更します。

クライアント

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
$(function(){
    $("#button1").on('click', function(event){
        event.preventDefault();
        var param = { "text": "Hello" };
 
        $.ajax({
            type: "GET",
            url: "get-jsonp.php",
            data: param,
            crossDomain: true,
            dataType : "jsonp",
            scriptCharset: 'utf-8'
        }).done(function(data){
            alert(data.text);
        }).fail(function(XMLHttpRequest, textStatus, errorThrown){
            alert(errorThrown);
        });
    });
});
</script>

ホスト

1
2
3
4
5
6
7
8
9
<?php
text = filter_input(INPUT_GET, 'text');
$callback = filter_input(INPUT_GET, 'callback');
$callback = htmlspecialchars(strip_tags($callback));
  
$param = ['text' => $text . ", World!"];
 
header('Content-type: text/javascript; charset=utf-8');
printf("{$callback}(%s)", json_encode( $param ));

JSONP ではクライアントから callback というデータが送信されます。これを javascript の関数を呼び出すような形にして出力するとデータの受け渡しができるようになります。

JSON を使ったクロスドメイン Ajax

JSON を使ってクロスドメイン通信を行う場合は次のヘッダーをサーバー側で出力しておく必要があります。

header("Access-Control-Allow-Origin: *");

「*」はすべてのサイトからの通信を許可するということを意味します。特定のサイトからのみリソースの利用を許可する場合は次のようにして URL を指定します。

header("Access-Control-Allow-Origin: http://foo.com http://bar.com");

不特定多数のクライアントにリソースシェアリングを行う場合は認証機能をつけることについても検討して下さい。

参考:
http://www.learningjquery.com/2010/03/detecting-ajax-events-on-the-server/

[PHP]中心から角度を指定して線を引く(座標の回転)

360

ある座標から角度を指定して線を引くサンプルです。
線の長さを同じにして角度をすこしずつ変えていくと円のようになります。
時計の針のようなものをイメージするとわかりやすいと思います。

サンプルとして中心から 80 ピクセルの線を 45 度で描画してみます。

45

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
//線分の長さ
$r = 80;
 
//角度
$angle = 45;
 
//キャンバスサイズ
$width   = 200;
$height  = 200;
 
//キャンバスの中心を原点とする
$origin = array( round($width / 2), round($height / 2) );
 
$image = imagecreatetruecolor($width, $height);
 
//線の色
$color = imagecolorallocate( $image, 255, 255, 255 );
 
list($x, $y) = point_rotate($r, $angle);
imageline($image, $origin[0], $origin[1], $origin[0] + $x, $origin[1] + $y, $color);
 
header('Content-Type: image/jpeg');
imagejpeg($image);
imagedestroy($image);
 
function point_rotate($r, $angle){
    $angle = deg2rad($angle);
    $x = round($r * cos($angle));
    $y = round($r * sin($angle));
    return array($x, $y);
}

0 度の時は水平線で、角度が増えるごとに時計回りに回転します。
時計のように 12 時から始める場合は 90 度引いて計算します。