PHPの特性–実際の例/ベストプラクティスはありますか?[閉まっている]


148

トレイトは、PHP 5.4の最大の追加機能の1つです。私は構文を知っており、ロギング、セキュリティ、キャッシングなどの一般的なものに水平コードを再利用するなど、特性の背後にある考え方を理解しています。

しかし、私は自分のプロジェクトでどのように特性を利用するのかまだわかりません。

すでに特性を使用しているオープンソースプロジェクトはありますか?特性を使用してアーキテクチャを構築する方法についての良い記事/資料はありますか?


8
これが私の意見です。このトピックについて書いた主題に関するブログ投稿です。TL; DR:基本的に、それらは強力であり、十分に使用できるが、私たちが目にする大部分の使用は完全なアンチパターンであり、それらが解決するよりもはるかに多くの苦痛を引き起こすだろう...
ircmaxell

1
scala標準ライブラリーを見てください。そうすれば、特性の多くの有用な例が見つかります。
ドミトリー2012年

回答:


89

私の個人的な見解では、クリーンなコードを記述する場合、実際には特性のアプリケーションはほとんどありません。

特性を使用してコードをクラスにハッキングする代わりに、コンストラクターまたはセッターを介して依存関係を渡すことをお勧めします。

class ClassName {
    protected $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    // or
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

特性を使用するよりも優れていると私が考える主な理由は、特性へのハードカップリングを削除することにより、コードがはるかに柔軟になるためです。たとえば、今すぐ単純に別のロガークラスを渡すことができます。これにより、コードの再利用とテストが可能になります。


4
トレイトを使用して、別のロガークラスを使用することもできますか?トレイトを編集するだけで、そのトレイトを使用するすべてのクラスが更新されます。私が間違っている場合は修正してください
rickchristie

14
@rickchristieもちろん、あなたはそれを行うことができます。ただし、特性のソースコードを編集する必要があります。したがって、別のロガーが必要な特定のクラスだけでなく、それを使用するすべてのクラスで変更します。また、同じクラスを2つの異なるロガーで使用したい場合はどうでしょうか。または、テスト中にモックロガーに合格したいですか?トレイトを使用する場合、依存性注入を使用する場合はできません。
NikiC、2011年

2
私はあなたの要点を見ることができます、また、特性がそれの価値があるかどうかについても考えています。つまり、Symfony 2のような最新のフレームワークでは、ほとんどすべてのケースで、特性より優れているように見える場所全体に依存性注入があります。現時点では、「コンパイラー支援のコピーと貼り付け」よりもずっと特徴が少ないと思います。;)
最大の

11
現時点では、「コンパイラー支援のコピーと貼り付け」よりもずっと特徴が少ないと思います。;):@Max:それがまさにそのように設計された特性であるので、それは完全に正しいです。定義が1つしかないため、「保守可能」になりますが、基本的にはc&p ...
ircmaxell

29
NikiCには要点がありません。トレイトを使用しても、依存性注入の使用は妨げられません。この場合、トレイトは、ロギングを実装するすべてのクラスに、setLogger()メソッドと$ loggerプロパティの作成を複製する必要がないようにします。特性はそれらを提供します。setLogger()は、例のようにLoggerInterfaceにヒントを入力するため、任意のタイプのロガーを渡すことができます。このアイデアは、以下のGordonの回答に似ています(Loggerインターフェースではなく、Loggerスーパークラスを示唆しているように見えます) )。
イーサン

205

受け入れられたグッド/ベストプラクティスを学ぶには、しばらくの間、特性を持つ言語を調べる必要があると思います。Traitに対する私の現在の意見は、同じ機能を共有する他のクラスで複製する必要があるコードにのみ使用するべきだというものです。

ロガートレイトの例:

interface Logger
{
    public function log($message, $level);    
}

class DemoLogger implements Logger
{
    public function log($message, $level)
    {
        echo "Logged message: $message with level $level", PHP_EOL; 
    }
}

trait Loggable // implements Logger
{
    protected $logger;
    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }
    public function log($message, $level)
    {
        $this->logger->log($message, $level);
    }
}

class Foo implements Logger
{
    use Loggable;
}

そして、あなたはします(デモ

$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);

トレイトを使用するときに考慮すべき重要なことは、トレイトは実際にはクラスにコピーされるコードの一部にすぎないということです。これにより、たとえば、メソッドの可視性を変更しようとすると、簡単に競合が発生する可能性があります。

trait T {
    protected function foo() {}
}
class A { 
    public function foo() {}
}
class B extends A
{
    use T;
}

上記はエラーになります(デモ)。同様に、traitで宣言されているメソッドのうち、usingクラスで既に宣言されているメソッドは、クラスにコピーされません。たとえば、

trait T {
    public function foo() {
    return 1;
}
}
class A { 
    use T;
    public function foo() {
    return 2;
}
}

$a = new A;
echo $a->foo();

2(デモ)を出力します。これらは、エラーを見つけにくくするため、避けたいものです。また、それを使用するクラスのプロパティまたはメソッドを操作する特性に物事を入れないようにする必要があります。

class A
{
    use T;
    protected $prop = 1;
    protected function getProp() {
        return $this->prop;
    }
}

trait T
{
    public function foo()
    {
        return $this->getProp();
    }
}

$a = new A;
echo $a->foo();

動作します(デモ)が、トレイトはAに密接に結合されており、水平方向の再利用の全体的なアイデアは失われています。

インターフェース分離の原則に従うと、多くの小さなクラスとインターフェースができます。そのため、Traitsは、たとえば横断的関心事など、あなたが言及したことの理想的な候補になりますが、(構造的な意味で)オブジェクトを構成することはできません。上記のロガーの例では、特性は完全に分離されています。具象クラスに依存しません。

(このページの他の場所に示すように)集約/構成を使用して同じ結果のクラスを実現できますが、集約/構成を使用する欠点は、プロキシ/デリゲーターメソッドを手動で各クラスすべてに追加する必要があることです。ログに記録できる。特性は、ボイラープレートを1か所に保持し、必要に応じて選択的に適用できるようにすることで、これをうまく解決します。

注:特性はPHPの新しい概念であるため、上記のすべての意見は変更される可能性があります。自分でコンセプトを評価する時間はまだありません。しかし、私はあなたが考えることをあなたに与えるのに十分良いことを望みます。


41
これは興味深いユースケースです。コントラクトを定義するインターフェースを使用し、そのコントラクトを満たすために特性を使用します。いいね。
最大の

13
私はこの種の真のプログラマーが好きです。彼らは、それぞれに短い説明が付いた実際の実用的な例を提案しています。Thx
アーサークッシュマン

1
誰かが代わりに抽象クラスを使用するとどうなりますか?インターフェイスとトレイトを置き換えると、抽象クラスを作成できます。また、アプリケーションにインターフェースが必要な場合は、抽象クラスもインターフェースを実装して、特性のようにメソッドを定義できます。それで、なぜ私たちはまだ特性が必要なのか説明できますか?
sumanchalki 2013年

12
@sumanchalki抽象クラスは継承のルールに従います。LoggableおよびCacheableを実装するクラスが必要な場合はどうなりますか?AbstractCacheを拡張する必要があるAbstractLoggerを拡張するクラスが必要です。しかし、それはすべてのLoggableがキャッシュであることを意味します。それはあなたが望まないカップリングです。再利用を制限し、継承グラフを混乱させます。
Gordon

1
デモリンクは機能していないと思います
Pmpr

19

:)私は何かで何をすべきかを理論化して議論するのは好きではありません。この場合の特性。私がどのような特性が役立つかを示し、そこから学ぶことも、無視することもできます。

特性 - 戦略を適用するのに最適です。つまり、戦略設計パターンは、同じデータを別の方法で処理(フィルター処理、並べ替えなど)したい場合に役立ちます。

たとえば、いくつかの基準(ブランド、仕様など)に基づいてフィルターで除外する、またはさまざまな手段(価格、ラベルなど)で並べ替える製品のリストがあるとします。さまざまなソートタイプ(数値、文字列、日付など)に対応するさまざまな関数を含むソートトレイトを作成できます。次に、このトレイトを製品クラス(例に示されている)だけでなく、同様の戦略が必要な他のクラス(数値ソートを一部のデータに適用するなど)でも使用できます。

それを試してみてください:

<?php
trait SortStrategy {
    private $sort_field = null;
    private function string_asc($item1, $item2) {
        return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
    }
    private function string_desc($item1, $item2) {
        return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
    }
    private function num_asc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
    }
    private function num_desc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
    }
    private function date_asc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 < $date2 ? -1 : 1 );
    }
    private function date_desc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 > $date2 ? -1 : 1 );
    }
}

class Product {
    public $data = array();

    use SortStrategy;

    public function get() {
        // do something to get the data, for this ex. I just included an array
        $this->data = array(
            101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
            101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
            101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
            101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
            101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
        );
    }

    public function sort_by($by = 'price', $type = 'asc') {
        if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
        switch ($by) {
            case 'name':
                $this->sort_field = 'label';
                uasort($this->data, array('Product', 'string_'.$type));
            break;
            case 'date':
                $this->sort_field = 'date_added';
                uasort($this->data, array('Product', 'date_'.$type));
            break;
            default:
                $this->sort_field = 'price';
                uasort($this->data, array('Product', 'num_'.$type));
        }
    }
}

$product = new Product();
$product->get();
$product->sort_by('name');
echo '<pre>'.print_r($product->data, true).'</pre>';
?>

締めくくりとして、アクセサリーなどの特性について考えます(データを変更するために使用できます)。私のクラスから切り取られ、単一の場所に配置できる同様のメソッドとプロパティにより、メンテナンスが容易になり、コードが短くてすっきりします。


1
これによりパブリックインターフェースがクリーンに保たれますが、内部のインターフェースはこれで本当に複雑になる可能性があります。これを、たとえば色などの他のものに拡張する場合は特にそうです。ここでは、単純な関数または静的メソッドの方が良いと思います。
セバスチャンマッハ

用語が好きstrategiesです。
Rannie Ollit

4

Magento eコマースプラットフォームの拡張機能を開発する際の一般的な問題を解決するため、Traitsに興奮しています。この問題は、拡張機能がコアクラス(ユーザーモデルなど)を拡張して機能を追加するときに発生します。これは、拡張機能からユーザーモデルを使用するように(XML構成ファイルを介して)Zendオートローダーをポイントし、新しいモデルでコアモデルを拡張することによって行われます。()しかし、2つの拡張が同じモデルをオーバーライドした場合はどうなりますか?「競合状態」が発生し、1つだけがロードされます。

現時点での解決策は、拡張を編集して一方が他方のモデルオーバーライドクラスをチェーンで拡張し、拡張構成を設定してそれらを正しい順序でロードし、継承チェーンが機能するようにすることです。

このシステムは頻繁にエラーを引き起こし、新しい拡張機能をインストールするときは、競合をチェックして拡張機能を編集する必要があります。これは苦痛であり、アップグレードプロセスを壊します。

Traitsを使用することは、この厄介なモデルが「競合状態」をオーバーライドすることなく同じことを達成するための良い方法だと思います。複数のトレイトが同じ名前のメソッドを実装する場合でも競合が発生する可能性があることは承知していますが、単純な名前空間の規則のようなものでほとんどの場合これを解決できると思います。

TL; DR Magentoのような大規模なPHPソフトウェアパッケージの拡張機能/モジュール/プラグインを作成するには、トレイトが役立つと思います。


0

次のような読み取り専用オブジェクトの特性を持つことができます:

  trait ReadOnly{  
      protected $readonly = false;

      public function setReadonly($value){ $this->readonly = (bool)$value; }
      public function getReadonly($value){ return $this->readonly; }
  }

その特性が使用されているかどうかを検出し、そのオブジェクトをデータベースやファイルなどに書き込む必要があるかどうかを判断できます。


そのため、useこの特性を持つクラスは次に呼び出されif($this -> getReadonly($value))ます。ただし、useこの特性を使用しないと、エラーが発生します。したがって、この例には欠陥があります。
Luceos 2013年

さて、あなたは最初に特性が使用されているかどうかを確認する必要があります。オブジェクトにReadOnlyトレイトが定義されている場合は、それが読み取り専用かどうかを確認できます。
ニコ


3
そのためには、ReadOnlyのインターフェイスを宣言する必要があります
Michael Tsang
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.