Completed
Push — master ( 4e86ed...433ab3 )
by Bram
15:33
created

Reservation::getName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
/**
3
 * Reservation.php
4
 *
5
 * @author Bram de Leeuw
6
 * Date: 09/03/17
7
 */
8
9
namespace Broarm\EventTickets;
10
11
use BetterButtonCustomAction;
12
use CalendarEvent;
13
use Config;
14
use DataObject;
15
use DropdownField;
16
use Email;
17
use FieldList;
18
use Folder;
19
use GridField;
20
use GridFieldConfig_RecordViewer;
21
use HasManyList;
22
use ManyManyList;
23
use ReadonlyField;
24
use SilverStripe\Omnipay\GatewayInfo;
25
use SiteConfig;
26
use Tab;
27
use TabSet;
28
29
/**
30
 * Class Reservation
31
 *
32
 * @package Broarm\EventTickets
33
 *
34
 * @property string Status
35
 * @property string Title
36
 * @property float  Subtotal
37
 * @property float  Total
38
 * @property string Comments
39
 * @property string ReservationCode
40
 * @property string Gateway
41
 *
42
 * @property int    EventID
43
 * @property int    MainContactID
44
 *
45
 * @method CalendarEvent|TicketExtension Event()
46
 * @method Attendee MainContact()
47
 * @method HasManyList Payments()
48
 * @method HasManyList Attendees()
49
 * @method ManyManyList PriceModifiers()
50
 */
51
class Reservation extends DataObject
52
{
53
    /**
54
     * Time to wait before deleting the discarded cart
55
     * Give a string that is parsable by strtotime
56
     *
57
     * @var string
58
     */
59
    private static $delete_after = '+1 day';
1 ignored issue
show
Unused Code introduced by
The property $delete_after is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
60
61
    /**
62
     * The address to whom the ticket notifications are sent
63
     * By default the admin email is used
64
     *
65
     * @config
66
     * @var string
67
     */
68
    private static $mail_sender;
1 ignored issue
show
Unused Code introduced by
The property $mail_sender is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
69
70
    /**
71
     * The address from where the ticket mails are sent
72
     * By default the admin email is used
73
     *
74
     * @config
75
     * @var string
76
     */
77
    private static $mail_receiver;
1 ignored issue
show
Unused Code introduced by
The property $mail_receiver is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
78
79
    private static $db = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
80
        'Status' => 'Enum("CART,PENDING,PAID,CANCELED","CART")',
81
        'Title' => 'Varchar(255)',
82
        'Subtotal' => 'Currency',
83
        'Total' => 'Currency',
84
        'Gateway' => 'Varchar(255)',
85
        'Comments' => 'Text',
86
        'ReservationCode' => 'Varchar(255)'
87
    );
88
89
    private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
90
        'Event' => 'CalendarEvent',
91
        'MainContact' => 'Broarm\EventTickets\Attendee'
92
    );
93
94
    private static $has_many = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
95
        'Payments' => 'Payment',
96
        'Attendees' => 'Broarm\EventTickets\Attendee.Reservation'
97
    );
98
99
    private static $belongs_many_many = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $belongs_many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
100
        'PriceModifiers' => 'Broarm\EventTickets\PriceModifier'
101
    );
102
103
    private static $indexes = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $indexes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
104
        'ReservationCode' => 'unique("ReservationCode")'
105
    );
106
107
    private static $summary_fields = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
108
        'ReservationCode' => 'Reservation',
109
        'Title' => 'Customer',
110
        'Total.Nice' => 'Total',
111
        'State' => 'Status',
112
        'GatewayNice' => 'Payment method',
113
        'Created.Nice' => 'Date'
114
    );
115
116
    /**
117
     * Actions usable on the cms detail view
118
     *
119
     * @var array
120
     */
121
    private static $better_buttons_actions = array(
1 ignored issue
show
Unused Code introduced by
The property $better_buttons_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
122
        'send'
123
    );
124
125
    public function getCMSFields()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
126
    {
127
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
128
        $gridFieldConfig = GridFieldConfig_RecordViewer::create();
129
        $fields->addFieldsToTab('Root.Main', array(
130
            ReadonlyField::create('ReservationCode', _t('Reservation.Code', 'Code')),
131
            ReadonlyField::create('Created', _t('Reservation.Created', 'Date')),
132
            DropdownField::create('Status', _t('Reservation.Status', 'Status'), $this->getStates()),
133
            ReadonlyField::create('Title', _t('Reservation.MainContact', 'Main contact')),
134
            ReadonlyField::create('GateWayNice', _t('Reservation.Gateway', 'Gateway')),
135
            ReadonlyField::create('Total', _t('Reservation.Total', 'Total')),
136
            ReadonlyField::create('Comments', _t('Reservation.Comments', 'Comments')),
137
            GridField::create('Attendees', 'Attendees', $this->Attendees(), $gridFieldConfig),
138
            GridField::create('Payments', 'Payments', $this->Payments(), $gridFieldConfig)
139
        ));
140
        $fields->addFieldsToTab('Root.Main', array());
141
        $this->extend('updateCMSFields', $fields);
142
        return $fields;
143
    }
144
145
    /**
146
     * Add utility actions to the reservation details view
147
     *
148
     * @return FieldList
149
     */
150
    public function getBetterButtonsActions()
151
    {
152
        /** @var FieldList $fields */
153
        $fields = parent::getBetterButtonsActions();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class DataObject as the method getBetterButtonsActions() does only exist in the following sub-classes of DataObject: Broarm\EventTickets\Reservation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
154
        $fields->push(BetterButtonCustomAction::create('send', _t('Reservation.RESEND', 'Resend the reservation')));
155
156
        return $fields;
157
    }
158
159
    /**
160
     * Generate a reservation code if it does not yet exists
161
     */
162
    public function onBeforeWrite()
163
    {
164
        // Set the title to the name of the reservation holder
165
        $this->Title = $this->getName();
166
167
        // Create a validation code to be used for confirmation and in the barcode
168
        if ($this->exists() && empty($this->ReservationCode)) {
169
            $this->ReservationCode = $this->createReservationCode();
170
        }
171
172
        parent::onBeforeWrite();
173
    }
174
175
    /**
176
     * After deleting a reservation, delete the attendees and files
177
     */
178
    public function onBeforeDelete()
179
    {
180
        // If a reservation is deleted remove the names from the guest list
181
        foreach ($this->Attendees() as $attendee) {
182
            /** @var Attendee $attendee */
183
            if ($attendee->exists()) {
184
                $attendee->delete();
185
            }
186
        }
187
188
        // Remove the folder
189
        if ($this->fileFolder()->exists()) {
190
            $this->fileFolder()->delete();
191
        }
192
193
        parent::onBeforeDelete();
194
    }
195
196
    /**
197
     * Gets a nice unnamespaced name
198
     *
199
     * @return string
200
     */
201
    public function singular_name()
202
    {
203
        $name = explode('\\', parent::singular_name());
204
        return trim(end($name));
205
    }
206
207
    /**
208
     * Returns the nice gateway title
209
     *
210
     * @return string
211
     */
212
    public function getGatewayNice()
213
    {
214
        return GatewayInfo::niceTitle($this->Gateway);
215
    }
216
217
    /**
218
     * Check if the cart is still in cart state and the delete_after time period has been exceeded
219
     *
220
     * @return bool
221
     */
222
    public function isDiscarded()
223
    {
224
        $deleteAfter = strtotime(self::config()->get('delete_after'), strtotime($this->Created));
225
        return ($this->Status === 'CART') && (time() > $deleteAfter);
226
    }
227
228
    /**
229
     * Get the full name
230
     *
231
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
232
     */
233
    public function getName()
234
    {
235
        /** @var Attendee $attendee */
236
        if ($this->MainContact()->exists()) {
237
            return $this->MainContact()->getName();
238
        } else {
239
            return 'new reservation';
240
        }
241
    }
242
243
    /**
244
     * Return the translated state
245
     *
246
     * @return string
247
     */
248
    public function getState()
249
    {
250
        return _t("Reservation.{$this->Status}", $this->Status);
251
    }
252
253
    /**
254
     * Get a the translated map of available states
255
     *
256
     * @return array
257
     */
258
    private function getStates()
259
    {
260
        return array_map(function ($state) {
261
            return _t("Reservation.$state", $state);
262
        }, $this->dbObject('Status')->enumValues());
263
    }
264
265
    /**
266
     * Get the total by querying the sum of attendee ticket prices
267
     *
268
     * @return float
269
     */
270
    public function calculateTotal()
271
    {
272
        $total = $this->Subtotal = $this->Attendees()->leftJoin(
273
            'Broarm\EventTickets\Ticket',
274
            '`Broarm\EventTickets\Attendee`.`TicketID` = `Broarm\EventTickets\Ticket`.`ID`'
275
        )->sum('Price');
276
277
        // Calculate any price modifications if added
278
        if ($this->PriceModifiers()->exists()) {
279
            foreach ($this->PriceModifiers() as $priceModifier) {
280
                $priceModifier->updateTotal($total);
281
            }
282
        }
283
284
        return $this->Total = $total;
285
    }
286
287
    /**
288
     * Safely change to a state
289
     * todo check if state direction matches
290
     *
291
     * @param $state
292
     *
293
     * @return boolean
294
     */
295
    public function changeState($state)
296
    {
297
        $availableStates = $this->dbObject('Status')->enumValues();
298
        if (in_array($state, $availableStates)) {
299
            $this->Status = $state;
300
            return true;
301
        } else {
302
            user_error(_t('Reservation.STATE_CHANGE_ERROR', 'Selected state is not available'));
303
            return false;
304
        }
305
    }
306
307
    /**
308
     * Set the main contact id
309
     *
310
     * @param $id
311
     */
312
    public function setMainContact($id)
313
    {
314
        $this->MainContactID = $id;
315
        $this->write();
316
    }
317
318
    /**
319
     * Create a reservation code
320
     *
321
     * @return string
322
     */
323
    public function createReservationCode()
324
    {
325
        return uniqid($this->ID);
326
    }
327
328
    /**
329
     * Create the folder for the qr code and ticket file
330
     *
331
     * @return Folder|DataObject|null
332
     */
333
    public function fileFolder()
334
    {
335
        return Folder::find_or_make("/event-tickets/{$this->ReservationCode}/");
336
    }
337
338
    /**
339
     * Generate the qr codes and downloadable pdf
340
     */
341
    public function createFiles()
342
    {
343
        $folder = $this->fileFolder();
344
        /** @var Attendee $attendee */
345
        foreach ($this->Attendees() as $attendee) {
346
            $attendee->createQRCode($folder);
347
            $attendee->createTicketFile($folder);
348
        }
349
    }
350
351
    /**
352
     * Send the reservation mail
353
     */
354
    public function sendReservation()
355
    {
356
        // Get the mail sender or fallback to the admin email
357
        if (empty($from = self::config()->get('mail_sender'))) {
358
            $from = Config::inst()->get('Email', 'admin_email');
359
        }
360
361
        // Create the email with given template and reservation data
362
        $email = new Email();
363
        $email->setSubject(_t(
364
            'ReservationMail.TITLE',
365
            'Your order at {sitename}',
366
            null,
367
            array(
0 ignored issues
show
Documentation introduced by
array('sitename' => \Sit...t_site_config()->Title) is of type array<string,string,{"sitename":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
368
                'sitename' => SiteConfig::current_site_config()->Title
369
            )
370
        ));
371
        $email->setFrom($from);
372
        $email->setTo($this->MainContact()->Email);
373
        $email->setTemplate('ReservationMail');
374
        $email->populateTemplate($this);
375
        $this->extend('updateReservationMail', $email);
376
        $email->send();
377
    }
378
379
    /**
380
     * Send the reserved tickets
381
     */
382
    public function sendTickets()
383
    {
384
        // Get the mail sender or fallback to the admin email
385
        if (empty($from = self::config()->get('mail_sender'))) {
386
            $from = Config::inst()->get('Email', 'admin_email');
387
        }
388
389
        // Send the tickets to the main contact
390
        $email = new Email();
391
        $email->setSubject(_t(
392
            'MainContactMail.TITLE',
393
            'Uw tickets voor {event}',
394
            null,
395
            array(
0 ignored issues
show
Documentation introduced by
array('event' => $this->Event()->Title) is of type array<string,?,{"event":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
396
                'event' => $this->Event()->Title
397
            )
398
        ));
399
        $email->setFrom($from);
400
        $email->setTo($this->MainContact()->Email);
401
        $email->setTemplate('MainContactMail');
402
        $email->populateTemplate($this);
403
        $this->extend('updateMainContactMail', $email);
404
        $email->send();
405
406
407
        // Get the attendees for this event that are checked as receiver
408
        $ticketReceivers = $this->Attendees()->filter('TicketReceiver', 1)->exclude('ID', $this->MainContactID);
409
        if ($ticketReceivers->exists()) {
410
            /** @var Attendee $ticketReceiver */
411
            foreach ($ticketReceivers as $ticketReceiver) {
412
                $email = new Email();
413
                $email->setSubject(_t(
414
                    'AttendeeMail.TITLE',
415
                    'Your ticket for {event}',
416
                    null,
417
                    array(
0 ignored issues
show
Documentation introduced by
array('event' => $this->Event()->Title) is of type array<string,?,{"event":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
418
                        'event' => $this->Event()->Title
419
                    )
420
                ));
421
                $email->setFrom($from);
422
                $email->setTo($ticketReceiver->Email);
423
                $email->setTemplate('AttendeeMail');
424
                $email->populateTemplate($ticketReceiver);
425
                $this->extend('updateTicketMail', $email);
426
                $email->send();
427
            }
428
        }
429
    }
430
431
432
    /**
433
     * Send a booking notification to the ticket mail sender or the site admin
434
     */
435
    public function sendNotification()
436
    {
437
        if (empty($from = self::config()->get('mail_sender'))) {
438
            $from = Config::inst()->get('Email', 'admin_email');
439
        }
440
441
        if (empty($to = self::config()->get('mail_receiver'))) {
442
            $to = Config::inst()->get('Email', 'admin_email');
443
        }
444
445
        $email = new Email();
446
        $email->setSubject(_t(
447
            'NotificationMail.TITLE',
448
            'Nieuwe reservering voor {event}',
449
            null, array('event' => $this->Event()->Title)
0 ignored issues
show
Documentation introduced by
array('event' => $this->Event()->Title) is of type array<string,?,{"event":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
450
        ));
451
452
        $email->setFrom($from);
453
        $email->setTo($to);
454
        $email->setTemplate('NotificationMail');
455
        $email->populateTemplate($this);
456
        $this->extend('updateNotificationMail', $email);
457
        $email->send();
458
    }
459
460
    /**
461
     * Create the files and send the reservation, notification and tickets
462
     */
463
    public function send()
464
    {
465
        $this->createFiles();
466
        $this->sendReservation();
467
        $this->sendNotification();
468
        $this->sendTickets();
469
    }
470
471
    /**
472
     * Get the download link
473
     *
474
     * @return string
475
     */
476
    public function getDownloadLink()
477
    {
478
        return $this->reservation->Attendees()->first()->TicketFile()->Link();
0 ignored issues
show
Bug introduced by
The property reservation does not seem to exist. Did you mean ReservationCode?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
479
    }
480
481
    public function canView($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
482
    {
483
        return $this->Event()->canView($member);
484
    }
485
486
    public function canEdit($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
487
    {
488
        return $this->Event()->canEdit($member);
1 ignored issue
show
Bug introduced by
The method canEdit does only exist in Broarm\EventTickets\TicketExtension, but not in CalendarEvent.

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...
489
    }
490
491
    public function canDelete($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
492
    {
493
        return $this->Event()->canDelete($member);
1 ignored issue
show
Bug introduced by
The method canDelete does only exist in Broarm\EventTickets\TicketExtension, but not in CalendarEvent.

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...
494
    }
495
496
    public function canCreate($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
497
    {
498
        return $this->Event()->canCreate($member);
1 ignored issue
show
Bug introduced by
The method canCreate does only exist in Broarm\EventTickets\TicketExtension, but not in CalendarEvent.

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...
499
    }
500
}
501