먼저 몇 가지 질문을 명확히 해야 합니다.
Q1. 이벤트란 무엇인가요?
A: 이벤트는 명명된 동작입니다. 이 동작이 발생하면 이벤트가 트리거된다고 합니다.
Q2. 모니터란 무엇인가요?
A: 리스너는 이벤트의 논리적 표현을 결정하고 이벤트에 의해 트리거됩니다. 리스너와 이벤트는 종종 쌍을 이룹니다. 물론 하나의 이벤트가 여러 리스너에 해당할 수도 있습니다. 청취자는 이벤트에 반응합니다. 이벤트가 트리거되면 반응하는 것은 리스너의 몫입니다. 이런 식으로 여러 이벤트가 발생하면 단일 리스너가 반응할 수 있습니다. 이벤트에는 이에 반응하는 여러 리스너가 있을 수도 있습니다. (한 문장으로 말하면 리스너와 이벤트의 관계는 일대다 또는 다대일일 수 있습니다)
Q3. 이벤트 매니저는 어떤 용도로 사용되나요?
A: 이벤트 관리자(EventManager)는 이름에서 알 수 있듯이 이벤트를 관리하는 데 사용됩니다. 하지만 그는 그것을 어떻게 관리합니까? 이벤트 관리자는 종종 여러 이벤트에 대해 여러 리스너를 집계합니다(여기서 이벤트와 리스너는 모두 무제한입니다[즉, 하나 또는 여러 개가 될 수 있음]). 이벤트 관리자는 이벤트 트리거도 담당합니다.
일반적으로 우리는 이벤트를 표현하기 위해 객체를 사용합니다. 이벤트 개체는 이벤트가 트리거되는 시기와 방법을 포함하여 이벤트의 기본 요소를 설명합니다.
이벤트의 기본 요소에 대해: 이벤트 이름, 대상(이벤트를 트리거하는 개체, 일반적으로 이벤트 개체 자체) 및 이벤트 매개변수입니다. 우리는 이전에 이벤트가 동작과 동일하다고 말했습니다. 프로그램에서는 종종 동작을 표현하기 위해 메서드나 함수를 사용합니다. 따라서 이벤트 매개변수는 종종 함수 매개변수이기도 합니다.
공유 관리자에 대해서도: 앞에서 언급했듯이 하나의 이벤트는 여러 청취자를 대상으로 할 수 있습니다. 이는 공유 관리자를 통해 달성됩니다. EventManager의 구현에는 SharedEventManagerInterface(생성자 또는 setSharedManager의 코드 삽입 사용, 자세한 내용은 소스 코드 참조)가 포함되며, SharedEventManagerInterface는 리스너를 집계하는 객체를 설명합니다. 이러한 리스너는 지정된 기호가 있는 객체에만 연결됩니다. 이벤트. SharedEventManager는 이벤트를 트리거하지 않으며 리스너만 제공하고 이벤트에 연결합니다. EventManger는 SharedEventManager를 쿼리하여 특정 식별자를 가진 리스너를 얻습니다.
EventManager의 몇 가지 중요한 동작:
1. 이벤트 만들기 : 이벤트를 만드는 것은 실제로 EventManagerInterface의 인스턴스를 만드는 것입니다.
2. 트리거 이벤트 : 일반적으로 트리거는 이벤트 동작에 사용되므로 해당 동작을 실행할 때 이벤트가 직접 트리거될 수 있습니다. 함수 프로토타입: Trigger($eventName,$target=null,$argv=[]); $eventName은 일반적으로 시간 작업 이름(종종 __FUNCTION__으로 대체됨)이고, $target은 이벤트 개체 자체이며 $this로 대체될 수 있습니다. $argv는 들어오는 이벤트의 매개변수입니다(일반적으로 이벤트 동작의 매개변수).
물론 이벤트를 트리거하는 방법은 Trigger뿐 아니라 TriggerUntil, TriggerEvent 및 TriggerEventUntil도 있습니다. 이름을 보면 Trigger와 TriggerEvent라는 두 가지 범주로 나누어져 있음을 알 수 있습니다. 트리거 클래스는 단순히 이벤트를 트리거할 뿐이며 이벤트 인스턴스를 생성하기 위해 구현할 필요는 없으며 트리거는 필요하지 않습니다. 이벤트만 트리거하지만 리스너도 트리거하므로 이벤트 인스턴스가 필요합니다. Until 접미사가 있는 메서드에는 콜백 함수가 필요하며, 각 리스너의 결과는 콜백 함수에 전달됩니다. 콜백 함수가 true bool 값을 반환하는 경우 EventManager는 리스너를 단락시켜야 합니다. (단락에 대해서는 아래 단락 참조)
자세한 내용은 공식 API 또는 EventMangerInterface의 특정 댓글을 확인하세요.
3. 리스너를 생성하고 이벤트에 연결합니다 :
리스너는 EventManager 또는 SharedEventManager를 통해 생성할 수 있습니다. 둘 다 연결 방법을 사용하지만 매개변수가 약간 다릅니다.
먼저 EventManager 메서드를 살펴보겠습니다.
메서드 프로토타입: attachment($eventName, callable $listener, $priority = 1)
매우 간단합니다. 이벤트 이름과 호출 가능한 함수만 필요하며, 마지막으로 기본 우선순위는 1입니다(zend에 내장된 이벤트의 우선순위는 대부분 음수이므로 리스너를 사용자 정의하려는 경우 우선순위가 상대적으로 높으므로 직접 양수를 할당하세요)
.호출 가능한 함수는 리스너입니다. 이벤트 이름에는 "*"라는 특별한 경우가 있습니다. 이는 모든 이벤트를 이 리스너에 연결하는 일반 일치와 유사합니다.
이제 SharedEventManager 메서드를 살펴보겠습니다.
메서드 프로토타입: attachment($identifier, $eventName, callable $listener, $priority = 1);
이전과 유일한 차이점은 식별자 매개변수입니다. 식별자에 대한 소스코드 설명은 다음과 같습니다.
SharedEventManagerInterface 인스턴스에서 공유 신호를 가져오는 데 사용됩니다.
SharedEventmanager 인스턴스에서 공유 신호를 가져오는 데 사용됩니다. 내 이해에 따르면 식별자는 배열입니다. 이벤트(SharedEventmanager는 이벤트를 생성할 수 없음)가 식별자를 정의하면 이벤트가 공유 가능하다는 의미입니다. 연결을 사용하여 리스너를 생성할 때 SharedEventManger 인스턴스가 식별자 매개변수를 전달하도록 합니다. EventManager는 식별자 매개변수를 사용하여 모든 리스너를 쿼리할 수 있습니다.
혼란스러운 점은 이벤트 이름이 있으므로 이벤트 이름을 통해 관련 리스너를 쿼리할 수 있는데 굳이 식별자 속성을 추가할 이유가 없다는 것입니다. 내가 고려하고 있는 것은 이벤트 상속 문제입니다. 이벤트 동작 행위를 포함하는 이벤트 클래스 Foo가 있다고 가정합니다. SubFoo는 Foo 클래스를 상속하고 내부의 이벤트 동작 행위를 재정의합니다. 두 클래스의 이벤트 동작은 동일한 이벤트 이름 act를 갖습니다. 이때 이벤트 이름으로 리스너를 쿼리하면 당연히 충돌이 발생하게 됩니다. 이때 식별자[__CLASS__, get_class($this)]를 정의하고 리스너에서 식별자를 SubFoo로 지정합니다. 이는 SubFoo 클래스의 이벤트 동작과 분명히 일치합니다.
위에서는 SharedEventManager를 통해 여러 이벤트를 수신할 수 있으며 리스너 집계를 통해 구현할 수도 있습니다. ZendEventManagerListenerAggregateInterface를 통해 클래스가 여러 이벤트를 수신하고 하나 이상의 인스턴스 메서드를 리스너로 연결하도록 합니다. 동일한 인터페이스는 attachment(EventManagerInterface $events) 및 detach(EventManagerInterface $events)도 정의합니다. 특정 연결 구현에서는 EventManager 인스턴스의 연결 메서드를 사용하여 여러 이벤트를 수신합니다.
<span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\EventInterface; </span><span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\EventManagerInterface; </span><span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\ListenerAggregateInterface; </span><span style="color: #0000ff;">use</span> Zend\<span style="color: #008080;">Log</span><span style="color: #000000;">\Logger; </span><span style="color: #0000ff;">class</span> LogEvents <span style="color: #0000ff;">implements</span><span style="color: #000000;"> ListenerAggregateInterface { </span><span style="color: #0000ff;">private</span> <span style="color: #800080;">$listeners</span> =<span style="color: #000000;"> []; </span><span style="color: #0000ff;">private</span> <span style="color: #800080;">$log</span><span style="color: #000000;">; </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> __construct(Logger <span style="color: #800080;">$log</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>-><span style="color: #008080;">log</span> = <span style="color: #800080;">$log</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> attach(EventManagerInterface <span style="color: #800080;">$events</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->listeners[] = <span style="color: #800080;">$events</span>->attach('do', [<span style="color: #800080;">$this</span>, 'log'<span style="color: #000000;">]); </span><span style="color: #800080;">$this</span>->listeners[] = <span style="color: #800080;">$events</span>->attach('doSomethingElse', [<span style="color: #800080;">$this</span>, 'log'<span style="color: #000000;">]); } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> detach(EventCollection <span style="color: #800080;">$events</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$this</span>->listeners <span style="color: #0000ff;">as</span> <span style="color: #800080;">$index</span> => <span style="color: #800080;">$listener</span><span style="color: #000000;">) { </span><span style="color: #800080;">$events</span>->detach(<span style="color: #800080;">$listener</span><span style="color: #000000;">); </span><span style="color: #0000ff;">unset</span>(<span style="color: #800080;">$this</span>->listeners[<span style="color: #800080;">$index</span><span style="color: #000000;">]); } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">log</span>(EventInterface <span style="color: #800080;">$e</span><span style="color: #000000;">) { </span><span style="color: #800080;">$event</span> = <span style="color: #800080;">$e</span>-><span style="color: #000000;">getName(); </span><span style="color: #800080;">$params</span> = <span style="color: #800080;">$e</span>-><span style="color: #000000;">getParams(); </span><span style="color: #800080;">$this</span>-><span style="color: #008080;">log</span>->info(<span style="color: #008080;">sprintf</span>('%s: %s', <span style="color: #800080;">$event</span>, json_encode(<span style="color: #800080;">$params</span><span style="color: #000000;">))); } }</span>
집계 사용의 이점:
1. 상태 저장 리스너를 사용할 수 있습니다
2. 유사한 청취자를 여러 명 모아서 한 번에 연결합니다
인트로스펙션 리스너가 반환한 결과
리스너가 있는데, 리스너가 반환하는 결과를 어떻게 받나요? EventManager의 기본 구현은 ResponseCollection의 인스턴스를 반환합니다. 이 클래스는 PHP의 SplStack을 상속합니다. 기본 구조는 스택이므로 응답을 역순으로 탐색할 수 있습니다.
ResponseCollection은 몇 가지 유용한 방법을 제공합니다.
first(): 첫 번째 결과 가져오기
last(): 마지막 결과 가져오기
contains($value): 스택에 특정 값이 포함되어 있는지 확인하고, 포함되어 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
짧은 루프 리스너 실행:
단락이란 무엇입니까? 결과가 나올 때까지 어떤 일을 하고 싶다고 가정해 보세요. 이것은 루프입니다. 이 작업의 결과를 미리 알고 있다면(예를 들어 이전에 이 작업을 수행한 적이 있는 경우) 완전히 완료할 필요는 없습니다. 이때 짧은 루프만 실행하면 됩니다.
EventManager를 추가할 때 캐싱 메커니즘이 있습니다. 메소드에서 이벤트를 트리거하고 캐시된 결과를 찾으면 직접 반환합니다. 캐시된 결과를 찾을 수 없으면 나중에 사용할 수 있도록 트리거된 이벤트를 캐시합니다. 실제로 컴퓨터 하드웨어의 캐시와 동일합니다.
EventManager 구성 요소는 두 가지 처리 방법을 제공합니다: 1. TriggerUntil() 2. TriggerEventUntil. 두 메서드 모두 콜백 함수를 첫 번째 매개변수로 받아들입니다. 콜백 함수가 true를 반환하면 실행이 중지됩니다.
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> someExpensiveCall(<span style="color: #800080;">$criteria1</span>, <span style="color: #800080;">$criteria2</span><span style="color: #000000;">) { </span><span style="color: #800080;">$params</span> = <span style="color: #008080;">compact</span>('criteria1', 'criteria2'<span style="color: #000000;">); </span><span style="color: #800080;">$results</span> = <span style="color: #800080;">$this</span>->getEventManager()-><span style="color: #000000;">triggerUntil( </span><span style="color: #0000ff;">function</span>(<span style="color: #800080;">$r</span><span style="color: #000000;">){ </span><span style="color: #0000ff;">return</span> (<span style="color: #800080;">$r</span><span style="color: #000000;"> instanceof SomeResultClass); }</span>, <span style="color: #ff00ff;">__FUNCTION__</span>, <span style="color: #800080;">$this</span>, <span style="color: #800080;">$params</span><span style="color: #000000;"> ); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$results</span>-><span style="color: #000000;">stopped()) { </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$results</span>->last()'<span style="color: #000000;"> } }</span>
위의 예를 통해 실행이 중지되면 스택의 마지막 결과가 요구 사항을 충족할 가능성이 가장 높다는 것을 알 수 있습니다. 이런 식으로 결과만 반환하면 되는데 왜 추가 계산을 수행해야 합니까?
프로세스는 이벤트에서 실행을 중지하며, 리스너에서도 실행을 중지할 수 있습니다. 그 이유는 이전에 어떤 이벤트를 받았고, 이번에도 같은 이벤트를 받았으니 당연하게도 이전 결과를 활용할 수 있기 때문입니다. 이 경우 리스너는 stopPropagation(true)를 호출하고 EventManager는 추가 리스너에게 알리지 않고 직접 반환합니다.
<span style="color: #800080;">$events</span>->attach('do', <span style="color: #0000ff;">function</span>(<span style="color: #800080;">$e</span><span style="color: #000000;">) { </span><span style="color: #800080;">$e</span>-><span style="color: #000000;">stopPropagation(); </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> SomeResultClass(); });</span>
当然,使用触发器范例可能会导致歧义,毕竟你并不知道最终的结果是否满足要求。
Keeping it in order.
偶尔你会关心监听器的执行顺序。我们通过监听器的优先级来控制执行顺序(上面说讲的短回路也会影响执行顺序)。每一个EventManager::attach()和SharedEventManager::attach()都会接受一个而外的参数:priority。默认情况下为1,我们可以省略该参数。如果你提供了该参数:高优先级执行的早,低优先级的可能会推迟执行。
自定义事件对象:
我们之前使用trigger()触发事件,在这同时我们也创建了事件。但trigger()的参数有限,我们只能指定事件的对象,参数,名称。实际上我们可以创建一个自定义事件,在Zendframework里面有个很重要的事件:MvcEvent。很显然MvcEvent便是一个自定义事件,该事件组合了application实例,路由器,路由匹配对象,请求和应答对象,视图模型还有结果。我们查看MvcEvent的源码会发现MvcEvent类实际上继承了Event类。同理我们的自定义事件对象也可以继承Event类或者继承MvcEvent。
<span style="color: #800080;">$event</span> = <span style="color: #0000ff;">new</span><span style="color: #000000;"> CustomEvent(); </span><span style="color: #800080;">$event</span>->setName('foo'<span style="color: #000000;">); </span><span style="color: #800080;">$event</span>->setTarget(<span style="color: #800080;">$this</span><span style="color: #000000;">); </span><span style="color: #800080;">$event</span>->setSomeKey(<span style="color: #800080;">$value</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">injected with event name and target:</span> <span style="color: #800080;">$events</span>->triggerEvent(<span style="color: #800080;">$event</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">Use triggerEventUntil() for criteria-based short-circuiting:</span> <span style="color: #800080;">$results</span> = <span style="color: #800080;">$events</span>->triggerEventUntil(<span style="color: #800080;">$callback</span>, <span style="color: #800080;">$event</span>);
上面的代码可以看到我们使用自定义事件类创建了一个事件对象,调用相关拦截器为事件对象设置属性。我们有了事件对象还是用trigger()触发事件吗?显然不是,我们使用triggerEvent($event)方法,该方法接收一个事件对象。而triggerEventUntil有一个回调函数,该回调函数作为是否进行短回路的依据。