I am trying to make a small e-commerce backend in php. I centralized all the static factory methods in a class called EntityFactory
. The main reason for that was to centralize the dependency on dependency injector and to make it easy for the client to initialize an entity.
class EntityFactory
{
private static $employeeRepository;
private static $productRepository;
public static function employee($name, $designation, $salary, $contactNo)
{
if (!self::$employeeRepository)
self::$employeeRepository = resolve(EmployeeRepository::class);
return new Employee(self::$employeeRepository, $name, $designation, $salary, $contactNo);
}
public static function product($itemCode, $name, Brand $brand, Category $category, $costPrice, $salePrice)
{
if (!self::$productRepository)
self::$productRepository = resolve(ProductRepository::class);
return new Product($itemCode, $name, $brand, $category, $costPrice, $salePrice, self::$productRepository);
}
public static function brand($name, $id = null)
{
$brand = new Brand($name);
if ($id) $brand->setId($id);
return $brand;
}
public static function category($name, $id = null)
{
$category = new Category($name);
if ($id) $category->setId($id);
return $category;
}
public static function purchaseOrder($customer, $user)
{
return new PurchaseOrder($customer, $user);
}
public static function orderItem($item, $qty)
{
return new PurchaseOrderItem($item, $qty);
}
}
The main problem I feel here is that this breaks SOLID, especially SRP, ISP and OCP. I may also face problems regarding unit testing in the future(though I've not yet faced one). The code for product purchasing:
//controller: create a new purchase order
public function store(Request $request)
{
$this->validateRequest($request);
$user = $this->productRepository->findById(Auth::id());
$customer = $this->supplierRepository->findById($request->input('id'));
$products = $request->input('products');
$purchaseOrder = EntityFactory::purchaseOrder($customer, $user);
foreach ($products as $product) {
$item = $this->productRepository->findById($product['id']);
$purchaseOrder->addItem(EntityFactory::orderItem($item, $product['qty']));
}
$this->purchaseOrderRepository->beginTransaction();
$this->purchaseOrderRepository->save($purchaseOrder);
$purchaseOrder->finalize();
$this->purchaseOrderRepository->endTransaction();
}
Here I can easily stub out the repositories in the controller
// class PurchaseOrder
public function addItem(PurchaseOrderItem $item)
{
$this->items[] = $item;
}
public function finalize()
{
foreach ($this->items as $item) {
$product = $item->getProduct();
$product->purchase($item->getQuantity());
}
}
//class Product
public function purchase($qty)
{
if ($this->stock - $qty < 0) {
throw new \RuntimeException("Product not available");
} else {
$this->stock += $qty;
$this->productRepository->update($this);
}
}
Here also the ProductOrderItem and Product can be stubbed.
I didn't want the factory to need instantiation. Otherwise I would have a dependency graph like factory -> repository -> mapper -> factory where mapper maps data from ORM classes to entity classes.
What are your thoughts?