1 | <?php |
||||||||
2 | |||||||||
3 | namespace Terox\SubscriptionBundle\Subscription; |
||||||||
4 | |||||||||
5 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
||||||||
6 | use Symfony\Component\Security\Core\User\UserInterface; |
||||||||
7 | use Terox\SubscriptionBundle\Event\SubscriptionEvent; |
||||||||
8 | use Terox\SubscriptionBundle\Event\SubscriptionEvents; |
||||||||
9 | use Terox\SubscriptionBundle\Exception\PermanentSubscriptionException; |
||||||||
10 | use Terox\SubscriptionBundle\Exception\ProductDefaultNotFoundException; |
||||||||
11 | use Terox\SubscriptionBundle\Exception\StrategyNotFoundException; |
||||||||
12 | use Terox\SubscriptionBundle\Exception\SubscriptionRenewalException; |
||||||||
13 | use Terox\SubscriptionBundle\Exception\SubscriptionStatusException; |
||||||||
14 | use Terox\SubscriptionBundle\Exception\SubscriptionIntegrityException; |
||||||||
15 | use Terox\SubscriptionBundle\Model\ProductInterface; |
||||||||
16 | use Terox\SubscriptionBundle\Model\SubscriptionInterface; |
||||||||
17 | use Terox\SubscriptionBundle\Registry\SubscriptionRegistry; |
||||||||
18 | use Terox\SubscriptionBundle\Repository\SubscriptionRepositoryInterface; |
||||||||
19 | use Terox\SubscriptionBundle\Strategy\SubscriptionStrategyInterface; |
||||||||
20 | |||||||||
21 | /** |
||||||||
22 | * Manages subscription workflow. |
||||||||
23 | * |
||||||||
24 | */ |
||||||||
25 | class SubscriptionManager |
||||||||
26 | { |
||||||||
27 | /** |
||||||||
28 | * @var SubscriptionRegistry |
||||||||
29 | */ |
||||||||
30 | private $registry; |
||||||||
31 | |||||||||
32 | /** |
||||||||
33 | * @var EventDispatcherInterface |
||||||||
34 | */ |
||||||||
35 | private $eventDispatcher; |
||||||||
36 | |||||||||
37 | /** |
||||||||
38 | * @var string |
||||||||
39 | */ |
||||||||
40 | private $config; |
||||||||
41 | |||||||||
42 | /** |
||||||||
43 | * Constructor. |
||||||||
44 | * |
||||||||
45 | * @param SubscriptionRegistry $registry Registry of strategies |
||||||||
46 | * @param SubscriptionRepositoryInterface $subscriptionRepository Subscription repository |
||||||||
47 | * @param EventDispatcherInterface $eventDispatcher Event Dispatcher |
||||||||
48 | * @param array $config Bundle configuration |
||||||||
49 | */ |
||||||||
50 | 16 | public function __construct( |
|||||||
51 | SubscriptionRegistry $registry, |
||||||||
52 | SubscriptionRepositoryInterface $subscriptionRepository, |
||||||||
53 | EventDispatcherInterface $eventDispatcher, |
||||||||
54 | $config |
||||||||
55 | ) |
||||||||
56 | { |
||||||||
57 | 16 | $this->registry = $registry; |
|||||||
58 | 16 | $this->subscriptionRepository = $subscriptionRepository; |
|||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||||
59 | 16 | $this->eventDispatcher = $eventDispatcher; |
|||||||
60 | 16 | $this->config = $config; |
|||||||
0 ignored issues
–
show
It seems like
$config of type array is incompatible with the declared type string of property $config .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||||||||
61 | 16 | } |
|||||||
62 | |||||||||
63 | /** |
||||||||
64 | * Create a new subscription with a determinate strategy. |
||||||||
65 | * |
||||||||
66 | * @param ProductInterface $product Product that you want associate with subscription |
||||||||
67 | * @param UserInterface $user User to associate to subscription |
||||||||
68 | * @param string $strategyName If you keep this null it will use product default strategy |
||||||||
69 | * |
||||||||
70 | * @return SubscriptionInterface |
||||||||
71 | * |
||||||||
72 | * @throws StrategyNotFoundException |
||||||||
73 | * @throws SubscriptionIntegrityException |
||||||||
74 | * @throws PermanentSubscriptionException |
||||||||
75 | */ |
||||||||
76 | 7 | public function create(ProductInterface $product, UserInterface $user, $strategyName = null) |
|||||||
77 | { |
||||||||
78 | // Get strategy |
||||||||
79 | 7 | $strategyName = $strategyName ?? $product->getStrategyCodeName(); |
|||||||
80 | 7 | $strategy = $this->registry->get($strategyName); |
|||||||
81 | |||||||||
82 | // Get current enabled subscriptions of product |
||||||||
83 | 7 | $subscriptions = $this->subscriptionRepository->findByProduct($product, $user); |
|||||||
84 | |||||||||
85 | // Check that subscriptions collection are a valid objects |
||||||||
86 | 7 | foreach ($subscriptions as $activeSubscription) { |
|||||||
87 | 4 | $this->checkSubscriptionIntegrity($activeSubscription); |
|||||||
88 | } |
||||||||
89 | |||||||||
90 | 7 | $subscription = $strategy->createSubscription($product, $subscriptions); |
|||||||
91 | 6 | $subscription->setStrategy($strategyName); |
|||||||
92 | 6 | $subscription->setUser($user); |
|||||||
93 | |||||||||
94 | 6 | return $subscription; |
|||||||
95 | } |
||||||||
96 | |||||||||
97 | /** |
||||||||
98 | * Activate subscription. |
||||||||
99 | * |
||||||||
100 | * @param SubscriptionInterface $subscription |
||||||||
101 | * @param boolean $isRenew |
||||||||
102 | * |
||||||||
103 | * @throws SubscriptionIntegrityException |
||||||||
104 | * @throws StrategyNotFoundException |
||||||||
105 | * @throws SubscriptionStatusException |
||||||||
106 | * @throws ProductDefaultNotFoundException |
||||||||
107 | */ |
||||||||
108 | 7 | public function activate(SubscriptionInterface $subscription, $isRenew = false) |
|||||||
109 | { |
||||||||
110 | 7 | $this->checkSubscriptionIntegrity($subscription); |
|||||||
111 | 7 | $this->checkSubscriptionNonActive($subscription); |
|||||||
112 | |||||||||
113 | 7 | $strategy = $this->getStrategyFromSubscription($subscription); |
|||||||
114 | 7 | $finalProduct = $strategy->getProductStrategy()->getFinalProduct($subscription->getProduct()); |
|||||||
115 | |||||||||
116 | 7 | $subscription->setProduct($finalProduct); |
|||||||
117 | 7 | $subscription->activate(); |
|||||||
118 | |||||||||
119 | 7 | $subscriptionEvent = new SubscriptionEvent($subscription, $isRenew); |
|||||||
120 | 7 | $this->eventDispatcher->dispatch(SubscriptionEvents::ACTIVATE_SUBSCRIPTION, $subscriptionEvent); |
|||||||
0 ignored issues
–
show
The call to
Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $subscriptionEvent .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() Terox\SubscriptionBundle...::ACTIVATE_SUBSCRIPTION of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
121 | 7 | } |
|||||||
122 | |||||||||
123 | /** |
||||||||
124 | * Renew subscription that has been expired. |
||||||||
125 | * |
||||||||
126 | * @param SubscriptionInterface $subscription |
||||||||
127 | * |
||||||||
128 | * @return SubscriptionInterface New |
||||||||
129 | * |
||||||||
130 | * @throws SubscriptionIntegrityException |
||||||||
131 | * @throws SubscriptionRenewalException |
||||||||
132 | * @throws ProductDefaultNotFoundException |
||||||||
133 | * @throws StrategyNotFoundException |
||||||||
134 | * @throws PermanentSubscriptionException |
||||||||
135 | * @throws SubscriptionStatusException |
||||||||
136 | */ |
||||||||
137 | 6 | public function renew(SubscriptionInterface $subscription) |
|||||||
138 | { |
||||||||
139 | 6 | $this->checkSubscriptionIntegrity($subscription); |
|||||||
140 | 6 | $this->checkSubscriptionRenewable($subscription); |
|||||||
141 | 3 | $this->checkSubscriptionActive($subscription); |
|||||||
142 | |||||||||
143 | // Expire the last subscription |
||||||||
144 | 3 | $this->expire($subscription, 'renew', true); |
|||||||
145 | |||||||||
146 | // Get the next renewal product |
||||||||
147 | 3 | $renewalProduct = $this->getRenewalProduct($subscription->getProduct()); |
|||||||
148 | 3 | $strategy = $this->getStrategyFromSubscription($subscription); |
|||||||
149 | 3 | $finalProduct = $strategy->getProductStrategy()->getFinalProduct($renewalProduct); |
|||||||
150 | |||||||||
151 | // Create new subscription (following the way of expired subscription) |
||||||||
152 | 3 | $newSubscription = $this->create($finalProduct, $subscription->getUser(), $finalProduct->getStrategyCodeName()); |
|||||||
153 | 3 | $newSubscription->setAutoRenewal(true); |
|||||||
154 | |||||||||
155 | // Activate the next subscription |
||||||||
156 | 3 | $this->activate($newSubscription, true); |
|||||||
157 | |||||||||
158 | 3 | $subscriptionEvent = new SubscriptionEvent($newSubscription); |
|||||||
159 | 3 | $this->eventDispatcher->dispatch(SubscriptionEvents::RENEW_SUBSCRIPTION, $subscriptionEvent); |
|||||||
0 ignored issues
–
show
The call to
Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $subscriptionEvent .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() Terox\SubscriptionBundle...nts::RENEW_SUBSCRIPTION of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
160 | |||||||||
161 | 3 | return $newSubscription; |
|||||||
162 | } |
||||||||
163 | |||||||||
164 | /** |
||||||||
165 | * Get next roll product. |
||||||||
166 | * |
||||||||
167 | * @param ProductInterface $product |
||||||||
168 | * |
||||||||
169 | * @return ProductInterface |
||||||||
170 | */ |
||||||||
171 | 3 | protected function getRenewalProduct(ProductInterface $product) |
|||||||
172 | { |
||||||||
173 | 3 | if (null === $product->getNextRenewalProduct()) { |
|||||||
174 | 2 | return $product; |
|||||||
175 | } |
||||||||
176 | |||||||||
177 | 1 | return $product->getNextRenewalProduct(); |
|||||||
178 | } |
||||||||
179 | |||||||||
180 | /** |
||||||||
181 | * Expire subscription. |
||||||||
182 | * |
||||||||
183 | * @param SubscriptionInterface $subscription |
||||||||
184 | * @param string $reason The reason codename that you want set into the subscription |
||||||||
185 | * @param boolean $isRenew |
||||||||
186 | */ |
||||||||
187 | 4 | public function expire(SubscriptionInterface $subscription, $reason = 'expire', $isRenew = false) |
|||||||
188 | { |
||||||||
189 | 4 | $subscription->setReason($this->config['reasons'][$reason]); |
|||||||
190 | 4 | $subscription->deactivate(); |
|||||||
191 | |||||||||
192 | 4 | $subscriptionEvent = new SubscriptionEvent($subscription, $isRenew); |
|||||||
193 | 4 | $this->eventDispatcher->dispatch(SubscriptionEvents::EXPIRE_SUBSCRIPTION, $subscriptionEvent); |
|||||||
0 ignored issues
–
show
Terox\SubscriptionBundle...ts::EXPIRE_SUBSCRIPTION of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() The call to
Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $subscriptionEvent .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||||
194 | 4 | } |
|||||||
195 | |||||||||
196 | /** |
||||||||
197 | * Disable subscription. |
||||||||
198 | * |
||||||||
199 | * @param SubscriptionInterface $subscription |
||||||||
200 | */ |
||||||||
201 | 1 | public function disable(SubscriptionInterface $subscription) |
|||||||
202 | { |
||||||||
203 | 1 | $subscription->setReason($this->config['reasons']['disable']); |
|||||||
204 | 1 | $subscription->deactivate(); |
|||||||
205 | |||||||||
206 | 1 | $subscriptionEvent = new SubscriptionEvent($subscription); |
|||||||
207 | 1 | $this->eventDispatcher->dispatch(SubscriptionEvents::DISABLE_SUBSCRIPTION, $subscriptionEvent); |
|||||||
0 ignored issues
–
show
The call to
Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $subscriptionEvent .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() Terox\SubscriptionBundle...s::DISABLE_SUBSCRIPTION of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
208 | 1 | } |
|||||||
209 | |||||||||
210 | /** |
||||||||
211 | * Get strategy from subscription. |
||||||||
212 | * |
||||||||
213 | * @param SubscriptionInterface $subscription |
||||||||
214 | * |
||||||||
215 | * @return SubscriptionStrategyInterface |
||||||||
216 | * |
||||||||
217 | * @throws StrategyNotFoundException |
||||||||
218 | */ |
||||||||
219 | 7 | private function getStrategyFromSubscription(SubscriptionInterface $subscription) |
|||||||
220 | { |
||||||||
221 | 7 | $strategyName = $subscription->getStrategy(); |
|||||||
222 | |||||||||
223 | 7 | return $this->registry->get($strategyName); |
|||||||
224 | } |
||||||||
225 | |||||||||
226 | /** |
||||||||
227 | * Check subscription integrity. |
||||||||
228 | * |
||||||||
229 | * @param SubscriptionInterface $subscription |
||||||||
230 | * |
||||||||
231 | * @throws SubscriptionIntegrityException |
||||||||
232 | */ |
||||||||
233 | 14 | private function checkSubscriptionIntegrity(SubscriptionInterface $subscription) |
|||||||
234 | { |
||||||||
235 | 14 | if (null === $subscription->getProduct()) { |
|||||||
236 | throw new SubscriptionIntegrityException('Subscription must have a product defined.'); |
||||||||
237 | } |
||||||||
238 | |||||||||
239 | 14 | if (null === $subscription->getUser()) { |
|||||||
240 | throw new SubscriptionIntegrityException('Subscription must have a user defined.'); |
||||||||
241 | } |
||||||||
242 | 14 | } |
|||||||
243 | |||||||||
244 | /** |
||||||||
245 | * Check if subscription is auto-renewable. |
||||||||
246 | * |
||||||||
247 | * @param SubscriptionInterface $subscription |
||||||||
248 | * |
||||||||
249 | * @throws SubscriptionRenewalException |
||||||||
250 | */ |
||||||||
251 | 6 | private function checkSubscriptionRenewable(SubscriptionInterface $subscription) |
|||||||
252 | { |
||||||||
253 | 6 | if (null === $subscription->getEndDate()) { |
|||||||
254 | 1 | throw new SubscriptionRenewalException('A permanent subscription can not be renewed.'); |
|||||||
255 | } |
||||||||
256 | |||||||||
257 | 5 | if (!$subscription->isAutoRenewal()) { |
|||||||
258 | 1 | throw new SubscriptionRenewalException('The current subscription is not auto-renewal.'); |
|||||||
259 | } |
||||||||
260 | |||||||||
261 | 4 | if (!$subscription->getProduct()->isAutoRenewal()) { |
|||||||
262 | 1 | throw new SubscriptionRenewalException(sprintf( |
|||||||
263 | 1 | 'The product "%s" is not auto-renewal. Maybe is disabled?', |
|||||||
264 | 1 | $subscription->getProduct()->getName() |
|||||||
265 | )); |
||||||||
266 | } |
||||||||
267 | 3 | } |
|||||||
268 | |||||||||
269 | /** |
||||||||
270 | * @param SubscriptionInterface $subscription |
||||||||
271 | * |
||||||||
272 | * @throws SubscriptionStatusException |
||||||||
273 | */ |
||||||||
274 | 7 | private function checkSubscriptionNonActive(SubscriptionInterface $subscription) |
|||||||
275 | { |
||||||||
276 | 7 | if (!$subscription->isActive()) { |
|||||||
277 | 7 | return; |
|||||||
278 | } |
||||||||
279 | |||||||||
280 | throw new SubscriptionStatusException('Subscription is active.'); |
||||||||
281 | } |
||||||||
282 | |||||||||
283 | /** |
||||||||
284 | * @param SubscriptionInterface $subscription |
||||||||
285 | * |
||||||||
286 | * @throws SubscriptionStatusException |
||||||||
287 | */ |
||||||||
288 | 3 | private function checkSubscriptionActive(SubscriptionInterface $subscription) |
|||||||
289 | { |
||||||||
290 | 3 | if ($subscription->isActive()) { |
|||||||
291 | 3 | return; |
|||||||
292 | } |
||||||||
293 | |||||||||
294 | throw new SubscriptionStatusException('Subscription is not active.'); |
||||||||
295 | } |
||||||||
296 | } |
||||||||
297 |