Completed
Push — master ( e1543f...d374d7 )
by Adolfo
20s queued 10s
created

HasSubscriptions   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 7
dl 0
loc 198
rs 9.76
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A subscribeTo() 0 8 2
A subscribeToPlan() 0 29 4
A getActiveSubscription() 0 6 1
A upgradeTo() 0 10 2
A getConsumables() 0 3 1
A subscriptions() 0 4 1
A calculateExpireDate() 0 19 4
A subscribeToInterval() 0 19 2
B changePlanTo() 0 30 8
A hasActiveSubscription() 0 6 1
A forceCancelSubscription() 0 7 1
A downgradeTo() 0 8 2
A renewSubscription() 0 23 3
A cancelSubscription() 0 6 1
1
<?php
2
3
namespace Sagitarius29\LaravelSubscriptions\Traits;
4
5
use Carbon\Carbon;
6
use Sagitarius29\LaravelSubscriptions\Entities\PlanInterval;
7
use Sagitarius29\LaravelSubscriptions\Entities\Subscription;
8
use Sagitarius29\LaravelSubscriptions\Contracts\PlanContract;
9
use Sagitarius29\LaravelSubscriptions\Contracts\SubscriptionContact;
10
use Sagitarius29\LaravelSubscriptions\Contracts\PlanIntervalContract;
11
use Sagitarius29\LaravelSubscriptions\Exceptions\SubscriptionErrorException;
12
13
trait HasSubscriptions
14
{
15
    /**
16
     * @param  PlanContract|PlanIntervalContract  $planOrInterval
17
     * @return \Illuminate\Database\Eloquent\Model
18
     */
19
    public function subscribeTo($planOrInterval)
20
    {
21
        if ($planOrInterval instanceof PlanContract) {
22
            return $this->subscribeToPlan($planOrInterval);
23
        }
24
25
        return $this->subscribeToInterval($planOrInterval);
26
    }
27
28
    public function subscribeToPlan(PlanContract $plan)
29
    {
30
        if ($plan->hasManyIntervals()) {
31
            throw new SubscriptionErrorException(
32
                'This plan has many intervals, please use subscribeToInterval() function'
33
            );
34
        }
35
36
        $currentSubscription = $this->getActiveSubscription();
37
        $start_at = null;
0 ignored issues
show
Unused Code introduced by
$start_at 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...
38
        $end_at = null;
0 ignored issues
show
Unused Code introduced by
$end_at 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...
39
40
        if ($currentSubscription == null) {
41
            $start_at = now();
42
        } else {
43
            $start_at = $currentSubscription->getExpirationDate();
44
        }
45
46
        if ($plan->isFree()) {
47
            $end_at = null;
48
        } else {
49
            $end_at = $this->calculateExpireDate($start_at, $plan->intervals()->first());
50
        }
51
52
        $subscription = Subscription::make($plan, $start_at, $end_at);
53
        $subscription = $this->subscriptions()->save($subscription);
54
55
        return $subscription;
56
    }
57
58
    public function getActiveSubscription(): ?SubscriptionContact
59
    {
60
        return $this->subscriptions()
61
            ->current()
62
            ->first();
63
    }
64
65
    public function subscriptions()
66
    {
67
        return $this->morphMany(config('subscriptions.entities.plan_subscription'), 'subscriber');
0 ignored issues
show
Bug introduced by
It seems like morphMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
68
    }
69
70
    private function calculateExpireDate(Carbon $start_at, PlanIntervalContract $interval)
71
    {
72
        $end_at = Carbon::createFromTimestamp($start_at->timestamp);
73
74
        switch ($interval->getType()) {
75
            case PlanInterval::$DAY:
76
                return $end_at->days($interval->getUnit());
77
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
78
            case PlanInterval::$MONTH:
79
                return $end_at->addMonths($interval->getUnit());
80
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
81
            case PlanInterval::$YEAR:
82
                return $end_at->addYears($interval->getUnit());
83
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
84
            default:
85
                //TODO error exception
86
                break;
87
        }
88
    }
89
90
    public function subscribeToInterval(PlanIntervalContract $interval): SubscriptionContact
91
    {
92
        $currentSubscription = $this->getActiveSubscription();
93
        $start_at = null;
0 ignored issues
show
Unused Code introduced by
$start_at 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...
94
        $end_at = null;
0 ignored issues
show
Unused Code introduced by
$end_at 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...
95
96
        if ($currentSubscription == null) {
97
            $start_at = now();
98
        } else {
99
            $start_at = $currentSubscription->getExpirationDate();
100
        }
101
102
        $end_at = $this->calculateExpireDate($start_at, $interval);
103
104
        $subscription = Subscription::make($interval->plan, $start_at, $end_at);
0 ignored issues
show
Bug introduced by
Accessing plan on the interface Sagitarius29\LaravelSubs...ts\PlanIntervalContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
105
        $subscription = $this->subscriptions()->save($subscription);
106
107
        return $subscription;
108
    }
109
110
    public function changePlanTo(PlanContract $plan, PlanIntervalContract $interval = null)
111
    {
112
        if (! $this->hasActiveSubscription()) {
113
            throw new SubscriptionErrorException('You need a subscription for upgrade to other.');
114
        }
115
116
        if ($plan->hasManyIntervals() && $interval == null) {
117
            throw new SubscriptionErrorException('The plan has many intervals, please indicate a interval.');
118
        }
119
120
        $currentSubscription = $this->getActiveSubscription();
121
        $currentPlan = $currentSubscription->plan;
122
        $currentIntervalPrice = $currentPlan->isFree() ? 0.00 : $currentPlan->getInterval()->getPrice();
123
124
        $toInterval = $plan->getInterval();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sagitarius29\LaravelSubs...\Contracts\PlanContract as the method getInterval() does only exist in the following implementations of said interface: Sagitarius29\LaravelSubscriptions\Entities\Plan.

Let’s take a look at an example:

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

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
125
126
        if ($currentPlan->id == $plan->id) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface Sagitarius29\LaravelSubs...\Contracts\PlanContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
127
            throw new SubscriptionErrorException('You can\'t change to same plan. You need change to other plan.');
128
        }
129
130
        if ($interval !== null) {
131
            $toInterval = $interval;
132
        }
133
134
        if ($currentIntervalPrice < $toInterval->getPrice()) {
135
            return $this->upgradeTo($toInterval);
136
        }
137
138
        return $this->downgradeTo($toInterval);
139
    }
140
141
    public function hasActiveSubscription(): bool
142
    {
143
        return $this->subscriptions()
144
            ->current()
145
            ->exists();
146
    }
147
148
    protected function upgradeTo(PlanIntervalContract $interval): SubscriptionContact
149
    {
150
        if (! $this->hasActiveSubscription()) {
151
            throw new SubscriptionErrorException('You need a subscription for upgrade to other.');
152
        }
153
154
        $this->forceCancelSubscription();
155
156
        return $this->subscribeToInterval($interval);
157
    }
158
159
    public function forceCancelSubscription()
160
    {
161
        $currentSubscription = $this->getActiveSubscription();
162
        $currentSubscription->end_at = now()->subSecond();
163
        $currentSubscription->cancelled_at = now();
164
        $currentSubscription->save();
165
    }
166
167
    protected function downgradeTo(PlanIntervalContract $interval): SubscriptionContact
168
    {
169
        if (! $this->hasActiveSubscription()) {
170
            throw new SubscriptionErrorException('You need a subscription for upgrade to other.');
171
        }
172
173
        return $this->subscribeToInterval($interval);
174
    }
175
176
    public function renewSubscription(PlanIntervalContract $interval = null)
177
    {
178
        $currentSubscription = $this->getActiveSubscription();
179
180
        if ($interval === null) {
181
            $plan = $currentSubscription->plan;
182
183
            if ($plan->hasManyIntervals()) {
184
                throw new SubscriptionErrorException(
185
                    'The plan you want will subscribe has many intervals, please consider renew to a interval of plan'
186
                );
187
            }
188
189
            $interval = $plan->intervals()->first();
190
        }
191
192
        $newExpireDate = $this->calculateExpireDate($currentSubscription->end_at, $interval);
193
194
        $currentSubscription->end_at = $newExpireDate;
195
        $currentSubscription->save();
196
197
        return $currentSubscription;
198
    }
199
200
    public function cancelSubscription()
201
    {
202
        $currentSubscription = $this->getActiveSubscription();
203
        $currentSubscription->cancelled_at = now();
204
        $currentSubscription->save();
205
    }
206
207
    public function getConsumables()
208
    {
209
    }
210
}
211