同時編集可能なアプリケーションを作る場合

本記事では、PHPで何かテキストを投稿するサイト（Blogやメモ帳など）を作る場合に同時編集をされたとき対応する方法がわからなかったので、記事にした

## はじめに
本記事は、PHPで同時編集可能なアプリケーションを作成するときにどのようなアプローチで開発するのかについて検討する

## 2 way merge
２つのファイル間で差分の分析を行う仕組み
エラーが発生しやすくあまり利用されていない

## 3 way merge
こっちが本命の方ですが、Gitのマージなどにも使われている普及した手法である
3 way mergeの場合には変更前の元のファイルと変更後のファイル、現在の状態のファイルの三種類のファイルが必要である
これら３つのファイルを比較し、元のファイルを起点に差分を分析する
今回はこの方法を使って投稿型サービスに導入する為にPHPで実装してみる

## 導入
今回は、[bircher/php-merge](https://github.com/bircher/php-merge)というライブラリを使って実現していく
少しスター数が少ないのが気になるが、ソースコードを読むと、[sebastian/diff](https://github.com/sebastianbergmann/diff)という信頼の置けるライブラリの機能をほとんどの部分で使っているラッパーライブラリなので、不安なら自分で実装することもできるだろうと考える

以下で少しお試しのプログラムを書いてみる
- とりあえず、composerでbircher/php-mergeをインストールする
- autoloadを読み込む

```
composer require bircher/php-merge
```

```3_way_merge.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$merger = new PhpMerge();

$original = <<<'EOD'
今日は
雨だけど
みんなで
代々木公園で
ピクニックをするために
外出の支度を始める

EOD;

$version1 = <<<'EOD'
今日は
晴れたけど
みんなで
代々木公園で
ピクニックをするのはちょっときついので、
家でゲームをする

EOD;

$version2 = <<<'EOD'
明日は
雨だけど
とりあえず、
支度しないと
代々木公園で
ピクニックをするために
外出の支度を始める

EOD;

try {
    $result = $merger->merge($original, $version1, $version2);
    print($result);
} catch (MergeException $exception) {
    print_r($exception->getConflicts());
}
```

上記のプログラムを実行すると以下のように出力される

```
明日は
晴れたけど
とりあえず、
支度しないと
代々木公園で
ピクニックをするのはちょっときついので、
家でゲームをする
```

見てわかる通り、どちらか片方で元のテキストから変更したものは変更が反映されている
改行した場合はその改行を抜かした部分で差分を比較している

試しに競合させてみる

```conflict.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$merger = new PhpMerge();

$original = <<<'EOD'
今日は
雨だけど
みんなで
代々木公園で
ピクニックをするために
外出の支度を始める

EOD;

$version1 = <<<'EOD'
今日は
晴れたけど
みんなで
代々木公園で
ピクニックをするのはちょっときついので、
家でゲームをする

EOD;

$version2 = <<<'EOD'
明日は
雨だけど
とりあえず、
支度しないと
代々木公園で
ピクニックをするために
外出の支度を始めない

EOD;

try {
    $result = $merger->merge($original, $version1, $version2);
    print($result);
} catch (MergeException $exception) {
    print_r($exception->getConflicts());
}
```

このプログラムの結果は以下のようになる

```
Array
(
    [0] => PhpMerge\MergeConflict Object
        (
            [base:protected] => Array
                (
                    [0] => ピクニックをするために

                    [1] => 外出の支度を始める

                )

            [remote:protected] => Array
                (
                    [0] => ピクニックをするのはちょっときついので、

                    [1] => 家でゲームをする

                )

            [local:protected] => Array
                (
                    [0] => ピクニックをするために

                    [1] => 外出の支度を始めない

                )

            [baseLine:protected] => 4
            [mergedLine:protected] => 5
        )

)
```
baseLineとmergedLineが出力されている
これは、競合する１つ前の行の出力と、コンフリクトした行の出力している行数を示している
5行目が悪いということがエラーメッセージからわかる

## ちなみに
PHPには元から3 way mergeができるextensionが用意されている(xdiff_string_merge3)
しかし、よほど使われていないのか、全くもって参考記事が見つからず、ドキュメントを読んでも、返り値が複数あるなど、少し使いにくい印象を受けた
PHPはPHP7から引数及び返り値の型指定ができるようになったので、できるだけ単一の型を返すメソッドを作った方が安定したアプリケーションを開発できる
よって、今回はsebastian/diffをベースにしたライブラリを使うことにした

## 終わりに
今回は共同編集可能なアプリケーションを作成するときにどうしたらいいかを軽く紹介した
調べてみると世の中では差分マージアルゴリズムに3 way mergeが広く使われていることがわかった
是非、ググる時間を減らせるような記事になっていることを祈る

## 参考
- [bircher/php-merge](https://github.com/bircher/php-merge)
- [Wikipedia マージ (バージョン管理システム)](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%BC%E3%82%B8_(%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0))
- [xdiff_string_merge3](https://www.php.net/manual/ja/function.xdiff-string-merge3.php)
- [esa.io 記事を同時編集中のユーザが表示されるようになりました](https://docs.esa.io/posts/90)