PHP であるクラスのインスタンスを生成する際はnew クラス名($初期化用引数)といった形のコードを記述します。この new クラス名 で呼ばれるメソッドの処理はインスタンス化対象のクラス内の __construct という名前のコンストラクタメソッドに記述されます。
PHP: コンストラクタとデストラクタ – Manual
コンストラクタメソッドを用いるよくあるパターンがプロパティの初期値、あるいは固定値を外部から与えるパターンです。例えばこれは次です。
<?php
/**
* @see https://www.php.net/manual/ja/language.oop5.decon.php
*/
class Point {
protected int $x;
protected int $y;
public function __construct(int $x, int $y = 0) {
$this->x = $x;
$this->y = $y;
}
}
// 引数を両方渡す
$p1 = new Point(4, 5);
// 必須の引数のみを渡す。$y はデフォルト値0になります。
$p2 = new Point(4);
// 名前付き引数(PHP 8.0 以降):
$p3 = new Point(y: 5, x: 4);
プロパティの x,y を初期値で与えています。しかし、時には必要な引数が複数パターンあり、かつどのパターンも用いたいときがあります。例えば、地球上のある一地点を表現する場合で、緯度経度から初期化する方法と平面直角座標から初期化する方法の二種類が要求される場合です。これをすべてコンストラクタメソッドにまとめて記述すると次の様になります。
/**
* 緯度経度、浜松市の属するゾーンのXY座標を持つ地点を表現するクラス
* Class GeoPoint
* @property-read float $lat 緯度
* @property-read float $lng 経度
* @property-read float $x X座標
* @property-read float $y Y座標
*/
class GeoPoint
{
private $lat;
private $lng;
private $x;
private $y;
/**
* GeoPoint constructor.
* @param $lat
* @param $lng
* @param $x
* @param $y
*/
public function __construct($lat, $lng, $x = null, $y = null)
{
if(isset($lat,$lng)){
$this->lat = $lat;
$this->lng = $lng;
[$x, $y] = get_xy_from_latlng($lat, $lng);// 緯度経度からXY座標を得る関数
$this->x = $x;
$this->y = $y;
}else if(isset($x,$y)){
$this->x = $x;
$this->y = $y;
[$lat, $lng] = get_latlng_from_xy($x, $y);// XY座標から緯度経度を得る関数
$this->lat = $lat;
$this->lng = $lng;
}else{
throw new \LogicException('不正な初期化が行われました');
}
}
// getter等色々
}
new GeoPoint(35,135);// 緯度経度から初期化
new GeoPoint(null, null, 500000, 3873043.064);// XY座標から初期化
第一、第二引数を使うか第三、第四引数を使うかの二択であり、異常が起きた際の例外処理が必要になります。今は 4 つの引数の 2 パターンであるため、この程度ですが増えるにつれて加速度的に複雑になるのは目に見えています。これを解決するにはコンストラクタメソッド相当のメソッドを増やすのが良い方法です。これは例えば次です。
/**
* 緯度経度、浜松市の属するゾーンのXY座標を持つ地点を表現するクラス
* Class GeoPoint
* @property-read float $lat 緯度
* @property-read float $lng 経度
* @property-read float $x X座標
* @property-read float $y Y座標
*/
class GeoPoint
{
private $lat;
private $lng;
private $x;
private $y;
/**
* GeoPoint constructor.
* @param $lat
* @param $lng
* @param $x
* @param $y
*
* privateメソッドにすることで外部から new GeoPoint と呼べなくします
*/
private function __construct($lat, $lng, $x, $y)
{
// シンプルにプロパティの初期値をセットするのみにします
$this->lat = $lat;
$this->lng = $lng;
$this->x = $x;
$this->y = $y;
}
/**
* 緯度経度からこのクラスをインスタンス化して返します。
* @param float|int $lat
* @param float|int $lng
* @return GeoPoint
*/
public static function createFromLatLng($lat, $lng): self
{
[$x, $y] = get_xy_from_latlng($lat, $lng);// 緯度経度からXY座標を得る関数
// 適切な緯度、経度、X座標、Y座標が与えられると信用できるこのクラス内のみで new します
return new self($lat, $lng, $x, $y);
}
/**
* XY座標からこのクラスをインスタンス化して返します。
* @param float|int $x
* @param float|int $y
* @return GeoPoint
*/
public static function createFromXY($x, $y): self
{
[$lat, $lng] = get_latlng_from_xy($x, $y);// XY座標から緯度経度を得る関数
// 適切な緯度、経度、X座標、Y座標が与えられると信用できるこのクラス内のみで new します
return new self($lat, $lng, $x, $y);
}
}
上記例の様にコンストラクタメソッドを private にすることで外部から new できなくして異常な値が入る状況をガードしつつ、単にプロパティを初期化するのみのシンプルな処理に書き換えられます。外部から new の代わりにインスタンス化する際に用いるメソッドは必要な引数のみを渡せばよい static メソッドです。
この方法はファクトリーパターンとして知られるデザインパターンの一種であり、初期化が更に複雑になった場合は静的メソッドにとどまらず他クラスに初期化処理を記述する様にするのも良いです。