Passed
Push — trunk ( f8d7b0...989dfd )
by Christian
10:03 queued 13s
created

CheckoutController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 10
dl 0
loc 12
rs 10
c 0
b 0
f 0

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 declare(strict_types=1);
2
3
namespace Shopware\Storefront\Controller;
4
5
use Shopware\Core\Checkout\Cart\CartException;
6
use Shopware\Core\Checkout\Cart\Error\Error;
7
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
8
use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;
9
use Shopware\Core\Checkout\Cart\SalesChannel\AbstractCartLoadRoute;
10
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
11
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
12
use Shopware\Core\Checkout\Order\Exception\EmptyCartException;
13
use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
14
use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;
15
use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;
16
use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
17
use Shopware\Core\Checkout\Payment\PaymentService;
18
use Shopware\Core\Framework\Log\Package;
19
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
20
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
21
use Shopware\Core\Profiling\Profiler;
22
use Shopware\Core\System\SalesChannel\SalesChannelContext;
23
use Shopware\Core\System\SystemConfig\SystemConfigService;
24
use Shopware\Storefront\Checkout\Cart\Error\PaymentMethodChangedError;
25
use Shopware\Storefront\Checkout\Cart\Error\ShippingMethodChangedError;
26
use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener;
27
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedHook;
28
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader;
29
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedHook;
30
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader;
31
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedHook;
32
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader;
33
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutInfoWidgetLoadedHook;
34
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutOffcanvasWidgetLoadedHook;
35
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader;
36
use Symfony\Component\HttpFoundation\RedirectResponse;
37
use Symfony\Component\HttpFoundation\Request;
38
use Symfony\Component\HttpFoundation\Response;
39
use Symfony\Component\HttpFoundation\Session\SessionInterface;
40
use Symfony\Component\Routing\Annotation\Route;
41
42
/**
43
 * @internal
44
 * Do not use direct or indirect repository calls in a controller. Always use a store-api route to get or put datas
45
 */
46
#[Route(defaults: ['_routeScope' => ['storefront']])]
47
#[Package('storefront')]
48
class CheckoutController extends StorefrontController
49
{
50
    private const REDIRECTED_FROM_SAME_ROUTE = 'redirected';
51
52
    /**
53
     * @internal
54
     */
55
    public function __construct(
56
        private readonly CartService $cartService,
57
        private readonly CheckoutCartPageLoader $cartPageLoader,
58
        private readonly CheckoutConfirmPageLoader $confirmPageLoader,
59
        private readonly CheckoutFinishPageLoader $finishPageLoader,
60
        private readonly OrderService $orderService,
61
        private readonly PaymentService $paymentService,
62
        private readonly OffcanvasCartPageLoader $offcanvasCartPageLoader,
63
        private readonly SystemConfigService $config,
64
        private readonly AbstractLogoutRoute $logoutRoute,
65
        private readonly AbstractCartLoadRoute $cartLoadRoute
66
    ) {
67
    }
68
69
    #[Route(path: '/checkout/cart', name: 'frontend.checkout.cart.page', options: ['seo' => false], defaults: ['_noStore' => true], methods: ['GET'])]
70
    public function cartPage(Request $request, SalesChannelContext $context): Response
71
    {
72
        $page = $this->cartPageLoader->load($request, $context);
73
        $cart = $page->getCart();
74
        $cartErrors = $cart->getErrors();
75
76
        $this->hook(new CheckoutCartPageLoadedHook($page, $context));
77
78
        $this->addCartErrors($cart);
79
80
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
81
            $cartErrors->clear();
82
83
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
84
            return $this->redirectToRoute(
85
                'frontend.checkout.cart.page',
86
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
87
            );
88
        }
89
        $cartErrors->clear();
90
91
        return $this->renderStorefront('@Storefront/storefront/page/checkout/cart/index.html.twig', ['page' => $page]);
92
    }
93
94
    #[Route(path: '/checkout/cart.json', name: 'frontend.checkout.cart.json', methods: ['GET'], options: ['seo' => false], defaults: ['XmlHttpRequest' => true])]
95
    public function cartJson(Request $request, SalesChannelContext $context): Response
96
    {
97
        return $this->cartLoadRoute->load($request, $context);
98
    }
99
100
    #[Route(path: '/checkout/confirm', name: 'frontend.checkout.confirm.page', options: ['seo' => false], defaults: ['XmlHttpRequest' => true, '_noStore' => true], methods: ['GET'])]
101
    public function confirmPage(Request $request, SalesChannelContext $context): Response
102
    {
103
        if (!$context->getCustomer()) {
104
            return $this->redirectToRoute('frontend.checkout.register.page');
105
        }
106
107
        if ($this->cartService->getCart($context->getToken(), $context)->getLineItems()->count() === 0) {
108
            return $this->redirectToRoute('frontend.checkout.cart.page');
109
        }
110
111
        $page = $this->confirmPageLoader->load($request, $context);
112
        $cart = $page->getCart();
113
        $cartErrors = $cart->getErrors();
114
115
        $this->hook(new CheckoutConfirmPageLoadedHook($page, $context));
116
117
        $this->addCartErrors($cart);
118
119
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
120
            $cartErrors->clear();
121
122
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
123
            return $this->redirectToRoute(
124
                'frontend.checkout.confirm.page',
125
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
126
            );
127
        }
128
129
        return $this->renderStorefront('@Storefront/storefront/page/checkout/confirm/index.html.twig', ['page' => $page]);
130
    }
131
132
    #[Route(path: '/checkout/finish', name: 'frontend.checkout.finish.page', options: ['seo' => false], defaults: ['_noStore' => true], methods: ['GET'])]
133
    public function finishPage(Request $request, SalesChannelContext $context, RequestDataBag $dataBag): Response
134
    {
135
        if ($context->getCustomer() === null) {
136
            return $this->redirectToRoute('frontend.checkout.register.page');
137
        }
138
139
        $page = $this->finishPageLoader->load($request, $context);
140
141
        $this->hook(new CheckoutFinishPageLoadedHook($page, $context));
142
143
        if ($page->isPaymentFailed() === true) {
144
            return $this->redirectToRoute(
145
                'frontend.account.edit-order.page',
146
                [
147
                    'orderId' => $request->get('orderId'),
148
                    'error-code' => 'CHECKOUT__UNKNOWN_ERROR',
149
                ]
150
            );
151
        }
152
153
        if ($context->getCustomer()->getGuest() && $this->config->get('core.cart.logoutGuestAfterCheckout', $context->getSalesChannelId())) {
154
            $this->logoutRoute->logout($context, $dataBag);
155
        }
156
157
        return $this->renderStorefront('@Storefront/storefront/page/checkout/finish/index.html.twig', ['page' => $page]);
158
    }
159
160
    #[Route(path: '/checkout/order', name: 'frontend.checkout.finish.order', options: ['seo' => false], methods: ['POST'])]
161
    public function order(RequestDataBag $data, SalesChannelContext $context, Request $request): Response
162
    {
163
        if (!$context->getCustomer()) {
164
            return $this->redirectToRoute('frontend.checkout.register.page');
165
        }
166
167
        try {
168
            $this->addAffiliateTracking($data, $request->getSession());
169
170
            $orderId = Profiler::trace('checkout-order', fn () => $this->orderService->createOrder($data, $context));
171
        } catch (ConstraintViolationException $formViolations) {
172
            return $this->forwardToRoute('frontend.checkout.confirm.page', ['formViolations' => $formViolations]);
173
        } catch (InvalidCartException|Error|EmptyCartException) {
174
            $this->addCartErrors(
175
                $this->cartService->getCart($context->getToken(), $context)
176
            );
177
178
            return $this->forwardToRoute('frontend.checkout.confirm.page');
179
        } catch (UnknownPaymentMethodException|CartException $e) {
180
            if ($e->getErrorCode() === CartException::CART_PAYMENT_INVALID_ORDER_STORED_CODE && $e->getParameter('orderId')) {
181
                return $this->forwardToRoute('frontend.checkout.finish.page', ['orderId' => $e->getParameter('orderId'), 'changedPayment' => false, 'paymentFailed' => true]);
182
            }
183
            $message = $this->trans('error.' . $e->getErrorCode());
184
            $this->addFlash('danger', $message);
185
186
            return $this->forwardToRoute('frontend.checkout.confirm.page');
187
        }
188
189
        try {
190
            $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]);
191
            $errorUrl = $this->generateUrl('frontend.account.edit-order.page', ['orderId' => $orderId]);
192
193
            $response = Profiler::trace('handle-payment', fn (): ?RedirectResponse => $this->paymentService->handlePaymentByOrder($orderId, $data, $context, $finishUrl, $errorUrl));
194
195
            return $response ?? new RedirectResponse($finishUrl);
196
        } catch (PaymentProcessException|InvalidOrderException|UnknownPaymentMethodException) {
197
            return $this->forwardToRoute('frontend.checkout.finish.page', ['orderId' => $orderId, 'changedPayment' => false, 'paymentFailed' => true]);
198
        }
199
    }
200
201
    #[Route(path: '/widgets/checkout/info', name: 'frontend.checkout.info', defaults: ['XmlHttpRequest' => true], methods: ['GET'])]
202
    public function info(Request $request, SalesChannelContext $context): Response
203
    {
204
        $cart = $this->cartService->getCart($context->getToken(), $context);
205
        if ($cart->getLineItems()->count() <= 0) {
206
            return new Response(null, Response::HTTP_NO_CONTENT);
207
        }
208
209
        $page = $this->offcanvasCartPageLoader->load($request, $context);
210
211
        $this->hook(new CheckoutInfoWidgetLoadedHook($page, $context));
212
213
        $response = $this->renderStorefront('@Storefront/storefront/layout/header/actions/cart-widget.html.twig', ['page' => $page]);
214
        $response->headers->set('x-robots-tag', 'noindex');
215
216
        return $response;
217
    }
218
219
    #[Route(path: '/checkout/offcanvas', name: 'frontend.cart.offcanvas', options: ['seo' => false], defaults: ['XmlHttpRequest' => true], methods: ['GET'])]
220
    public function offcanvas(Request $request, SalesChannelContext $context): Response
221
    {
222
        $page = $this->offcanvasCartPageLoader->load($request, $context);
223
224
        $this->hook(new CheckoutOffcanvasWidgetLoadedHook($page, $context));
225
226
        $cart = $page->getCart();
227
        $this->addCartErrors($cart);
228
        $cartErrors = $cart->getErrors();
229
230
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
231
            $cartErrors->clear();
232
233
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
234
            return $this->redirectToRoute(
235
                'frontend.cart.offcanvas',
236
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
237
            );
238
        }
239
240
        $cartErrors->clear();
241
242
        return $this->renderStorefront('@Storefront/storefront/component/checkout/offcanvas-cart.html.twig', ['page' => $page]);
243
    }
244
245
    private function addAffiliateTracking(RequestDataBag $dataBag, SessionInterface $session): void
246
    {
247
        $affiliateCode = $session->get(AffiliateTrackingListener::AFFILIATE_CODE_KEY);
248
        $campaignCode = $session->get(AffiliateTrackingListener::CAMPAIGN_CODE_KEY);
249
        if ($affiliateCode) {
250
            $dataBag->set(AffiliateTrackingListener::AFFILIATE_CODE_KEY, $affiliateCode);
251
        }
252
253
        if ($campaignCode) {
254
            $dataBag->set(AffiliateTrackingListener::CAMPAIGN_CODE_KEY, $campaignCode);
255
        }
256
    }
257
258
    private function routeNeedsReload(ErrorCollection $cartErrors): bool
259
    {
260
        foreach ($cartErrors as $error) {
261
            if ($error instanceof ShippingMethodChangedError || $error instanceof PaymentMethodChangedError) {
262
                return true;
263
            }
264
        }
265
266
        return false;
267
    }
268
}
269