web-dev-qa-db-ja.com

PHPの特徴–実際の例/ベストプラクティスはありますか?

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

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

既に特性を使用しているオープンソースプロジェクトはありますか?特性を使用してアーキテクチャを構築する方法に関する優れた記事/読み物はありますか?

145
Max

私の個人的な意見では、きれいなコードを書くとき、特性のアプリケーションはほとんどありません。

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

class ClassName {
    protected $logger;

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

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

84
NikiC

受け入れられたグッド/ベストプラクティスを習得するには、しばらくの間Traitsのある言語を調べる必要があると思います。 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;
}

上記はエラーになります( demo )。同様に、使用クラスでも既に宣言されているトレイトで宣言されたメソッドは、クラスにコピーされません。

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

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

2を出力します( demo )。これらは、エラーを見つけにくくするため、避けたいものです。また、それを使用するクラスのプロパティまたはメソッドを操作する特性に物事を置くことを避けたいでしょう。

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();

動作します( demo )が、今では特性はAと密接に結びついており、水平再利用の概念全体が失われています。

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

aggregation/composition を使用して(このページの他の場所に示すように)同じ結果のクラスを実現できますが、aggregation/compositionを使用することの欠点は、proxy/delegatorメソッドを手動で追加する必要があることですあらゆるクラスがログに記録できるはずです。特徴は、ボイラープレートを1か所に保持し、必要に応じて選択的に適用できるようにすることで、これをうまく解決します。

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

194
Gordon

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

Traits-適用するのが素晴らしいstrategies。要するに、戦略設計パターンは、同じデータを異なる方法で処理(フィルター、ソートなど)したい場合に役立ちます。

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

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

<?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>';
?>

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

19
D. Marti

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

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

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

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

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

4
thaddeusmt

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

  trait ReadOnly{  
      protected $readonly = false;

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

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

0
Nico