トレイトは、PHP 5.4の最大の追加機能の1つです。私は構文を知っており、ロギング、セキュリティ、キャッシングなどの一般的なものに水平コードを再利用するなど、特性の背後にある考え方を理解しています。
しかし、私は自分のプロジェクトでどのように特性を利用するのかまだわかりません。
すでに特性を使用しているオープンソースプロジェクトはありますか?特性を使用してアーキテクチャを構築する方法についての良い記事/資料はありますか?
トレイトは、PHP 5.4の最大の追加機能の1つです。私は構文を知っており、ロギング、セキュリティ、キャッシングなどの一般的なものに水平コードを再利用するなど、特性の背後にある考え方を理解しています。
しかし、私は自分のプロジェクトでどのように特性を利用するのかまだわかりません。
すでに特性を使用しているオープンソースプロジェクトはありますか?特性を使用してアーキテクチャを構築する方法についての良い記事/資料はありますか?
回答:
私の個人的な見解では、クリーンなコードを記述する場合、実際には特性のアプリケーションはほとんどありません。
特性を使用してコードをクラスにハッキングする代わりに、コンストラクターまたはセッターを介して依存関係を渡すことをお勧めします。
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
特性を使用するよりも優れていると私が考える主な理由は、特性へのハードカップリングを削除することにより、コードがはるかに柔軟になるためです。たとえば、今すぐ単純に別のロガークラスを渡すことができます。これにより、コードの再利用とテストが可能になります。
受け入れられたグッド/ベストプラクティスを学ぶには、しばらくの間、特性を持つ言語を調べる必要があると思います。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の新しい概念であるため、上記のすべての意見は変更される可能性があります。自分でコンセプトを評価する時間はまだありません。しかし、私はあなたが考えることをあなたに与えるのに十分良いことを望みます。
:)私は何かで何をすべきかを理論化して議論するのは好きではありません。この場合の特性。私がどのような特性が役立つかを示し、そこから学ぶことも、無視することもできます。
特性 - 戦略を適用するのに最適です。つまり、戦略設計パターンは、同じデータを別の方法で処理(フィルター処理、並べ替えなど)したい場合に役立ちます。
たとえば、いくつかの基準(ブランド、仕様など)に基づいてフィルターで除外する、またはさまざまな手段(価格、ラベルなど)で並べ替える製品のリストがあるとします。さまざまなソートタイプ(数値、文字列、日付など)に対応するさまざまな関数を含むソートトレイトを作成できます。次に、このトレイトを製品クラス(例に示されている)だけでなく、同様の戦略が必要な他のクラス(数値ソートを一部のデータに適用するなど)でも使用できます。
それを試してみてください:
<?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>';
?>
締めくくりとして、アクセサリーなどの特性について考えます(データを変更するために使用できます)。私のクラスから切り取られ、単一の場所に配置できる同様のメソッドとプロパティにより、メンテナンスが容易になり、コードが短くてすっきりします。
strategies
です。
Magento eコマースプラットフォームの拡張機能を開発する際の一般的な問題を解決するため、Traitsに興奮しています。この問題は、拡張機能がコアクラス(ユーザーモデルなど)を拡張して機能を追加するときに発生します。これは、拡張機能からユーザーモデルを使用するように(XML構成ファイルを介して)Zendオートローダーをポイントし、新しいモデルでコアモデルを拡張することによって行われます。(例)しかし、2つの拡張が同じモデルをオーバーライドした場合はどうなりますか?「競合状態」が発生し、1つだけがロードされます。
現時点での解決策は、拡張を編集して一方が他方のモデルオーバーライドクラスをチェーンで拡張し、拡張構成を設定してそれらを正しい順序でロードし、継承チェーンが機能するようにすることです。
このシステムは頻繁にエラーを引き起こし、新しい拡張機能をインストールするときは、競合をチェックして拡張機能を編集する必要があります。これは苦痛であり、アップグレードプロセスを壊します。
Traitsを使用することは、この厄介なモデルが「競合状態」をオーバーライドすることなく同じことを達成するための良い方法だと思います。複数のトレイトが同じ名前のメソッドを実装する場合でも競合が発生する可能性があることは承知していますが、単純な名前空間の規則のようなものでほとんどの場合これを解決できると思います。
TL; DR Magentoのような大規模なPHPソフトウェアパッケージの拡張機能/モジュール/プラグインを作成するには、トレイトが役立つと思います。
次のような読み取り専用オブジェクトの特性を持つことができます:
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
この特性を使用しないと、エラーが発生します。したがって、この例には欠陥があります。