[PHP]ついやってしまいがちな汚いソース10種

私自身よくやってしまうので、自分への戒めも込めて「悪いソースコード」の例を幾つか上げてみようと思います。

1. 型を意識しないで変数を使いまわす

$num = 1234;
$num = 'number' . $num;
echo $num;

気をつけてないとかなりの頻度でこうなっています。PHP ならではの現象。

2. 条件分岐やループのネストが深すぎる

if($param[0] == 'a'){
  if($param[1] == 'b'){
    foreach($arr as $value){
      if($param[2] == $value){
        for($i=0;$i<=10;$i++){
          echo 'Hello, World!';
        }
      }
    }
  }
}

たいていは目的ごとに関数を定義すると綺麗にまとまることが多い気がします。

3. 一行に詰め込みすぎる

echo htmlspecialchars(number_format($dom->getElementsByTagName('test')->item(0)->nodeValue), ENT_QUOTES);

一旦変数に入れておかないとどんどん伸びていきます。

4. マジックナンバーを使う

someFunction(100,300,200);

書いてる途中は覚えてますが後で見直すとなんの数字だったかわからなくなります。

5. 後から for 文を書き足した結果 $i, $j を変な順番で使う

for($j=0;$j<=10;$j++){
  for($i=0;$i<=10;$i++){
    echo 'hello, world';
  }
}

書き換えるのが面倒くさかったりしてそのままにしておくとややこしくなります。

6. 細か過ぎる連結

echo '<a href="' . $url . '">' . '<img src="' . $src . '" alt="' . $alt . '" />' . "</a>\n";

ヒアドキュメント使うまでもないとき使いたくなりますが、ダブルコーテーションで括ったほうがまだましです。
厳密にはシングルクォートで連結するとわずかに早くなるそうですが我々は陸上選手ではありません。
printf() を使うのも良いと思います。

7. 省略しすぎる

if($p > $m && $t != 0){
    $nx = true;
    $cp = cp($p);
}

Javascriptなら有効かもしれませんが、変数に何が入っているか検討がつかないのはバグの元になります。

8. やたら複雑な配列変数を一発で宣言する

$array = array(
  'food' => array(
    'fruit' => array('apple', 'orange')
  ),
  'animals' => array(
    array(
      'name' => 'rabbit',
      'color' => array('name' => 'white', 'code' => '#FFFFFF')
    )
  )
);

どちらかというと関数設計段階での問題なので避けられないケースも有ります。
丁寧にインデントして可読性を高める等の工夫が必要です。

9. 命名規則があやふや

$arrUser    = array();
$categories = array();
$STRING     = 123;

getSomething( $arrUser );
load_something( $category );

すみません、私です。

10. すごく日本語

$toukousuu = 100;
$ninzuu    = 20;

$heikin = $toukousuu / $ninzuu;

これは絶対にやらない。でも他人のソースがこうなっていると嫌がらせかと思います。
$_POST[‘me-ru’] とか書いてあると鳥肌が立ちます。

[PHP]日付から曜日を割り出す

PHP は標準関数が豊富に用意されているので、年月日がわかっていれば曜日を取得するのは難しくありません。
PHP で日付を扱う方法は主に2つあり、date() を使う方法と、PHP 5.2 以降で使用できる DateTime クラスを使う方法です。date() は古くからある方法で、ほとんどの環境で使うことが出来ますが、32bit環境でタイムスタンプ(UNIX時間)を使用する場合 2038年1月19日 までしか使用できないという欠点を抱えています。(2038年問題)
ここでは将来的に主流になっていくと考えられている DateTime クラスを使った方法に絞って解説していきます。

DateTime クラスは通常通りクラスとして扱う方法と、関数のように扱える手続き型を利用する方法が用意されています。どちらも曜日は 0~6 の数字の形で返すので日本語で曜日を表すには 日~土 の配列変数を作っておくと簡単です。

【現在の曜日】

<?php
$datetime = new DateTime();
$week = array("日", "月", "火", "水", "木", "金", "土");
$w = (int)$datetime->format('w');
echo $week[$w];
<?php
$week = array("日", "月", "火", "水", "木", "金", "土");
$datetime = date_create();
$w = (int)date_format($datetime, 'w');
echo $week[$w];

【日付が文字列として与えられている場合】

<?php
$date = "2012-12-05";
$datetime = new DateTime($date);
$week = array("日", "月", "火", "水", "木", "金", "土");
$w = (int)$datetime->format('w')
echo $week[$w];
<?php
$date = "2012-12-05";
$datetime = date_create($date);
$week = array("日", "月", "火", "水", "木", "金", "土");
$w = (int)date_format($datetime, 'w');
echo $week[$w];

【日付の年月日がそれぞれ数値で与えられている場合】

<?php
$year  = 2012;
$month = 12;
$day   = 5;
$datetime = new DateTime();
$datetime->setDate($year, $month, $day);
$week = array("日", "月", "火", "水", "木", "金", "土");
$w = (int)$datetime->format('w');
echo $week[$w];
$year  = 2012;
$month = 12;
$day   = 5;
$datetime = date_create();
date_date_set($datetime, $year, $month, $day);
$week = array("日", "月", "火", "水", "木", "金", "土");
$w = (int)date_format($datetime, 'w');
echo $week[$w];

format() は日付を文字列の形で取得する関数です。年月日を表示する場合 format('Y-m-d') のようにします。
「w」を使うと 0 ~ 6 の値を得ることが出来ます。これは週の何日目であるか(Day of Week)を表すもので、要するに曜日です。

DateTime で取得される時間はタイムゾーンの影響を受けます。php.ini や date_default_timezone_set() で設定をしていない場合は、「new DateTime('now', new DateTimeZone('Asia/Tokyo'))」あるいは「date_create('now', timezone_open('Asia/Tokyo'))」のように設定するか、「$datetime->setTimezone(new DateTimeZone('Asia/Tokyo'))」、「date_timezone_set($datetime, timezone_open('Asia/Tokyo'))」のように後から指定する必要があります。

[PHP]HSV(HSB)色空間を比較して似た色合いの画像を検索する

Lab 色空間を利用した精度の高い類似画像の検索についてはこちらの記事を御覧ください。


以前の記事でRGB値を元に画像を検索しましたが、今回はHSVモデルを使ってより人間の知覚に近い色比較を行なってみます。

HSV色空間は、色を「色相」「彩度」「明度」の要素に分けて考えます。
つまり、色の違い、鮮やかさ、明るさの違いを数値的に比較することができます。

<?php
//比較元となる画像
$filepath = "sample.jpg";
 
//比較対象用画像ディレクトリ
$dir = "images/";
 
//要素の重要度
$priority = array(
  'h' => 1,
  's' => 1,
  'v' => 1
);
 
$sample = loadImage($filepath);
$sample_hsv = imageHsv($sample);
imagedestroy($sample);
$list = scandir($dir);
 
$files = array();
foreach($list as $value){
  if(is_file($dir . $value)){
    $files[] = $dir . $value;
  }
}
 
$diff = array();
foreach($files as $file){
  $image = loadImage($file);
  $hsv = imageHsv($image);
  imagedestroy($image);
  $name = basename($file);
  $diff[$name] = hsvDistance($sample_hsv, $hsv, $priority);
}
 
asort($diff);
$result = array_keys($diff);
header("Content-type: text/html;charset=utf-8");
foreach($result as $img){
	echo '<img src="' . $dir . $img . '" width="50" height="50" alt="" />';
}
 
function loadImage($filepath){
  $checkimg = getimagesize($filepath);
 
  if($checkimg['mime'] == "image/jpeg" || $checkimg['mime'] == "image/pjpeg"){
    $extension = "jpg";
  } else if ($checkimg['mime'] == "image/gif"){
    $extension = "gif";
  } else if ($checkimg['mime'] == "image/png" || $checkimg['mime'] == "image/x-png"){
    $extension = "png";
  } else {
    exit;
  }
 
  if($extension == 'jpg'){$image = ImageCreateFromJPEG($filepath);}
  if($extension == 'gif'){$image = ImageCreateFromGIF($filepath);}
  if($extension == 'png'){$image = ImageCreateFromPNG($filepath);}
   
  return $image;
}
 
function imageHsv($image){
  $width	 = imagesx($image);
  $height	 = imagesy($image);

  $thumb_width	 = 16;
  $thumb_height	 = 16;
  $thumb = imagecreatetruecolor($thumb_width, $thumb_height);
  imagecopyresampled($thumb, $image, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
   
  $red   = 0;
  $green   = 0;
  $blue   = 0;
   
  for($x=0; $x < $thumb_width; $x++){
    for($y=0; $y < $thumb_height; $y++){
      $index   = imagecolorat($thumb, $x, $y);
      $rgb   = imagecolorsforindex($thumb, $index);
      $red   += $rgb['red'];
      $green   += $rgb['green'];
      $blue   += $rgb['blue'];
    }
  }
   
  $average = array();
  $pixel = $thumb_width * $thumb_height;
  $average['red']     = round($red / $pixel);
  $average['green']   = round($green / $pixel);
  $average['blue']   = round($blue / $pixel);
 
  return rgb2hsv($average);
}
 
function hsvDistance($hsv1, $hsv2, $priority){
  $dist_h = abs($hsv1['h']   - $hsv2['h']);
  $distance = 0;
  $distance += min($dist_h, 360 - $dist_h) * $priority['h'];
  $distance += abs($hsv1['s']   - $hsv2['s']) * 180 * $priority['s'];
  $distance += abs($hsv1['v']   - $hsv2['v']) * 180 * $priority['v'];
  return $distance;
}
 
function rgb2hsv($rgb){
  $r = $rgb['red'] / 255;
  $g = $rgb['green'] / 255;
  $b = $rgb['blue'] / 255;
   
  $max = max($r, $g, $b);
  $min = min($r, $g, $b);
  $v = $max;
   
  if($max === $min){
    $h = 0;
  } else if($r === $max){
    $h = 60 * ( ($g - $b) / ($max - $min) ) + 0;
  } else if($g === $max){
    $h = 60 * ( ($b - $r) / ($max - $min) ) + 120;
  } else {
    $h = 60 * ( ($r - $g) / ($max - $min) ) + 240;
  }
  if($h < 0) $h = $h + 360;
 
  $s = ($v != 0) ? ($max - $min) / $max : 0;
   
  $hsv = array("h" => $h, "s" => $s, "v" => $v);
  return $hsv;
}

プログラムの構造は前回とほぼ同じです。
比較元の画像ファイルと、比較先の画像が複数入ったフォルダを指定し、最も近い色を持つ画像を表示するものです。

今回は $priority というパラメータをもたせました。
これは HSV のどの要素を重視するかというもので、数字が大きいほどその要素が重要視されます。色の違いを2倍重要視したいのであれば h を 2 にします。
画像の明るさだけを比較したい場合は v 以外の数値を 0 にします。
感覚的には色相(h)を重要視したほうがより近い画像が得られる気がします。

必要に応じて thumb_width や thumb_height も変更します。
これは、高速化のために、色を拾う際に画像を小さくしてから拾っています。
小さければ小さいほど精度は下がりますが処理は高速になります。

比較の結果は連想配列 $diff に入っています。キーがファイル名で値が相似の度合いです。数値が小さいほど似た画像であることを示し、完全に同じ時は 0 になります。
上のサンプルでは reset() で最も似た画像だけを表示しています。

注意すべき点は、本来彩度が 0 の時、色相は無視されるべきですが、便宜的に 0 としているのでモノクロの画像比較を行うには工夫が必要です。
モノクロの画像を HSV に変換する際、彩度を「FALSE」にしておき、比較を行わないようにすると良いと思います。