PHPでversion_compare()を使うときは桁を合わせよう

## TL;DR

`version_compare()` 関数を使うときは、桁区切りのフォーマットは同一にしたほうが安心です。

もし、比較時に `version_compare("2.0.0", "2.0", ">")` と２つのフォーマットが違うと、直感的には同じバージョンだったとしても、小数点で区切られた数が多い方が大きいと判定され、想定しない動きになる可能性があります。

## 前提条件

```
$ php -v
PHP 5.6.27 (cli) (built: Oct 15 2016 09:29:55)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
```

## バージョン判定がうまくいかない？

### 比較する２つの値が同じフォーマットの時

`version_compare()` を用い、比較する値がどちらも同じフォーマット(`x.y.z`)となっている場合です。以下コードを書き、実行してみます。

```php:version_compare_3_3.php
<?php

print "2.0.0 > 2.0.0 = False? : " . (version_compare("2.0.0", "2.0.0", ">") ? "True" : "False") . "\n";
print "2.0.0 < 2.0.0 = False? : " . (version_compare("2.0.0", "2.0.0", "<") ? "True" : "False") . "\n";
print "2.0.0 >= 2.0.0 = True? : " . (version_compare("2.0.0", "2.0.0", ">=") ? "True" : "False") . "\n";
print "2.0.0 <= 2.0.0 = True? : " . (version_compare("2.0.0", "2.0.0", "<=") ? "True" : "False") . "\n";
print "2.0.0 == 2.0.0 = True? : " . (version_compare("2.0.0", "2.0.0", "==") ? "True" : "False") . "\n";
print "2.0.0 != 2.0.0 = False? : " . (version_compare("2.0.0", "2.0.0", "!=") ? "True" : "False") . "\n";
```

想定した動きになっています。

```bash
$ php version_compare_3_3.php
2.0.0 > 2.0.0 = False? : False
2.0.0 < 2.0.0 = False? : False
2.0.0 >= 2.0.0 = True? : True
2.0.0 <= 2.0.0 = True? : True
2.0.0 == 2.0.0 = True? : True
2.0.0 != 2.0.0 = False? : False
```

### 左辺の桁が1桁長いが右辺のほうが大きい値の時

左辺は2.0.0、右辺は2.1となったとき、どんな動きになるでしょうか。

```php:version_compare_3_2_1.php
<?php

print "2.0.0 > 2.1 = False? : " . (version_compare("2.0.0", "2.1", ">") ? "True" : "False") . "\n";
print "2.0.0 < 2.1 = True? : " . (version_compare("2.0.0", "2.1", "<") ? "True" : "False") . "\n";
print "2.0.0 >= 2.1 = False? : " . (version_compare("2.0.0", "2.1", ">=") ? "True" : "False") . "\n";
print "2.0.0 <= 2.1 = True? : " . (version_compare("2.0.0", "2.1", "<=") ? "True" : "False") . "\n";
print "2.0.0 == 2.1 = False? : " . (version_compare("2.0.0", "2.1", "==") ? "True" : "False") . "\n";
print "2.0.0 != 2.1 = True? : " . (version_compare("2.0.0", "2.1", "!=") ? "True" : "False") . "\n";
```

これは、おそらく多くの方が想定した動きになります。

```bash 
$ php version_compare_3_2_1.php
2.0.0 > 2.1 = False? : False
2.0.0 < 2.1 = True? : True
2.0.0 >= 2.1 = False? : False
2.0.0 <= 2.1 = True? : True
2.0.0 == 2.1 = False? : False
2.0.0 != 2.1 = True? : True
```

### 左辺の桁が1桁長い時

では、左辺は2.0.0、右辺は2.0となったとき、どんな動きになるでしょうか。人が見たら、これはイコールと思えるかもしれませんが、実際は違います。

```php:version_compare_3_2.php
<?php

print "2.0.0 > 2.0 = False? : " . (version_compare("2.0.0", "2.0", ">") ? "True" : "False") . "\n";
print "2.0.0 < 2.0  = False? : " . (version_compare("2.0.0", "2.0", "<") ? "True" : "False") . "\n";
print "2.0.0 >= 2.0 = True? : " . (version_compare("2.0.0", "2.0", ">=") ? "True" : "False") . "\n";
print "2.0.0 <= 2.0 = True? : " . (version_compare("2.0.0", "2.0", "<=") ? "True" : "False") . "\n";
print "2.0.0 == 2.0 = True? : " . (version_compare("2.0.0", "2.0", "==") ? "True" : "False") . "\n";
print "2.0.0 != 2.0 = False? : " . (version_compare("2.0.0", "2.0 ", "!=") ? "True" : "False") . "\n";
```

桁が長い左辺が大きいと評価されます。

```bash
$ php version_compare_3_2.php
2.0.0 > 2.0 = False? : True
2.0.0 < 2.0  = False? : False
2.0.0 >= 2.0 = True? : True
2.0.0 <= 2.0 = True? : False
2.0.0 == 2.0 = True? : False
2.0.0 != 2.0 = False? : True
```

PHPの `ext/standard/versioning.c` の `php_version_compare()` をみてください。

メジャーバージョンの桁から順に評価されています。そのうえで、左辺か右辺、どちらかの評価が尽きると、桁が大きい方が小さいという処理に移っています。

### `2.1.0` と `2.01.0` はどうか

では、フォーマットがにているようで、minorバージョンが1桁の場合と2桁の場合ではどうでしょうか。これも変わった動きをします。

```php:version_compare_3_a.php
<?php

print "2.1.0 > 2.01.0 = False? : " . (version_compare("2.0.0", "2.01.0", ">") ? "True" : "False") . "\n";
print "2.1.0 < 2.01.0 = False? : " . (version_compare("2.0.0", "2.01.0", "<") ? "True" : "False") . "\n";
print "2.1.0 >= 2.01.0 = True? : " . (version_compare("2.0.0", "2.01.0", ">=") ? "True" : "False") . "\n";
print "2.1.0 <= 2.01.0 = True? : " . (version_compare("2.0.0", "2.01.0", "<=") ? "True" : "False") . "\n";
print "2.1.0 == 2.01.0 = True? : " . (version_compare("2.0.0", "2.01.0", "==") ? "True" : "False") . "\n";
print "2.1.0 != 2.01.0 = False? : " . (version_compare("2.0.0", "2.01.0", "!=") ? "True" : "False") . "\n";
```

桁が長いほうが、大きいと評価されました。

```bash
$ php php:version_compare_3_a.php
2.1.0 > 2.01.0 = False? : False
2.1.0 < 2.01.0 = False? : True
2.1.0 >= 2.01.0 = True? : False
2.1.0 <= 2.01.0 = True? : True
2.1.0 == 2.01.0 = True? : False
2.1.0 != 2.01.0 = False? : True
```

PHPの `ext/standard/versioning.c` の `php_version_compare()` をみてください。

…と思ったのですが、あれ、なんでこうなるんだろう。調査中です。

## 参考資料

- [PHP.net - version_compare](http://php.net/manual/ja/function.version-compare.php)
- https://github.com/php/php-src/ (ext/standard/versioning.c)