Passed
Push — master ( 6e710f...a80202 )
by Andrii
12:46
created

BillingContext::billWithTime()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 17
c 3
b 0
f 0
nc 2
nop 8
dl 0
loc 20
rs 9.7

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHP Billing Library
4
 *
5
 * @link      https://github.com/hiqdev/php-billing
6
 * @package   php-billing
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017-2020, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\tests\behat\bootstrap;
12
13
use Behat\Gherkin\Node\TableNode;
14
use BehatExpectException\ExpectException;
15
use DateTimeImmutable;
16
use hiqdev\php\billing\bill\BillInterface;
17
use hiqdev\php\billing\charge\ChargeInterface;
18
use PHPUnit\Framework\Assert;
19
20
class BillingContext extends BaseContext
21
{
22
    use ExpectException;
23
24
    protected $saleTime;
25
26
    protected $bill;
27
28
    protected $charges = [];
29
30
    /**
31
     * @Given reseller :reseller
32
     */
33
    public function reseller($reseller)
34
    {
35
        $this->builder->buildReseller($reseller);
36
    }
37
38
    /**
39
     * @Given customer :customer
40
     */
41
    public function customer($customer)
42
    {
43
        $this->builder->buildCustomer($customer);
44
    }
45
46
    /**
47
     * @Given manager :manager
48
     */
49
    public function manager($manager)
50
    {
51
        $this->builder->buildManager($manager);
0 ignored issues
show
Bug introduced by
The method buildManager() does not exist on hiqdev\php\billing\tests...tstrap\BuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

51
        $this->builder->/** @scrutinizer ignore-call */ 
52
                        buildManager($manager);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
52
    }
53
54
    /**
55
     * @Given /^(\S+ )?(\S+) tariff plan (\S+)/
56
     */
57
    public function plan($prefix, $type, $plan)
58
    {
59
        $prefix = strtr($prefix, ' ', '_');
60
        $grouping = $prefix === 'grouping_';
61
        $type = $grouping ? $type : $prefix.$type;
62
        $this->builder->buildPlan($plan, $type, $grouping);
63
    }
64
65
    protected function fullPrice(array $data)
66
    {
67
        if (!empty($data['price'])) {
68
            $data['rate'] = $data['price'];
69
        }
70
        $this->builder->buildPrice($data);
71
    }
72
73
    /**
74
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) for target (.+)$/
75
     */
76
    public function priceWithTarget($type, $price, $currency, $unit, $target)
77
    {
78
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...cy', 'unit', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
79
    }
80
81
    /**
82
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+)$/
83
     */
84
    public function priceWithPrepaid($type, $price, $currency, $unit, $prepaid)
85
    {
86
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'prepaid'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...y', 'unit', 'prepaid')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
87
    }
88
89
    /**
90
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+) prepaid (\S+) for target (\S+)$/
91
     */
92
    public function priceWithPrepaidAndTarget($type, $price, $currency, $unit, $prepaid, $target)
93
    {
94
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit', 'prepaid', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact..., 'prepaid', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
95
    }
96
97
    /**
98
     * @Given /price for (\S+) is +(\S+) (\S+) per (\S+)$/
99
     */
100
    public function price($type, $price, $currency, $unit)
101
    {
102
        return $this->fullPrice(compact('type', 'price', 'currency', 'unit'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...', 'currency', 'unit')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
103
    }
104
105
    /**
106
     * @Given /price for (\S+) is +(\S+) (\S+) per 1 (\S+) and (\S+) (\S+) per 2 (\S+) for target (\S+)/
107
     */
108
    public function enumPrice($type, $price, $currency, $unit, $price2, $currency2, $unit2, $target)
0 ignored issues
show
Unused Code introduced by
The parameter $currency2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
    public function enumPrice($type, $price, $currency, $unit, $price2, /** @scrutinizer ignore-unused */ $currency2, $unit2, $target)

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

Loading history...
Unused Code introduced by
The parameter $unit2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
    public function enumPrice($type, $price, $currency, $unit, $price2, $currency2, /** @scrutinizer ignore-unused */ $unit2, $target)

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

Loading history...
109
    {
110
        $sums = [1 => $price, 2 => $price2];
111
112
        return $this->fullPrice(compact('type', 'sums', 'currency', 'unit', 'target'));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fullPrice(compact...cy', 'unit', 'target')) targeting hiqdev\php\billing\tests...ingContext::fullPrice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
113
    }
114
115
    /**
116
     * @Given /^remove and recreate tariff plan (\S+)/
117
     */
118
    public function recreatePlan($plan)
119
    {
120
        $this->builder->recreatePlan($plan);
121
    }
122
123
    /**
124
     * @Given /sale target (\S+) by plan (\S+) at (\S+)/
125
     */
126
    public function sale($target, $plan, $time): void
127
    {
128
        $this->saleTime = $this->prepareTime($time);
129
        $this->builder->buildSale($target, $plan, $this->saleTime);
0 ignored issues
show
Bug introduced by
It seems like $this->saleTime can also be of type null; however, parameter $time of hiqdev\php\billing\tests...rInterface::buildSale() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

129
        $this->builder->buildSale($target, $plan, /** @scrutinizer ignore-type */ $this->saleTime);
Loading history...
130
    }
131
132
    /**
133
     * @Given /purchase target (\S+) by plan (\S+) at (\S+)$/
134
     */
135
    public function purchaseTarget(string $target, string $plan, string $time): void
136
    {
137
        $time = $this->prepareTime($time);
138
        $this->builder->buildPurchase($target, $plan, $time);
139
    }
140
141
    /**
142
     * @Given /^purchase target "([^"]*)" by plan "([^"]*)" at "([^"]*)" with the following initial uses:$/
143
     */
144
    public function purchaseTargetWithInitialUses(string $target, string $plan, string $time, TableNode $usesTable): void
145
    {
146
        $time = $this->prepareTime($time);
147
        $uses = array_map(static function (array $row) {
148
            return [
149
                'type' => $row['type'],
150
                'unit' => $row['unit'],
151
                'amount' => $row['amount'],
152
            ];
153
        }, $usesTable->getColumnsHash());
154
155
        $this->mayFail(
156
            fn() => $this->builder->buildPurchase($target, $plan, $time, $uses)
157
        );
158
    }
159
160
    /**
161
     * @Given /resource consumption for (\S+) is +(\S+) (\S+) for target (\S+) at (.+)$/
162
     */
163
    public function setConsumption(string $type, int $amount, string $unit, string $target, string $time): void
164
    {
165
        $time = $this->prepareTime($time);
166
        $this->builder->setConsumption($type, $amount, $unit, $target, $time);
0 ignored issues
show
Bug introduced by
The method setConsumption() does not exist on hiqdev\php\billing\tests...tstrap\BuilderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to hiqdev\php\billing\tests...tstrap\BuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
        $this->builder->/** @scrutinizer ignore-call */ 
167
                        setConsumption($type, $amount, $unit, $target, $time);
Loading history...
167
    }
168
169
    /**
170
     * @Given /perform billing at (\S+)/
171
     */
172
    public function performBilling(string $time): void
173
    {
174
        $this->builder->performBilling($this->prepareTime($time));
175
    }
176
177
    /**
178
     * @Given /action for (\S+) is +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
179
     */
180
    public function setAction(string $type, int $amount, string $unit, string $target, string $at = null, string $time = null): void
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

180
    public function setAction(string $type, int $amount, string $unit, string $target, /** @scrutinizer ignore-unused */ string $at = null, string $time = null): void

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

Loading history...
181
    {
182
        $time = $this->prepareTime($time);
183
        $this->builder->setAction($type, $amount, $unit, $target, $time);
0 ignored issues
show
Bug introduced by
It seems like $time can also be of type null; however, parameter $time of hiqdev\php\billing\tests...rInterface::setAction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
        $this->builder->setAction($type, $amount, $unit, $target, /** @scrutinizer ignore-type */ $time);
Loading history...
184
    }
185
186
    /**
187
     * @Given /perform calculation( at (\S+))?/
188
     */
189
    public function performCalculation(string $at = null, string $time = null): array
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

189
    public function performCalculation(/** @scrutinizer ignore-unused */ string $at = null, string $time = null): array

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

Loading history...
190
    {
191
        $this->charges = $this->builder->performCalculation($this->prepareTime($time));
0 ignored issues
show
Bug introduced by
It seems like $this->prepareTime($time) can also be of type null; however, parameter $time of hiqdev\php\billing\tests...e::performCalculation() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

191
        $this->charges = $this->builder->performCalculation(/** @scrutinizer ignore-type */ $this->prepareTime($time));
Loading history...
192
        return $this->charges;
193
    }
194
195
    /**
196
     * @Given /bill +for (\S+) is +(\S+) (\S+) per (\S+) (\S+) for target (.+?)( +at (.+))?$/
197
     */
198
    public function billWithTime($type, $sum, $currency, $quantity, $unit, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

198
    public function billWithTime($type, $sum, $currency, $quantity, $unit, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

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

Loading history...
199
    {
200
        $quantity = $this->prepareQuantity($quantity);
201
        $sum = $this->prepareSum($sum, $quantity);
202
        $time = $this->prepareTime($time);
203
        $bill = $this->findBill([
204
            'type' => $type,
205
            'target' => $target,
206
            'sum' => "$sum $currency",
207
            'quantity' => "$quantity $unit",
208
            'time' => $time,
209
        ]);
210
        Assert::assertSame($type, $bill->getType()->getName());
211
        Assert::assertSame($target, $bill->getTarget()->getFullName());
212
        Assert::assertEquals($sum * 100, $bill->getSum()->getAmount());
213
        Assert::assertSame($currency, $bill->getSum()->getCurrency()->getCode());
214
        Assert::assertEquals((float)$quantity, (float)$bill->getQuantity()->getQuantity());
215
        Assert::assertEquals(strtolower($unit), strtolower($bill->getQuantity()->getUnit()->getName()));
216
        if ($time) {
217
            Assert::assertEquals(new DateTimeImmutable($time), $bill->getTime());
218
        }
219
    }
220
221
    public function findBill(array $params): BillInterface
222
    {
223
        $bills = $this->builder->findBills($params);
224
        $this->bill = reset($bills);
225
        $this->charges = $this->bill->getCharges();
226
227
        return $this->bill;
228
    }
229
230
    /**
231
     * @Given /bills number is (\d+) for (\S+) for target (.+?)( +at (\S+))?$/
232
     */
233
    public function billsNumberWithTime($number, $type, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

233
    public function billsNumberWithTime($number, $type, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

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

Loading history...
234
    {
235
        $count = count($this->builder->findBills(array_filter([
236
            'type' => $type,
237
            'target' => $target,
238
            'time' => $this->prepareTime($time),
239
        ])));
240
241
        Assert::assertEquals($number, $count);
242
    }
243
244
    /**
245
     * @Given /charges number is (\d+)/
246
     */
247
    public function chargesNumber($number)
248
    {
249
        Assert::assertEquals($number, count($this->charges));
250
    }
251
252
    /**
253
     * @Given /charge for (\S+) is +(\S+) (\S+) per (\S+) (\S+)$/
254
     */
255
    public function charge($type, $amount, $currency, $quantity, $unit)
256
    {
257
        $this->chargeWithTarget($type, $amount, $currency, $quantity, $unit, null);
258
    }
259
260
    /**
261
     * @Given /charge for (\S+) is +(\S+) (\S+) per +(\S+) (\S+) +for target (.+?)( +at (\S+))?$/
262
     */
263
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, $at = null, $time = null)
0 ignored issues
show
Unused Code introduced by
The parameter $time is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

263
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, $at = null, /** @scrutinizer ignore-unused */ $time = null)

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

Loading history...
Unused Code introduced by
The parameter $at is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

263
    public function chargeWithTarget($type, $amount, $currency, $quantity, $unit, $target, /** @scrutinizer ignore-unused */ $at = null, $time = null)

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

Loading history...
264
    {
265
        $quantity = $this->prepareQuantity($quantity);
266
        $amount = $this->prepareSum($amount, $quantity);
267
        $charge = $this->findCharge($type, $target);
268
        Assert::assertNotNull($charge);
269
        Assert::assertSame($type, $charge->getType()->getName());
270
        Assert::assertSame($target, $charge->getTarget()->getFullName());
271
        Assert::assertEquals($amount * 100, $charge->getSum()->getAmount());
272
        Assert::assertSame($currency, $charge->getSum()->getCurrency()->getCode());
273
        Assert::assertEquals((float)$quantity, (float)$charge->getUsage()->getQuantity());
274
        Assert::assertEquals(strtolower($unit), strtolower($charge->getUsage()->getUnit()->getName()));
275
    }
276
277
    public function findCharge($type, $target): ?ChargeInterface
278
    {
279
        foreach ($this->charges as $charge) {
280
            if ($charge->getType()->getName() !== $type) {
281
                continue;
282
            }
283
            if ($charge->getTarget()->getFullName() !== $target) {
284
                continue;
285
            }
286
287
            return $charge;
288
        }
289
290
        return null;
291
    }
292
293
    public function getNextCharge(): ChargeInterface
294
    {
295
        $charge = current($this->charges);
296
        next($this->charges);
297
298
        return $charge;
299
    }
300
301
    /**
302
     * @return string|false|null
303
     */
304
    protected function prepareTime(string $time = null)
305
    {
306
        if ($time === null) {
307
            return null;
308
        }
309
310
        if ($time === 'midnight second day of this month') {
311
            return date('Y-m-02');
312
        }
313
        if (strncmp($time, 'Y', 1) === 0) {
314
            return date($time);
315
        }
316
317
        return $time;
318
    }
319
320
    private function prepareQuantity($quantity)
321
    {
322
        if ($quantity[0] === 's') {
323
            return $this->getSaleQuantity();
324
        }
325
326
        return $quantity;
327
    }
328
329
    private function prepareSum($sum, $quantity)
330
    {
331
        if ($sum[0] === 's') {
332
            $sum = round(substr($sum, 1) * $quantity*100)/100;
333
        }
334
335
        return $sum;
336
    }
337
338
    public function getSaleQuantity()
339
    {
340
        return $this->days2quantity(new DateTimeImmutable($this->saleTime));
341
    }
342
343
    private function days2quantity(DateTimeImmutable $from)
344
    {
345
        $till = new DateTimeImmutable('first day of next month midnight');
346
        $diff = $from->diff($till);
347
        if ($diff->m) {
348
            return 1;
349
        }
350
351
        return $diff->d/date('t');
352
    }
353
354
    /**
355
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)"$/
356
     */
357
    public function tariffPlanChangeIsRequestedForTarget(string $target, string $planName, string $date)
358
    {
359
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date)));
360
    }
361
362
    /**
363
     * @When /^tariff plan change is requested for target "([^"]*)" to plan "([^"]*)" at "([^"]*)", assuming current time is "([^"]*)"$/
364
     */
365
    public function tariffPlanChangeIsRequestedForTargetAtSpecificTime(string $target, string $planName, string $date, ?string $wallTime = null)
366
    {
367
        $this->mayFail(fn () => $this->builder->targetChangePlan($target, $planName, $this->prepareTime($date), $this->prepareTime($wallTime)));
368
    }
369
370
    /**
371
     * @Then /^target "([^"]*)" is sold to customer by plan "([^"]*)" since "([^"]*)"(?: till "([^"]*)")?$/
372
     */
373
    public function targetIsSoldToCustomerByPlanSinceTill(string $target, string $planName, string $saleDate, ?string $saleCloseDate = null)
374
    {
375
        $sales = $this->builder->findHistoricalSales([
376
            'target' => $target,
377
        ]);
378
379
        $saleDateTime = new DateTimeImmutable($saleDate);
380
        $saleCloseDateTime = new DateTimeImmutable($saleCloseDate);
381
382
        foreach ($sales as $sale) {
383
            /** @noinspection PhpBooleanCanBeSimplifiedInspection */
384
            $saleExists = true
385
                && str_contains($sale->getPlan()->getName(), $planName)
386
                && $sale->getTime()->format(DATE_ATOM) === $saleDateTime->format(DATE_ATOM)
387
                && (
388
                    ($saleCloseDate === null && $sale->getCloseTime() === null)
389
                    ||
390
                    ($saleCloseDate !== null && $sale->getCloseTime()->format(DATE_ATOM) === $saleCloseDateTime->format(DATE_ATOM))
391
                );
392
393
            if ($saleExists) {
394
                return;
395
            }
396
        }
397
398
        Assert::fail('Requested sale does not exist');
399
    }
400
401
    /**
402
     * @Then /^target "([^"]*)" has exactly (\d+) sales for customer$/
403
     */
404
    public function targetHasExactlySalesForCustomer(string $target, int $count)
405
    {
406
        $sales = $this->builder->findHistoricalSales([
407
            'target' => $target,
408
        ]);
409
410
        Assert::assertCount($count, $sales);
411
    }
412
413
    /**
414
     * @Then /^caught error is "([^"]*)"$/
415
     */
416
    public function caughtErrorIs(string $errorMessage): void
417
    {
418
        $this->assertCaughtExceptionMatches(\Throwable::class, $errorMessage);
419
    }
420
421
    /**
422
     * @Given /^target "([^"]*)"$/
423
     */
424
    public function target(string $target)
425
    {
426
        $this->builder->buildTarget($target);
427
    }
428
429
    /**
430
     * @Then /^flush entities cache$/
431
     */
432
    public function flushEntitiesCache()
433
    {
434
        $this->builder->flushEntitiesCache();
435
    }
436
437
    /**
438
     * @Given /^target "([^"]*)" has the following uses:$/
439
     */
440
    public function targetHasTheFollowingUses(string $target, TableNode $usesTable)
441
    {
442
        foreach ($usesTable->getColumnsHash() as $row) {
443
            $uses = $this->builder->findUsage($row['time'], $target, $row['type']);
444
            Assert::assertCount(1, $uses);
445
446
            $use = reset($uses);
447
            Assert::assertEquals($row['amount'], $use['total']);
448
        }
449
    }
450
}
451