Черты в PHP-любые примеры реального мира / лучшие практики? [закрытый]


черт были одним из самых больших дополнений для PHP 5.4. Я знаю синтаксис и понимаю идею черт, таких как повторное использование горизонтального кода для общих вещей, таких как ведение журнала, безопасность, кэширование и т. д.

тем не менее, я до сих пор не знаю, как бы я использовал черты в своих проектах.

есть какие-то проекты с открытым исходным кодом, которые уже используют черты? Любые хорошие статьи / Материалы для чтения о том, как структурировать архитектуры с использованием черт?

5 132

5 ответов:

мое личное мнение заключается в том, что на самом деле очень мало приложений для черт при написании чистого кода.

вместо того, чтобы использовать черты для взлома кода в класс, лучше передать зависимости через конструктор или через сеттеры:

class ClassName {
    protected $logger;

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

основная причина, по которой я считаю, что лучше, чем использовать черты, заключается в том, что ваш код гораздо более гибкий, удаляя жесткую связь с чертой. Например, теперь вы можете просто передать другой класс регистратора. Этот делает ваш код многоразовым и тестируемым.

Я думаю, что нужно было бы заглянуть в языки, которые имеют черты в течение некоторого времени, чтобы узнать принятые хорошие/лучшие практики. Мое текущее мнение о черте заключается в том, что вы должны использовать их только для кода, который вам придется дублировать в других классах, которые имеют ту же функциональность.

пример для признака регистратора:

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;
}

вышеизложенное приведет к ошибке (демо). Аналогично, любые методы, объявленные в признаке, которые также уже объявлены в классе 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, и вся идея горизонтального повторного использования теряется.

при выполнении Принцип Разделения Интерфейса у вас будет много маленьких классов и интерфейсов. Это делает черты идеальным кандидатом для вещи, которые вы упомянули, например,пересекающимися, но не для создания объектов (в структурном смысле). В нашем примере регистратора выше, черта полностью изолирована. Он не имеет зависимостей от конкретных классов.

мы могли бы использовать агрегации/композиции (как показано в другом месте на этой странице) для достижения того же результирующего класса, но недостаток использования агрегации/композиции заключается в том, что нам придется вручную добавлять методы прокси/делегатора к каждому и каждый класс, который должен быть в состоянии войти. Черты решают это красиво, позволяя мне держать шаблон в одном месте и выборочно применять его там, где это необходимо.

Примечание: учитывая, что черты являются новым понятием в 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>';
?>

Как a заключительное замечание, я думаю о таких чертах, как аксессуары (которые я могу использовать для изменения своих данных). Подобные методы и свойства, которые могут быть вырезаны из моих классов и помещены в одно место, для легкого обслуживания, более короткого и чистого кода.

Я в восторге от черт, потому что они решают a распространенная проблема при разработке расширений для платформы электронной коммерции Magento. Проблема возникает, когда расширения добавляют функциональность в основной класс (например, модель пользователя), расширяя его. Это делается путем указания Zend autoloader (через файл конфигурации XML) для использования модели пользователя из расширения и расширения этой новой модели основной модели. (пример), но что, если два расширения переопределить одну и ту же модель? Вы получите "состояние гонки", и только один загружается.

решение прямо сейчас заключается в редактировании расширений, чтобы один расширял класс переопределения модели другого в цепочке, а затем устанавливал конфигурацию расширения для загрузки их в правильном порядке, чтобы цепочка наследования работала.

эта система часто вызывает ошибки, и при установке новых расширений необходимо проверить наличие конфликтов и редактировать расширения. Это боль, и ломает процесс обновления.

I думаю, что использование черт было бы хорошим способом добиться того же самого без этой раздражающей модели переопределения "состояния гонки". Конечно, все еще могут быть конфликты, если несколько признаков реализуют методы с одинаковыми именами, но я бы предположил, что по большей части это может решить что-то вроде простого соглашения о пространстве имен.

TL; DR я думаю, что черты могут быть полезны для создания расширений/модулей/плагинов для больших программных пакетов PHP, таких как Magento.

у вас может быть черта для объекта только для чтения:

  trait ReadOnly{  
      protected $readonly = false;

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

вы можете определить, используется ли эта черта, и определить, следует ли записывать этот объект в базу данных, файл и т. д.