PHP の private property + getter と public readonly property どちらを使うべきか？

# 1行結論

僕では絶対こちらがいい、という決め手を見つけられませんでした。プロジェクト次第。

# サンプルコードを書いてみる

サンプルコードを書いてメリット・デメリットを考える。

- 姓、名、フルネームを持つユーザークラスを作成したい
- User, FirstName, LastName, FullName クラスをそれぞれ作成する
- PHP8.2
- Qiita 上でササッと書いたのでコードに間違いあるかも

# private property + getter の場合

```php
namespace Kazumacchi\Sample;

class FirstName
{
    /**    
     * @param  string  $firstName
     */
    public function __construct(
        private string $firstName,
    ) {
    }

    /**
     * @return string
     */
    public function getValue(): string
    {
        return $this->firstName;
    }
}
```

```php
namespace Kazumacchi\Sample;

class LastName
{
    /**     
     * @param  string  $lastName
     */
    public function __construct(
        private string $lastName,
    ) {
    }

    /**
     * @return string
     */
    public function getValue(): string
    {
        return $this->lastName;
    }
}
```

```php
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();
    }
}
```

```php
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;
    }
}
```

```php
$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 プロパティを強制する

```php
interface UserInterface
{
    /**
     * @return FirstName
     */
    public function getFirstName(): FirstName;

    /**
     * @return LastName
     */
    public function getLastName(): LastName

    /**
     * @return FullName
     */
    public function getFullName(): FullName;
}
```

```php
class User implements UserInterface
{
    // 省略
}
```

### クラスメソッド内で取得方法の変更や追加のロジックを入れることが出来るためコード変更の柔軟性が高い

```php
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原則だとこういうのはどうなるのか気になる。

```php
class FirstName
{
    // 省略

    public function getValue()
    {
        // プロパティを返しているだけ
        return $this->firstName;
    }
}
```


# public readonly property の場合

```php
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;
    }
}
```

```php
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;
    }
}
```

```php
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;
    }
}
```


```php
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,
        );
    }
}
```

```php
$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のように外部からプロパティが書き換えられる心配もない

```php
echo $user->fullName;
```

### readonly なので __construct() 以外でプロパティの値が変更されないことを読み手に伝えられる

- 他のクラスメソッドで値が変わる可能性は一切考慮する必要なし
- setter メソッドも存在しないことが分かる

## public readonly property のデメリット

### interface, abstract でプロパティを持つことを強制できない

元々プロパティを持つことを強制は出来ませんが、メソッドで擬似的に強制することもできない
（※private setter メソッドでも書いて __construct() で呼び出しとかすれば行けるかもしれないですが…）

そのため何も書かれていない interface を継承することになりそう
このコードが無意味とまでは言わないが、interface を使うメリットは薄くなる

```php
interface UserInterface
{
}
```

```php
class User implements UserInterface
{
    // 省略
}
```

# 結論

どちらにも無視できないメリットデメリットがあって僕ではよく分からない
結局プロジェクト次第ということになるのかな

スーパーエンジニアにこういうのを聞いてみたいが、社内に見当たらない件

もし interface や abstract でプロパティそのものを強制出来るようになった場合、そこが getter の寿命かも
（そんな機能に需要があるかどうかは知りませんけど！）