Completed
Push — master ( 5c0e9b...12ea82 )
by Jeroen De
58:20 queued 45:47
created

testGivenExistingTransactionIdForBookedDonation_handler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\DonationContext\Tests\Integration\UseCases\HandlePayPalPaymentNotification;
6
7
use Psr\Log\NullLogger;
8
use WMDE\Fundraising\Frontend\DonationContext\DataAccess\DoctrineDonationRepository;
9
use WMDE\Fundraising\Frontend\DonationContext\Domain\Model\Donation;
10
use WMDE\Fundraising\Frontend\DonationContext\Domain\Model\DonorName;
11
use WMDE\Fundraising\Frontend\DonationContext\Infrastructure\DonationConfirmationMailer;
12
use WMDE\Fundraising\Frontend\DonationContext\Infrastructure\DonationEventLogger;
13
use WMDE\Fundraising\Frontend\DonationContext\UseCases\HandlePayPalPaymentNotification\HandlePayPalPaymentNotificationUseCase;
14
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalData;
15
use WMDE\Fundraising\Frontend\Tests\Data\ValidDonation;
16
use WMDE\Fundraising\Frontend\Tests\Data\ValidPayPalNotificationRequest;
17
use WMDE\Fundraising\Frontend\DonationContext\Tests\Fixtures\DonationEventLoggerSpy;
18
use WMDE\Fundraising\Frontend\DonationContext\Tests\Fixtures\DonationRepositorySpy;
19
use WMDE\Fundraising\Frontend\DonationContext\Tests\Fixtures\FailingDonationAuthorizer;
20
use WMDE\Fundraising\Frontend\DonationContext\Tests\Fixtures\FakeDonationRepository;
21
use WMDE\Fundraising\Frontend\Tests\Fixtures\LoggerSpy;
22
use WMDE\Fundraising\Frontend\DonationContext\Tests\Fixtures\SucceedingDonationAuthorizer;
23
use WMDE\Fundraising\Frontend\Tests\Fixtures\ThrowingEntityManager;
24
25
/**
26
 * @covers WMDE\Fundraising\Frontend\DonationContext\UseCases\HandlePayPalPaymentNotification\HandlePayPalPaymentNotificationUseCase
27
 *
28
 * @licence GNU GPL v2+
29
 * @author Kai Nissen < [email protected] >
30
 * @author Gabriel Birke < [email protected] >
31
 */
32
class HandlePayPalPaymentNotificationUseCaseTest extends \PHPUnit_Framework_TestCase {
33
34
	public function testWhenRepositoryThrowsException_handlerReturnsFalse() {
35
		$useCase = new HandlePayPalPaymentNotificationUseCase(
36
			new DoctrineDonationRepository( ThrowingEntityManager::newInstance( $this ) ),
37
			new FailingDonationAuthorizer(),
38
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
39
			new NullLogger(),
40
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
41
		);
42
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
43
		$this->assertFalse( $useCase->handleNotification( $request ) );
44
	}
45
46
	public function testWhenAuthorizationFails_handlerReturnsFalse() {
47
		$fakeRepository = new FakeDonationRepository();
48
		$fakeRepository->storeDonation( ValidDonation::newIncompletePayPalDonation() );
49
50
		$useCase = new HandlePayPalPaymentNotificationUseCase(
51
			$fakeRepository,
52
			new FailingDonationAuthorizer(),
53
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
54
			new NullLogger(),
55
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
56
		);
57
58
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
59
		$this->assertFalse( $useCase->handleNotification( $request ) );
60
	}
61
62
	public function testWhenAuthorizationSucceeds_handlerReturnsTrue() {
63
		$fakeRepository = new FakeDonationRepository();
64
		$fakeRepository->storeDonation( ValidDonation::newIncompletePayPalDonation() );
65
66
		$useCase = new HandlePayPalPaymentNotificationUseCase(
67
			$fakeRepository,
68
			new SucceedingDonationAuthorizer(),
69
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
70
			new NullLogger(),
71
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
72
		);
73
74
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
75
		$this->assertTrue( $useCase->handleNotification( $request ) );
76
	}
77
78
	public function testWhenPaymentTypeIsNonPayPal_handlerReturnsFalse() {
79
		$fakeRepository = new FakeDonationRepository();
80
		$fakeRepository->storeDonation( ValidDonation::newDirectDebitDonation() );
81
82
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
83
		$useCase = new HandlePayPalPaymentNotificationUseCase(
84
			$fakeRepository,
85
			new SucceedingDonationAuthorizer(),
86
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
87
			new NullLogger(),
88
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
89
		);
90
91
		$this->assertFalse( $useCase->handleNotification( $request ) );
92
	}
93
94
	public function testWhenPaymentStatusIsPending_handlerReturnsFalse() {
95
		$request = ValidPayPalNotificationRequest::newPendingPayment();
96
97
		$useCase = new HandlePayPalPaymentNotificationUseCase(
98
			new FakeDonationRepository(),
99
			new SucceedingDonationAuthorizer(),
100
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
101
			new NullLogger(),
102
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
103
		);
104
105
		$this->assertFalse( $useCase->handleNotification( $request ) );
106
	}
107
108
	public function testWhenPaymentStatusIsPending_handlerLogsStatus() {
109
		$logger = new LoggerSpy();
110
111
		$request = $request = ValidPayPalNotificationRequest::newPendingPayment();
112
113
		$useCase = new HandlePayPalPaymentNotificationUseCase(
114
			new FakeDonationRepository(),
115
			new SucceedingDonationAuthorizer(),
116
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
117
			$logger,
118
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
119
		);
120
121
		$useCase->handleNotification( $request );
122
123
		$logger->assertCalledOnceWithMessage( 'Unhandled PayPal notification: Pending', $this );
124
	}
125
126
	public function testWhenTransactionTypeIsForSubscriptionChanges_handlerReturnsFalse() {
127
		$request = ValidPayPalNotificationRequest::newSubscriptionModification();
128
129
		$useCase = new HandlePayPalPaymentNotificationUseCase(
130
			new FakeDonationRepository(),
131
			new SucceedingDonationAuthorizer(),
132
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
133
			new NullLogger(),
134
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
135
		);
136
		$this->assertFalse( $useCase->handleNotification( $request ) );
137
	}
138
139
	public function testWhenTransactionTypeIsForSubscriptionChanges_handlerLogsStatus() {
140
		$logger = new LoggerSpy();
141
142
		$useCase = new HandlePayPalPaymentNotificationUseCase(
143
			new FakeDonationRepository(),
144
			new SucceedingDonationAuthorizer(),
145
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
146
			$logger,
147
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
148
		);
149
150
		$request = ValidPayPalNotificationRequest::newSubscriptionModification();
151
152
		$useCase->handleNotification( $request );
153
154
		$logger->assertCalledOnceWithMessage( 'Unhandled PayPal subscription notification: subscr_modify', $this );
155
	}
156
157
	public function testWhenAuthorizationSucceeds_confirmationMailIsSent() {
158
		$donation = ValidDonation::newIncompletePayPalDonation();
159
		$fakeRepository = new FakeDonationRepository();
160
		$fakeRepository->storeDonation( $donation );
161
162
		$mailer = $this->getMailer();
163
		$mailer->expects( $this->once() )
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in WMDE\Fundraising\Fronten...ationConfirmationMailer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
164
			->method( 'sendConfirmationMailFor' );
165
			// TODO: assert that the correct values are passed to the mailer
166
167
		$useCase = new HandlePayPalPaymentNotificationUseCase(
168
			$fakeRepository,
169
			new SucceedingDonationAuthorizer(),
170
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 162 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
171
			new NullLogger(),
172
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
173
		);
174
175
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
176
		$this->assertTrue( $useCase->handleNotification( $request ) );
177
	}
178
179
	public function testWhenAuthorizationSucceedsForAnonymousDonation_confirmationMailIsNotSent() {
180
		$donation = ValidDonation::newIncompleteAnonymousPayPalDonation();
181
		$fakeRepository = new FakeDonationRepository();
182
		$fakeRepository->storeDonation( $donation );
183
184
		$mailer = $this->getMailer();
185
		$mailer->expects( $this->never() )
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in WMDE\Fundraising\Fronten...ationConfirmationMailer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
186
			->method( 'sendConfirmationMailFor' );
187
188
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
189
		$useCase = new HandlePayPalPaymentNotificationUseCase(
190
			$fakeRepository,
191
			new SucceedingDonationAuthorizer(),
192
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 184 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
193
			new NullLogger(),
194
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
195
		);
196
197
		$this->assertTrue( $useCase->handleNotification( $request ) );
198
	}
199
200
	public function testWhenAuthorizationSucceeds_donationIsStored() {
201
		$donation = ValidDonation::newIncompletePayPalDonation();
202
		$repositorySpy = new DonationRepositorySpy( $donation );
203
204
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
205
		$useCase = new HandlePayPalPaymentNotificationUseCase(
206
			$repositorySpy,
207
			new SucceedingDonationAuthorizer(),
208
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
209
			new NullLogger(),
210
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
211
		);
212
213
		$this->assertTrue( $useCase->handleNotification( $request ) );
214
		$this->assertCount( 1, $repositorySpy->getStoreDonationCalls() );
215
	}
216
217
	public function testWhenAuthorizationSucceeds_donationIsBooked() {
218
		$donation = ValidDonation::newIncompletePayPalDonation();
219
		$repository = new FakeDonationRepository( $donation );
220
221
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
222
		$useCase = new HandlePayPalPaymentNotificationUseCase(
223
			$repository,
224
			new SucceedingDonationAuthorizer(),
225
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
226
			new NullLogger(),
227
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
228
		);
229
230
		$this->assertTrue( $useCase->handleNotification( $request ) );
231
		$this->assertTrue( $repository->getDonationById( $donation->getId() )->isBooked() );
232
	}
233
234
	public function testWhenAuthorizationSucceeds_bookingEventIsLogged() {
235
		$donation = ValidDonation::newIncompletePayPalDonation();
236
		$repositorySpy = new DonationRepositorySpy( $donation );
237
238
		$eventLogger = new DonationEventLoggerSpy();
239
240
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
241
		$useCase = new HandlePayPalPaymentNotificationUseCase(
242
			$repositorySpy,
243
			new SucceedingDonationAuthorizer(),
244
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
245
			new NullLogger(),
246
			$eventLogger
247
		);
248
249
		$this->assertTrue( $useCase->handleNotification( $request ) );
250
251
		$this->assertEventLogContainsExpression( $eventLogger, $donation->getId(), '/booked/' );
252
	}
253
254
	public function testWhenSendingConfirmationMailFails_handlerReturnsTrue() {
255
		$fakeRepository = new FakeDonationRepository();
256
		$fakeRepository->storeDonation( ValidDonation::newIncompletePayPalDonation() );
257
258
		$mailer = $this->getMailer();
259
		$mailer->expects( $this->once() )
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in WMDE\Fundraising\Fronten...ationConfirmationMailer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
260
			->method( 'sendConfirmationMailFor' )
261
			->willThrowException( new \RuntimeException( 'Oh noes!' ) );
262
263
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
264
		$useCase = new HandlePayPalPaymentNotificationUseCase(
265
			$fakeRepository,
266
			new SucceedingDonationAuthorizer(),
267
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 258 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
268
			new NullLogger(),
269
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
270
		);
271
272
		$this->assertTrue( $useCase->handleNotification( $request ) );
273
	}
274
275
	public function testGivenNewTransactionIdForBookedDonation_transactionIdShowsUpInChildPayments() {
276
		$donation = ValidDonation::newBookedPayPalDonation();
277
		$transactionId = '16R12136PU8783961';
278
279
		$fakeRepository = new FakeDonationRepository();
280
		$fakeRepository->storeDonation( $donation );
281
282
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
283
284
		$useCase = new HandlePayPalPaymentNotificationUseCase(
285
			$fakeRepository,
286
			new SucceedingDonationAuthorizer(),
287
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
288
			new NullLogger(),
289
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
290
		);
291
292
		$this->assertTrue( $useCase->handleNotification( $request ) );
293
294
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
295
		$payment = $fakeRepository->getDonationById( $donation->getId() )->getPaymentMethod();
296
297
		$this->assertTrue(
298
			$payment->getPayPalData()->hasChildPayment( $transactionId ),
299
			'Parent payment must have new transaction ID in its list'
300
		);
301
	}
302
303
	public function testGivenNewTransactionIdForBookedDonation_childTransactionWithSameDataIsCreated() {
304
		$donation = ValidDonation::newBookedPayPalDonation();
305
		$transactionId = '16R12136PU8783961';
306
307
		$fakeRepository = new FakeDonationRepository();
308
		$fakeRepository->storeDonation( $donation );
309
310
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
311
312
		$useCase = new HandlePayPalPaymentNotificationUseCase(
313
			$fakeRepository,
314
			new SucceedingDonationAuthorizer(),
315
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
316
			new NullLogger(),
317
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
318
		);
319
320
		$this->assertTrue( $useCase->handleNotification( $request ) );
321
322
		$donation = $fakeRepository->getDonationById( $donation->getId() );
323
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
324
		$payment = $donation->getPaymentMethod();
325
		$childDonation = $fakeRepository->getDonationById( $payment->getPayPalData()->getChildPaymentEntityId( $transactionId ) );
326
		$this->assertNotNull( $childDonation );
327
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $childDonationPaymentMethod */
328
		$childDonationPaymentMethod = $childDonation->getPaymentMethod();
329
		$this->assertEquals( $transactionId, $childDonationPaymentMethod->getPayPalData()->getPaymentId() );
330
		$this->assertEquals( $donation->getAmount(), $childDonation->getAmount() );
331
		$this->assertEquals( $donation->getDonor(), $childDonation->getDonor() );
332
		$this->assertEquals( $donation->getPaymentIntervalInMonths(), $childDonation->getPaymentIntervalInMonths() );
333
		$this->assertTrue( $childDonation->isBooked() );
334
	}
335
336
	public function testGivenNewTransactionIdForBookedDonation_childCreationeventIsLogged() {
337
		$donation = ValidDonation::newBookedPayPalDonation();
338
		$transactionId = '16R12136PU8783961';
339
340
		$fakeRepository = new FakeDonationRepository();
341
		$fakeRepository->storeDonation( $donation );
342
343
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
344
345
		$eventLogger = new DonationEventLoggerSpy();
346
347
		$useCase = new HandlePayPalPaymentNotificationUseCase(
348
			$fakeRepository,
349
			new SucceedingDonationAuthorizer(),
350
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
351
			new NullLogger(),
352
			$eventLogger
353
		);
354
355
		$this->assertTrue( $useCase->handleNotification( $request ) );
356
357
		$donation = $fakeRepository->getDonationById( $donation->getId() );
358
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
359
		$payment = $donation->getPaymentMethod();
360
		$childDonationId = $payment->getPayPalData()->getChildPaymentEntityId( $transactionId );
361
362
		$this->assertEventLogContainsExpression( $eventLogger, $donation->getId(), '/child donation.*' . $childDonationId .'/' );
363
		$this->assertEventLogContainsExpression( $eventLogger, $childDonationId, '/parent donation.*' . $donation->getId() .'/' );
364
	}
365
366
	public function testGivenExistingTransactionIdForBookedDonation_handlerReturnsFalse() {
367
		$fakeRepository = new FakeDonationRepository();
368
		$fakeRepository->storeDonation( ValidDonation::newBookedPayPalDonation() );
369
370
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
371
372
		$useCase = new HandlePayPalPaymentNotificationUseCase(
373
			$fakeRepository,
374
			new SucceedingDonationAuthorizer(),
375
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
376
			new NullLogger(),
377
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
378
		);
379
380
		$this->assertFalse( $useCase->handleNotification( $request ) );
381
	}
382
383
	public function testGivenTransactionIdInBookedChildDonation_noNewDonationIsCreated() {
384
		$transactionId = '16R12136PU8783961';
385
		$fakeChildEntityId = 2;
386
		$donation = ValidDonation::newBookedPayPalDonation();
387
		$donation->getPaymentMethod()->getPaypalData()->addChildPayment( $transactionId, $fakeChildEntityId );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WMDE\Fundraising\Fronten...ain\Model\PaymentMethod as the method getPaypalData() does only exist in the following implementations of said interface: WMDE\Fundraising\Fronten...ain\Model\PayPalPayment.

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...
388
389
		$fakeRepository = new FakeDonationRepository();
390
		$fakeRepository->storeDonation( $donation );
391
392
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
393
394
		$useCase = new HandlePayPalPaymentNotificationUseCase(
395
			$fakeRepository,
396
			new SucceedingDonationAuthorizer(),
397
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
398
			new NullLogger(),
399
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
400
		);
401
402
		$this->assertFalse( $useCase->handleNotification( $request ) );
403
	}
404
405
	public function testWhenNotificationIsForNonExistingDonation_newDonationIsCreated() {
406
		$repositorySpy = new DonationRepositorySpy();
407
408
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
409
		$useCase = new HandlePayPalPaymentNotificationUseCase(
410
			$repositorySpy,
411
			new SucceedingDonationAuthorizer(),
412
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
413
			new NullLogger(),
414
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
415
		);
416
417
		$useCase->handleNotification( $request );
418
419
		$storeDonationCalls = $repositorySpy->getStoreDonationCalls();
420
		$this->assertCount( 1, $storeDonationCalls, 'Donation is stored' );
421
		$this->assertNull( $storeDonationCalls[0]->getId(), 'ID is not taken from request' );
422
		$this->assertDonationIsCreatedWithNotficationRequestData( $storeDonationCalls[0] );
423
	}
424
425
	public function testGivenRecurringPaymentForBookedDonation_newDonationIsCreated() {
426
		$donation = ValidDonation::newBookedPayPalDonation();
427
		$repositorySpy = new DonationRepositorySpy( $donation );
428
429
		$request = ValidPayPalNotificationRequest::newRecurringPayment( $donation->getId() );
430
431
		$useCase = new HandlePayPalPaymentNotificationUseCase(
432
			$repositorySpy,
433
			new SucceedingDonationAuthorizer(),
434
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
435
			new NullLogger(),
436
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
437
		);
438
439
		$useCase->handleNotification( $request );
440
441
		$this->assertCount( 1, $repositorySpy->getStoreDonationCalls() );
442
		/** @var \WMDE\Fundraising\Frontend\DonationContext\Domain\Model\Donation $newDonation */
443
		$newDonation = $repositorySpy->getStoreDonationCalls()[0];
444
		$this->assertNotEquals( $donation, $newDonation );
445
446
		$this->assertDonationIsCreatedWithNotficationRequestData( $newDonation );
447
	}
448
449
	public function testGivenRecurringPaymentForIncompleteDonation_donationIsBooked() {
450
		$donation = ValidDonation::newIncompletePayPalDonation();
451
		$repositorySpy = new DonationRepositorySpy( $donation );
452
453
		$request = ValidPayPalNotificationRequest::newRecurringPayment( $donation->getId() );
454
455
		$useCase = new HandlePayPalPaymentNotificationUseCase(
456
			$repositorySpy,
457
			new SucceedingDonationAuthorizer(),
458
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
459
			new NullLogger(),
460
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
461
		);
462
463
		$useCase->handleNotification( $request );
464
		$donation = $repositorySpy->getDonationById( $donation->getId() );
465
466
		$this->assertCount( 1, $repositorySpy->getStoreDonationCalls() );
467
		$this->assertEquals( $donation, $repositorySpy->getStoreDonationCalls()[0] );
468
		$this->assertTrue( $donation->isBooked() );
469
	}
470
471
	public function testWhenNotificationIsForNonExistingDonation_confirmationMailIsSent() {
472
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
473
		$mailer = $this->getMailer();
474
		$mailer->expects( $this->once() )
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in WMDE\Fundraising\Fronten...ationConfirmationMailer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
475
			->method( 'sendConfirmationMailFor' )
476
			->with( $this->anything() );
477
		$useCase = new HandlePayPalPaymentNotificationUseCase(
478
			new FakeDonationRepository(),
479
			new SucceedingDonationAuthorizer(),
480
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 473 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
481
			new NullLogger(),
482
			$this->getEventLogger()
0 ignored issues
show
Bug introduced by
It seems like $this->getEventLogger() targeting WMDE\Fundraising\Fronten...eTest::getEventLogger() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...re\DonationEventLogger>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
483
		);
484
485
		$useCase->handleNotification( $request );
486
	}
487
488
	public function testWhenNotificationIsForNonExistingDonation_bookingEventIsLogged() {
489
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
490
		$eventLogger = new DonationEventLoggerSpy();
491
492
		$useCase = new HandlePayPalPaymentNotificationUseCase(
493
			new FakeDonationRepository(),
494
			new SucceedingDonationAuthorizer(),
495
			$this->getMailer(),
0 ignored issues
show
Bug introduced by
It seems like $this->getMailer() targeting WMDE\Fundraising\Fronten...seCaseTest::getMailer() can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, WMDE\Fundraising\Fronten...nUseCase::__construct() does only seem to accept object<WMDE\Fundraising\...tionConfirmationMailer>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
496
			new NullLogger(),
497
			$eventLogger
498
		);
499
500
		$useCase->handleNotification( $request );
501
502
		$this->assertEventLogContainsExpression( $eventLogger, 1, '/booked/' ); // 1 is the generated donation id
503
	}
504
505
	private function assertDonationIsCreatedWithNotficationRequestData( Donation $donation ) {
506
		$this->assertSame( 0, $donation->getPaymentIntervalInMonths(), 'Payment interval is always empty' );
507
		$this->assertTrue( $donation->isBooked() );
508
509
		$donorName = $donation->getDonor()->getName();
510
		$this->assertSame( DonorName::PERSON_PRIVATE, $donorName->getPersonType(), 'Person is always private' );
511
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_NAME, $donorName->getFullName() );
512
513
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_EMAIL, $donation->getDonor()->getEmailAddress() );
514
515
		$address = $donation->getDonor()->getPhysicalAddress();
516
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_STREET, $address->getStreetAddress() );
517
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_CITY, $address->getCity() );
518
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_POSTAL_CODE, $address->getPostalCode() );
519
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_COUNTRY_CODE, $address->getCountryCode() );
520
521
		$payment = $donation->getPayment();
522
		$this->assertSame( ValidPayPalNotificationRequest::AMOUNT_GROSS_CENTS, $payment->getAmount()->getEuroCents() );
523
524
		/** @var PayPalData $paypalData */
525
		$paypalData = $payment->getPaymentMethod()->getPaypalData();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WMDE\Fundraising\Fronten...ain\Model\PaymentMethod as the method getPaypalData() does only exist in the following implementations of said interface: WMDE\Fundraising\Fronten...ain\Model\PayPalPayment.

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...
526
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_NAME, $paypalData->getAddressName() );
527
	}
528
529
	private function assertEventLogContainsExpression( DonationEventLoggerSpy $eventLoggerSpy, int $donationId, string $expr ) {
530
		$foundCalls = array_filter( $eventLoggerSpy->getLogCalls(), function( $call ) use ( $donationId, $expr ) {
531
			return $call[0] == $donationId && preg_match( $expr, $call[1] );
532
		} );
533
		$assertMsg = 'Failed to assert that donation event log log contained "' . $expr . '" for donation id '.$donationId;
534
		$this->assertCount( 1, $foundCalls, $assertMsg );
535
	}
536
537
	/**
538
	 * @return DonationConfirmationMailer|\PHPUnit_Framework_MockObject_MockObject
539
	 */
540
	private function getMailer(): DonationConfirmationMailer {
541
		return $this->getMockBuilder( DonationConfirmationMailer::class )->disableOriginalConstructor()->getMock();
542
	}
543
544
	/**
545
	 * @return DonationEventLogger|\PHPUnit_Framework_MockObject_MockObject
546
	 */
547
	private function getEventLogger(): DonationEventLogger {
548
		return $this->createMock( DonationEventLogger::class );
549
	}
550
551
}
552