2
\$\begingroup\$

I built an application that extracts and updates data from multiple ecommerces websites. Each eCommerce is either built using platforms/frameworks such as Shopify, Prestashop or WooCommerce. This being said, each Shop has a different webservice that I invoke using Shop->getWebservice(). This method returns a different class depending on the platform/framework used by a Shop.

So when I call Shop->getWebservice() I can get either get ShopifyFactory, WooCommerceFactory or PrestashopFactory. All of these three classes extend the ShopFrameworkFactory that dictates which methods all these three classes must implement.

The aspect I'm focusing on is running the function updatedOrderStatesFromShop which updates the different states an order can have in a given a Shop. An order state is the status of an order, for instance it can be : awaiting payment, paid, prepared, shipped, delivered, cancelled, etc.

So the process is as follows:

  1. Invoke updateOrderStatesFromShop(Shop $shop) with a shop object.
  2. Shop->getWebservice returns an instance of (for the example) PrestashopFactory
  3. PrestashopFactory->getOrderStatuses which should return an array of value objects called ShopOrderState that encapsulate the information of an Order State
  4. The method PrestashopFactory->getOrderStatuses iterates through an array of XML objects, instantiates a new ShopOrderState Value Object and runs ShopOrderState->hydratePrestashop() which parses the XML object to itself
  5. I retrive all known Order States of given Shop from the database
  6. I iterate through the array of ShopOrderState from bullet 3
  7. I try to find a match comparing ShopOrderState->getId() method from the value object with OrderState->getShopOrderId() method from the Entity.
  8. If there is a match I invoke a method from the entity OrderState->hydrateOrderStateVO(ShopOrderState) which updates each value from the value object to itself
  9. I save changes made by the update

My concerns are around the bold in bullet 5 and bullet 8, see how the XML Object is then parsed to a Value Object and then this value object is then parsed to the Entity.

I have stripped away a lot of code in order to get the essentials of what's the reasoning here. There are actually 3 functions in my ShopOrderState Value Object that hydrate depending on the platform. hydratePrestashop the one shown here, there is also hydrateWoocommerce and hydrateShopify. Each hydrate the object differently because they come with different names and formats and these hydrate function are used as an adapter in order to convert the information given by a platform to the standards of my application.

Shouldn't these hydrate functions be in a separate service in my application ? Is there some sort of pattern design for such process ?

As a matter of fact, there are many other things that are updated from the eCommerces to my application, I focused on one to illustrate the issue but other such as sales, carts, products, etc are also updated. I wonder if there isn't a different approach to do this as it seems pretty difficult to mantain and explain how it works.

The service :

class OrderStateLogic
{

    private $em;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->em = $entityManager;
    }

    public function updateOrderStatesFromShop(Shop $shop)
    {

        try {

            $shopOrderStates = $shop->getWebservice()->getOrderStatuses();

        } catch (\Exception $exception) {
            return false;
        }

        if (! is_array($shopOrderStates) || empty($shopOrderStates)) {
            return false;

        }

        $orderStates = $this->em->getRepository(OrderState::class)->findBy(['shop' => $shop]);

        foreach ($shopOrderStates as $shopOrderState) {

            if (! $shopOrderState instanceof ShopOrderState) {
                continue;
            }

            $updated = false;

            if (is_array($orderStates) && ! empty($orderStates)) {

                foreach ($orderStates as $orderState) {

                    if ($shopOrderState->getId() === $orderState->getShopOrderId()) {

                        $orderState->hydrateOrderStateVO($shopOrderState);
                        $updated = true;
                    }

                }

            }

            if(!$updated){

                $newOrderState = new OrderState();
                $newOrderState->hydrateOrderStateVO($shopOrderState);
                $newOrderState->setShop($shop);
                $this->em->persist($newOrderState);
            }

        }

        $this->em->flush();

        return true;
    }

}

The ShopOrderState is the Value Object used to have a common object between all different platforms

    class ShopOrderState
    {

        private $id;
        private $name;
        private $paid;
        private $delivered;
        private $shipped;

        // Getter and setters

        public function hydratePrestashop($prestashopEntity): self
        {

            $this->setId((int)$prestashopEntity->id);
            $this->setDelivered((int)$prestashopEntity->delivery);
            $this->setPaid((int)$prestashopEntity->paid);
            $this->setShipped((int)$prestashopEntity->shipped);
            $this->setName((string)$prestashopEntity->name->language[ 0 ]);

            return $this;

        }
    }

The OrderState is the Entity.

class OrderState
{

    private $id;
    private $shopOrderId;
    private $name;
    private $paid;
    private $delivered;
    private $shipped;
    private $createdAt;
    private $updatedAt;
    private $shop;

    // Setter and getters

    /**
     * Hydrates the object from the OrderState Value Object.
     *
     * @param ShopOrderState $orderState
     *
     * @return $this
     */
    public function hydrateOrderStateVO(ShopOrderState $orderState) :self
    {
        $this->setShopOrderId($orderState->getId());
        $this->setName($orderState->getName());
        $this->setPaid($orderState->isPaid());
        $this->setDelivered($orderState->isDelivered());
        $this->setShipped($orderState->isShipped());

        return $this;

    }

}

The Shop Entity looks like this.

As you can see depending on the $platform value it will return a different class.

class Shop
{
    private $id;
    private $shopName;
    private $domain;
    private $platform;
    private $apiKey;
    private $createdAt;
    private $updatedAt;

    // Getters and setters

    public function getWebservice(): ShopFrameworkFactory
    {
        switch ($this->getPlatform()){

            case ShopFrameworkFactory::SHOPIFY :
                return new ShopifyFactory($this);
                break;
            case ShopFrameworkFactory::WOOCOMMERCE :
                return new WooCommerceFactory($this);
                break;
            case ShopFrameworkFactory::PRESTASHOP :
                return new PrestashopFactory($this);
                break;
            default :
                return new PrestashopFactory($this);
                break;
        }
    }

}

Here is the ShopFrameworkFactory that draws what each webservice should return

abstract class ShopFrameworkFactory
{

    public const SHOPIFY = 1;
    public const PRESTASHOP = 2;
    public const WOOCOMMERCE = 3;

    private $shop;
    private $debug;

    public function __construct (Shop $shop, $debug = false) {
        $this->shop = $shop;
        $this->debug = $debug;
    }

    abstract public function getOrderStatuses();

}

And lastly one of the 3 ecommerce platforms bridge

class PrestashopFactory extends ShopFrameworkFactory {

    public function getOrderStatuses()
    {
        try {
            /**
             * @var $configuration \SimpleXMLElement
             */
            $orderStates = $this->ws->get(
                array (
                    'resource' => 'order_states',
                    'display' => 'full'
                )
            );
        } catch (\PrestaShopWebserviceException $exception) {

            return $exception;
        }

        $orderStatesFormatted = [];

        foreach ($orderStates->order_states->order_state as $orderState) {

            $orderStateVO = new ShopOrderState();
            $orderStateVO->hydratePrestashop($orderState);

            $orderStatesFormatted[] = $orderStateVO;
        }

        return $orderStatesFormatted;
    }

}
\$\endgroup\$
  • 2
    \$\begingroup\$ I will speak purely for myself: I find your explanation and code quite confusing. I am missing a basic and clear explanation, at the beginning of your question, of what your code is exactly supposed to do. I can try to guess, but should I? A bit of context would also help greatly. \$\endgroup\$ – KIKO Software Oct 3 '18 at 17:45
  • \$\begingroup\$ Hi @KIKOSoftware i have updated the description \$\endgroup\$ – db306 Oct 3 '18 at 22:06
  • \$\begingroup\$ @KIKOSoftware I have completelly reformatted the question in order to be clear and intelligible to you. Feedback would be highly appreciated. \$\endgroup\$ – db306 Oct 4 '18 at 7:57
  • \$\begingroup\$ I appreaciate the effort you took to improve your question. I have really tried to understand how your code hangs to together, but I couldn't work it out. To be clear, I do understand what you're trying to achieve. It's just that I cannot figure out how you did it. One thing that I have noticed is that a lot of your classes contain references to a specific shop framework: ShopOrderState, OrderState, Shop, ShopFrameworkFactory and of course PrestashopFactory. If you want to add another shop platform, you will have to modify all these classes, which is not a desireable situation. \$\endgroup\$ – KIKO Software Oct 4 '18 at 10:14
  • \$\begingroup\$ @KIKOSoftware thank you very much for your effort trying to mingle out my code which is not obvious, and hence, my request for feedback. Your comment was useful, I'm going to try out decoupling specific frameworks from the other classes. \$\endgroup\$ – db306 Oct 4 '18 at 11:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.