Completed
Pull Request — master (#545)
by Roman
28:04
created

Order::statusTransition()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 15
cts 15
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 13
nc 6
nop 2
crap 7
1
<?php
2
3
/**
4
 * The order class is a databound object for handling Orders
5
 * within SilverStripe.
6
 *
7
 * @property string|float Currency
8
 * @property string Reference
9
 * @property string Placed
10
 * @property string Paid
11
 * @property string ReceiptSent
12
 * @property string Printed
13
 * @property string Dispatched
14
 * @property string Status
15
 * @property string FirstName
16
 * @property string Surname
17
 * @property string Email
18
 * @property string Notes
19
 * @property string IPAddress
20
 * @property string|bool SeparateBillingAddress
21
 * @property string Locale
22
 * @property string|int MemberID
23
 * @property string|int ShippingAddressID
24
 * @property string|int BillingAddressID
25
 * @method Member|ShopMember Member
26
 * @method Address BillingAddress
27
 * @method Address ShippingAddress
28
 * @method OrderItem[]|HasManyList Items
29
 * @method OrderModifier[]|HasManyList Modifiers
30
 * @method OrderStatusLog[]|HasManyList OrderStatusLogs
31
 *
32
 * @package shop
33
 */
34
class Order extends DataObject
0 ignored issues
show
Complexity introduced by
This class has 24 fields which exceeds the configured maximum of 15.

Too many fields generally indicate a class which does too much and does not follow the single responsibility principle.

We suggest taking a look at the “Code” section for further suggestions on how to fix this.

Loading history...
Complexity introduced by
This class has a complexity of 98 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
35
{
36
    /**
37
     * Status codes and what they mean:
38
     *
39
     * Unpaid (default): Order created but no successful payment by customer yet
40
     * Query: Order not being processed yet (customer has a query, or could be out of stock)
41
     * Paid: Order successfully paid for by customer
42
     * Processing: Order paid for, package is currently being processed before shipping to customer
43
     * Sent: Order paid for, processed for shipping, and now sent to the customer
44
     * Complete: Order completed (paid and shipped). Customer assumed to have received their goods
45
     * AdminCancelled: Order cancelled by the administrator
46
     * MemberCancelled: Order cancelled by the customer (Member)
47
     */
48
    private static $db                = 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...
49
        'Total'                  => 'Currency',
50
        'Reference'              => 'Varchar', //allow for customised order numbering schemes
51
        //status
52
        'Placed'                 => "SS_Datetime", //date the order was placed (went from Cart to Order)
53
        'Paid'                   => 'SS_Datetime', //no outstanding payment left
54
        'ReceiptSent'            => 'SS_Datetime', //receipt emailed to customer
55
        'Printed'                => 'SS_Datetime',
56
        'Dispatched'             => 'SS_Datetime', //products have been sent to customer
57
        'Status'                 => "Enum('Unpaid,Paid,Processing,Sent,Complete,AdminCancelled,MemberCancelled,Cart','Cart')",
58
        //customer (for guest orders)
59
        'FirstName'              => 'Varchar',
60
        'Surname'                => 'Varchar',
61
        'Email'                  => 'Varchar',
62
        'Notes'                  => 'Text',
63
        'IPAddress'              => 'Varchar(15)',
64
        //separate shipping
65
        'SeparateBillingAddress' => 'Boolean',
66
        // keep track of customer locale
67
        'Locale'                 => 'DBLocale',
68
    );
69
70
    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...
71
        'Member'          => 'Member',
72
        'ShippingAddress' => 'Address',
73
        'BillingAddress'  => 'Address',
74
    );
75
76
    private static $has_many          = 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...
77
        'Items'           => 'OrderItem',
78
        'Modifiers'       => 'OrderModifier',
79
        'OrderStatusLogs' => 'OrderStatusLog',
80
    );
81
82
    private static $defaults          = 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...
83
        'Status' => 'Cart',
84
    );
85
86
    private static $casting           = 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...
87
        'FullBillingAddress'  => 'Text',
88
        'FullShippingAddress' => 'Text',
89
        'Total'               => 'Currency',
90
        'SubTotal'            => 'Currency',
91
        'TotalPaid'           => 'Currency',
92
        'Shipping'            => 'Currency',
93
        'TotalOutstanding'    => 'Currency',
94
    );
95
96
    private static $summary_fields    = 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...
97
        'Reference'   => 'Order No',
98
        'Placed'      => 'Date',
99
        'Name'        => 'Customer',
100
        'LatestEmail' => 'Email',
101
        'Total'       => 'Total',
102
        'Status'      => 'Status',
103
    );
104
105
    private static $searchable_fields = 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...
106
        'Reference' => array(),
107
        'FirstName' => array(
108
            'title' => 'Customer Name',
109
        ),
110
        'Email'     => array(
111
            'title' => 'Customer Email',
112
        ),
113
        'Status'    => array(
114
            'filter' => 'ExactMatchFilter',
115
            'field'  => 'CheckboxSetField',
116
        ),
117
    );
118
119
    private static $singular_name     = "Order";
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...
120
121
    private static $plural_name       = "Orders";
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...
122
123
    private static $default_sort      = "\"Placed\" DESC, \"Created\" DESC";
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...
124
125
    /**
126
     * Statuses for orders that have been placed.
127
     */
128
    private static $placed_status = array(
129
        'Paid',
130
        'Unpaid',
131
        'Processing',
132
        'Sent',
133
        'Complete',
134
        'MemberCancelled',
135
        'AdminCancelled',
136
    );
137
138
    /**
139
     * Statuses for which an order can be paid for
140
     */
141
    private static $payable_status = array(
142
        'Cart',
143
        'Unpaid',
144
        'Processing',
145
        'Sent',
146
    );
147
148
    /**
149
     * Statuses that shouldn't show in user account.
150
     */
151
    private static $hidden_status = array('Cart');
152
153
154
    /**
155
     * Status for logging changes
156
     * @var array
157
     */
158
    private static $log_status = array();
159
160
    /**
161
     * Flags to determine when an order can be cancelled.
162
     */
163
    private static $cancel_before_payment    = true;
164
165
    private static $cancel_before_processing = false;
166
167
    private static $cancel_before_sending    = false;
168
169
    private static $cancel_after_sending     = false;
170
171
    /**
172
     * Place an order before payment processing begins
173
     *
174
     * @var boolean
175
     */
176
    private static $place_before_payment = false;
177
178
    /**
179
     * Modifiers represent the additional charges or
180
     * deductions associated to an order, such as
181
     * shipping, taxes, vouchers etc.
182
     */
183
    private static $modifiers            = array();
184
185
    private static $rounding_precision   = 2;
186
187
    private static $reference_id_padding = 5;
188
189
    /**
190
     * @var boolean Will allow completion of orders with GrandTotal=0,
191
     * which could be the case for orders paid with loyalty points or vouchers.
192
     * Will send the "Paid" date on the order, even though no actual payment was taken.
193
     * Will trigger the payment related extension points:
194
     * Order->onPayment, OrderItem->onPayment, Order->onPaid.
195
     */
196
    private static $allow_zero_order_total = false;
197
198
    /**
199
     * A flag indicating that an order-status-log entry should be written
200
     * @var bool
201
     */
202
    protected $flagOrderStatusWrite = false;
203
204
    public static function get_order_status_options()
205
    {
206
        $values = array();
207
        foreach (singleton('Order')->dbObject('Status')->enumValues(false) as $value) {
208
            $values[$value] = _t('Order.STATUS_' . strtoupper($value), $value);
209
        }
210
        return $values;
211
    }
212
213
    /**
214
     * Create CMS fields for cms viewing and editing orders
215
     */
216
    public function getCMSFields()
217
    {
218
        $fields = FieldList::create(TabSet::create('Root', Tab::create('Main')));
219
        $fs = "<div class=\"field\">";
220
        $fe = "</div>";
221
        $parts = array(
222
            DropdownField::create("Status", _t('Order.db_Status', "Status"), self::get_order_status_options()),
223
            LiteralField::create('Customer', $fs . $this->renderWith("OrderAdmin_Customer") . $fe),
224
            LiteralField::create('Addresses', $fs . $this->renderWith("OrderAdmin_Addresses") . $fe),
225
            LiteralField::create('Content', $fs . $this->renderWith("OrderAdmin_Content") . $fe),
226
        );
227
        if ($this->Notes) {
228
            $parts[] = LiteralField::create('Notes', $fs . $this->renderWith("OrderAdmin_Notes") . $fe);
229
        }
230
        $fields->addFieldsToTab('Root.Main', $parts);
231
        $this->extend('updateCMSFields', $fields);
232
        if ($payments = $fields->fieldByName("Root.Payments.Payments")) {
233
            $fields->removeByName("Payments");
234
            $fields->insertAfter($payments, "Content");
235
            $payments->addExtraClass("order-payments");
236
        }
237
238
        return $fields;
239
    }
240
241
    /**
242
     * Adjust scafolded search context
243
     *
244
     * @return SearchContext the updated search context
245
     */
246
    public function getDefaultSearchContext()
247
    {
248
        $context = parent::getDefaultSearchContext();
249
        $fields = $context->getFields();
250
        $fields->push(
251
            ListboxField::create("Status", _t('Order.db_Status', "Status"))
252
                ->setSource(
253
                    array_combine(
254
                        self::config()->placed_status,
255
                        self::config()->placed_status
256
                    )
257
                )
258
                ->setMultiple(true)
259
        );
260
261
        // add date range filtering
262
        $fields->insertBefore(
263
            DateField::create("DateFrom", _t('Order.DateFrom', "Date from"))
264
                ->setConfig('showcalendar', true),
265
            'Status'
0 ignored issues
show
Documentation introduced by
'Status' is of type string, but the function expects a object<FormField>.

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...
266
        );
267
        $fields->insertBefore(
268
            DateField::create("DateTo", _t('Order.DateTo', "Date to"))
269
                ->setConfig('showcalendar', true),
270
            'Status'
0 ignored issues
show
Documentation introduced by
'Status' is of type string, but the function expects a object<FormField>.

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...
271
        );
272
273
        // get the array, to maniplulate name, and fullname seperately
274
        $filters = $context->getFilters();
275
        $filters['DateFrom'] = GreaterThanFilter::create('Placed');
276
        $filters['DateTo'] = LessThanFilter::create('Placed');
277
278
        // filter customer need to use a bunch of different sources
279
        $filters['FirstName'] = new MultiFieldPartialMatchFilter(
280
            'FirstName', false,
281
            array('SplitWords'),
282
            array(
283
                'Surname',
284
                'Member.FirstName',
285
                'Member.Surname',
286
                'BillingAddress.FirstName',
287
                'BillingAddress.Surname',
288
                'ShippingAddress.FirstName',
289
                'ShippingAddress.Surname',
290
            )
291
        );
292
293
        $context->setFilters($filters);
294
295
        $this->extend('updateDefaultSearchContext', $context);
296 43
        return $context;
297
    }
298 43
299 43
    /**
300 39
     * Hack for swapping out relation list with OrderItemList
301 39
     */
302 39
    public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null)
303 39
    {
304 39
        $components = parent::getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null);
305 39
        if ($componentName === "Items" && get_class($components) !== "UnsavedRelationList") {
306 39
            $query = $components->dataQuery();
307 39
            $components = OrderItemList::create("OrderItem", "OrderID");
308 43
            if ($this->model) {
309
                $components->setDataModel($this->model);
310
            }
311
            $components->setDataQuery($query);
312
            $components = $components->forForeignID($this->ID);
313
        }
314 25
        return $components;
315
    }
316 25
317 19
    /**
318
     * Returns the subtotal of the items for this order.
319
     */
320 8
    public function SubTotal()
321
    {
322
        if ($this->Items()->exists()) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
323
            return $this->Items()->SubTotal();
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
324
        }
325
326
        return 0;
327
    }
328 18
329
    /**
330 18
     * Calculate the total
331 1
     *
332
     * @return the final total
333 17
     */
334 17
    public function calculate()
335
    {
336
        if (!$this->IsCart()) {
337
            return $this->Total;
0 ignored issues
show
Documentation introduced by
The property Total does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
338
        }
339
        $calculator = new OrderTotalCalculator($this);
340
        return $this->Total = $calculator->calculate();
0 ignored issues
show
Documentation introduced by
The property Total does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
341
    }
342
343
    /**
344
     * This is needed to maintain backwards compatiability with
345
     * some subsystems using modifiers. eg discounts
346
     */
347
    public function getModifier($className, $forcecreate = false)
348
    {
349
        $calculator = new OrderTotalCalculator($this);
350 76
        return $calculator->getModifier($className, $forcecreate);
351
    }
352 76
353 76
    /**
354
     * Enforce rounding precision when setting total
355
     */
356
    public function setTotal($val)
357
    {
358
        $this->setField("Total", round($val, self::$rounding_precision));
359 21
    }
360
361 21
    /**
362
     * Get final value of order.
363
     * Retrieves value from DataObject's record array.
364
     */
365
    public function Total()
366
    {
367 19
        return $this->getField("Total");
368
    }
369 19
370
    /**
371
     * Alias for Total.
372
     */
373
    public function GrandTotal()
374
    {
375
        return $this->Total();
376
    }
377
378
    /**
379
     * Calculate how much is left to be paid on the order.
380
     * Enforces rounding precision.
381
     *
382
     * Payments that have been authorized via a non-manual gateway should count towards the total paid amount.
383
     * However, it's possible to exclude these by setting the $includeAuthorized parameter to false, which is
384 17
     * useful to determine the status of the Order. Order status should only change to 'Paid' when all
385
     * payments are 'Captured'.
386 17
     *
387 17
     * @param bool $includeAuthorized whether or not to include authorized payments (excluding manual payments)
388 17
     * @return float
389 17
     */
390
    public function TotalOutstanding($includeAuthorized = true)
391
    {
392
        return round(
393
            $this->GrandTotal() - ($includeAuthorized ? $this->TotalPaidOrAuthorized() : $this->TotalPaid()),
0 ignored issues
show
Documentation Bug introduced by
The method TotalPaidOrAuthorized does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method TotalPaid does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
394
            self::config()->rounding_precision
395
        );
396
    }
397 4
398
    /**
399 4
     * Get the order status. This will return a localized value if available.
400
     *
401
     * @return string the payment status
402
     */
403
    public function getStatusI18N()
404
    {
405 3
        return _t('Order.STATUS_' . strtoupper($this->Status), $this->Status);
406
    }
407 3
408 1
    /**
409
     * Get the link for finishing order processing.
410 2
     */
411
    public function Link()
412
    {
413
        if (Member::currentUser()) {
414
            return Controller::join_links(AccountPage::find_link(), 'order', $this->ID);
415
        }
416
        return CheckoutPage::find_link(false, "order", $this->ID);
417
    }
418
419 4
    /**
420
     * Returns TRUE if the order can be cancelled
421 4
     * PRECONDITION: Order is in the DB.
422 4
     *
423 4
     * @return boolean
424 1
     */
425 1
    public function canCancel()
426 1
    {
427 1
        switch ($this->Status) {
428 1
            case 'Unpaid' :
429 1
                return self::config()->cancel_before_payment;
430 1
            case 'Paid' :
431 1
                return self::config()->cancel_before_processing;
432 1
            case 'Processing' :
433
                return self::config()->cancel_before_sending;
434
            case 'Sent' :
435
            case 'Complete' :
436
                return self::config()->cancel_after_sending;
437
        }
438
        return false;
439
    }
440 7
441
    /**
442 7
     * Check if an order can be paid for.
443 1
     *
444
     * @return boolean
445 7
     */
446 5
    public function canPay($member = null)
0 ignored issues
show
Unused Code introduced by
The parameter $member is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
447
    {
448 3
        if (!in_array($this->Status, self::config()->payable_status)) {
449
            return false;
450
        }
451
        if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) {
452
            return true;
453
        }
454
        return false;
455 1
    }
456
457 1
    /*
458
     * Prevent deleting orders.
459
     * @return boolean
460
     */
461
    public function canDelete($member = null)
462
    {
463
        return false;
464
    }
465
466
    /**
467
     * Check if an order can be viewed.
468
     *
469
     * @return boolean
470
     */
471
    public function canView($member = null)
472
    {
473
        return true;
474
    }
475 1
476
    /**
477 1
     * Check if an order can be edited.
478
     *
479
     * @return boolean
480
     */
481
    public function canEdit($member = null)
482
    {
483
        return true;
484
    }
485 1
486
    /**
487 1
     * Prevent standard creation of orders.
488
     *
489
     * @return boolean
490
     */
491
    public function canCreate($member = null, $context = array())
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
492
    {
493
        return false;
494
    }
495
496 7
    /**
497
     * Return the currency of this order.
498 7
     * Note: this is a fixed value across the entire site.
499
     *
500
     * @return string
501
     */
502
    public function Currency()
503
    {
504 8
        return ShopConfig::get_site_currency();
505
    }
506 8
507 5
    /**
508
     * Get the latest email for this order.z
509 3
     */
510
    public function getLatestEmail()
511
    {
512
        if ($this->MemberID && ($this->Member()->LastEdited > $this->LastEdited || !$this->Email)) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
513
            return $this->Member()->Email;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
514
        }
515
        return $this->getField('Email');
516
    }
517
518
    /**
519
     * Gets the name of the customer.
520
     */
521
    public function getName()
522
    {
523
        $firstname = $this->FirstName ? $this->FirstName : $this->Member()->FirstName;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
524
        $surname = $this->FirstName ? $this->Surname : $this->Member()->Surname;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
525
        return implode(" ", array_filter(array($firstname, $surname)));
526
    }
527
528
    public function getTitle()
529
    {
530 9
        return $this->Reference . " - " . $this->dbObject('Placed')->Nice();
531
    }
532 9
533
    /**
534
     * Get shipping address, or member default shipping address.
535
     */
536
    public function getShippingAddress()
537
    {
538
        return $this->getAddress('Shipping');
539 8
    }
540
541 8
    /**
542 8
     * Get billing address, if marked to use seperate address, otherwise use shipping address,
543
     * or the member default billing address.
544
     */
545
    public function getBillingAddress()
546
    {
547
        if (!$this->SeparateBillingAddress && $this->ShippingAddressID === $this->BillingAddressID) {
548
            return $this->getShippingAddress();
549
        } else {
550
            return $this->getAddress('Billing');
551
        }
552
    }
553 9
554
    /**
555 9
     * @param string $type - Billing or Shipping
556
     * @return Address
557 9
     * @throws Exception
558 7
     */
559 7
    protected function getAddress($type)
560
    {
561
        $address = $this->getComponent($type . 'Address');
562
563
        if (!$address || !$address->exists() && $this->Member()) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
564
            $address = $this->Member()->{"Default${type}Address"}();
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
565
        }
566
567
        if (empty($address->Surname) && empty($address->FirstName)) {
568
            if ($member = $this->Member()) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
569
                // If there's a member object, use information from the Member.
570
                // The information from Order should have precendence if set though!
571
                $address->FirstName = $this->FirstName ?: $member->FirstName;
572
                $address->Surname = $this->Surname ?: $member->Surname;
573
            } else {
574
                $address->FirstName = $this->FirstName;
575
                $address->Surname = $this->Surname;
576
            }
577
        }
578
579
        return $address;
580
    }
581
582
    /**
583
     * Check if the two addresses saved differ.
584
     *
585
     * @return boolean
586
     */
587
    public function getAddressesDiffer()
588
    {
589
        return $this->SeparateBillingAddress || $this->ShippingAddressID !== $this->BillingAddressID;
590
    }
591
592
    /**
593
     * Has this order been sent to the customer?
594 1
     * (at "Sent" status).
595
     *
596
     * @return boolean
597
     */
598
    public function IsSent()
599
    {
600
        return $this->Status == 'Sent';
601
    }
602
603
    /**
604
     * Is this order currently being processed?
605 1
     * (at "Sent" OR "Processing" status).
606
     *
607
     * @return boolean
608
     */
609
    public function IsProcessing()
610
    {
611
        return $this->IsSent() || $this->Status == 'Processing';
612
    }
613
614
    /**
615
     * Return whether this Order has been paid for (Status == Paid)
616
     * or Status == Processing, where it's been paid for, but is
617 2
     * currently in a processing state.
618
     *
619
     * @return boolean
620
     */
621
    public function IsPaid()
622 95
    {
623
        return (boolean)$this->Paid || $this->Status == 'Paid';
624
    }
625
626
    public function IsCart()
627
    {
628
        return $this->Status == 'Cart';
629
    }
630 78
631 78
    /**
632 78
     * Create a unique reference identifier string for this order.
633
     */
634 78
    public function generateReference()
635 78
    {
636 76
        $reference = str_pad($this->ID, self::$reference_id_padding, '0', STR_PAD_LEFT);
637 76
        $this->extend('generateReference', $reference);
638 76
        $candidate = $reference;
639 78
        //prevent generating references that are the same
640 78
        $count = 0;
641
        while (DataObject::get_one('Order', "\"Reference\" = '$candidate'")) {
642
            $count++;
643
            $candidate = $reference . "" . $count;
644
        }
645
        $this->Reference = $candidate;
646
    }
647 78
648
    /**
649
     * Get the reference for this order, or fall back to order ID.
650
     */
651
    public function getReference()
652
    {
653
        return $this->getField('Reference') ? $this->getField('Reference') : $this->ID;
654
    }
655 98
656 98
    /**
657 78
     * Force creating an order reference
658 78
     */
659
    protected function onBeforeWrite()
660
    {
661 98
        parent::onBeforeWrite();
662 8
        if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) {
663 8
            $this->generateReference();
664 8
        }
665 8
666 8
        // perform status transition
667
        if ($this->isInDB() && $this->isChanged('Status')) {
668
            $this->statusTransition(
669
                empty($this->original['Status']) ? 'Cart' : $this->original['Status'],
670 98
                $this->Status
671 98
            );
672 98
        }
673 98
674
        // While the order is unfinished/cart, always store the current locale with the order.
675
        // We do this everytime an order is saved, because the user might change locale (language-switch).
676
        if ($this->Status == 'Cart') {
677
            $this->Locale = ShopTools::get_current_locale();
678
        }
679
    }
680
681
    /**
682
     * Called from @see onBeforeWrite whenever status changes
683 8
     * @param string $fromStatus status to transition away from
684
     * @param string $toStatus target status
685 8
     */
686 4
    protected function statusTransition($fromStatus, $toStatus)
687 4
    {
688 3
        // Add extension hook to react to order status transitions.
689 4
        $this->extend('onStatusChange', $fromStatus, $toStatus);
690
691 4
        if ($toStatus == 'Paid' && !$this->Paid) {
692
            $this->Paid = SS_Datetime::now()->Rfc2822();
693 4
            foreach ($this->Items() as $item) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
694 4
                $item->onPayment();
695 4
            }
696 4
            //all payment is settled
697 4
            $this->extend('onPaid');
698 8
699
            if (!$this->ReceiptSent) {
700
                OrderEmailNotifier::create($this)->sendReceipt();
701
                $this->ReceiptSent = SS_Datetime::now()->Rfc2822();
702
            }
703
        }
704
705 2
        $logStatus = $this->config()->log_status;
706 1
        if (!empty($logStatus) && in_array($toStatus, $logStatus)) {
707 2
            $this->flagOrderStatusWrite = $fromStatus != $toStatus;
708
        }
709 2
    }
710 1
711 2
    /**
712
     * delete attributes, statuslogs, and payments
713 2
     */
714 1
    protected function onBeforeDelete()
715 2
    {
716
        foreach ($this->Items() as $item) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
717
            $item->delete();
718
        }
719 2
720
        foreach ($this->Modifiers() as $modifier) {
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
721 2
            $modifier->delete();
722 2
        }
723
724
        foreach ($this->OrderStatusLogs() as $logEntry) {
0 ignored issues
show
Documentation Bug introduced by
The method OrderStatusLogs does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
725
            $logEntry->delete();
726 98
        }
727
728
        // just remove the payment relations…
729
        // that way payment objects still persist (they might be relevant for book-keeping?)
730
        $this->Payments()->removeAll();
0 ignored issues
show
Documentation Bug introduced by
The method Payments does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
731 98
732 2
        parent::onBeforeDelete();
733
    }
734
735 2
    public function onAfterWrite()
736 2
    {
737 2
        parent::onAfterWrite();
738 2
739 2
        //create an OrderStatusLog
740 2
        if ($this->flagOrderStatusWrite) {
741 2
            $this->flagOrderStatusWrite = false;
742 2
            $log = OrderStatusLog::create();
743 2
744 2
            // populate OrderStatusLog
745 2
            $log->Title = _t(
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
746 2
                'ShopEmail.StatusChanged',
747 98
                'Status for order #{OrderNo} changed to "{OrderStatus}"',
748
                '',
749
                array('OrderNo' => $this->Reference, 'OrderStatus' => $this->getStatusI18N())
0 ignored issues
show
Documentation introduced by
array('OrderNo' => $this...$this->getStatusI18N()) is of type array<string,string,{"Or...OrderStatus":"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...
750
            );
751
            $log->Note = _t('ShopEmail.StatusChange' . $this->Status . 'Note');
0 ignored issues
show
Documentation introduced by
The property Note does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
752
            $log->OrderID = $this->ID;
0 ignored issues
show
Documentation introduced by
The property OrderID does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
753
            OrderEmailNotifier::create($this)->sendStatusChange($log->Title, $log->Note);
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<OrderStatusLog>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property Note does not exist on object<OrderStatusLog>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
754
            $log->SentToCustomer = true;
0 ignored issues
show
Documentation introduced by
The property SentToCustomer does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
755
            $this->extend('updateOrderStatusLog', $log);
756
            $log->write();
757
        }
758
    }
759
760
    public function debug()
761
    {
762
        $val = "<div class='order'><h1>$this->class</h1>\n<ul>\n";
763
        if ($this->record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
764
            foreach ($this->record as $fieldName => $fieldVal) {
765
                $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
766
            }
767
        }
768
        $val .= "</ul>\n";
769
        $val .= "<div class='items'><h2>Items</h2>";
770
        if ($items = $this->Items()) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Unused Code introduced by
$items is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
771
            $val .= $this->Items()->debug();
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
772
        }
773
        $val .= "</div><div class='modifiers'><h2>Modifiers</h2>";
774
        if ($modifiers = $this->Modifiers()) {
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
775
            $val .= $modifiers->debug();
776
        }
777
        $val .= "</div></div>";
778
779
        return $val;
780
    }
781
782
    /**
783
     * Provide i18n entities for the order class
784
     *
785
     * @return array
786
     */
787
    public function provideI18nEntities()
788
    {
789
        $entities = parent::provideI18nEntities();
790
791
        // collect all the payment status values
792 2
        foreach ($this->dbObject('Status')->enumValues() as $value) {
793
            $key = strtoupper($value);
794
            $entities["Order.STATUS_$key"] = array(
795
                $value,
796
                "Translation of the order status '$value'",
797
            );
798
        }
799
800
        return $entities;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $entities; (array<*,array>) is incompatible with the return type of the parent method DataObject::provideI18nEntities of type array<*,array<array|inte...double|string|boolean>>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
801
    }
802
}
803