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:
- Invoke
updateOrderStatesFromShop(Shop $shop)
with a shop object. Shop->getWebservice
returns an instance of (for the example)PrestashopFactory
PrestashopFactory->getOrderStatuses
which should return an array of value objects calledShopOrderState
that encapsulate the information of an Order State- The method
PrestashopFactory->getOrderStatuses
iterates through an array of XML objects, instantiates a new ShopOrderState Value Object and runsShopOrderState->hydratePrestashop()
which parses the XML object to itself - I retrive all known Order States of given
Shop
from the database - I iterate through the array of
ShopOrderState
from bullet 3 - I try to find a match comparing
ShopOrderState->getId()
method from the value object withOrderState->getShopOrderId()
method from the Entity. - 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 - 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;
}
}
ShopOrderState
,OrderState
,Shop
,ShopFrameworkFactory
and of coursePrestashopFactory
. If you want to add another shop platform, you will have to modify all these classes, which is not a desireable situation. \$\endgroup\$