PHP'deki özellikler - herhangi bir gerçek dünya örneği / en iyi uygulama? [kapalı]


151

Özellikler , PHP 5.4 için en büyük eklemelerden biri olmuştur. Sözdizimini biliyorum ve günlük kaydı, güvenlik, önbelleğe alma gibi yaygın şeyler için yatay kodun yeniden kullanılması gibi özelliklerin arkasındaki fikri anlıyorum.

Ancak yine de projelerimde bu özellikleri nasıl kullanacağımı bilmiyorum.

Zaten özellikleri kullanan herhangi bir açık kaynak projesi var mı? Mimarileri özellikleri kullanarak nasıl yapılandıracağınıza dair iyi makaleler / okuma materyali var mı?


8
İşte benim fikrim: konu hakkında yazdığım konuyla ilgili bir blog yazısı . TL; DR: Temel olarak, güçlü olmalarına ve iyilik için kullanılmalarına rağmen, göreceğimiz kullanımların çoğunun tamamen anti-kalıplar olacağından ve çözdüklerinden çok daha fazla acıya neden
olacağından korkuyorum

1
Scala standart kitaplığına bir göz atın ve birçok yararlı özellik örneği bulacaksınız.
dmitry

Yanıtlar:


92

Kişisel görüşüm, temiz kod yazarken özellikler için aslında çok az uygulama olduğu.

Bir sınıfa kod kırmak için özellikler kullanmak yerine, bağımlılıkları yapıcı veya ayarlayıcılar aracılığıyla geçirmek daha iyidir:

class ClassName {
    protected $logger;

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

Bunu özellikleri kullanmaktan daha iyi bulmamın ana nedeni, kodunuzun bir özelliğe olan sert bağlantıyı kaldırarak çok daha esnek olmasıdır. Örneğin, şimdi farklı bir günlükçü sınıfını geçebilirsiniz. Bu, kodunuzu yeniden kullanılabilir ve test edilebilir hale getirir.


4
Özellikleri kullanarak başka bir günlükçü sınıfı da kullanabilirsiniz, değil mi? Sadece özelliği düzenleyin ve özelliği kullanan tüm sınıflar güncellenir. Yanılıyorsam düzeltin
rickchristie

15
@rickchristie Elbette, bunu yapabilirsin. Ancak özelliğin kaynak kodunu düzenlemeniz gerekir. Yani onu kullanan her sınıf için değiştirirsiniz, sadece farklı bir kaydedici istediğinizi değil. Ya aynı sınıfı iki farklı kaydedici ile kullanmak isterseniz? Ya da test sırasında bir sahte günlükçüyü geçmek istiyorsanız? Yapamazsınız, özellikleri kullanırsanız, bağımlılık enjeksiyonu kullanırsanız kullanabilirsiniz.
NikiC

2
Demek istediğini anlıyorum, özelliklerin buna değer olup olmadığını da düşünüyorum. Demek istediğim, Symfony 2 gibi modern çerçevelerde, çoğu durumda özelliklerden üstün görünen her yere bağımlılık enjeksiyonu var. Şu anda, özelliklerin "derleyici destekli kopyala ve yapıştır" dan daha fazla olmadığını görüyorum. ;)
Fazla

11
Şu anda, özelliklerin "derleyici destekli kopyala ve yapıştır" dan daha fazla olmadığını görüyorum. ;) : @Max: Bu özelliklerin tam olarak tasarlandığı şey, yani bu tamamen doğru. Tek bir tanım olduğu için onu daha "sürdürülebilir" kılıyor, ancak temelde sadece c & p ...
ircmaxell

29
NikiC'ler asıl noktayı kaçırıyor: bir özellik kullanmak Bağımlılık Enjeksiyonunu kullanmayı engellemez. Bu durumda, bir özellik, günlüğe kaydetmeyi uygulayan her sınıfın setLogger () yöntemini ve $ logger özelliğinin oluşturulmasını çoğaltmasına gerek kalmayacaktır. Özellik onlara sağlayacaktır. setLogger (), örnekte olduğu gibi LoggerInterface üzerinde ipucu yazacaktır, böylece herhangi bir günlükçü türü aktarılabilir. Bu fikir, aşağıdaki Gordon'un cevabına benzer (yalnızca, Logger arayüzü yerine bir Logger süper sınıfına ipucu veriyor gibi görünüyor) ).
Ethan

207

Sanırım kabul edilen İyi / En İyi uygulamaları öğrenmek için bir süredir Özellikleri olan dillere bakmak gerekecek. Özellik hakkındaki şu anki fikrim, onları yalnızca aynı işlevi paylaşan diğer sınıflarda çoğaltmanız gereken kod için kullanmanız gerektiğidir.

Bir Logger özelliği örneği:

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

Ve sonra yaparsın ( demo )

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

Nitelikleri kullanırken dikkate alınması gereken önemli şey, bunların gerçekten sınıfa kopyalanan kod parçaları olmalarıdır. Bu, örneğin yöntemlerin görünürlüğünü değiştirmeye çalıştığınızda kolayca çatışmalara yol açabilir, örn.

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

Yukarıdakiler bir hatayla ( demo ) sonuçlanacaktır . Aynı şekilde, kullanım sınıfında zaten bildirilmiş olan özellikte bildirilen herhangi bir yöntem, sınıfa kopyalanmayacaktır, örn.

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

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

2 ( demo ) yazdıracaktır . Bunlar, hataları bulmayı zorlaştırdığı için kaçınmak isteyeceğiniz şeylerdir. Ayrıca, onu kullanan sınıfın özellikleri veya yöntemleri üzerinde çalışan özelliklere şeyler koymaktan da kaçınmak isteyeceksiniz, örn.

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

çalışır ( demo ) ancak şimdi özellik A ile yakından ilişkilidir ve yatay yeniden kullanım fikri tamamen kaybolur.

Arayüz Ayrımı İlkesini izlediğinizde birçok küçük sınıf ve arayüze sahip olacaksınız. Bu, Traits'i bahsettiğiniz şeyler için ideal bir aday yapar, örneğin endişeleri kesiştirmek , ancak nesneleri oluşturmak için değil (yapısal anlamda). Yukarıdaki Logger örneğimizde, özellik tamamen izole edilmiştir. Somut sınıflara bağımlılığı yoktur.

Ortaya çıkan aynı sınıfı elde etmek için toplama / kompozisyon kullanabiliriz (bu sayfada başka bir yerde gösterildiği gibi), ancak toplama / kompozisyon kullanmanın dezavantajı, proxy / temsilci yöntemlerini her sınıfa manuel olarak eklememiz gerekecek olmasıdır. giriş yapabilmek. Özellikler bunu güzel bir şekilde, standart metnini tek bir yerde tutmama ve gerektiğinde onu seçerek uygulamama izin vererek çözüyor.

Not: Özelliklerin PHP'de yeni bir kavram olduğu düşünüldüğünde, yukarıda ifade edilen tüm görüşler değişebilir. Henüz konsepti kendim değerlendirmek için fazla zamanım olmadı. Ama umarım size düşünecek bir şey verecek kadar iyidir.


42
Bu ilginç bir kullanım durumu: sözleşmeyi tanımlayan bir arayüz kullanın, bu sözleşmeyi yerine getirmek için özelliği kullanın. İyi bir.
Max

13
Her biri için kısa açıklamalarla gerçek çalışma örnekleri sunan bu tür gerçek programcıları seviyorum. Thx
Arthur Kushman

1
Ya biri bunun yerine soyut bir sınıf kullanırsa? Arayüzü ve özelliği değiştirerek, soyut bir sınıf yaratılabilir. Ayrıca uygulama için arayüz çok gerekliyse, soyut sınıf da arayüzü uygulayabilir ve traitin yaptığı gibi yöntemleri tanımlayabilir. Öyleyse neden hala özelliklere ihtiyacımız olduğunu açıklayabilir misiniz?
sumanchalki

12
@sumanchalki Soyut sınıf, Kalıtım kurallarına uyar. Loggable ve Cacheable'ı uygulayan bir sınıfa ihtiyacınız varsa ne olur? AbstractLogger'ı genişletmek için bu sınıfa ihtiyacınız olacak ve bu durumda AbstractCache'yi genişletmesi gerekir. Ancak bu, tüm Loggable'ların Önbellek olduğu anlamına gelir. Bu, istemediğiniz bir bağlantıdır. Yeniden kullanımı sınırlar ve kalıtım grafiğinizi bozar.
Gordon

1
Bence demo bağlantıları
kesildi

19

:) Bir şeyle ne yapılması gerektiğine dair teori ve tartışmayı sevmiyorum. Bu durumda özellikler. Size hangi özellikler için yararlı bulduğumu göstereceğim ve ondan öğrenebilir veya görmezden gelebilirsiniz.

Özellikler - stratejileri uygulamak için harikadırlar . Kısacası, strateji tasarım modelleri, aynı verilerin farklı şekilde işlenmesini (filtrelenmesi, sıralanması vb.) İstediğinizde kullanışlıdır.

Örneğin, bazı kriterlere göre (markalar, özellikler, her neyse) veya farklı yöntemlerle (fiyat, etiket, her neyse) sınıflandırmak istediğiniz bir ürün listeniz var. Farklı sıralama türleri (sayısal, dize, tarih vb.) İçin farklı işlevler içeren bir sıralama özelliği oluşturabilirsiniz. Daha sonra bu özelliği yalnızca ürün sınıfınızda (örnekte verildiği gibi) değil, aynı zamanda benzer stratejilere ihtiyaç duyan diğer sınıflarda da (bazı verilere sayısal sıralama uygulamak, vb.) Kullanabilirsiniz.

Dene:

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

Kapanış notu olarak, aksesuarlar gibi özellikler hakkında düşünüyorum (verilerimi değiştirmek için kullanabileceğim). Kolay bakım, daha kısa ve daha temiz kod için sınıflarımdan çıkarılıp tek bir yere konulabilen benzer yöntemler ve özellikler.


1
Bu, genel arayüzü temiz tutarken, iç arayüz, özellikle bunu başka şeylere, örneğin renkler gibi genişletirseniz, gerçekten karmaşık hale gelebilir. Bence basit fonksiyonlar veya statik yöntemler burada daha iyi.
Sebastian Mach

Bu terimi beğendim strategies.
Rannie Ollit

4

Traits için heyecanlıyım çünkü Magento e-ticaret platformu için uzantılar geliştirirken ortak bir sorunu çözüyorlar. Sorun, uzantılar bir çekirdek sınıfa (Kullanıcı modeli gibi) onu genişleterek işlevsellik eklediğinde ortaya çıkar. Bu, uzantıdaki Kullanıcı modelini kullanmak için Zend otomatik yükleyiciyi (bir XML yapılandırma dosyası aracılığıyla) işaret ederek ve bu yeni modelin çekirdek modeli genişletmesini sağlayarak yapılır. ( örnek ) Peki ya iki uzantı aynı modeli geçersiz kılarsa? Bir "yarış durumu" elde edersiniz ve yalnızca biri yüklenir.

Şu anda çözüm, uzantıları düzenleyerek biri diğerinin model geçersiz kılma sınıfını bir zincirde genişletmek ve ardından uzantı yapılandırmasını, miras zincirinin çalışması için doğru sırayla yüklemek üzere ayarlamaktır.

Bu sistem sık sık hatalara neden olur ve yeni uzantılar yüklerken, çakışmaları kontrol etmek ve uzantıları düzenlemek gerekir. Bu bir acıdır ve yükseltme sürecini bozar.

Bence Özellikler'i kullanmak, bu sinir bozucu modelin "yarış koşulunu" geçersiz kılması olmadan aynı şeyi başarmanın iyi bir yolu olacaktır. Eğer birden fazla Özellik aynı adlara sahip yöntemleri uygularsa yine de çatışmalar olabilir, ancak basit bir ad alanı kuralı gibi bir şeyin bunu büyük ölçüde çözebileceğini düşünürdüm.

TL; DR Bence Traits, Magento gibi büyük PHP yazılım paketleri için uzantılar / modüller / eklentiler oluşturmak için yararlı olabilir.


0

Bunun gibi salt okunur nesneler için bir özelliğiniz olabilir:

  trait ReadOnly{  
      protected $readonly = false;

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

Bu özelliğin kullanılıp kullanılmadığını tespit edebilir ve bu nesneyi bir veritabanına, dosyaya vb. Yazıp yazmayacağınıza karar verebilirsiniz.


Yani usebu özelliği olacak sınıf o zaman çağırır if($this -> getReadonly($value)); ancak bu useözelliği yapmazsanız bu bir hata oluşturacaktır . Bu nedenle bu örnek kusurludur.
Luceos

Öncelikle özelliğin kullanımda olup olmadığını kontrol etmelisin. ReadOnly özelliği bir nesnede tanımlandıysa, bunun salt okunur olup olmadığını kontrol edebilirsiniz.
Nico


3
Bu amaç için ReadOnly için bir arayüz bildirmelisiniz
Michael Tsang
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.