2013-01-08 130 views
2

PHP特徵的目標是管理一堆邏輯。然而,根據一些專用屬性並避免命名衝突,使這一堆邏輯能夠工作的最好方法是什麼?具有屬性的特徵

我在想MVC,特別是模型類。實際上,模型似乎是特徵的良好候選者。模型可以實現樹形結構,可繪製,可修改,可縮放等等。

我想編寫這樣的事:

class MyModel extends Model { 
    use Tree, Revision, Slug; 
    protected $_behaviors = array(
     'Tree' => array('parentFieldname' => 'p_id'), 
     'Revision' => array(/* some options */), 
     'Slug' => array(/* some options */) 
    ); 
    public function __construct() { 
     foreach($this->_behaviors as &$options) { 
      $options += /* trait defaults ? */ 
     } 
    } 
} 

如果我打算設置樹特徵是這樣的:

trait Tree { 
    protected $_defaults = array(
     'parentFieldname' => 'parent_id', 
     /*...other options...*/ 
    ); 
    public function moveUp(); 
    public function moveDown(); 
    public function setParent(); //<- need the `'parentFieldname' => 'p_id'`attribute 
    /*...and so on...*/ 
} 

我會深入到每個以來由於$_defaults命名衝突特質需要它自己的默認值。使用特徵的名稱作爲屬性名稱暗示使用類似(new ReflectionClass(__CLASS__))->getTraits()) ......這不是很棒。

換句話說,有沒有一種方法可以創建具有「可重寫默認值」的特徵並避免命名衝突?

回答

2

就像你會在每個OOP概念中做的一樣:睜開你的眼睛!這完全一樣,因爲你擴展了一個類並濫用了已有的屬性。而已。

class A { 
    protected $defaults = array ('foo' => 'bar'); 
} 
class B extends A { 
    protected $defaults = array('foo2' => 42); // Bum: 'foo' disappeared 
} 

有一個通用的$_defaults - 屬性的聲音,我像一個代碼味道反正什麼「默認」?這個默認值?系統默認?應用程序默認? [1]設置你的使用值,而不是「默認」級,因爲多數民衆贊成(擴展默認值)的東西用於初始化過程,或具體屬性(public $parentFieldName = 'parent_id';

trait A { 
    public $parentFieldName = 'parent_id'; 
    public function construct ($options) {/
    if (isset($options['parentFieldName'])) { 
     $this->parentFieldName = $options['parentFieldName']; 
    } 
    } 
} 
class Foo { 
    use A { 
    A::construct as protected constructA; 
    } 
    public function __construct ($options) { 
    $this->constructA($options['A']); 
    } 
} 

的一些注意事項的初始化:這是重要,你別名construct(),因爲它會與其他方法(來自其他特性)衝突,construct()不是一個特殊的方法。它只是以這種方式命名(從我這裏)澄清,它是「一種」的構造函數。其他名稱,如init()等等當然也會起作用;) 您必須自己在「真實」構造函數中調用它(請參閱Foo::__construct())。

[1]作爲標識符的「default」就像是「type」,「status」,「i」,...:它們是通用的以便有用。

+1

,如果我錯過了簡單的方法,只是想知道(即使用命名約定或其他)來輕鬆配置/引入某些trait默認屬性,並避免每次「手動」解決命名衝突。但似乎總是需要一個手動解決步驟的地方。 – Jails

0

當你需要從他們添加的類中獲取信息時,你可以在你的特質中有一個initsetOptions方法。然後您可以從您的__construct方法調用此方法。

真正的問題是,你不能在特徵屬性和類屬性之間產生衝突,這會導致致命錯誤。沒有衝突解決方法,就像在方法名稱中一樣。

最簡單的事情將是有這樣的事情:

trait Tree { 
    protected $Tree_options; // Prefixing with the trait name should protect you from naming collision (less than optimal) 
    public function init($options) { 
     // This should be called int he __construct of MyModel 
     $this->Tree_options = array_merge($this->Tree_options, $options); 
    } 
    public function moveUp(); 
    public function moveDown(); 
    public function setParent() { 
     $parent = $this->Tree_options['p_id']; 
    }; 
} 
+0

謝謝,我想知道是否有一個更簡單的方法,但看起來像你總是需要解決衝突並手工定義你的'__construct'(如果你不想使用'ReflectionClass')。 – Jails

+1

我不認爲有另一種方法。我想這是你必須付出使用特質的一些樣板代碼。此外,它是一個最不明確的,而不是一些約定(讀「魔術」);) –

1

有幾種技術,其中multiple inheritance是靈丹妙藥,但他們不是可行的PHP。

關於如何避免複雜的設計模式以保持簡單的原則,當嘗試執行諸如此類的操作時,會刺傷您後背。

是,Traits進行了介紹,這可能是一個強大的工具,但你可以看到它在的情況下,很多無用的,僅僅因爲是properties沒有conflict resolutionaliases

還有一個選項,看看Aspect-Oriented Programming(AOP)。很多人都遇到了問題,但我會建議任何人進入它,這是一個範例,這將變得非常重要(即使對於PHPers)未來幾年

+0

你引用哪個選項?真正的面向方面的編程是不可能與PHP,或?我認爲特質可以解決這個差距,但是當你命名它時,解決屬性衝突是一個問題。 – velop

0

我有一個範例類似的問題在我的實施。我必須擴展Controller類來創建我的Child,但它也需要是Dynamic的特性,因爲並非所有的Dynamics都是控制器,我不想使用相同的代碼編寫ControllerDynamic和nonControllerDynamic在裏面。
我碰到了一個數組:trait->$allowedCallbackNames在性狀中定義的問題,由性狀使用爲if(in_array(name, $this->allowedCallbackNames)),然後在子中不能被覆蓋。你有同樣的問題。我們擊中了3個選項。

  1. 使用方法,而不是(它們可以被重寫,並且存在衝突避免使用use trait {... as ...};其允許性狀方法被重新定義爲一些其它的名字(use trait {oldmethod as newmethod;}
  2. 使用性狀定義集合/在所述性狀得到邏輯重新定義孩子的屬性,而不是兒童的私人屬性
  3. 將整個行爲更改爲使用可調用鉤子,並使用null鉤子表示默認行爲(考慮到我們已經決定,這更有意義默認應允許任何回調名稱)

首先,不是在性狀中使用上面的代碼,而是使用if ($this->isInAllowedCallbackNames),然後按照最初的預期定義性狀中的屬性private $fullTraitName_allowedCallbackName。然後在trait->isInAllowedCallbackNames function中添加in_array邏輯,而不是覆蓋Child中的屬性,重寫該方法(假定使用您在子中定義的數組,並且需要調用重新定義的父/特徵方法來擴展行爲)。

第二個選項只需添加public function getAllowedCallbackNames()public function setAllowedCallbackNames()的特質,然後叫他們init功能設置默認爲array_merge(getAllowedCallbackNames(), array(<new names>))

三opion是一個我已經用了,因爲我想第一個選項,但我認爲PHPs碰撞避免方法會導致messier代碼(在解除時更改函數名稱是不好的)。所以我創建trait->$methodFilter=null和分配調用到methodFilter(並拋出一個異常,如果它不是可調用的)的功能trait->setMethodFilter($callable),然後在他們的特性我用if ((is_callable($this->methodFilter)?$this->methodFilter($name):true))