PHP の列挙型のない列挙型

in Vlog

(jp) =

PHP では列挙型がまだ不足しています — スクラッチ: 列挙型は PHP 8.1 で追加されました — それでも、外部依存関係を使用せずに、コード ベースで列挙型のような動作を行うクリーンな方法があります。 日付範囲の境界の例を見てみましょう。その境界は、含めたり除外したりできます。 方法は次のとおりです。 Boundaries 列挙型が使用されます:

$dateRange = DateRange::make(
    '2020-02-01', 
    '2020-03-01', 
    Boundaries::INCLUDE_ALL()
);

これは、のコンストラクターの署名です DateRange 次のようになります。

public function __construct($start, $end, Boundaries $boundaries);

それが最初の要件です。 型システムを使用して、有効な列挙値のみが使用されるようにしたい.

次に、次のように、どの境界が含まれているかを enum に尋ねられるようにしたいと考えています。

$dateRange->boundaries->startIncluded();
$dateRange->boundaries->endIncluded();

これは、各列挙値が独自の実装をサポートする必要があることを意味します startIncludedendIncluded.

それが 2 番目の要件です。 列挙型が値固有の動作をサポートするようにしたい.

一見すると、最も簡単な解決策は、 Boundaries クラス、および実装 startIncludedendIncluded そのようです:

final class Boundaries

    private const INCLUDE_NONE = 'none';
    private const INCLUDE_START = 'start';
    private const INCLUDE_END = 'end';
    private const INCLUDE_ALL = 'all';

    private string $value;

    public static function INCLUDE_START(): self
    
        return new self(self::INCLUDE_START);
    

    private function __construct(string $value) 
    
        $this->value = $value;
    

    public function startIncluded(): bool
    
        return $this->value === self::INCLUDE_START
            
    
    public function endIncluded(): bool
    

つまり、列挙型の値に条件を使用して動作を追加します。

この例では、これは十分にクリーンなソリューションです。 ただし、それほどうまくスケーリングしません。 列挙型がより複雑な値固有の機能を必要としていると想像してください。 多くの場合、大きな条件付きブロックを含む大きな関数になってしまいます。

条件が多いほど、コードがたどるパスが多くなり、理解と維持がより複雑になり、バグが発生しやすくなります。

それが 3 番目の要件です。 列挙値に条件を使用することは避けたい.

要約すると、列挙型が次の 3 つの要件に一致する必要があります。

  • 型システムがチェックできるように、列挙値は厳密に型指定する必要があります。
  • 列挙型は値固有の動作をサポートする必要があります
  • 値固有の条件は、何としてでも避ける必要があります

ここでポリモーフィズムが解決策を提供できます。各列挙値は独自のクラスで表すことができ、 Boundaries 列挙。 したがって、各値は独自のバージョンの startIncludedendIncluded、単純なブール値を返します。

たぶん、次のようなものを作るでしょう:

abstract class Boundaries

    public static function INCLUDE_NONE(): IncludeNone
    
        return new IncludeNone();
    
    
    
    
    abstract public function startIncluded(): bool;

    abstract public function endIncluded(): bool;

そして、具体的な実装を持っています Boundaries このように—他の3つがどのように見えるか想像できます:

final class IncludeNone extends Boundaries

    public function startIncluded(): bool
    
        return false;
    

    public function endIncluded(): bool
    
        return false;
    
 

これらの列挙型をプログラムするための最初の作業はまだありますが、現在はすべての要件を満たしています。

改善すべき点がもう 1 つあります。 特定の値に専用のクラスを使用する必要はありません。 単独で使用されることはありません。 したがって、4 つのクラスを拡張する代わりに、 Boundaries、匿名クラスを使用できます。

abstract class Boundaries

    abstract public function startIncluded(): bool;

    abstract public function endIncluded(): bool;

    public static function INCLUDE_NONE(): Boundaries
    
        return new class extends Boundaries 
        
            public function startIncluded(): bool 
                return false; 
            

            public function endIncluded(): bool 
                return false; 
            
        ;
    

    public static function INCLUDE_START(): Boundaries
    
        return new class extends Boundaries 
        
            public function startIncluded(): bool 
                return true; 
            

            public function endIncluded(): bool 
                return false; 
            
        ;
    

    public static function INCLUDE_END(): Boundaries
    
        return new class extends Boundaries 
        
            public function startIncluded(): bool 
                return false; 
            

            public function endIncluded(): bool 
                return true; 
            
        ;
    

    public static function INCLUDE_ALL(): Boundaries
    
        return new class extends Boundaries 
        
            public function startIncluded(): bool 
                return true; 
            

            public function endIncluded(): bool 
                return true; 
            
        ;
    

OK、私は間違っていました。さらに 2 つの改善が必要でした。 これは多くの繰り返しコードです。 しかし、これにも解決策があります。 それぞれの値固有のクラスで 2 つのプロパティを単純に定義しましょう ($startIncluded$endIncluded) そして、abstract にゲッターを実装しましょう Boundaries 代わりにクラス!

abstract class Boundaries

    protected bool $startIncluded;
    protected bool $endIncluded;
    
    public function startIncluded(): bool 
    
        return $this->startIncluded;
    
    
    public function endIncluded(): bool 
    
        return $this->endIncluded;
    

    public static function INCLUDE_NONE(): Boundaries
    
        return new class extends Boundaries 
        
            protected bool $startIncluded = false;
            protected bool $endIncluded = false;
        ;
    

    public static function INCLUDE_START(): Boundaries
    
        return new class extends Boundaries
        
            protected bool $startIncluded = true;
            protected bool $endIncluded = false;
        ;
    

    public static function INCLUDE_END(): Boundaries
    
        return new class extends Boundaries
        
            protected bool $startIncluded = false;
            protected bool $endIncluded = true;
        ;
    

    public static function INCLUDE_ALL(): Boundaries
    
        return new class extends Boundaries
        
            protected bool $startIncluded = true;
            protected bool $endIncluded = true;
        ;
    

上記は、PHP で列挙型を実装するための私のお気に入りのアプローチです。 私が考えることができる欠点が 1 つあるとすれば、セットアップ作業が少し必要なことですが、これは小さな 1 回限りのコストであり、長期的には非常に見返りがあります.

//platform.twitter.com/widgets.js

関連記事

前の投稿
世界で最も面白い鳥の 9
次の投稿
ヘン・オブ・ザ・ウッズ・マッシュルーム:完全ガイド