PHP 프로젝트에서 도우미 개체를 저장, 액세스 및 구성하는 데 어떤 패턴이 있습니까? [닫은]


114

PHP 기반의 객체 지향 프로젝트에서 데이터베이스 엔진, 사용자 알림, 오류 처리 등과 같은 도우미 객체를 어떻게 구성하고 관리합니까?

큰 PHP CMS가 있다고 가정합니다. CMS는 다양한 수업으로 구성됩니다. 몇 가지 예 :

  • 데이터베이스 객체
  • 사용자 관리
  • 항목 생성 / 수정 / 삭제를위한 API
  • 최종 사용자에게 메시지를 표시하는 메시징 개체
  • 올바른 페이지로 이동하는 컨텍스트 핸들러
  • 버튼을 표시하는 탐색 모음 클래스
  • 로깅 객체
  • 가능하면 사용자 정의 오류 처리

기타

나는 영원한 질문을 다루고 있습니다. 어떻게 이러한 객체를 필요로하는 시스템의 각 부분에 접근 할 수있게 만드는지에 대한 것입니다.

몇 년 전의 첫 번째 접근은 이러한 클래스의 초기화 된 인스턴스를 포함하는 $ application 전역을 갖는 것이 었습니다.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

그런 다음 Singleton 패턴과 공장 기능으로 변경했습니다.

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

하지만 그것도 만족스럽지 않습니다. 단위 테스트와 캡슐화는 나에게 점점 더 중요 해지고 있으며, 글로벌 / 싱글 톤의 논리를 이해하면 OOP의 기본 아이디어가 파괴됩니다.

물론 각 개체에 필요한 도우미 개체에 대한 여러 포인터를 제공 할 가능성이 있습니다. 아마도 가장 깨끗하고 리소스를 절약하고 테스트하기 쉬운 방법이지만 장기적으로는 이것의 유지 관리 가능성에 의문이 있습니다.

내가 살펴본 대부분의 PHP 프레임 워크는 단일 패턴 또는 초기화 된 객체에 액세스하는 함수를 사용합니다. 둘 다 좋은 접근 방식이지만 내가 말했듯이 나는 둘 다 만족하지 않습니다.

여기에 어떤 공통 패턴이 존재하는지에 대한 내 시야를 넓히고 싶습니다. 장기 적이고 실제적인 관점 에서이를 논의하는 리소스에 대한 예제, 추가 아이디어 및 포인터를 찾고 있습니다 .

또한 이 문제에 대한 전문적, 틈새 시장 또는 평범한 이상한 접근 방식에 대해 듣고 싶습니다 .


1
저는 현상금이있는 매우 유사한 질문을했습니다. 당신은 거기에 몇 가지 답변을 주셔서 감사합니다 수 있습니다 stackoverflow.com/questions/1967548/...
philfreo

3
참조로 새 객체를 반환하는 $mh=&factory("messageHandler");것은 무의미하며 성능상의 이점을 제공하지 않습니다. 또한 이것은 5.3에서 더 이상 사용되지 않습니다.
ryeguy

답변:


68

나는 Flavius가 제안한 Singleton 접근 방식을 피할 것입니다. 이 접근 방식을 피해야하는 이유는 많습니다. 그것은 좋은 OOP 원칙을 위반합니다. Google 테스트 블로그에는 Singleton에 대한 좋은 기사와이를 방지하는 방법이 있습니다.

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

대안

  1. 서비스 제공자

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. 의존성 주입

    http://en.wikipedia.org/wiki/Dependency_injection

    그리고 PHP 설명 :

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

다음은 이러한 대안에 대한 좋은 기사입니다.

http://martinfowler.com/articles/injection.html

DI (종속성 주입) 구현 :

Flavius의 솔루션에 대한 더 많은 생각. 나는이 포스트가 안티 포스트가되기를 원하지 않지만, 적어도 나에게는 왜 의존성 주입이 글로벌보다 나은지 아는 것이 중요하다고 생각합니다.

'진정한' Singleton 구현 은 아니지만 Flavius가 잘못했다고 생각합니다. 전역 상태가 잘못되었습니다 . 이러한 솔루션은 또한 테스트하기 어려운 정적 메서드를 사용 합니다 .

나는 많은 사람들이 그것을하고 그것을 승인하고 사용한다는 것을 알고 있습니다. 그러나 Misko Heverys 블로그 기사 ( 구글 테스트 가능성 전문가 )를 읽고 다시 읽고 그가 말한 내용을 천천히 소화하면 디자인을 보는 방식이 많이 달라졌 습니다.

애플리케이션을 테스트 할 수 있으려면 애플리케이션 디자인에 다른 접근 방식을 채택해야합니다. 테스트 우선 프로그래밍을 할 때 다음과 같은 일에 어려움을 겪을 것입니다. '다음에이 코드에서 로깅을 구현하고 싶습니다. 기본 메시지를 기록하는 테스트를 먼저 작성해 보겠습니다. '그런 다음 대체 할 수없는 글로벌 로거를 작성하고 사용하도록 강요하는 테스트를 생각해보십시오.

나는 여전히 그 블로그에서 얻은 모든 정보로 어려움겪고 있으며 구현하기가 항상 쉬운 것은 아니며 많은 질문이 있습니다. 그러나 Misko Hevery가 말한 것을 파악한 후에는 이전에했던 작업 (예, 글로벌 상태 및 Singletons (big S))으로 돌아갈 수있는 방법이 없습니다. :-)


DI의 경우 +1. 내가 원하는만큼 많이 사용하지는 않지만, 소량으로 사용해도 큰 도움이되었습니다.
Anurag

1
@koen : PHP에서 DI / SP 구현의 PHP 예제를 제공 하시겠습니까? 제안한 대체 패턴을 사용하여 구현 된 @Flavius ​​코드일까요?
Alix Axel

내 대답에 DI 구현 및 컨테이너에 대한 링크를 추가했습니다.
토마스

지금은이 모든 내용을 읽고 있지만 아직 모두 읽지 않았습니다. 묻고 싶습니다. 의존성 주입 프레임 워크가 기본적으로 레지스트리일까요?
JasonDavis 2010 년

아니 정말. 그러나 Dependency Injection Container는 레지스트리 역할도 할 수 있습니다. 내 대답에 게시 한 링크를 읽으십시오. DI의 개념은 실제로 실제로 설명됩니다.
Thomas

16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

이것이 내가 할 방법입니다. 요청시 객체를 생성합니다.

Application::foo()->bar();

내가하는 방식이고, OOP 원칙을 존중하고, 지금하는 방식보다 코드가 적으며, 코드가 처음으로 필요할 때만 객체가 생성됩니다.

참고 : 내가 제시 한 것은 실제 싱글 톤 패턴도 아닙니다. 싱글 톤은 생성자 (Foo :: __ constructor ())를 private으로 정의하여 자신의 인스턴스를 하나만 허용합니다. 모든 "응용 프로그램"인스턴스에서 사용할 수있는 "전역"변수 일뿐입니다. 그것이 좋은 OOP 원칙을 무시하지 않기 때문에 그 사용이 유효하다고 생각하는 이유입니다. 물론,이 "패턴"도 세상의 어떤 것으로도 남용되어서는 안됩니다!

저는 이것이 많은 PHP 프레임 워크, Zend Framework 및 Yii에서 사용되는 것을 보았습니다. 그리고 프레임 워크를 사용해야합니다. 나는 당신에게 어떤 것을 말하지 않을 것입니다.

부록 TDD에 대해 걱정하는 사람들을 위해 여전히 일부 배선을 구성하여 종속성 주입을 수행 할 수 있습니다. 다음과 같이 보일 수 있습니다.

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

개선의 여지가 충분합니다. 그것은 단지 PoC입니다. 상상력을 발휘하십시오.

왜 그런가요? 대부분의 경우 애플리케이션은 단위 테스트를 거치지 않고 실제로 프로덕션 환경에서 실행 됩니다 . PHP의 강점은 속도입니다. PHP는 Java와 같은 "깨끗한 OOP 언어"가 아닙니다.

애플리케이션 내에는 하나의 Application 클래스와 각 헬퍼의 인스턴스가 하나만 있습니다 (위와 같이 지연로드에 따라). 물론, 싱글 톤은 나쁘지만 실제 세계를 따르지 않는 경우에만 그렇습니다 . 제 예에서는 그렇습니다.

"싱글 톤은 나쁘다"와 같은 고정 관념 "규칙"은 악의 근원이며, 스스로 생각하지 않으려는 게으른 사람들을위한 것입니다.

예, 알고 있습니다. PHP 선언문은 기술적으로 말하면 나쁩니다. 그러나 그것은 끔찍한 방식으로 성공적인 언어입니다.

추가

하나의 기능 스타일 :

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();

2
나는 문제를 처리하기 위해 싱글 톤 패턴을 제안하는 것이 견고한 OOP 원칙에 위배된다고 믿기 때문에 대답을 거절했습니다.
koen

1
@koen : 당신이 말하는 것은 일반적으로 말해서 사실이지만, 내가 그의 문제를 이해하는 한, 그는 응용 프로그램에 대한 도우미에 대해 이야기하고 있으며 응용 프로그램 내에는 단 하나의 ... 음, 응용 프로그램이 있습니다.
Flavius

참고 : 내가 제시 한 것은 실제 싱글 톤 패턴도 아닙니다. 싱글 톤은 생성자를 private으로 정의하여 클래스의 인스턴스를 하나만 허용합니다. 모든 "응용 프로그램"인스턴스에서 사용할 수있는 "전역"변수 일뿐입니다. 그것이 유효한 것이 좋은 OOP 원칙을 무시하지 않는 이유입니다. 물론,이 "패턴"도 과용되어서는 안됩니다.
Flavius

나도 -1. Singleton DP의 절반에 불과할 수 있지만 추악한 것입니다 : "전역 액세스를 제공하십시오".
그냥 누군가

2
이것은 실제로 그의 기존 접근 방식을 훨씬 깨끗하게 만듭니다.
Daniel Von Fange

15

나는 의존성 주입의 개념을 좋아한다.

"종속성 주입은 구성 요소가 생성자, 메서드를 통해 또는 필드에 직접 종속성을 부여하는 곳입니다. ( Pico 컨테이너 웹 사이트에서 )"

Fabien Potencier는 Dependency Injection 과이를 사용해야하는 필요성에 대한 멋진 기사 시리즈를 작성 했습니다. 그는 또한 내가 정말 사용하고 싶은 Pimple 이라는 멋지고 작은 Dependency Injection Container 를 제공합니다 ( github 에 대한 자세한 정보 ).

위에서 언급했듯이 저는 싱글 톤 사용을 좋아하지 않습니다. 싱글 톤이 좋은 디자인이 아닌 이유에 대한 좋은 요약은 여기 Steve Yegge의 블로그에서 찾을 수 있습니다 .


PHP에서 클로저를 통해 구현, 매우 흥미로운 독서와 같은 I
유라이 Blahunka

나도 그의 사이트의 폐쇄와 관련하여 다른 필요 사항이 있습니다 : fabien.potencier.org/article/17/…
Thomas

2
완전한 기능을 갖춘 php 5.3 서버를 보는 것이 아직 일반적이지 않기 때문에 주류 웹 하우스가 곧 PHP 5.3으로 마이그레이션되기를 희망합시다
Juraj Blahunka

Zend Framework 2.0과 같이 점점 더 많은 프로젝트에 PHP 5.3이 필요한 경우 framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
의존성 주입은 또한 다음에 대한 질문에 대한 답변으로 받아 들여졌습니다 decupling from GOD object. stackoverflow.com/questions/1580210/… 아주 좋은 예와 함께
Juraj Blahunka

9

가장 좋은 방법은 이러한 리소스를위한 일종의 컨테이너 를 보유하는 것입니다 . 이 컨테이너 를 구현하는 가장 일반적인 방법은 다음과 같습니다.

하나씩 일어나는 것

테스트하기 어렵고 글로벌 상태를 의미하므로 권장되지 않습니다. (단일 염)

기재

싱글 톤도 제거하기 때문에 레지스트리도 추천하지 않습니다. (단위 테스트에 어려움)

계승

안타깝게도 PHP에는 다중 상속이 없으므로 모든 것이 체인으로 제한됩니다.

의존성 주입

이것은 더 나은 접근 방식이지만 더 큰 주제입니다.

전통적인

이를 수행하는 가장 간단한 방법은 생성자 또는 setter 주입을 사용하는 것입니다 (setter 또는 클래스 생성자를 사용하여 종속성 개체 전달).

프레임 워크

자신의 의존성 주입기를 굴 리거나 의존성 주입 프레임 워크 중 일부를 사용할 수 있습니다. 야 디프

애플리케이션 리소스

컨테이너 역할을하는 애플리케이션 부트 스트랩에서 각 리소스를 초기화하고 부트 스트랩 객체에 액세스하는 앱의 어느 곳에서나 액세스 할 수 있습니다.

이것은 Zend Framework 1.x에서 구현 된 접근 방식입니다.

리소스 로더

필요할 때만 필요한 리소스를로드 (생성)하는 일종의 정적 개체입니다. 이것은 매우 현명한 접근 방식입니다. 실행중인 것을 볼 수 있습니다. 예를 들어 Symfony의 종속성 주입 컴포넌트 구현

특정 층에 주입

응용 프로그램의 어느 곳에서나 리소스가 항상 필요한 것은 아닙니다. 때로는 컨트롤러 (MV C ) 에서 필요한 경우가 있습니다 . 그런 다음 거기에만 리소스를 주입 할 수 있습니다.

이에 대한 일반적인 접근 방식은 docblock 주석을 사용하여 주입 메타 데이터를 추가하는 것입니다.

여기에 대한 나의 접근 방식을 참조하십시오.

Zend Framework에서 종속성 주입을 사용하는 방법은 무엇입니까? - 스택 오버플로

마지막으로 여기에 매우 중요한 사항 인 캐싱에 대한 메모를 추가하고 싶습니다.
일반적으로 선택한 기술에도 불구하고 리소스가 캐시되는 방식을 생각해야합니다. 캐시는 리소스 자체가됩니다.

응용 프로그램은 매우 클 수 있으며 각 요청에 따라 모든 리소스를로드하는 데 비용이 많이 듭니다. 이 appserver-in-php-Project Hosting on Google Code를 포함하여 많은 접근 방식이 있습니다.


6

개체를 전역 적으로 사용할 수 있도록 하려면 레지스트리 패턴 이 흥미로울 수 있습니다. 영감을 얻으 려면 Zend Registry를 살펴보세요 .

또한 레지스트리 대 싱글 톤 질문입니다.


Zend 프레임 워크를 사용하지 않으려면 다음은 PHP5 용 레지스트리 패턴의 멋진 구현입니다. phpbar.de/w/Registry
Thomas

Registry :: GetDatabase ( "master");와 같은 형식화 된 레지스트리 패턴을 선호합니다. 레지스트리 :: GetSession ($ user-> SessionKey ()); 레지스트리 :: GetConfig ( "local"); [...] 및 각 유형에 대한 인터페이스 정의. 이렇게하면 다른 항목에 사용 된 키를 실수로 덮어 쓰지 않도록 할 수 있습니다 (예 : "마스터 데이터베이스"및 "마스터 구성"이있을 수 있습니다. 인터페이스를 사용하여 유효한 개체 만 사용되는지 확인합니다. Ofc). 또한 여러 레지스트리 클래스를 사용하여 구현할 수 있지만 단일 하나가 더 간단하고 사용하기 쉽지만 여전히 장점이 있습니다.
Morfildur

또는 물론 PHP에 내장 된 것-$ _GLOBALS
Gnuffo1

4

단위 테스트에서 보았 듯이 PHP의 객체는 많은 양의 메모리를 차지합니다. 따라서 다른 프로세스를 위해 메모리를 절약하기 위해 가능한빨리 불필요한 개체를 파괴하는 것이 이상적입니다 . 이를 염두에두고 모든 물체가 두 개의 금형 중 하나에 맞습니다.

1) 객체에는 유용한 메서드가 많거나 한 번 이상 호출해야 할 수 있습니다.이 경우 싱글 톤 / 레지스트리를 구현합니다.

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) 객체는 객체를 호출하는 메서드 / 함수의 수명 동안 만 존재합니다.이 경우 단순한 생성은 느린 객체 참조가 객체를 너무 오래 유지하는 것을 방지하는 데 도움이됩니다.

$object = new Class();

임시 개체를 임의의 위치에 저장 하면 나머지 스크립트 동안 개체를 메모리에 유지하는 것에 대한 참조를 잊어 버릴 수 있으므로 메모리 누수가 발생할 수 있습니다.


3

초기화 된 객체를 반환하는 함수를 사용합니다.

A('Users')->getCurrentUser();

테스트 환경에서는 모형을 반환하도록 정의 할 수 있습니다. debug_backtrace ()를 사용하여 함수를 호출하는 내부를 감지하고 다른 객체를 반환 할 수도 있습니다. 프로그램 내부에서 실제로 진행되는 작업에 대한 통찰력을 얻기 위해 어떤 개체를 얻고 자하는 사람을 내부에 등록 할 수 있습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.