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