Completed
Push — master ( c2123c...66812a )
by
unknown
12:37
created

CartStrategy::findExistingCustomer()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 14
Ratio 100 %

Importance

Changes 0
Metric Value
dl 14
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
1
<?php
2
3
namespace OroCRM\Bundle\MagentoBundle\ImportExport\Strategy;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
7
use Oro\Bundle\AddressBundle\Entity\Region;
8
use Oro\Bundle\IntegrationBundle\Entity\Channel;
9
10
use OroCRM\Bundle\MagentoBundle\Entity\Cart;
11
use OroCRM\Bundle\MagentoBundle\Entity\CartAddress;
12
use OroCRM\Bundle\MagentoBundle\Entity\CartStatus;
13
use OroCRM\Bundle\MagentoBundle\Entity\Customer;
14
use OroCRM\Bundle\MagentoBundle\Entity\MagentoSoapTransport;
15
use OroCRM\Bundle\MagentoBundle\ImportExport\Converter\GuestCustomerDataConverter;
16
use OroCRM\Bundle\MagentoBundle\Provider\Reader\ContextCustomerReader;
17
18
class CartStrategy extends AbstractImportStrategy
19
{
20
    /**
21
     * @var Cart
22
     */
23
    protected $existingEntity;
24
25
    /**
26
     * @var array
27
     */
28
    protected $existingCartItems;
29
30
    /**
31
     * @param Cart $entity
32
     *
33
     * {@inheritdoc}
34
     */
35
    protected function beforeProcessEntity($entity)
36
    {
37
        $this->existingEntity = $this->databaseHelper->findOneByIdentity($entity);
38
        if ($this->existingEntity) {
39
            $this->existingCartItems = $this->existingEntity->getCartItems()->toArray();
40
        } else {
41
            $this->existingEntity = $entity;
42
        }
43
44
        return parent::beforeProcessEntity($entity);
45
    }
46
47
    /**
48
     * @param Cart $entity
49
     *
50
     * {@inheritdoc}
51
     */
52 View Code Duplication
    public function process($entity)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
    {
54
        if (!$this->isProcessingAllowed($entity)) {
55
            $this->appendDataToContext(
56
                CartWithExistingCustomerStrategy::CONTEXT_CART_POST_PROCESS,
57
                $this->context->getValue('itemData')
58
            );
59
60
            return null;
61
        }
62
63
        return parent::process($entity);
64
    }
65
66
    /**
67
     * @param Cart $cart
68
     * @return bool
69
     */
70
    protected function isProcessingAllowed(Cart $cart)
71
    {
72
        $isProcessingAllowed = true;
73
74
        if ($cart->getCustomer()) {
75
            $customer = $this->findExistingCustomer($cart);
76
            $customerOriginId = $cart->getCustomer()->getOriginId();
77
            if (!$customer && $customerOriginId) {
78
                $this->appendDataToContext(ContextCustomerReader::CONTEXT_POST_PROCESS_CUSTOMERS, $customerOriginId);
79
80
                $isProcessingAllowed = false;
81
            }
82
83
            /**
84
             * If registered customer add items to cart in Magento
85
             * but after customer was deleted in Magento before customer and cart were synced
86
             * Cart will not have connection to the customer
87
             * Customer for such Carts should be processed as guest if Guest Customer synchronization is allowed
88
             */
89
            if (!$customer && !$customerOriginId) {
90
                /** @var Channel $channel */
91
                $channel = $this->databaseHelper->findOneByIdentity($cart->getChannel());
92
                /** @var MagentoSoapTransport $transport */
93
                $transport = $channel->getTransport();
94
                if ($transport->getGuestCustomerSync()) {
95
                    $this->appendDataToContext(
96
                        'postProcessGuestCustomers',
97
                        GuestCustomerDataConverter::extractCustomersValues((array)$this->context->getValue('itemData'))
98
                    );
99
100
                    $isProcessingAllowed = false;
101
                }
102
            }
103
        }
104
105
        return $isProcessingAllowed;
106
    }
107
108
    /**
109
     * Get existing registered customer or existing guest customer
110
     * If customer not found by Identifier
111
     * find existing customer using entity data for entities containing customer like Order and Cart
112
     *
113
     * @param Cart $entity
114
     *
115
     * @return null|Customer
116
     */
117 View Code Duplication
    protected function findExistingCustomer($entity)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
    {
119
        $existingEntity = null;
120
        $customer = $entity->getCustomer();
121
122
        if ($customer->getId() || $customer->getOriginId()) {
123
            $existingEntity = parent::findExistingEntity($customer);
0 ignored issues
show
Bug introduced by
It seems like $customer defined by $entity->getCustomer() on line 120 can be null; however, Oro\Bundle\ImportExportB...y::findExistingEntity() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (findExistingEntity() instead of findExistingCustomer()). Are you sure this is correct? If so, you might want to change this to $this->findExistingEntity().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
124
        }
125
        if (!$existingEntity) {
126
            $existingEntity = $this->findExistingCustomerByContext($entity);
127
        }
128
129
        return $existingEntity;
130
    }
131
132
    /**
133
     * @param Cart $entity
134
     *
135
     * {@inheritdoc}
136
     */
137
    protected function afterProcessEntity($entity)
138
    {
139
        if ($this->existingEntity->getStatus()->getName() === CartStatus::STATUS_OPEN) {
140
            $this->updateRemovedCartItems($entity);
141
        }
142
143
        if (!$this->hasContactInfo($entity)) {
144
            return null;
145
        }
146
147
        $this
148
            ->updateCustomer($entity)
149
            ->updateAddresses($entity)
150
            ->updateCartItems($entity)
151
            ->updateCartStatus($entity);
152
153
        $now = new \DateTime('now', new \DateTimeZone('UTC'));
154
        if (!$entity->getImportedAt()) {
155
            $entity->setImportedAt($now);
156
        }
157
        $entity->setSyncedAt($now);
158
159
        $this->existingEntity = null;
160
        $this->existingCartItems = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $existingCartItems.

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..

Loading history...
161
162
        return parent::afterProcessEntity($entity);
163
    }
164
165
    /**
166
     * Update removed cart items - set `removed` field to true if cart item was removed from a cart
167
     *
168
     * @param Cart $entity
169
     */
170
    protected function updateRemovedCartItems(Cart $entity)
171
    {
172
        if ((int)$entity->getItemsQty() === 0) {
173 View Code Duplication
            foreach ($entity->getCartItems() as $cartItem) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
                if (!$cartItem->isRemoved()) {
175
                    $cartItem->setUpdatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
176
                    $cartItem->setRemoved(true);
177
                }
178
            }
179
        } elseif ($this->existingCartItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->existingCartItems of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
180
            $existingCartItems = new ArrayCollection($this->existingCartItems);
181
            $newCartItems = $entity->getCartItems();
182
183 View Code Duplication
            foreach ($existingCartItems as $existingCartItem) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
                if (!$newCartItems->contains($existingCartItem)) {
185
                    if (!$existingCartItem->isRemoved()) {
186
                        $existingCartItem->setUpdatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
187
                        $existingCartItem->setRemoved(true);
188
                    }
189
                }
190
            }
191
        }
192
    }
193
194
    /**
195
     * Update Customer email
196
     *
197
     * @param Cart $cart
198
     *
199
     * @return CartStrategy
200
     */
201
    protected function updateCustomer(Cart $cart)
202
    {
203
        $customer = $cart->getCustomer();
204
        if ($customer && !$customer->getEmail()) {
205
            $customer->setEmail($cart->getEmail());
206
        }
207
208
        return $this;
209
    }
210
211
    /**
212
     * @param Cart $cart
213
     *
214
     * @return CartStrategy
215
     */
216
    protected function updateCartItems(Cart $cart)
217
    {
218
        foreach ($cart->getCartItems() as $cartItem) {
219
            $cartItem->setOwner($cart->getOrganization());
220
            $cartItem->setCart($cart);
221
        }
222
223
        return $this;
224
    }
225
226
    /**
227
     * @param Cart $entity
228
     *
229
     * @return CartStrategy
230
     */
231
    protected function updateAddresses(Cart $entity)
232
    {
233
        $addresses = ['shippingAddress', 'billingAddress'];
234
235
        foreach ($addresses as $addressName) {
236
            /** @var CartAddress $address */
237
            $address = $this->getPropertyAccessor()->getValue($entity, $addressName);
238
239
            if (!$address) {
240
                continue;
241
            }
242
243
            // at this point imported address region have code equal to region_id in magento db field
244
            $mageRegionId = $address->getRegion() ? $address->getRegion()->getCode() : null;
245
            $this->addressHelper->updateAddressCountryRegion($address, $mageRegionId);
246
            if ($address->getCountry()) {
247
                $this->getPropertyAccessor()->setValue($entity, $addressName, $address);
248
            } else {
249
                $this->getPropertyAccessor()->setValue($entity, $addressName, null);
250
            }
251
        }
252
253
        return $this;
254
    }
255
256
    /**
257
     * @param Cart $entity
258
     * @return null
259
     */
260
    protected function hasContactInfo(Cart $entity)
261
    {
262
        $hasContactInfo = ($entity->getBillingAddress() && $entity->getBillingAddress()->getPhone())
263
            || $entity->getEmail();
264
265
        if (!$hasContactInfo) {
266
            $this->context->incrementErrorEntriesCount();
267
            $this->logger->debug(
268
                sprintf('Cart ID: %d was skipped because lack of contact info', $entity->getOriginId())
269
            );
270
271
            return false;
272
        }
273
274
        return true;
275
    }
276
277
    /**
278
     * Update cart status
279
     *
280
     * @param Cart $cart
281
     *
282
     * @return CartStrategy
283
     */
284
    protected function updateCartStatus(Cart $cart)
285
    {
286
        // allow to modify status only for "open" carts
287
        // because magento can only expire cart, so for different statuses this useless
288
        if ($this->existingEntity->getStatus()->getName() !== CartStatus::STATUS_OPEN) {
289
            $status = $this->existingEntity->getStatus();
290
        } else {
291
            $status = $cart->getStatus();
292
        }
293
294
        $cart->setStatus($status);
295
296
        return $this;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    protected function findExistingEntity($entity, array $searchContext = [])
303
    {
304
        $existingEntity = null;
0 ignored issues
show
Unused Code introduced by
$existingEntity is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
305
306
        if ($entity instanceof Region) {
307
            /** @var \OroCRM\Bundle\MagentoBundle\Entity\Region $existingEntity */
308
            $existingEntity = $this->findRegionEntity($entity);
309 View Code Duplication
        } elseif ($entity instanceof Customer && !$entity->getOriginId() && $this->existingEntity) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
            /**
311
             * Get existing customer entity
312
             * As guest customer entity not exist in Magento as separate entity and saved in order
313
             * find guest by customer email
314
             */
315
            $existingEntity = $this->findExistingCustomerByContext($this->existingEntity);
316
        } else {
317
            $existingEntity = parent::findExistingEntity($entity, $searchContext);
318
        }
319
320
        return $existingEntity;
321
    }
322
323
    /**
324
     * Add cart customer email to customer search context
325
     *
326
     * {@inheritdoc}
327
     */
328
    protected function getEntityCustomerSearchContext($cart)
329
    {
330
        /** @var Cart $cart */
331
        $searchContext = parent::getEntityCustomerSearchContext($cart);
332
        $searchContext['email'] = $cart->getEmail();
333
334
        return $searchContext;
0 ignored issues
show
Best Practice introduced by
The expression return $searchContext; seems to be an array, but some of its elements' types (string) are incompatible with the return type of the parent method OroCRM\Bundle\MagentoBun...tyCustomerSearchContext of type array<string,object>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
335
    }
336
}
337