testDispatchSomeEventsThroughAdHocListener()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Onoi\EventDispatcher\Tests\Integration;
4
5
use Onoi\EventDispatcher\EventDispatcherFactory;
6
use Onoi\EventDispatcher\EventDispatcher;
7
use Onoi\EventDispatcher\DispatchContext;
8
use Onoi\EventDispatcher\EventListener;
9
use Onoi\EventDispatcher\EventListenerCollection;
10
11
/**
12
 * @group onoi-event-dispatcher
13
 *
14
 * @license GNU GPL v2+
15
 * @since 1.0
16
 *
17
 * @author mwjames
18
 */
19
class EventRegistryDispatchTest extends \PHPUnit_Framework_TestCase {
20
21
	public function testDispatchSomeEventsFromCollectionOfListenersWithoutPropagationStop() {
22
23
		$mockTester = $this->getMockBuilder( '\stdClass' )
24
			->setMethods( array( 'doSomething', 'doSomethingElse' ) )
25
			->getMock();
26
27
		$mockTester->expects( $this->once() )
28
			->method( 'doSomething' );
29
30
		$mockTester->expects( $this->once() )
31
			->method( 'doSomethingElse' );
32
33
		$eventDispatcherFactory = new EventDispatcherFactory();
34
35
		$listenerCollectionRegistery = new ListenerCollectionRegistery(
36
			$eventDispatcherFactory->newGenericEventListenerCollection()
37
		);
38
39
		$eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
40
		$eventDispatcher->addListenerCollection( $listenerCollectionRegistery );
41
42
		$dispatchContext = new DispatchContext();
43
		$dispatchContext->set( 'mock', $mockTester );
44
45
		$eventDispatcher->dispatch( 'do.something', $dispatchContext );
46
	}
47
48
	public function testDispatchSomeEventsFromCollectionOfListenersWithPropagationStop() {
49
50
		$mockTester = $this->getMockBuilder( '\stdClass' )
51
			->setMethods( array( 'doSomething', 'doSomethingElse' ) )
52
			->getMock();
53
54
		$mockTester->expects( $this->once() )
55
			->method( 'doSomething' );
56
57
		$mockTester->expects( $this->never() ) // PropagationStop
58
			->method( 'doSomethingElse' );
59
60
		$eventDispatcherFactory = new EventDispatcherFactory();
61
62
		$listenerCollectionRegistery = new ListenerCollectionRegistery(
63
			$eventDispatcherFactory->newGenericEventListenerCollection()
64
		);
65
66
		$eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
67
		$eventDispatcher->addListenerCollection( $listenerCollectionRegistery );
68
69
		$dispatchContext = new DispatchContext();
70
		$dispatchContext->set( 'mock', $mockTester );
71
		$dispatchContext->set( 'propagationstop', true );
72
73
		$eventDispatcher->dispatch( 'do.something', $dispatchContext );
74
	}
75
76
	public function testDispatchSomeEventsThroughAdHocListener() {
77
78
		$mockTester = $this->getMockBuilder( '\stdClass' )
79
			->setMethods( array( 'doSomething' ) )
80
			->getMock();
81
82
		$mockTester->expects( $this->once() )
83
			->method( 'doSomething' );
84
85
		$eventDispatcherFactory = new EventDispatcherFactory();
86
		$eventDispatcher = $eventDispatcherFactory->newGenericEventDispatcher();
87
88
		$eventDispatcher->addListener( 'notify.bar', new BarListener() );
89
90
		$dispatchContext = new DispatchContext();
91
		$dispatchContext->set( 'mock', $mockTester );
92
93
		$eventDispatcher->dispatch( 'notify.bar', $dispatchContext );
94
		$eventDispatcher->dispatch( 'try.notify.empty.listener' );
95
	}
96
97
}
98
99
/**
100
 * Example required for the test
101
 */
102
class ListenerCollectionRegistery implements EventListenerCollection {
103
104
	private $eventListenerCollection;
105
106
	public function __construct( EventListenerCollection $eventListenerCollection ) {
107
		$this->eventListenerCollection = $eventListenerCollection;
108
	}
109
110
	public function getCollection() {
111
		return $this->addToListenerCollection()->getCollection();
112
	}
113
114
	private function addToListenerCollection() {
115
116
		$this->eventListenerCollection->registerCallback( 'do.something', function( DispatchContext $dispatchContext ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Onoi\EventDispatcher\EventListenerCollection as the method registerCallback() does only exist in the following implementations of said interface: Onoi\EventDispatcher\Lis...EventListenerCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
117
			$dispatchContext->get( 'mock' )->doSomething();
118
		} );
119
120
		$this->eventListenerCollection->registerCallback( 'do.something', function( DispatchContext $dispatchContext ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Onoi\EventDispatcher\EventListenerCollection as the method registerCallback() does only exist in the following implementations of said interface: Onoi\EventDispatcher\Lis...EventListenerCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
121
			$dispatchContext->get( 'mock' )->doSomethingElse();
122
		} );
123
124
		return $this->eventListenerCollection;
125
	}
126
127
}
128
129
class BarListener implements EventListener {
130
131
	public function execute( DispatchContext $dispatchContext = null ) {
132
		$dispatchContext->get( 'mock' )->doSomething();
0 ignored issues
show
Bug introduced by
It seems like $dispatchContext is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
133
	}
134
135
	public function isPropagationStopped() {
136
		return false;
137
	}
138
}
139