CartFormBuilder::buildForm()   B
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 38
rs 8.8571
cc 1
eloc 24
nc 1
nop 1
1
<?php
2
/*
3
 * WellCommerce Open-Source E-Commerce Platform
4
 *
5
 * This file is part of the WellCommerce package.
6
 *
7
 * (c) Adam Piotrowski <[email protected]>
8
 *
9
 * For the full copyright and license information,
10
 * please view the LICENSE file that was distributed with this source code.
11
 */
12
namespace WellCommerce\Bundle\OrderBundle\Form\Front;
13
14
use WellCommerce\Bundle\CoreBundle\Form\AbstractFormBuilder;
15
use WellCommerce\Bundle\OrderBundle\Context\OrderContext;
16
use WellCommerce\Bundle\OrderBundle\Entity\Order;
17
use WellCommerce\Bundle\OrderBundle\Entity\PaymentMethod;
18
use WellCommerce\Bundle\OrderBundle\Entity\ShippingMethod;
19
use WellCommerce\Bundle\OrderBundle\Entity\ShippingMethodCost;
20
use WellCommerce\Bundle\OrderBundle\Provider\Front\OrderProviderInterface;
21
use WellCommerce\Bundle\OrderBundle\Provider\PaymentMethodProviderInterface;
22
use WellCommerce\Bundle\OrderBundle\Provider\ShippingMethodOptionsProviderInterface;
23
use WellCommerce\Bundle\OrderBundle\Provider\ShippingMethodProviderInterface;
24
use WellCommerce\Component\Form\Elements\ElementInterface;
25
use WellCommerce\Component\Form\Elements\FormInterface;
26
use WellCommerce\Component\Form\Elements\Optioned\RadioGroup;
27
28
/**
29
 * Class CartFormBuilder
30
 *
31
 * @author  Adam Piotrowski <[email protected]>
32
 */
33
class CartFormBuilder extends AbstractFormBuilder
34
{
35
    public function getAlias(): string
36
    {
37
        return 'front.cart';
38
    }
39
    
40
    public function buildForm(FormInterface $form)
41
    {
42
        $order = $this->getOrderProvider()->getCurrentOrder();
43
        
44
        $shippingAddress = $form->addChild($this->getElement('nested_fieldset', [
45
            'name'  => 'shippingAddress',
46
            'label' => 'client.heading.shipping_address',
47
        ]));
48
        
49
        $shippingAddress->addChild($this->getElement('select', [
50
            'name'    => 'shippingAddress.country',
51
            'label'   => 'client.label.address.country',
52
            'options' => $this->get('country.repository')->all(),
53
            'default' => $this->getShopStorage()->getCurrentShop()->getDefaultCountry(),
54
        ]));
55
        
56
        $shippingMethods = $form->addChild($this->getElement('radio_group', [
57
            'name'        => 'shippingMethod',
58
            'label'       => 'order.label.shipping_method',
59
            'transformer' => $this->getRepositoryTransformer('entity', $this->get('shipping_method.repository')),
60
        ]));
61
        
62
        $this->addShippingMethods($order, $shippingMethods);
63
        
64
        $this->addShippingOptions($order, $form);
65
        
66
        $paymentMethods = $form->addChild($this->getElement('radio_group', [
67
            'name'        => 'paymentMethod',
68
            'label'       => 'order.label.payment_method',
69
            'transformer' => $this->getRepositoryTransformer('entity', $this->get('payment_method.repository')),
70
        ]));
71
        
72
        $this->addPaymentMethods($order, $paymentMethods);
73
        
74
        $form->addFilter($this->getFilter('no_code'));
75
        $form->addFilter($this->getFilter('trim'));
76
        $form->addFilter($this->getFilter('secure'));
77
    }
78
    
79
    /**
80
     * Adds shipping options if available for order's shipping method
81
     *
82
     * @param Order         $order
83
     * @param FormInterface $form
84
     */
85
    private function addShippingOptions(Order $order, FormInterface $form)
86
    {
87
        if ($order->getShippingMethod() instanceof ShippingMethod) {
88
            $provider = $this->getOptionsProvider($order->getShippingMethod());
89
            if ($provider instanceof ShippingMethodOptionsProviderInterface) {
90
                $form->addChild($this->getElement('select', [
91
                    'name'    => 'shippingMethodOption',
92
                    'label'   => 'order.label.shipping_method',
93
                    'options' => $provider->getShippingOptions(),
94
                ]));
95
            }
96
        }
97
    }
98
    
99
    /**
100
     * Adds shipping method options to select
101
     *
102
     * @param Order                       $order
103
     * @param ElementInterface|RadioGroup $radioGroup
104
     */
105
    private function addShippingMethods(Order $order, ElementInterface $radioGroup)
106
    {
107
        $collection = $this->getShippingMethodProvider()->getCosts(new OrderContext($order));
108
        
109
        $collection->map(function (ShippingMethodCost $shippingMethodCost) use ($radioGroup) {
110
            $shippingMethod = $shippingMethodCost->getShippingMethod();
111
            $baseCurrency   = $shippingMethod->getCurrency()->getCode();
112
            $grossAmount    = $shippingMethodCost->getCost()->getGrossAmount();
113
            
114
            $label = [
115
                'name'    => $shippingMethod->translate()->getName(),
116
                'comment' => $this->getCurrencyHelper()->convertAndFormat($grossAmount, $baseCurrency),
117
            ];
118
            
119
            $radioGroup->addOptionToSelect($shippingMethod->getId(), $label);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WellCommerce\Component\F...ements\ElementInterface as the method addOptionToSelect() does only exist in the following implementations of said interface: WellCommerce\Component\F...d\AbstractOptionedField, WellCommerce\Component\F...Elements\Optioned\Combo, WellCommerce\Component\F...Optioned\DataGridSelect, WellCommerce\Component\F...ts\Optioned\MultiSelect, WellCommerce\Component\F...\Optioned\ProductSelect, WellCommerce\Component\F...nts\Optioned\RadioGroup, WellCommerce\Component\F...lements\Optioned\Select.

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...
120
        });
121
    }
122
    
123
    /**
124
     * Adds payment method options to select
125
     *
126
     * @param Order                       $order
127
     * @param ElementInterface|RadioGroup $radioGroup
128
     */
129
    private function addPaymentMethods(Order $order, ElementInterface $radioGroup)
130
    {
131
        $paymentMethods = $this->getPaymentMethodProvider()->getPaymentMethodsForOrder($order);
132
        
133
        $paymentMethods->map(function (PaymentMethod $paymentMethod) use ($radioGroup) {
134
            $radioGroup->addOptionToSelect($paymentMethod->getId(), $paymentMethod->translate()->getName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WellCommerce\Component\F...ements\ElementInterface as the method addOptionToSelect() does only exist in the following implementations of said interface: WellCommerce\Component\F...d\AbstractOptionedField, WellCommerce\Component\F...Elements\Optioned\Combo, WellCommerce\Component\F...Optioned\DataGridSelect, WellCommerce\Component\F...ts\Optioned\MultiSelect, WellCommerce\Component\F...\Optioned\ProductSelect, WellCommerce\Component\F...nts\Optioned\RadioGroup, WellCommerce\Component\F...lements\Optioned\Select.

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...
135
        });
136
    }
137
    
138
    private function getShippingMethodProvider(): ShippingMethodProviderInterface
139
    {
140
        return $this->get('shipping_method.provider');
141
    }
142
    
143
    private function getPaymentMethodProvider(): PaymentMethodProviderInterface
144
    {
145
        return $this->get('payment_method.provider');
146
    }
147
    
148
    private function getOrderProvider(): OrderProviderInterface
149
    {
150
        return $this->get('order.provider.front');
151
    }
152
    
153
    private function getOptionsProvider(ShippingMethod $method)
154
    {
155
        $provider   = $method->getOptionsProvider();
156
        $collection = $this->get('shipping_method.options_provider.collection');
157
        
158
        if ($collection->containsKey($provider)) {
159
            return $collection->get($provider);
160
        }
161
        
162
        return null;
163
    }
164
}
165