PHP의 장점 – 실제 사례 / 모범 사례? [닫은]


148

특성 은 PHP 5.4의 가장 큰 추가 사항 중 하나입니다. 로깅, 보안, 캐싱 등과 같은 일반적인 것들에 대한 수평 코드 재사용과 같은 구문을 이해하고 특성의 개념을 이해합니다.

그러나 나는 여전히 프로젝트에서 특성을 어떻게 활용할 것인지 모른다.

이미 특성을 사용하는 오픈 소스 프로젝트가 있습니까? 특성을 사용하여 아키텍처를 구성하는 방법에 대한 좋은 기사 / 읽기 자료가 있습니까?


8
내 의견은 다음과 같습니다 . 주제에 대해 쓴 주제에 대한 블로그 게시물 입니다. TL; DR : 기본적으로, 나는 그것들이 강력하고 좋은 것으로 사용될 수 있지만, 우리가 보게 될 대부분의 사용은 완전한 안티 패턴이되어서 해결하는 것보다 훨씬 더 많은 고통을 초래할 것입니다 ...
ircmaxell

1
스칼라 표준 라이브러리를 살펴보면 많은 유용한 특성 예제를 찾을 수 있습니다.
dmitry

답변:


89

내 개인적인 의견은 깨끗한 코드를 작성할 때 실제로 특성에 대한 응용 프로그램이 거의 없다는 것입니다.

특성을 사용하여 코드를 클래스에 해킹하는 대신 생성자를 통해 또는 setter를 통해 종속성을 전달하는 것이 좋습니다.

class ClassName {
    protected $logger;

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

특성을 사용하는 것보다 낫다는 주요 이유는 특성에 대한 하드 커플 링을 제거하여 코드가 훨씬 유연하기 때문입니다. 예를 들어 이제 다른 로거 클래스를 전달할 수 있습니다. 이를 통해 코드를 재사용하고 테스트 할 수 있습니다.


4
특성을 사용하여 다른 로거 클래스를 사용할 수도 있습니까? 특성을 편집하면 특성을 사용하는 모든 클래스가 업데이트됩니다. 내가 틀렸다면 나를 바로
잡으십시오

14
@rickchristie 물론입니다. 그렇게 할 수 있습니다. 그러나 특성의 소스 코드를 편집해야합니다. 따라서 다른 로거를 원하는 특정 클래스뿐만 아니라 클래스를 사용하는 모든 클래스에 대해 변경합니다. 동일한 클래스를 사용하지만 두 개의 다른 로거를 사용하려면 어떻게해야합니까? 또는 테스트하는 동안 모의 로거를 전달하려면? 형질을 사용하면 의존성 주입을 사용할 수 없습니다.
NikiC

2
나는 당신의 요점을 볼 수 있습니다. 나는 또한 특성이 가치가 있는지 여부를 숙고하고 있습니다. 내 말은, Symfony 2와 같은 현대 프레임 워크에서 대부분의 경우 특성보다 초월한 것처럼 보이는 곳곳에 종속성 주입이 있습니다. 현재 나는 특성이 "컴파일러 지원 복사 및 붙여 넣기"보다 많지 않다고 본다. ;)
Max

11
현재 나는 특성이 "컴파일러 지원 복사 및 붙여 넣기"보다 많지 않다고 본다. ;) : @Max : 이것이 바로 특성이 설계된 것이므로 완전히 맞습니다. 정의가 하나뿐이기 때문에 "유지 관리가 가능"합니다. 그러나 기본적으로 C & P입니다.
ircmaxell

29
NikiC의 요점은 누락되었습니다. 특성을 사용해도 Dependency Injection을 사용할 수 없습니다. 이 경우 특성은 로깅을 구현하는 모든 클래스가 setLogger () 메소드와 $ logger 특성 작성을 복제하지 않아도되도록합니다. 특성은 그들을 제공 할 것입니다. setLogger ()는 예제와 같이 LoggerInterface에 힌트를 입력하여 모든 유형의 로거를 전달할 수 있습니다.이 아이디어는 아래 Gordon의 답변과 비슷합니다 (Logger 인터페이스가 아닌 Logger 수퍼 클래스에 대한 힌트 유형 만 나타납니다) ).
Ethan

205

받아 들여진 좋은 / 최고의 관행을 배우기 위해 한동안 특성이있는 언어를 조사해야한다고 생각합니다. 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;
}

위의 오류가 발생합니다 ( demo ). 마찬가지로, 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와 밀접한 관련이 있으며 수평 재사용에 대한 모든 아이디어가 사라집니다.

인터페이스 분리 원칙 을 따르면 많은 작은 클래스와 인터페이스가 있습니다. 즉 특색 당신이 언급 한 것들에 대한 이상적인 후보를 만드는 등 우려를 크로스 커팅 있지만 작성 객체의 (a 구조적인 의미에서). 위의 로거 예제에서 특성은 완전히 분리되었습니다. 구체적인 클래스에 의존하지 않습니다.

우리가 사용할 수있는 통합 / 작문 같은 결과 클래스를 달성하기 위해 (이 페이지에 다른 표시를 같은) 만 집계 / 조성물을 사용의 단점은 우리가 각각의 모든 클래스에 수동으로 프록시 / 위임자 방법을 그해야 추가해야 할 것입니다 기록 할 수 있습니다. 특성은 보일러 플레이트를 한 곳에 보관하고 필요한 곳에 선택적으로 적용 할 수있게 해주므로이 문제를 잘 해결할 수 있습니다.

참고 : 특성은 PHP에서 새로운 개념이므로 위에서 언급 한 모든 의견은 변경 될 수 있습니다. 아직 개념을 직접 평가할 시간이 없었습니다. 그러나 나는 당신에게 생각할만한 것을 줄 수 있기를 바랍니다.


41
흥미로운 유스 케이스 : 계약을 정의하는 인터페이스를 사용하고 계약을 만족시키기 위해 특성을 사용하십시오. 좋아요
Max

13
나는 이런 종류의 진정한 프로그래머를 좋아하는데, 이들은 각각 짧은 설명으로 실제 작업 예제를 제안합니다. Thx
Arthur Kushman

1
누군가 추상 클래스를 대신 사용하면 어떻게 되나요? 인터페이스와 특성을 대체하여 추상 클래스를 만들 수 있습니다. 또한 응용 프로그램에 인터페이스가 필요한 경우 추상 클래스는 인터페이스를 구현하고 특성과 같은 메서드를 정의 할 수도 있습니다. 왜 우리에게 여전히 특성이 필요한지 설명 할 수 있습니까?
sumanchalki

12
@sumanchalki 추상 클래스는 상속 규칙을 따릅니다. Loggable 및 Cacheable을 구현하는 클래스가 필요한 경우 어떻게해야합니까? AbstractCache를 확장해야하는 AbstractLogger를 확장하려면 클래스가 필요합니다. 그러나 이는 모든 로그 파일이 캐시임을 의미합니다. 그것은 원하지 않는 커플 링입니다. 재사용을 제한하고 상속 그래프를 엉망으로 만듭니다.
Gordon

1
데모 링크가 죽었다고 생각합니다
Pmpr

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

마지막으로, 액세서리와 같은 특성 (데이터를 변경하는 데 사용할 수 있음)에 대해 생각합니다. 쉬운 유지 관리, 더 짧고 깔끔한 코드를 위해 클래스에서 잘라내어 단일 장소에 배치 할 수있는 비슷한 메서드와 속성입니다.


1
이것은 공용 인터페이스를 깨끗하게 유지하지만 특히 인터페이스와 같은 다른 것들로 확장하는 경우 내부 인터페이스가 실제로 복잡해질 수 있습니다. 간단한 함수 또는 정적 메소드가 더 좋을 것이라고 생각합니다.
Sebastian Mach

나는 용어를 좋아한다 strategies.
Rannie Ollit

4

Magento 전자 상거래 플랫폼을위한 확장 기능을 개발할 때 일반적인 문제 를 해결했기 때문에 Traits에 대해 기쁘게 생각 합니다. 확장이 확장을 통해 사용자 클래스와 같은 확장이 핵심 클래스에 기능을 추가 할 때 문제가 발생합니다. 확장에서 사용자 모델을 사용하도록 Zend 오토로더 (XML 구성 파일을 통해)를 가리키고 새 모델이 코어 모델을 확장하도록합니다. ( ) 그러나 두 개의 확장이 동일한 모델을 재정의하면 어떻게됩니까? "경주 조건"이 발생하고 하나만로드됩니다.

해결책은 확장을 편집하여 체인에서 다른 모델 재정의 클래스를 확장 한 다음 확장 구성을 설정하여 상속 체인이 작동하도록 올바른 순서로로드하는 것입니다.

이 시스템은 종종 오류를 발생 시키며 새 확장을 설치할 때 충돌을 확인하고 확장을 편집해야합니다. 이것은 고통스럽고 업그레이드 프로세스를 중단시킵니다.

나는이 성가신 모델이 "경주 조건"을 재정의하지 않고서도 Traits를 사용하는 것이 같은 일을 달성하는 좋은 방법이라고 생각합니다. 여러 Traits가 동일한 이름으로 메소드를 구현하는 경우 여전히 충돌이 발생할 수 있지만 간단한 네임 스페이스 규칙과 같은 것이 대부분 이것을 해결할 수 있다고 생각합니다.

TL; DR Traits가 Magento와 같은 대규모 PHP 소프트웨어 패키지 용 확장 / 모듈 / 플러그인을 만드는 데 유용 할 수 있다고 생각합니다.


0

다음과 같이 읽기 전용 객체의 특성을 가질 수 있습니다.

  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특성 이 없으면 오류가 발생합니다 . 따라서이 예에는 결함이 있습니다.
Luceos

음, 먼저 특성이 사용 중인지 확인해야합니다. ReadOnly 특성이 개체에 정의되어 있으면 읽기 전용인지 여부를 확인할 수 있습니다.
Nico


3
이를 위해 ReadOnly에 대한 인터페이스를 선언해야합니다
Michael Tsang
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.