1行結論
僕では絶対こちらがいい、という決め手を見つけられませんでした。プロジェクト次第。
サンプルコードを書いてみる
サンプルコードを書いてメリット・デメリットを考える。
- 姓、名、フルネームを持つユーザークラスを作成したい
- User, FirstName, LastName, FullName クラスをそれぞれ作成する
- PHP8.2
- Qiita 上でササッと書いたのでコードに間違いあるかも
private property + getter の場合
namespace Kazumacchi\Sample;
class FirstName
{
/**
* @param string $firstName
*/
public function __construct(
private string $firstName,
) {
}
/**
* @return string
*/
public function getValue(): string
{
return $this->firstName;
}
}
namespace Kazumacchi\Sample;
class LastName
{
/**
* @param string $lastName
*/
public function __construct(
private string $lastName,
) {
}
/**
* @return string
*/
public function getValue(): string
{
return $this->lastName;
}
}
namespace Kazumacchi\Sample;
class FullName
{
/**
* @param FirstName $firstName
* @param LastName $lastName
*/
public function __construct(
private FirstName $firstName,
private LastName $lastName,
) {
}
/**
* @return string
*/
public function getValue(): string
{
return $this->firstName->getValue() . ' ' . $this->lastName->getValue();
}
}
namespace Kazumacchi\Sample;
class User
{
/**
* @var FullName
*/
private FullName $fullName;
/**
* @param FirstName $firstName
* @param LastName $lastName
*/
public function __construct(
private FirstName $firstName,
private LastName $lastName
) {
$this->fillName = new FullName(
$this->firstName,
$this->lastName,
);
}
/**
* @return FirstName
*/
public function getFirstName(): FirstName
{
return $this->firstName;
}
/**
* @return LastName
*/
public function getLastName(): LastName
{
return $this->lastName;
}
/**
* @return FullName
*/
public function getFullName(): FullName
{
return $this->fullName;
}
}
$user = new User(
new FirstName('タナカ'),
new LastName('タロウ'),
);
// 名を出力
echo $user->getFirstName()->getValue();
// 姓を出力
echo $user->getLastName()->getValue();
// フルネームを出力
echo $user->getFullName()->getValue();
private property + getter のメリット
interface, abstract で擬似的にプロパティを持つことを強制でき、クラスの振る舞いが明確になる
interface を利用して、メソッドを介して擬似的に firstName, lastName, fullName プロパティを強制する
interface UserInterface
{
/**
* @return FirstName
*/
public function getFirstName(): FirstName;
/**
* @return LastName
*/
public function getLastName(): LastName
/**
* @return FullName
*/
public function getFullName(): FullName;
}
class User implements UserInterface
{
// 省略
}
クラスメソッド内で取得方法の変更や追加のロジックを入れることが出来るためコード変更の柔軟性が高い
class FullName
{
// 省略
/**
* @return string
*/
public function getValue(): string
{
return $this->firstName->getValue() . ' ' . $this->lastName->getValue();
}
/**
* 敬称をつける
*
* @return string
*/
public function getSuffixValue(): string
{
return $this->getValue() . ' 様';
}
}
private property + getter のデメリット
- コード量が多い。経験上、getter メソッドのほとんどは private property をそのまま返却しているため、わざわざメソッドを介することが無駄に感じる。YAGNI原則だとこういうのはどうなるのか気になる。
class FirstName
{
// 省略
public function getValue()
{
// プロパティを返しているだけ
return $this->firstName;
}
}
public readonly property の場合
namespace Kazumacchi\Sample;
/**
* @property-read string $value
*/
class FirstName
{
/**
* @param string $value
*/
public function __construct(
public readonly string $value;
) {
}
/**
* @return string
*/
public function __toString(): string
{
return $this->value;
}
}
namespace Kazumacchi\Sample;
/**
* @property-read string $value
*/
class LastName
{
/**
* @param string $value
*/
public function __construct(
public readonly string $value;
) {
}
/**
* @return string
*/
public function __toString(): string
{
return $this->value;
}
}
namespace Kazumacchi\Sample;
/**
* @property-read FirstName $firstName
* @property-read LastName $lastName
*/
class FullName
{
/**
* @param FirstName $firstName
* @param LastName $lastName
*/
public function __construct(
public readonly FirstName $firstName,
public readonly LastName $lastName,
) {
}
/**
* @return string
*/
public function __toString(): string
{
return $this->firstName . ' ' . $this->lastName;
}
}
namespace Kazumacchi\Sample;
/**
* @property-read FirstName $firstName
* @property-read LastName $lastName
* @property-read FullName $fullName
*/
class User
{
/**
* @var FullName
*/
public readonly FullName $fullName;
/**
* @param FirstName $firstName
* @param LastName $lastName;
*/
public function __construct(
public readonly FirstName $firstName,
public readonly LastName $lastName,
) {
$this->fullName = new FullName(
$this->firstName,
$this->lastName,
);
}
}
$user = new User(
new FirstName('タナカ'),
new LastName('タロウ'),
);
// 名を出力
echo $user->firstName;
// or
echo $user->firstName->value;
// 姓を出力
echo $user->lastName;
// or
echo $user->lastName->value;
// フルネームを出力
echo $user->fullName;
public readonly property のメリット
コード量が少なめ
コード量が少ないということは、コードが読みやすくなるということ
プロパティの値をシンプルに取得できる
古いPHPのように外部からプロパティが書き換えられる心配もない
echo $user->fullName;
readonly なので __construct() 以外でプロパティの値が変更されないことを読み手に伝えられる
- 他のクラスメソッドで値が変わる可能性は一切考慮する必要なし
- setter メソッドも存在しないことが分かる
public readonly property のデメリット
interface, abstract でプロパティを持つことを強制できない
元々プロパティを持つことを強制は出来ませんが、メソッドで擬似的に強制することもできない
(※private setter メソッドでも書いて __construct() で呼び出しとかすれば行けるかもしれないですが…)
そのため何も書かれていない interface を継承することになりそう
このコードが無意味とまでは言わないが、interface を使うメリットは薄くなる
interface UserInterface
{
}
class User implements UserInterface
{
// 省略
}
結論
どちらにも無視できないメリットデメリットがあって僕ではよく分からない
結局プロジェクト次第ということになるのかな
スーパーエンジニアにこういうのを聞いてみたいが、社内に見当たらない件
もし interface や abstract でプロパティそのものを強制出来るようになった場合、そこが getter の寿命かも
(そんな機能に需要があるかどうかは知りませんけど!)