Completed
Push — master ( 80402e...12832d )
by Jeroen De
44s
created

testGivenExistingTransactionIdForBookedDonation_handler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 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_errorResponseIsReturned() {
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
			$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...
40
		);
41
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
42
		$reponse = $useCase->handleNotification( $request );
43
		$this->assertFalse( $reponse->notificationWasHandled() );
44
		$this->assertTrue( $reponse->hasErrors() );
45
	}
46
47
	public function testWhenAuthorizationFails_unhandledResponseIsReturned() {
48
		$fakeRepository = new FakeDonationRepository();
49
		$fakeRepository->storeDonation( ValidDonation::newIncompletePayPalDonation() );
50
51
		$useCase = new HandlePayPalPaymentNotificationUseCase(
52
			$fakeRepository,
53
			new FailingDonationAuthorizer(),
54
			$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...
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 )->notificationWasHandled() );
60
	}
61
62
	public function testWhenAuthorizationSucceeds_successResponseIsReturned() {
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
			$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...
71
		);
72
73
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
74
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
75
	}
76
77
	public function testWhenPaymentTypeIsNonPayPal_unhandledResponseIsReturned() {
78
		$fakeRepository = new FakeDonationRepository();
79
		$fakeRepository->storeDonation( ValidDonation::newDirectDebitDonation() );
80
81
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
82
		$useCase = new HandlePayPalPaymentNotificationUseCase(
83
			$fakeRepository,
84
			new SucceedingDonationAuthorizer(),
85
			$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...
86
			$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...
87
		);
88
89
		$this->assertFalse( $useCase->handleNotification( $request )->notificationWasHandled() );
90
	}
91
92
	public function testWhenPaymentStatusIsPending_unhandledResponseIsReturned() {
93
		$request = ValidPayPalNotificationRequest::newPendingPayment();
94
95
		$useCase = new HandlePayPalPaymentNotificationUseCase(
96
			new FakeDonationRepository(),
97
			new SucceedingDonationAuthorizer(),
98
			$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...
99
			$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...
100
		);
101
102
		$this->assertFalse( $useCase->handleNotification( $request )->notificationWasHandled() );
103
	}
104
105
	public function testWhenPaymentStatusIsPending_responseContainsMoreInformation() {
106
		$request = $request = ValidPayPalNotificationRequest::newPendingPayment();
107
108
		$useCase = new HandlePayPalPaymentNotificationUseCase(
109
			new FakeDonationRepository(),
110
			new SucceedingDonationAuthorizer(),
111
			$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...
112
			$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...
113
		);
114
115
		$response = $useCase->handleNotification( $request );
116
		$this->assertSame( 'Unhandled PayPal instant payment notification', $response->getContext()['message'] );
117
		$this->assertSame( 'Pending', $response->getContext()['request']['paymentStatus'] );
118
	}
119
120
	public function testWhenTransactionTypeIsForSubscriptionChanges_unhandledResponseIsReturned() {
121
		$request = ValidPayPalNotificationRequest::newSubscriptionModification();
122
123
		$useCase = new HandlePayPalPaymentNotificationUseCase(
124
			new FakeDonationRepository(),
125
			new SucceedingDonationAuthorizer(),
126
			$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...
127
			$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...
128
		);
129
		$this->assertFalse( $useCase->handleNotification( $request )->notificationWasHandled() );
130
	}
131
132
	public function testWhenTransactionTypeIsForSubscriptionChanges_responseContainsMoreInformation() {
133
		$useCase = new HandlePayPalPaymentNotificationUseCase(
134
			new FakeDonationRepository(),
135
			new SucceedingDonationAuthorizer(),
136
			$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...
137
			$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...
138
		);
139
140
		$request = ValidPayPalNotificationRequest::newSubscriptionModification();
141
142
		$response = $useCase->handleNotification( $request );
143
		$this->assertSame( 'Unhandled PayPal subscription notification', $response->getContext()['message'] );
144
		$this->assertSame( 'subscr_modify', $response->getContext()['request']['transactionType'] );
145
	}
146
147
	public function testWhenAuthorizationSucceeds_confirmationMailIsSent() {
148
		$donation = ValidDonation::newIncompletePayPalDonation();
149
		$fakeRepository = new FakeDonationRepository();
150
		$fakeRepository->storeDonation( $donation );
151
152
		$mailer = $this->getMailer();
153
		$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...
154
			->method( 'sendConfirmationMailFor' );
155
			// TODO: assert that the correct values are passed to the mailer
156
157
		$useCase = new HandlePayPalPaymentNotificationUseCase(
158
			$fakeRepository,
159
			new SucceedingDonationAuthorizer(),
160
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 152 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...
161
			$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...
162
		);
163
164
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
165
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
166
	}
167
168
	public function testWhenAuthorizationSucceedsForAnonymousDonation_confirmationMailIsNotSent() {
169
		$donation = ValidDonation::newIncompleteAnonymousPayPalDonation();
170
		$fakeRepository = new FakeDonationRepository();
171
		$fakeRepository->storeDonation( $donation );
172
173
		$mailer = $this->getMailer();
174
		$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...
175
			->method( 'sendConfirmationMailFor' );
176
177
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
178
		$useCase = new HandlePayPalPaymentNotificationUseCase(
179
			$fakeRepository,
180
			new SucceedingDonationAuthorizer(),
181
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 173 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...
182
			$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...
183
		);
184
185
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
186
	}
187
188
	public function testWhenAuthorizationSucceeds_donationIsStored() {
189
		$donation = ValidDonation::newIncompletePayPalDonation();
190
		$repositorySpy = new DonationRepositorySpy( $donation );
191
192
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
193
		$useCase = new HandlePayPalPaymentNotificationUseCase(
194
			$repositorySpy,
195
			new SucceedingDonationAuthorizer(),
196
			$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...
197
			$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...
198
		);
199
200
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
201
		$this->assertCount( 1, $repositorySpy->getStoreDonationCalls() );
202
	}
203
204
	public function testWhenAuthorizationSucceeds_donationIsBooked() {
205
		$donation = ValidDonation::newIncompletePayPalDonation();
206
		$repository = new FakeDonationRepository( $donation );
207
208
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
209
		$useCase = new HandlePayPalPaymentNotificationUseCase(
210
			$repository,
211
			new SucceedingDonationAuthorizer(),
212
			$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...
213
			$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...
214
		);
215
216
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
217
		$this->assertTrue( $repository->getDonationById( $donation->getId() )->isBooked() );
218
	}
219
220
	public function testWhenAuthorizationSucceeds_bookingEventIsLogged() {
221
		$donation = ValidDonation::newIncompletePayPalDonation();
222
		$repositorySpy = new DonationRepositorySpy( $donation );
223
224
		$eventLogger = new DonationEventLoggerSpy();
225
226
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
227
		$useCase = new HandlePayPalPaymentNotificationUseCase(
228
			$repositorySpy,
229
			new SucceedingDonationAuthorizer(),
230
			$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...
231
			$eventLogger
232
		);
233
234
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
235
236
		$this->assertEventLogContainsExpression( $eventLogger, $donation->getId(), '/booked/' );
237
	}
238
239
	public function testWhenSendingConfirmationMailFails_handlerReturnsTrue() {
240
		$fakeRepository = new FakeDonationRepository();
241
		$fakeRepository->storeDonation( ValidDonation::newIncompletePayPalDonation() );
242
243
		$mailer = $this->getMailer();
244
		$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...
245
			->method( 'sendConfirmationMailFor' )
246
			->willThrowException( new \RuntimeException( 'Oh noes!' ) );
247
248
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
249
		$useCase = new HandlePayPalPaymentNotificationUseCase(
250
			$fakeRepository,
251
			new SucceedingDonationAuthorizer(),
252
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 243 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...
253
			$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...
254
		);
255
256
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
257
	}
258
259
	public function testGivenNewTransactionIdForBookedDonation_transactionIdShowsUpInChildPayments() {
260
		$donation = ValidDonation::newBookedPayPalDonation();
261
		$transactionId = '16R12136PU8783961';
262
263
		$fakeRepository = new FakeDonationRepository();
264
		$fakeRepository->storeDonation( $donation );
265
266
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
267
268
		$useCase = new HandlePayPalPaymentNotificationUseCase(
269
			$fakeRepository,
270
			new SucceedingDonationAuthorizer(),
271
			$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...
272
			$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...
273
		);
274
275
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
276
277
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
278
		$payment = $fakeRepository->getDonationById( $donation->getId() )->getPaymentMethod();
279
280
		$this->assertTrue(
281
			$payment->getPayPalData()->hasChildPayment( $transactionId ),
282
			'Parent payment must have new transaction ID in its list'
283
		);
284
	}
285
286
	public function testGivenNewTransactionIdForBookedDonation_childTransactionWithSameDataIsCreated() {
287
		$donation = ValidDonation::newBookedPayPalDonation();
288
		$transactionId = '16R12136PU8783961';
289
290
		$fakeRepository = new FakeDonationRepository();
291
		$fakeRepository->storeDonation( $donation );
292
293
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
294
295
		$useCase = new HandlePayPalPaymentNotificationUseCase(
296
			$fakeRepository,
297
			new SucceedingDonationAuthorizer(),
298
			$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...
299
			$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...
300
		);
301
302
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
303
304
		$donation = $fakeRepository->getDonationById( $donation->getId() );
305
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
306
		$payment = $donation->getPaymentMethod();
307
		$childDonation = $fakeRepository->getDonationById( $payment->getPayPalData()->getChildPaymentEntityId( $transactionId ) );
308
		$this->assertNotNull( $childDonation );
309
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $childDonationPaymentMethod */
310
		$childDonationPaymentMethod = $childDonation->getPaymentMethod();
311
		$this->assertEquals( $transactionId, $childDonationPaymentMethod->getPayPalData()->getPaymentId() );
312
		$this->assertEquals( $donation->getAmount(), $childDonation->getAmount() );
313
		$this->assertEquals( $donation->getDonor(), $childDonation->getDonor() );
314
		$this->assertEquals( $donation->getPaymentIntervalInMonths(), $childDonation->getPaymentIntervalInMonths() );
315
		$this->assertTrue( $childDonation->isBooked() );
316
	}
317
318
	public function testGivenNewTransactionIdForBookedDonation_childCreationeventIsLogged() {
319
		$donation = ValidDonation::newBookedPayPalDonation();
320
		$transactionId = '16R12136PU8783961';
321
322
		$fakeRepository = new FakeDonationRepository();
323
		$fakeRepository->storeDonation( $donation );
324
325
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
326
327
		$eventLogger = new DonationEventLoggerSpy();
328
329
		$useCase = new HandlePayPalPaymentNotificationUseCase(
330
			$fakeRepository,
331
			new SucceedingDonationAuthorizer(),
332
			$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...
333
			$eventLogger
334
		);
335
336
		$this->assertTrue( $useCase->handleNotification( $request )->notificationWasHandled() );
337
338
		$donation = $fakeRepository->getDonationById( $donation->getId() );
339
		/** @var \WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment $payment */
340
		$payment = $donation->getPaymentMethod();
341
		$childDonationId = $payment->getPayPalData()->getChildPaymentEntityId( $transactionId );
342
343
		$this->assertEventLogContainsExpression( $eventLogger, $donation->getId(), '/child donation.*' . $childDonationId .'/' );
344
		$this->assertEventLogContainsExpression( $eventLogger, $childDonationId, '/parent donation.*' . $donation->getId() .'/' );
345
	}
346
347
	public function testGivenExistingTransactionIdForBookedDonation_handlerReturnsFalse() {
348
		$fakeRepository = new FakeDonationRepository();
349
		$fakeRepository->storeDonation( ValidDonation::newBookedPayPalDonation() );
350
351
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 1 );
352
353
		$useCase = new HandlePayPalPaymentNotificationUseCase(
354
			$fakeRepository,
355
			new SucceedingDonationAuthorizer(),
356
			$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...
357
			$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...
358
		);
359
360
		$this->assertFalse( $useCase->handleNotification( $request )->notificationWasHandled() );
361
	}
362
363
	public function testGivenTransactionIdInBookedChildDonation_noNewDonationIsCreated() {
364
		$transactionId = '16R12136PU8783961';
365
		$fakeChildEntityId = 2;
366
		$donation = ValidDonation::newBookedPayPalDonation();
367
		$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...
368
369
		$fakeRepository = new FakeDonationRepository();
370
		$fakeRepository->storeDonation( $donation );
371
372
		$request = ValidPayPalNotificationRequest::newDuplicatePaymentForDonation( $donation->getId(), $transactionId );
373
374
		$useCase = new HandlePayPalPaymentNotificationUseCase(
375
			$fakeRepository,
376
			new SucceedingDonationAuthorizer(),
377
			$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...
378
			$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...
379
		);
380
381
		$this->assertFalse( $useCase->handleNotification( $request )->notificationWasHandled() );
382
	}
383
384
	public function testWhenNotificationIsForNonExistingDonation_newDonationIsCreated() {
385
		$repositorySpy = new DonationRepositorySpy();
386
387
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
388
		$useCase = new HandlePayPalPaymentNotificationUseCase(
389
			$repositorySpy,
390
			new SucceedingDonationAuthorizer(),
391
			$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...
392
			$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...
393
		);
394
395
		$useCase->handleNotification( $request );
396
397
		$storeDonationCalls = $repositorySpy->getStoreDonationCalls();
398
		$this->assertCount( 1, $storeDonationCalls, 'Donation is stored' );
399
		$this->assertNull( $storeDonationCalls[0]->getId(), 'ID is not taken from request' );
400
		$this->assertDonationIsCreatedWithNotficationRequestData( $storeDonationCalls[0] );
401
	}
402
403
	public function testGivenRecurringPaymentForIncompleteDonation_donationIsBooked() {
404
		$donation = ValidDonation::newIncompletePayPalDonation();
405
		$repositorySpy = new DonationRepositorySpy( $donation );
406
407
		$request = ValidPayPalNotificationRequest::newRecurringPayment( $donation->getId() );
408
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
			$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...
414
		);
415
416
		$useCase->handleNotification( $request );
417
		$donation = $repositorySpy->getDonationById( $donation->getId() );
418
419
		$this->assertCount( 1, $repositorySpy->getStoreDonationCalls() );
420
		$this->assertEquals( $donation, $repositorySpy->getStoreDonationCalls()[0] );
421
		$this->assertTrue( $donation->isBooked() );
422
	}
423
424
	public function testWhenNotificationIsForNonExistingDonation_confirmationMailIsSent() {
425
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
426
		$mailer = $this->getMailer();
427
		$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...
428
			->method( 'sendConfirmationMailFor' )
429
			->with( $this->anything() );
430
		$useCase = new HandlePayPalPaymentNotificationUseCase(
431
			new FakeDonationRepository(),
432
			new SucceedingDonationAuthorizer(),
433
			$mailer,
0 ignored issues
show
Bug introduced by
It seems like $mailer defined by $this->getMailer() on line 426 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...
434
			$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...
435
		);
436
437
		$useCase->handleNotification( $request );
438
	}
439
440
	public function testWhenNotificationIsForNonExistingDonation_bookingEventIsLogged() {
441
		$request = ValidPayPalNotificationRequest::newInstantPaymentForDonation( 12345 );
442
		$eventLogger = new DonationEventLoggerSpy();
443
444
		$useCase = new HandlePayPalPaymentNotificationUseCase(
445
			new FakeDonationRepository(),
446
			new SucceedingDonationAuthorizer(),
447
			$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...
448
			$eventLogger
449
		);
450
451
		$useCase->handleNotification( $request );
452
453
		$this->assertEventLogContainsExpression( $eventLogger, 1, '/booked/' ); // 1 is the generated donation id
454
	}
455
456
	private function assertDonationIsCreatedWithNotficationRequestData( Donation $donation ) {
457
		$this->assertSame( 0, $donation->getPaymentIntervalInMonths(), 'Payment interval is always empty' );
458
		$this->assertTrue( $donation->isBooked() );
459
460
		$donorName = $donation->getDonor()->getName();
461
		$this->assertSame( DonorName::PERSON_PRIVATE, $donorName->getPersonType(), 'Person is always private' );
462
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_NAME, $donorName->getFullName() );
463
464
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_EMAIL, $donation->getDonor()->getEmailAddress() );
465
466
		$address = $donation->getDonor()->getPhysicalAddress();
467
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_STREET, $address->getStreetAddress() );
468
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_CITY, $address->getCity() );
469
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_POSTAL_CODE, $address->getPostalCode() );
470
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_COUNTRY_CODE, $address->getCountryCode() );
471
472
		$payment = $donation->getPayment();
473
		$this->assertSame( ValidPayPalNotificationRequest::AMOUNT_GROSS_CENTS, $payment->getAmount()->getEuroCents() );
474
475
		/** @var PayPalData $paypalData */
476
		$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...
477
		$this->assertSame( ValidPayPalNotificationRequest::PAYER_ADDRESS_NAME, $paypalData->getAddressName() );
478
	}
479
480
	private function assertEventLogContainsExpression( DonationEventLoggerSpy $eventLoggerSpy, int $donationId, string $expr ) {
481
		$foundCalls = array_filter( $eventLoggerSpy->getLogCalls(), function( $call ) use ( $donationId, $expr ) {
482
			return $call[0] == $donationId && preg_match( $expr, $call[1] );
483
		} );
484
		$assertMsg = 'Failed to assert that donation event log log contained "' . $expr . '" for donation id '.$donationId;
485
		$this->assertCount( 1, $foundCalls, $assertMsg );
486
	}
487
488
	/**
489
	 * @return DonationConfirmationMailer|\PHPUnit_Framework_MockObject_MockObject
490
	 */
491
	private function getMailer(): DonationConfirmationMailer {
492
		return $this->getMockBuilder( DonationConfirmationMailer::class )->disableOriginalConstructor()->getMock();
493
	}
494
495
	/**
496
	 * @return DonationEventLogger|\PHPUnit_Framework_MockObject_MockObject
497
	 */
498
	private function getEventLogger(): DonationEventLogger {
499
		return $this->createMock( DonationEventLogger::class );
500
	}
501
502
}
503