LoginSignup
7
7

More than 5 years have passed since last update.

プロパティの型を強制するオブジェクトを作る

Posted at

PHPで、強い型付けが欲しくなることがあったので作ってみた。

他にも型っぽいコードをパッケージに含めてある。
まだREADMEとか色々書きかけです。

使い方

composerでインストールできる。

$ composer require spindle/types:*

Spindle\\Types\\TypedObject を継承してクラスを作る。
TypedObjectは2つのメソッドの実装を強制する。staticメソッドのschema() および checkErrors() である。

typed.php
<?php
namespace Acme;

use Spindle\Types;

class Klass extends Types\TypedObject
{
  static function schema()
  {
    return [
      //プロパティ名 => 型, デフォルト値(省略可), を繰り返して書く
      'propInt' => self::INT,
      'propStr' => self::STR, 'initial value',
      'propBool' => self::BOOL, false,
      'propDate' => 'DateTime', new DateTime,
    ];
  }

  //単純な型チェックではとらえきれないバリデーションを行うときに使う。何かエラーを表すものの配列を返すイメージ。
  //例えば「他のプロパティの状態によってチェック条件が変わる」など
  function checkErrors()
  {
    $errors = [];
    if ($this->propInt < 0) {
      $errors['propInt'][] = 'propIntに負数が入っている';
    }
    return $errors;
  }
}

$klass = new Klass;
$klass->propInt = 1;
echo $klass->propInt;

$klass->propInt = '1'; //例外を発生させる

schema()は上記のような書き方でプロパティの定義を配列で返す。型がオブジェクトの状態に依存するはずがないから、staticを強制しておいた。

想定する用途

リファクタリングのパターンに出てくる、「引数オブジェクトの導入」あたりを念頭に置いている。

before.php
<?php

function doSomething1($firstname, $lastname, $age)
{
  //...
}

function doSomething2($firstname, $lastname, $age)
{
  //...
}

こういう「同じ形式の引数」があちこちに存在するコードはくさい。意図がわかりにくいし、関数によって引数の順序が統一されていないという気持ち悪い状態になりやすい。

そのためTypedObjectを作ってわかりやすくする。

after.php
<?php
class User extends Spindle\Types\TypedObject
{
  static function schema()
  {
    return [
      'firstname' => self::STR,
      'lastname' => self::STR,
      'age' => self::INT,
    ];
  }

  function checkErrors()
  {
    return [];
  }
}

function doSomething1(User $user)
{
  $user->firstname;
  $user->lastname;
  $user->age;
  //...
}

function doSomething2(User $user)
{
  //...
}

別に素のクラスで作ってもよいが、getter/setterを型の保証のためだけに作るのは面倒くさいし、getter/setterのテストは書きたくない。カバレッジの実態もずれてしまう。

そこでTypedObjectを使えばお手軽に型だけ保証したクラスを作ることができる。ただし、getやsetをfinalで奪ってしまうので、用途は限られるかもしれない。PHPであえて型の保証をしようとすると、クラスから能力を奪うことになる、というのは面白い。

あ、これだとカプセル化したことにならないので、ConstObjectなどでImmutable化することはできる。(JavaScript風?C言語のconst風?)

もんやり

いろんな関数/メソッドに引き渡され、長時間生き残るデータは、カチッとしたクラスに押し込めておいた方が扱いを間違えない。

PHPにおいて安全性とかは動的型付けで保証されているので、別にこんな努力は不要なのだが、データに印を打ってプログラムの論理的整合性を保証するという型システムのメリットをちょっとだけでも使えれば、テストケースとテストケースの隙間を埋めることができそうに思って、いろいろジタバタしている。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7