Completed
Push — master ( 339fdb...9dc0a8 )
by Rafał
08:30 queued 04:25
created

PayumController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 8

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
declare(strict_types=1);
4
5
namespace PH\Bundle\PayumBundle\Controller;
6
7
use FOS\RestBundle\View\View;
8
use Payum\Core\Payum;
9
use Payum\Core\Security\GenericTokenFactoryInterface;
10
use Payum\Core\Security\HttpRequestVerifierInterface;
11
use PH\Bundle\PayumBundle\Factory\ResolveNextUrlFactoryInterface;
12
use PH\Component\Core\Model\SubscriptionInterface;
13
use PH\Component\Core\Model\PaymentInterface;
14
use PH\Component\Core\Repository\SubscriptionRepositoryInterface;
15
use PH\Component\Core\Repository\PaymentRepositoryInterface;
16
use Sylius\Bundle\PayumBundle\Request\GetStatus;
17
use Sylius\Bundle\ResourceBundle\Controller\RequestConfigurationFactoryInterface;
18
use Sylius\Bundle\ResourceBundle\Controller\ViewHandlerInterface;
19
use Sylius\Component\Resource\Metadata\MetadataInterface;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpKernel\Exception\HttpException;
23
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
24
use Symfony\Component\Routing\RouterInterface;
25
26
class PayumController
27
{
28
    /**
29
     * @var Payum
30
     */
31
    private $payum;
32
33
    /**
34
     * @var SubscriptionRepositoryInterface
35
     */
36
    private $subscriptionRepository;
37
38
    /**
39
     * @var MetadataInterface
40
     */
41
    private $subscriptionMetadata;
42
43
    /**
44
     * @var RequestConfigurationFactoryInterface
45
     */
46
    private $requestConfigurationFactory;
47
48
    /**
49
     * @var ViewHandlerInterface
50
     */
51
    private $viewHandler;
52
53
    /**
54
     * @var RouterInterface
55
     */
56
    private $router;
57
58
    /**
59
     * @var PaymentRepositoryInterface
60
     */
61
    private $paymentRepository;
62
63
    /**
64
     * @var ResolveNextUrlFactoryInterface
65
     */
66
    private $resolveNextUrlFactory;
67
68
    /**
69
     * @param Payum                                $payum
70
     * @param SubscriptionRepositoryInterface      $subscriptionRepository
71
     * @param MetadataInterface                    $subscriptionMetadata
72
     * @param RequestConfigurationFactoryInterface $requestConfigurationFactory
73
     * @param ViewHandlerInterface                 $viewHandler
74
     * @param RouterInterface                      $router
75
     * @param PaymentRepositoryInterface           $paymentRepository
76
     * @param ResolveNextUrlFactoryInterface       $resolveNextUrlFactory
77
     */
78
    public function __construct(
79
        Payum $payum,
80
        SubscriptionRepositoryInterface $subscriptionRepository,
81
        MetadataInterface $subscriptionMetadata,
82
        RequestConfigurationFactoryInterface $requestConfigurationFactory,
83
        ViewHandlerInterface $viewHandler,
84
        RouterInterface $router,
85
        PaymentRepositoryInterface $paymentRepository,
86
        ResolveNextUrlFactoryInterface $resolveNextUrlFactory
87
    ) {
88
        $this->payum = $payum;
89
        $this->subscriptionRepository = $subscriptionRepository;
90
        $this->subscriptionMetadata = $subscriptionMetadata;
91
        $this->requestConfigurationFactory = $requestConfigurationFactory;
92
        $this->viewHandler = $viewHandler;
93
        $this->router = $router;
94
        $this->paymentRepository = $paymentRepository;
95
        $this->resolveNextUrlFactory = $resolveNextUrlFactory;
96
    }
97
98
    /**
99
     * @param Request $request
100
     * @param mixed   $token
101
     *
102
     * @return Response
103
     */
104
    public function prepareCaptureAction(Request $request, string $token): Response
105
    {
106
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
107
108
        /** @var SubscriptionInterface $subscription */
109
        $subscription = $this->subscriptionRepository->findOneByTokenValue($token);
0 ignored issues
show
Bug introduced by
The method findOneByTokenValue() does not exist on PH\Component\Core\Reposi...tionRepositoryInterface. Did you maybe mean findOneBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
110
111
        if (null === $subscription) {
112
            throw new NotFoundHttpException(sprintf('Subscription with token "%s" does not exist.', $token));
113
        }
114
115
        $options = $configuration->getParameters()->get('redirect');
116
117
        /** @var PaymentInterface $payment */
118
        $payment = $subscription->getLastPayment(PaymentInterface::STATE_NEW);
119
120
        if (null === $payment) {
121
            return $this->viewHandler->handle($configuration, View::create([], 404));
122
        }
123
124
        $captureToken = $this->getTokenFactory()->createCaptureToken(
125
            $payment->getMethod()->getGatewayConfig()->getGatewayName(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Payment...\PaymentMethodInterface as the method getGatewayConfig() does only exist in the following implementations of said interface: PH\Component\Core\Model\PaymentMethod, Sylius\Component\Core\Model\PaymentMethod.

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...
126
            $payment,
127
            $options['route'] ?? null,
128
            $options['parameters'] ?? []
129
        );
130
131
        $view = View::createRedirect($captureToken->getTargetUrl());
132
133
        return $this->viewHandler->handle($configuration, $view);
134
    }
135
136
    /**
137
     * @param Request $request
138
     *
139
     * @return Response
140
     *
141
     * @throws \InvalidArgumentException
142
     */
143
    public function afterCaptureAction(Request $request): Response
144
    {
145
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
146
        $token = $this->getHttpRequestVerifier()->verify($request);
147
        $status = new GetStatus($token);
148
149
        $this->payum->getGateway($token->getGatewayName())->execute($status);
150
151
        if (!$request->query->has('redirect')) {
152
            $resolveNextUrl = $this->resolveNextUrlFactory->createNewWithModel($status->getFirstModel());
153
            $this->payum->getGateway($token->getGatewayName())->execute($resolveNextUrl);
154
            $redirect = $resolveNextUrl->getUrl();
155
        } else {
156
            $redirect = $request->query->get('redirect');
157
        }
158
159
        $this->getHttpRequestVerifier()->invalidate($token);
160
161
        $view = View::createRedirect($redirect);
162
163
        return $this->viewHandler->handle($configuration, $view);
164
    }
165
166
    /**
167
     * @param Request $request
168
     * @param string  $subscriptionId
169
     * @param string  $id
170
     *
171
     * @return Response
172
     *
173
     * @throws \InvalidArgumentException
174
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
175
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
176
     */
177
    public function cancelAction(Request $request, string $subscriptionId, string $id): Response
178
    {
179
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
180
181
        /** @var PaymentInterface|null $payment */
182
        $payment = $this->paymentRepository->findOneBySubscriptionId($id, $subscriptionId);
183
184
        if (null === $payment) {
185
            throw new NotFoundHttpException(sprintf('Payment with id "%s" does not exist.', $id));
186
        }
187
188
        if (PaymentInterface::STATE_CANCELLED === $payment->getState()) {
189
            throw new HttpException(409, 'The payment has been already cancelled!');
190
        }
191
192
        $options = $configuration->getParameters()->get('redirect');
193
194
        $cancelToken = $this->getTokenFactory()->createCancelToken(
195
            $payment->getMethod()->getGatewayConfig()->getGatewayName(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Payment...\PaymentMethodInterface as the method getGatewayConfig() does only exist in the following implementations of said interface: PH\Component\Core\Model\PaymentMethod, Sylius\Component\Core\Model\PaymentMethod.

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...
196
            $payment,
197
            $options['route'] ?? null,
198
            $options['parameters'] ?? []
199
        );
200
201
        $view = View::createRedirect($cancelToken->getTargetUrl());
202
203
        return $this->viewHandler->handle($configuration, $view);
204
    }
205
206
    /**
207
     * @param Request $request
208
     *
209
     * @return Response
210
     *
211
     * @throws \InvalidArgumentException
212
     */
213
    public function afterCancelAction(Request $request): Response
214
    {
215
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
216
        $token = $this->getHttpRequestVerifier()->verify($request);
217
        $status = new GetStatus($token);
218
219
        $this->payum->getGateway($token->getGatewayName())->execute($status);
220
        $this->getHttpRequestVerifier()->invalidate($token);
221
222
        $view = View::create([], 204);
223
224
        return $this->viewHandler->handle($configuration, $view);
225
    }
226
227
    /**
228
     * @return GenericTokenFactoryInterface
229
     */
230
    private function getTokenFactory(): GenericTokenFactoryInterface
231
    {
232
        return $this->payum->getTokenFactory();
233
    }
234
235
    /**
236
     * @return HttpRequestVerifierInterface
237
     */
238
    private function getHttpRequestVerifier(): HttpRequestVerifierInterface
239
    {
240
        return $this->payum->getHttpRequestVerifier();
241
    }
242
}
243