自作ライブラリの管理(git+composer)

0
1059

プログラマーにとって処理の共通化/汎用化は永遠のテーマかと思います。
同じような処理がN箇所に点在していた場合、その処理に問題があればN箇所修正する必要があります。これはさすがに効率が悪く不具合を作り込むリスクも増大しており気持ちが悪い。一方で共通化してみたものの、使用ケースによって微妙に期待される処理内容に差があり、気付いたら条件分岐だらけの美しくないロジックになっていたと言うのも割とあるあるです。どのような機能をどのような粒度で作るかと言うことが実に難しい。

ただ、試行錯誤の中で使い勝手の良い関数が生まれることもままあり、汎用性の高い物であればプロジェクトをまたいで流用したくなります。単純な方法としては該当処理をコピペでも良いですが、せっかくなので同様の処理を1つ(あるいは目的ごとに複数)のファイルにまとめておいて使い回し易くするくらいまで考えたくなります。このようにして今までもいくつかの”オレオレ”ライブラリが誕生しました。
しかし、残念ながら当初の目的通り汎用的に活用されたライブラリは今のところ皆無と言えます。なぜならば管理がいい加減だったからです。

ファイルを紛失してしまったり(消したか保存場所を忘れたかすら不明だったり)、プロジェクトごとに若干のアレンジを加えて行く中で結局はそのプロジェクトでしか使えないライブラリに成り下がったりと、かなり低次元なレベルで汎用化計画が頓挫して来ました。
そろそろこの不毛な連鎖を断ち切り、ちゃんと使えるライブラリを作りたいと思います。

目指すは、
(1)在処が明確
(2)適用し易い
(3)世代管理もしたい
と言う方式ですが、そうなれば現在の選択肢(特に自分の知識や技術)では、やはりgitが最適と言うことになります。
また、特に昨今はLaravelでの開発が主なので適用にcomposerが使えると良いと思ったのですが、調べてみると当たり前のようにgitとcomposerは連携するんですね。これはgit+composerで行くしかないです。

gitに関しては普段から自社サーバ上のGitLabを共用(ベア)リポジトリとしていますので、これをそのまま使用します。あくまで自社開発での利用が目的です(世間様に広く使ってもらえるような高いレベルを目指してないので…)。
実験環境としては例によってVirtualbox/Vagrant上にWebmin/Virtualminで構築したサーバ環境にLaravelをインストールした物を使います。本環境構築までの操作は私の過去記事を参照してください。ただ、過去記事の手順通り進めた場合、サーバ環境にgitが入っていない状態になっているかと思いますので、yum辺りを使ってさくっと追加しておいてください。
なお、共用リポジトリでは自己証明書を使用しているのですが、デフォルト状態ではgit君が自己証明書を信用してくれません。共用リポジトリに自己証明書でアクセスしたい場合は以下のコマンドを実行しておきましょう。

git config --global http.sslVerify false

今回は試作的に以下のようなライブラリを作成してみたいと思います。

  • クラス名は「LibSample」とし、ファイル名は「LibSample.php」とする
  • 上記クラスを「Wetch\Laralib\LibSample」として使用できるようにする
  • インストール結果は「vendor/wetch/laralib」配下に格納される

ライブラリ作成

では、composerで管理可能なライブラリを作成するためには何をすれば良いか?
ポイントは以下の2つのみです。
(1)所定の書式でcomposer.jsonを作成し、当該ライブラリ用git環境直下に配置
(2)バージョン管理できるように適切なブランチおよびタグを設定

composer.json

開発環境の適当なディレクトリ配下にgit環境を作成し、その直下に以下のような内容のcomposer.jsonを配置します。

{
    "name": "wetch/laralib",
    "description": "Laravel用ライブラリ",
    "require": {
        "php": "^7.0"
    },
    "autoload": {
        "psr-4": {"Wetch\\Laralib\\": "sample/"}
    }
}

「name」はcomposer実行時に当該ライブラリを識別する名称として指定するもので、インストール後にはLaravel環境のcomposer.json、composer.lock内でも当該ライブラリを識別する情報として使用されます。
「description」は当該ライブラリの簡単な説明文で、なくても良いのかもしれませんが、ライブラリが増えた際にはこのような補足情報があった方が便利かと思いますので設定しておきます。
「require」は当該ライブラリとして必要な条件を記載したもので、今回はPHPのバージョンとして7以上を指定しておきます。
「autoload」はオートローダに関する指定です(「オートローダ」って何?と言う方はGoogle先生に聞いてください)。「name」を「wetch/laralib」とすることで当該ライブラリのインストール先が「vendor/wetch/laralib」になるようですが、「autoload」を上記のように記述することで「vendor/wetch/laralib/sample」配下を名前空間「Wetch\Laralib」として認識できるようになります。

なお、ネット情報などで上記ファイルに「version」が設定されている例も見受けられたのですが、これを行うと後述するgitのブランチまたはタグによるバージョン管理が正しく機能しなくなります。よって上記composer.jsonには「version」を記述しないようにすべきです。
また、「type」として「library」と設定しておくケースも見受けられますが、「type」としては「library」がデフォルトであるため、あえて記述する必要はありません。なお、デフォルト状態含めて「library」が指定された状態だとインストール結果が「vendor」ディレクトリ配下に反映されるようです。

gitにおけるブランチとタグの管理

今回管理対象となるファイル群は、単にファイルとしての世代管理を行いたいだけではなく、ライブラリとしての世代(バージョン)管理も必要とします。両者は別物で、ファイル自体は複数回に渡って更新しつつ、最終的にリリース可能な状態になったものをライブラリとしての1つの世代(バージョン)として管理する形になります。

ここでライブラリのバージョン表記に関して整理しておきたいと思います。
今回のテーマはcomposerによる世代管理なので当然ながらcomposerが認識できるバージョン表現が必要となります。これは「セマンティックバージョニング」と言われる方式で、以下の3つの番号から生成されます。

メジャーバージョン後方互換性を持たない変更が行われた場合にアップする数字
マイナーバージョン後方互換性を保ちつつ機能追加・変更した場合にアップする数字
パッチバージョン不具合修正時にアップする数字(本来期待される正常動作に戻すための変更なので基本的には後方互換があるはず)

X:メジャーバージョン、Y:マイナーバージョン、Z:パッチバージョンとすると、バージョン番号は「X.Y.Z」と言う形式で表現することになります。特に後方互換性(旧バージョンを使用していたプロジェクトが新バージョンを使用できるかどうか)の有無と言う点で、メジャーバージョンの違いは重要になります。
よって、gitにおけるライブラリの世代(バージョン)管理としては、メジャーバージョンの違いでブランチを分け、マイナーバージョン以下の変更を同ブランチ内のタグで識別するようにします。

上記方針に従い、まずは初期バージョンを管理するブランチとして「v1」を作成します。

ライブラリ本体作成

当該ライブラリは「vendor/wetch/laralib」配下にインストールされ、名前空間「Wetch\Laralib」を指定することで「vendor/wetch/laralib/sample」配下を参照するようにcomposer.jsonを作成しました。よって開発環境(git環境)においてはライブラリを「./sample」に配置する必要があります。
最初の方針に合わせて上記ディレクトリにファイル「LibSample.php」を作成し、クラス「LibSample」を定義します。
今回は動作確認が目的なので、ごく簡単に以下程度の内容にしておきます。

<?php

namespace Wetch\Laralib;

class LibSample
{
    public static function sample()
    {
        echo "LibSample v1.0.0";
    }
}

タグによるバージョン管理

上記初期バージョンをコミットし、タグとして「v1.0.0」を設定、このブランチ「v1」を共用リポジトリにプッシュします。
なお、タグにおけるバージョン表記方法としては上記のように「v」から始めないとcomposerが正しく認識できないとの情報があります(細かく確認していませんが)。

以上で本ライブラリを配布するための環境が整いました。

ライブラリのインストール

上記で作成したライブラリをLaravel環境にインストールしてみます。

まずはcomposerに対してライブラリ提供元であるgit共用リポジトリの設定を行います。

composer config repositories.laralib vcs https://<git共用リポジトリ>/root/laralib.git

上記で「repositories.laralib」の部分の「laralib」は実は適当な名称で良いようです。重要なのは「repositories」として、タイプが「vsc」(バージョン管理システム)を指定しつつ目的の共用リポジトリのURLを指定することです。URL部分は使用している共用リポジトリに合わせて適宜読み替えてください。
上記結果としてcomposer.jsonに以下のような定義が追加されます。

"repositories": {
    "laralib": {
        "type": "vcs",
        "url": "https://<git共用リポジトリ>/root/laralib.git"
    }   
}

上記設定ができたら、ライブラリをインストールしてみます。

composer require wetch/laralib

初回だけ共用リポジトリへのアクセスに必要な認証情報の入力を求められますので、適正なユーザー名、パスワードを入力します。
その後、以下のように聞かれますので「y」とすると認証情報が記録され、以降の操作では認証情報の入力を求められなくなります。

Do you want to store credentials for <共用リポジトリ> in /home/misc/.config/composer/auth.json ? [Yn]

なお、ネット情報では上記リポジトリやライブラリに関する情報のcomposer.jsonへの反映をエディタで直接行うような手順が多く見受けられますが、不適切な変更を避けるために上記のようにコマンドを使うべきとの意見もあり、私としては後者に賛同します。

上記までの作業で問題がなければ「vendor/wetch」ディレクトリが作成され、その配下にも想定通りのディレクトリやファイルが配置されていると思います。

では実際にライブラリを使ってみましょう。
コントローラ「SampleController」を作成します。

php artisan make:controller SampleController

中身は以下の通り。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Wetch\Laralib\LibSample;

class SampleController extends Controller
{
    public function sample()
    {
        LibSample::sample();
    }
}

「Wetch\Laralib\LibSample」と言う名称で自作ライブラリが使えるようになっているかが重要なところです。

上記コントローラにアクセスできるようにルート設定します。

Route::get('/sample', 'SampleController@sample');

ブラウザ等から上記SampleController内sampleメソッドを呼び出して、「LibSample v1.0.0」と表示されれば成功です。

バージョン管理の確認

まずは基本的な動作から(マイナー&パッチバージョンアップ)

ライブラリの更新が適正に認識されていることを確認してみます。
開発環境で「sample/LibSample.php」内の

echo "LibSample v1.0.0";

echo "LibSample v1.0.1";

に変更、コミットしてタグ「v1.0.1」を適用し、共用リポジトリにプッシュします。

Laravel環境ではcomposerのアップデートを実行します。

composer update

以下のようなメッセージを出しつつ処理が完了します。

Loading composer repositories with package information
Updating dependencies                                 
Lock file operations: 0 installs, 1 update, 0 removals
  - Upgrading wetch/laralib (v1.0.0 => v1.0.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Upgrading wetch/laralib (v1.0.0 => v1.0.1): Checking out e1be6ca029
...

メッセージの内容からも「v1.0.1」にアップデートされたっぽいですが、一応先のSampleController内sampleメソッドを呼び出して、「LibSample v1.0.1」と表示されることも確認しておきます。

メジャーバージョンアップ

ではメジャーバージョンを変更した場合はどうなるでしょう?

メジャーバージョンを変える必要性があるのは後方互換性がない場合なので、旧バージョンを使用しているプロジェクトに継続してアップデートの提供は行っていくためには世代管理を分ける必要があります。よってメジャーバージョンアップ時には同バージョン専用のブランチを生成し、旧バージョンブランチでは継続して同バージョンの世代管理ができるようにします。
と言うことで、既存ブランチ「v1」から「v2」を生成します。

例によって「sample/LibSample.php」内のバージョン表記を「v2.0.0」に変更し、コミットしてタグ「v2.0.0」を適用し、共用リポジトリにプッシュします。
で、composerのアップデートを実行…しても多分何も起こりません。メジャーバージョンアップは後方互換性がないので、旧バージョンを使用していた環境に勝手に新バージョンを適用されても困ります。その辺がちゃんと考慮されている訳です。

そもそも初期インストールの段階では以下のようにコマンドを実行しました。

composer require wetch/laralib

上記ではバージョンを明記していませんが、結果としてはその段階での最新のバージョンが適用されています。
composer.jsonの中を見ると以下のような設定になっていると思います。

...
"require": {
    ...
    "wetch/laralib": "^1.0"
},
...

この設定は、あくまでメジャーバージョンが「1」の中で最新のものを適用することを意味しており、別メジャーバージョンにおける更新結果は対象になりません。

では、メジャーバージョンアップリリース後でも旧バージョンのアップデートが正しく適用されるかを見てみましょう。
ブランチを「v1」に戻して「sample/LibSample.php」内のバージョン表記を「v1.0.2」に変更し、コミットしてタグ「v1.0.2」を適用し、共用リポジトリにプッシュします。
その後composerのアップデートを実行すると、ちゃんと「v1.0.2」が適用されます。

次に、インストールされている「wetch/laralib」を削除し、再度インストールを実行してみましょう。

composer remove wetch/laralib
composer require wetch/laralib

今度は「v2.0.0」が適用されたと思います。アップデートに関しても「v2」の範囲で正しく適用されることを確認できるはずです。

もし、新バージョンリリース後に旧バージョンを適用したければ、インストール時にバージョンを明示します。

composer remove wetch/laralib
composer require wetch/laralib:^1.0

「v1」としての最新版「v1.0.2」が適用されると思います。便利ですね。

なお、git上で「v1」と「v2」を別ブランチにしていますが、このことと上記composerによるメジャーバージョンの区別は直接は関係しません。あくまでタグとして設定されたバージョン番号がアップデート対象の判断に影響を与えます。例えば「master」ブランチ上で複数のメジャーバージョンを混在させて管理することや、1つのメジャーバージョンを複数のブランチに分けて管理することも可能かと思います(論理上は)。特にブランチ名自体もcomposerのバージョン判断に反映されますので、アップデートごとにブランチを分け、適正なバージョン番号をブランチ名として設定することでもバージョン管理はできます。
ただ、バージョン管理をブランチ名とタグのどちらに基づいて行うかは統一しておいた方が分かりやすいと思いますし、ブランチはあくまで異なるメジャーバージョンに対する世代管理の独立性の確保のみに用いた方が良いと思いますので、先にあげたようにメジャーバージョンごとにブランチ分け、バージョン番号はタグで管理するのが良いと思っています。

これで自作ライブラリをgit+composerで管理するための方式が確認できました。
処理の共通化/汎用化の土台となる環境構築方法が確立したと言う点がまずはめでたいですが、実は今までgitのタグは使用したことがなく、composerに関してもネット情報に基づいて漠然と使っていたようなところがあったので、それらに関しても今回の作業を通して多少なりとも理解が深まったことは僥倖でした。