Completed
Push — master ( 0e04ed...9b5d60 )
by Antony
9s
created

Order::onAfterWrite()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
ccs 0
cts 13
cp 0
rs 8.9713
cc 2
eloc 14
nc 2
nop 0
crap 6
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 23 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 96 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
    public static function get_order_status_options()
199
    {
200
        $values = array();
201
        foreach (singleton('Order')->dbObject('Status')->enumValues(false) as $value) {
202
            $values[$value] = _t('Order.STATUS_' . strtoupper($value), $value);
203
        }
204
        return $values;
205
    }
206
207
    /**
208
     * Create CMS fields for cms viewing and editing orders
209
     */
210
    public function getCMSFields()
211
    {
212
        $fields = FieldList::create(TabSet::create('Root', Tab::create('Main')));
213
        $fs = "<div class=\"field\">";
214
        $fe = "</div>";
215
        $parts = array(
216
            DropdownField::create("Status", _t('Order.db_Status', "Status"), self::get_order_status_options()),
217
            LiteralField::create('Customer', $fs . $this->renderWith("OrderAdmin_Customer") . $fe),
218
            LiteralField::create('Addresses', $fs . $this->renderWith("OrderAdmin_Addresses") . $fe),
219
            LiteralField::create('Content', $fs . $this->renderWith("OrderAdmin_Content") . $fe),
220
        );
221
        if ($this->Notes) {
222
            $parts[] = LiteralField::create('Notes', $fs . $this->renderWith("OrderAdmin_Notes") . $fe);
223
        }
224
        $fields->addFieldsToTab('Root.Main', $parts);
225
        $this->extend('updateCMSFields', $fields);
226
        if ($payments = $fields->fieldByName("Root.Payments.Payments")) {
227
            $fields->removeByName("Payments");
228
            $fields->insertAfter($payments, "Content");
229
            $payments->addExtraClass("order-payments");
230
        }
231
232
        return $fields;
233
    }
234
235
    /**
236
     * Adjust scafolded search context
237
     *
238
     * @return SearchContext the updated search context
239
     */
240
    public function getDefaultSearchContext()
241
    {
242
        $context = parent::getDefaultSearchContext();
243
        $fields = $context->getFields();
244
        $fields->push(
245
            ListboxField::create("Status", _t('Order.db_Status', "Status"))
246
                ->setSource(
247
                    array_combine(
248
                        self::config()->placed_status,
249
                        self::config()->placed_status
250
                    )
251
                )
252
                ->setMultiple(true)
253
        );
254
255
        // add date range filtering
256
        $fields->insertBefore(
257
            DateField::create("DateFrom", _t('Order.DateFrom', "Date from"))
258
                ->setConfig('showcalendar', true),
259
            '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...
260
        );
261
        $fields->insertBefore(
262
            DateField::create("DateTo", _t('Order.DateTo', "Date to"))
263
                ->setConfig('showcalendar', true),
264
            '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...
265
        );
266
267
        // get the array, to maniplulate name, and fullname seperately
268
        $filters = $context->getFilters();
269
        $filters['DateFrom'] = GreaterThanFilter::create('Placed');
270
        $filters['DateTo'] = LessThanFilter::create('Placed');
271
272
        // filter customer need to use a bunch of different sources
273
        $filters['FirstName'] = new MultiFieldPartialMatchFilter(
274
            'FirstName', false,
275
            array('SplitWords'),
276
            array(
277
                'Surname',
278
                'Member.FirstName',
279
                'Member.Surname',
280
                'BillingAddress.FirstName',
281
                'BillingAddress.Surname',
282
                'ShippingAddress.FirstName',
283
                'ShippingAddress.Surname',
284
            )
285
        );
286
287
        $context->setFilters($filters);
288
289 42
        $this->extend('updateDefaultSearchContext', $context);
290
        return $context;
291 42
    }
292 42
293 38
    /**
294 38
     * Hack for swapping out relation list with OrderItemList
295 38
     */
296 38
    public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null)
297 38
    {
298 38
        $components = parent::getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null);
299 38
        if ($componentName === "Items" && get_class($components) !== "UnsavedRelationList") {
300 38
            $query = $components->dataQuery();
301 42
            $components = OrderItemList::create("OrderItem", "OrderID");
302
            if ($this->model) {
303
                $components->setDataModel($this->model);
304
            }
305
            $components->setDataQuery($query);
306
            $components = $components->forForeignID($this->ID);
307 23
        }
308
        return $components;
309 23
    }
310 17
311
    /**
312
     * Returns the subtotal of the items for this order.
313 8
     */
314
    public function SubTotal()
315
    {
316
        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...
317
            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...
318
        }
319
320
        return 0;
321 18
    }
322
323 18
    /**
324 1
     * Calculate the total
325
     *
326 17
     * @return the final total
327 17
     */
328
    public function calculate()
329
    {
330
        if (!$this->IsCart()) {
331
            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...
332
        }
333
        $calculator = new OrderTotalCalculator($this);
334
        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...
335
    }
336
337
    /**
338
     * This is needed to maintain backwards compatiability with
339
     * some subsystems using modifiers. eg discounts
340
     */
341
    public function getModifier($className, $forcecreate = false)
342
    {
343 76
        $calculator = new OrderTotalCalculator($this);
344
        return $calculator->getModifier($className, $forcecreate);
345 76
    }
346 76
347
    /**
348
     * Enforce rounding precision when setting total
349
     */
350
    public function setTotal($val)
351
    {
352 19
        $this->setField("Total", round($val, self::$rounding_precision));
353
    }
354 19
355
    /**
356
     * Get final value of order.
357
     * Retrieves value from DataObject's record array.
358
     */
359
    public function Total()
360 17
    {
361
        return $this->getField("Total");
362 17
    }
363
364
    /**
365
     * Alias for Total.
366
     */
367
    public function GrandTotal()
368
    {
369
        return $this->Total();
370
    }
371
372
    /**
373
     * Calculate how much is left to be paid on the order.
374
     * Enforces rounding precision.
375
     *
376
     * Payments that have been authorized via a non-manual gateway should count towards the total paid amount.
377 15
     * However, it's possible to exclude these by setting the $includeAuthorized parameter to false, which is
378
     * useful to determine the status of the Order. Order status should only change to 'Paid' when all
379 15
     * payments are 'Captured'.
380 15
     *
381 15
     * @param bool $includeAuthorized whether or not to include authorized payments (excluding manual payments)
382 15
     * @return float
383
     */
384
    public function TotalOutstanding($includeAuthorized = true)
385
    {
386
        return round(
387
            $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...
388
            self::config()->rounding_precision
389
        );
390 2
    }
391
392 2
    /**
393
     * Get the order status. This will return a localized value if available.
394
     *
395
     * @return string the payment status
396
     */
397
    public function getStatusI18N()
398 3
    {
399
        return _t('Order.STATUS_' . strtoupper($this->Status), $this->Status);
400 3
    }
401 1
402
    /**
403 2
     * Get the link for finishing order processing.
404
     */
405
    public function Link()
406
    {
407
        if (Member::currentUser()) {
408
            return Controller::join_links(AccountPage::find_link(), 'order', $this->ID);
409
        }
410
        return CheckoutPage::find_link(false, "order", $this->ID);
411
    }
412 4
413
    /**
414 4
     * Returns TRUE if the order can be cancelled
415 4
     * PRECONDITION: Order is in the DB.
416 4
     *
417 1
     * @return boolean
418 1
     */
419 1
    public function canCancel()
420 1
    {
421 1
        switch ($this->Status) {
422 1
            case 'Unpaid' :
423 1
                return self::config()->cancel_before_payment;
424 1
            case 'Paid' :
425 1
                return self::config()->cancel_before_processing;
426
            case 'Processing' :
427
                return self::config()->cancel_before_sending;
428
            case 'Sent' :
429
            case 'Complete' :
430
                return self::config()->cancel_after_sending;
431
        }
432
        return false;
433 5
    }
434
435 5
    /**
436 1
     * Check if an order can be paid for.
437
     *
438 5
     * @return boolean
439 5
     */
440
    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...
441 1
    {
442
        if (!in_array($this->Status, self::config()->payable_status)) {
443
            return false;
444
        }
445
        if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) {
446
            return true;
447
        }
448 1
        return false;
449
    }
450 1
451
    /*
452
     * Prevent deleting orders.
453
     * @return boolean
454
     */
455
    public function canDelete($member = null)
456
    {
457
        return false;
458
    }
459
460
    /**
461
     * Check if an order can be viewed.
462
     *
463
     * @return boolean
464
     */
465
    public function canView($member = null)
466
    {
467
        return true;
468 1
    }
469
470 1
    /**
471
     * Check if an order can be edited.
472
     *
473
     * @return boolean
474
     */
475
    public function canEdit($member = null)
476
    {
477
        return true;
478 1
    }
479
480 1
    /**
481
     * Prevent standard creation of orders.
482
     *
483
     * @return boolean
484
     */
485
    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...
486
    {
487
        return false;
488
    }
489 5
490
    /**
491 5
     * Return the currency of this order.
492
     * Note: this is a fixed value across the entire site.
493
     *
494
     * @return string
495
     */
496
    public function Currency()
497 5
    {
498
        return ShopConfig::get_site_currency();
499 5
    }
500 3
501
    /**
502 2
     * Get the latest email for this order.z
503
     */
504
    public function getLatestEmail()
505
    {
506
        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...
507
            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...
508
        }
509
        return $this->getField('Email');
510
    }
511
512
    /**
513
     * Gets the name of the customer.
514
     */
515
    public function getName()
516
    {
517
        $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...
518
        $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...
519
        return implode(" ", array_filter(array($firstname, $surname)));
520
    }
521
522
    public function getTitle()
523 7
    {
524
        return $this->Reference . " - " . $this->dbObject('Placed')->Nice();
525 7
    }
526
527
    /**
528
     * Get shipping address, or member default shipping address.
529
     */
530
    public function getShippingAddress()
531
    {
532 6
        return $this->getAddress('Shipping');
533
    }
534 6
535 6
    /**
536
     * Get billing address, if marked to use seperate address, otherwise use shipping address,
537
     * or the member default billing address.
538
     */
539
    public function getBillingAddress()
540
    {
541
        if (!$this->SeparateBillingAddress && $this->ShippingAddressID === $this->BillingAddressID) {
542
            return $this->getShippingAddress();
543
        } else {
544
            return $this->getAddress('Billing');
545
        }
546 7
    }
547
548 7
    /**
549
     * @param string $type - Billing or Shipping
550 7
     * @return Address
551 7
     * @throws Exception
552 7
     */
553
    protected function getAddress($type)
554
    {
555
        $address = $this->getComponent($type . 'Address');
556
557
        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...
558
            $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...
559
        }
560
561
        if (empty($address->Surname) && empty($address->FirstName)) {
562
            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...
563
                // If there's a member object, use information from the Member.
564
                // The information from Order should have precendence if set though!
565
                $address->FirstName = $this->FirstName ?: $member->FirstName;
566
                $address->Surname = $this->Surname ?: $member->Surname;
567
            } else {
568
                $address->FirstName = $this->FirstName;
569
                $address->Surname = $this->Surname;
570
            }
571
        }
572
573
        return $address;
574
    }
575
576
    /**
577
     * Check if the two addresses saved differ.
578
     *
579
     * @return boolean
580
     */
581
    public function getAddressesDiffer()
582
    {
583
        return $this->SeparateBillingAddress || $this->ShippingAddressID !== $this->BillingAddressID;
584
    }
585
586
    /**
587 1
     * Has this order been sent to the customer?
588
     * (at "Sent" status).
589
     *
590
     * @return boolean
591
     */
592
    public function IsSent()
593
    {
594
        return $this->Status == 'Sent';
595
    }
596
597
    /**
598 1
     * Is this order currently being processed?
599
     * (at "Sent" OR "Processing" status).
600
     *
601
     * @return boolean
602
     */
603
    public function IsProcessing()
604
    {
605
        return $this->IsSent() || $this->Status == 'Processing';
606
    }
607
608
    /**
609
     * Return whether this Order has been paid for (Status == Paid)
610 2
     * or Status == Processing, where it's been paid for, but is
611
     * currently in a processing state.
612
     *
613
     * @return boolean
614
     */
615 93
    public function IsPaid()
616
    {
617
        return (boolean)$this->Paid || $this->Status == 'Paid';
618
    }
619
620
    public function IsCart()
621
    {
622
        return $this->Status == 'Cart';
623 76
    }
624 76
625 76
    /**
626
     * Create a unique reference identifier string for this order.
627 76
     */
628 76
    public function generateReference()
629 76
    {
630 76
        $reference = str_pad($this->ID, self::$reference_id_padding, '0', STR_PAD_LEFT);
631 76
        $this->extend('generateReference', $reference);
632 76
        $candidate = $reference;
633 76
        //prevent generating references that are the same
634
        $count = 0;
635
        while (DataObject::get_one('Order', "\"Reference\" = '$candidate'")) {
636
            $count++;
637
            $candidate = $reference . "" . $count;
638
        }
639
        $this->Reference = $candidate;
640 76
    }
641
642
    /**
643
     * Get the reference for this order, or fall back to order ID.
644
     */
645
    public function getReference()
646
    {
647
        return $this->getField('Reference') ? $this->getField('Reference') : $this->ID;
648 96
    }
649 96
650 76
    /**
651 76
     * Force creating an order reference
652
     */
653
    protected function onBeforeWrite()
654 96
    {
655 6
        parent::onBeforeWrite();
656 6
        if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) {
657 6
            $this->generateReference();
658 6
        }
659 6
660
        // perform status transition
661
        if ($this->isInDB() && $this->isChanged('Status')) {
662
            $this->statusTransition(
663 96
                empty($this->original['Status']) ? 'Cart' : $this->original['Status'],
664 96
                $this->Status
665 96
            );
666 96
        }
667
668
        // While the order is unfinished/cart, always store the current locale with the order.
669
        // We do this everytime an order is saved, because the user might change locale (language-switch).
670
        if ($this->Status == 'Cart') {
671
            $this->Locale = ShopTools::get_current_locale();
672
        }
673
    }
674
675
    /**
676 6
     * Called from @see onBeforeWrite whenever status changes
677
     * @param string $fromStatus status to transition away from
678 6
     * @param string $toStatus target status
679 2
     */
680 2
    protected function statusTransition($fromStatus, $toStatus)
681 1
    {
682 2
        // Add extension hook to react to order status transitions.
683
        $this->extend('onStatusChange', $fromStatus, $toStatus);
684 2
685
        if ($toStatus == 'Paid' && !$this->Paid) {
686 2
            $this->Paid = SS_Datetime::now()->Rfc2822();
687 2
            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...
688 2
                $item->onPayment();
689 2
            }
690 2
            //all payment is settled
691 6
            $this->extend('onPaid');
692
693
            if (!$this->ReceiptSent) {
694
                OrderEmailNotifier::create($this)->sendReceipt();
695
                $this->ReceiptSent = SS_Datetime::now()->Rfc2822();
696
            }
697
        }
698 2
    }
699 1
700 2
    /**
701
     * delete attributes, statuslogs, and payments
702 2
     */
703 1
    protected function onBeforeDelete()
704 2
    {
705
        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...
706 2
            $item->delete();
707 1
        }
708 2
709
        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...
710
            $modifier->delete();
711
        }
712 2
713
        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...
714 2
            $logEntry->delete();
715 2
        }
716
717
        // just remove the payment relations…
718
        // that way payment objects still persist (they might be relevant for book-keeping?)
719
        $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...
720
721
        parent::onBeforeDelete();
722
    }
723
724
    public function onAfterWrite()
725
    {
726
        parent::onAfterWrite();
727
728
        /**
729
         * create an OrderStatusLog
730
         */
731
        if (in_array($this->Status, self::config()->log_status)) {
732
            $log = OrderStatusLog::create();
733
734
            // populate OrderStatusLog
735
            $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...
736
                'ShopEmail.StatusChanged',
737
                'Status for order #{OrderNo} changed to "{OrderStatus}"',
738
                '',
739
                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...
740
            );
741
            $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...
742
            $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...
743
            $log->SentToCustomer = true; // triggers the sending of an email.  See OrderStatusLog onAfterWrite function.
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...
744
            $this->extend('updateOrderStatusLog', $log);
745
            $log->write();
746
        }
747
    }
748
749
    public function debug()
750
    {
751
        $val = "<div class='order'><h1>$this->class</h1>\n<ul>\n";
752
        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...
753
            foreach ($this->record as $fieldName => $fieldVal) {
754
                $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
755
            }
756
        }
757
        $val .= "</ul>\n";
758
        $val .= "<div class='items'><h2>Items</h2>";
759
        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...
760 2
            $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...
761
        }
762
        $val .= "</div><div class='modifiers'><h2>Modifiers</h2>";
763
        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...
764
            $val .= $modifiers->debug();
765
        }
766
        $val .= "</div></div>";
767
768
        return $val;
769
    }
770
771
    /**
772
     * Provide i18n entities for the order class
773
     *
774
     * @return array
775
     */
776
    public function provideI18nEntities()
777
    {
778
        $entities = parent::provideI18nEntities();
779
780
        // collect all the payment status values
781
        foreach ($this->dbObject('Status')->enumValues() as $value) {
782
            $key = strtoupper($value);
783
            $entities["Order.STATUS_$key"] = array(
784
                $value,
785
                "Translation of the order status '$value'",
786
            );
787
        }
788
789
        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...
790
    }
791
}
792