PHP中的特征 - 任何现实世界的例子/最佳实践?

时间:2011-10-25 16:45:16

标签: php traits

Traits是PHP 5.4最大的补充之一。我知道语法并理解特征背后的想法,比如日志,安全性,缓存等常见内容的水平代码重用。

但是,我仍然不知道如何在项目中使用特征。

是否有任何已经使用特征的开源项目?关于如何使用特征构建体系结构的任何好文章/阅读材料?

5 个答案:

答案 0 :(得分:191)

我想我们现在需要研究具有特征的语言一段时间来学习公认的优秀/最佳实践。我目前对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;
}

然后你做(demo

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

我想在使用traits时要考虑的重要事项是它们实际上只是被复制到类中的代码片段。例如,当您尝试更改方法的可见性时,这很容易导致冲突,例如

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

上述操作会导致错误(demo)。同样,在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(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成为您提到的事物的理想候选者,例如: crosscutting concerns,但不是撰写对象(在结构意义上)。在我们上面的Logger示例中,特征是完全孤立的。它对具体类没有依赖性。

我们可以使用aggregation/composition(如本页其他地方所示)来实现相同的结果类,但使用聚合/组合的缺点是我们必须手动将代理/委托方法添加到每个然后每个类应该能够记录。 Traits通过允许我将样板保存在一个位置并在需要的地方选择性地应用它来很好地解决这个问题。

注意:鉴于特征是PHP中的一个新概念,上述所有观点都可能发生变化。我自己还没有太多时间来评估这个概念。但我希望能给你一些思考的东西已经足够了。

答案 1 :(得分:83)

我个人认为,在编写干净的代码时,实际上很少有特征申请。

不是使用traits将代码破解到类中,而是通过构造函数或通过setter传递依赖项更好:

class ClassName {
    protected $logger;

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

我发现比使用特征更好的主要原因是通过删除与特征的硬耦合,您的代码更灵活。例如,您现在可以简单地传递一个不同的记录器类。这使您的代码可重用且可测试。

答案 2 :(得分: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>';
?>

作为结束语,我想到了配件之类的特性(我可以用它来改变我的数据)。类似的方法和属性可以从我的类中删除并放在一个地方,以便于维护,更短和更清晰的代码。

答案 3 :(得分:4)

我对Traits很兴奋,因为他们在为Magento电子商务平台开发扩展时解决了common issue问题。当扩展通过扩展将核心类(例如用户模型)添加到核心类时,会出现问题。这是通过指向Zend自动加载器(通过XML配置文件)来使用扩展中的用户模型来完成的,并让新模型扩展核心模型。 (example)但是,如果两个扩展覆盖相同的模型怎么办?你得到一个“竞争条件”,只加载一个。

现在的解决方案是编辑扩展,以便在链中扩展另一个模型覆盖类,然后设置扩展配置以正确的顺序加载它们,以便继承链工作。

此系统经常导致错误,并且在安装新扩展时,必须检查冲突并编辑扩展。这很痛苦,打破了升级过程。

我认为使用Traits是一个很好的方法来完成同样的事情,而不会让这个烦人的模型超越“竞争条件”。如果多个Traits实现具有相同名称的方法,那么仍然可能存在冲突,但我想像一个简单的命名空间约定可以解决这个问题。

TL; DR我认为Traits可用于为像Magento这样的大型PHP软件包创建扩展/模块/插件。

答案 4 :(得分:0)

你可以有一个只读对象的特征:

  trait ReadOnly{  
      protected $readonly = false;

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

您可以检测是否使用了该特征,并确定您是否应该在数据库,文件等中写入该对象。