This post is part of a series on [Code Challenges]().
This code challenge included a JSON representation of an ordering system api response and needed to output three (3) things:
- The highest order amount.
- The order totals for each of the past three (3) years.
- The name of the customer with the most orders.
Note: This code was intentionally written in a single-file for this exercise.
View on Repl.it.
<?php
declare(strict_types=1);
/**
* Class Item
*
* Represents an Order Item and functionality
*/
class Item {
/**
* @var string
*/
private string $name = '';
/**
* @var float
*/
private float $price = 0;
/**
* @param array $itemData
*
* @return Item
*/
public static function create(array $itemData): Item
{
return (new self())
->setName($itemData['name'])
->setPrice($itemData['price']);
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
*
* @return $this
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return float
*/
public function getPrice(): float
{
return $this->price;
}
/**
* @param float $price
*
* @return $this
*/
public function setPrice(float $price): self
{
$this->price = $price;
return $this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Class Order
*
* Represents a Customer Order and functionality
*/
class Order {
/**
* @var string
*/
private string $id = '';
/**
* @var string
*/
private string $customerId;
/**
* @var DateTime
*/
private DateTime $createdDate;
/**
* @var DateTime
*/
private DateTime $fulfilledDate;
/**
* @var CollectionOfItems
*/
private CollectionOfItems $orderItems;
/**
* @var float
*/
private float $totalPrice = 0;
/**
* @param array $orderData
* @param CollectionOfItems $orderItems
*
* @return Order
* @throws Exception
*/
public static function create(array $orderData, CollectionOfItems $orderItems): Order
{
return (new self())
->setId($orderData['id'])
->setCustomerId($orderData['customer_id'])
->setCreatedDate($orderData['created_date'])
->setFulfilledDate($orderData['fulfilled_date'])
->setOrderItems($orderItems)
->setTotalPrice($orderData['total_price']);
}
/**
* @return string
*/
public function getId(): string
{
return $this->id;
}
/**
* @param string $id
*
* @return $this
*/
public function setId(string $id): self
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getCustomerId(): string
{
return $this->customerId;
}
/**
* @param string $customerId
*
* @return $this
*/
public function setCustomerId(string $customerId): self
{
$this->customerId = $customerId;
return $this;
}
/**
* @return DateTime
*/
public function getCreatedDate(): DateTime
{
return $this->createdDate;
}
/**
* @param string $createdDate
*
* @return $this
* @throws Exception
*/
public function setCreatedDate(string $createdDate): self
{
$this->createdDate = new DateTime($createdDate);
return $this;
}
/**
* @return DateTime
*/
public function getFulfilledDate(): DateTime
{
return $this->fulfilledDate;
}
/**
* @param string $fulfilledDate
*
* @return $this
* @throws Exception
*/
public function setFulfilledDate(string $fulfilledDate): self
{
$this->fulfilledDate = new DateTime($fulfilledDate);
return $this;
}
/**
* @return CollectionOfItems
*/
public function getOrderItems(): CollectionOfItems
{
return $this->orderItems;
}
/**
* @param CollectionOfItems $orderItems
*
* @return $this
*/
public function setOrderItems(CollectionOfItems $orderItems): self
{
$this->orderItems = $orderItems;
return $this;
}
/**
* @param Item $item
*/
public function addItem(Item $item): void
{
$this->orderItems[$item->getName()] = $item;
}
/**
* @param string $id
*
* @return Item|null
*/
public function getItem(string $id): ?Item
{
return $this->items[$id] ?? null;
}
/**
* @return float
*/
public function getTotalPrice(): float
{
return $this->totalPrice;
}
/**
* @param float $totalPrice
*
* @return $this
*/
public function setTotalPrice(float $totalPrice): self
{
$this->totalPrice = $totalPrice;
return $this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Class Collection
*/
abstract class Collection implements Countable {
/**
* @var array
*/
protected $items = [];
/**
* @var array
*/
protected array $properties = [];
/**
* @param $items
*
* @return $this
*/
public function setItems($items): self
{
$this->items = $items;
return $this;
}
/**
* @param string $property
* @param bool $descending
*
* @return $this
* @throws Exception
*/
public function sortBy(string $property, bool $descending = true): self
{
if (!in_array($property, $this->properties, true)) {
throw new \RuntimeException("Invalid property {$property}");
}
usort($this->items, static function($itemA, $itemB) use ($property, $descending) {
if ($descending) {
return $itemA->{$property} - $itemB->{$property};
}
return $itemB->{$property} - $itemA->{$property};
});
return $this;
}
/**
* @return int
*/
public function count(): int
{
return count($this->items);
}
/**
* @return array
*/
public function toArray(): array
{
return $this->items;
}
/**
* @param callable $filter
*
* @return $this
*/
public function filter(callable $filter): self
{
return (new static)->setItems(array_filter($this->items, $filter));
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Class CollectionOfOrders
*/
class CollectionOfOrders extends Collection {
/**
* @var array|string[]
*/
protected array $properties = [
'id',
'customerId',
'createdDate',
'fulfilledDate',
'items',
'totalPrice',
];
/**
* @param Order $order
*
* @return $this
*/
public function addItem(Order $order): self
{
$this->items[] = $order;
return $this;
}
/**
* @return float
*/
public function sum(): float
{
$sum = 0;
foreach($this->items as $order) {
$sum += $order->getTotalPrice();
};
return $sum;
}
/**
* @param string $year
*
* @return CollectionOfOrders
*/
public function fromYear(string $year): CollectionOfOrders
{
return $this->filter(static function(Order $order) use ($year): bool {
return $order->getCreatedDate()->format('Y') === $year;
});
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Class CollectionOfItems
*/
class CollectionOfItems extends Collection {
/**
* @var array|string[]
*/
protected array $properties = [
'name',
'price',
];
/**
* @param Item $item
*
* @return $this
*/
public function addItem(Item $item): self
{
$this->items[] = $item;
return $this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Class Customer
*
* Represents a Customer and functionality
*/
class Customer {
/**
* @var string
*/
private string $id = '';
/**
* @var DateTime
*/
private DateTime $createdDate;
/**
* @var string
*/
private string $email = '';
/**
* @var string
*/
private string $name = '';
/**
* @var CollectionOfOrders
*/
private CollectionOfOrders $orders;
/**
* @param array $customerData
* @param CollectionOfOrders $orders
*
* @return Customer
* @throws Exception
*/
public static function create(array $customerData, CollectionOfOrders $orders): Customer
{
return (new self())
->setId($customerData['id'])
->setCreatedDate($customerData['created_date'])
->setEmail($customerData['email'])
->setName($customerData['name'])
->setOrders($orders);
}
/**
* @param string $id
*
* @return $this
*/
public function setId(string $id): self
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getId(): string
{
return $this->id;
}
/**
* @param string $createdDate
*
* @return $this
* @throws Exception
*/
public function setCreatedDate(string $createdDate): self
{
$this->createdDate = new DateTime($createdDate);
return $this;
}
/**
* @param string $email
*
* @return $this
*/
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
*
* @return $this
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @param Order $order
*/
public function addOrder(Order $order): void
{
$this->orders->addItem($order);
}
/**
* @param string $orderId
*
* @return Order|null
*/
public function getOrder(string $orderId): ?Order
{
return $this->orders[$orderId] ?? null;
}
/**
* @return CollectionOfOrders
*/
public function getOrders(): CollectionOfOrders
{
return $this->orders;
}
/**
* @param CollectionOfOrders $orders
*
* @return $this
*/
public function setOrders(CollectionOfOrders $orders): self
{
$this->orders = $orders;
return $this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
$response = json_decode(file_get_contents('data.json'), true, 512, JSON_THROW_ON_ERROR);
$customerData = $response['customers'];
$orderData = $response['orders'];
$customersArray = [];
$ordersArray = [];
foreach ($orderData as $orderDatum) {
$customerId = $orderDatum['customer_id'];
$orders = $ordersArray[$customerId] ?? new CollectionOfOrders();
$itemsCollection = new CollectionOfItems();
foreach ($orderDatum['items'] as $item) {
$itemsCollection->addItem(Item::create($item));
}
$orders->addItem(Order::create($orderDatum, $itemsCollection));
$ordersArray[$customerId] = $orders;
}
foreach($response['customers'] as $customer) {
$orderData = $ordersArray[$customer['id']] ?? new CollectionOfOrders();
$customersArray[] = Customer::create($customer, $orderData);
}
/**
* Output the most expensive order
*/
$mostExpensiveOrderTotal = 0;
$allOrders = [];
foreach ($customersArray as $customer) {
if (empty($customer->getOrders())) {
continue;
}
$allOrders = array_merge($allOrders, $customer->getOrders()->toArray());
}
usort( $allOrders, static function(Order $orderA, Order $orderB) {
return $orderB->getTotalPrice() - $orderA->getTotalPrice();
});
$mostExpensiveOrderTotal = $allOrders[0]->getTotalPrice();
echo 'Most expensive order = ' . sprintf('%.2f', $mostExpensiveOrderTotal) . "\n";
/**
* Output the total order price for the past 3 years.
*/
foreach ([2, 1, 0] as $yearsBack) {
$year = date('Y', strtotime("-{$yearsBack} year"));
$ordersTotal = 0;
foreach($customersArray as $customer) {
$ordersTotal += $customer->getOrders()->fromYear((string)$year)->sum();
};
echo "Total price of orders in {$year} = " . sprintf('%.2f', $ordersTotal) . "\n";
}
/**
* Output the customer with the most orders
*/
$sortedCustomers = $customersArray;
usort( $sortedCustomers, static function(Customer $customerA, Customer $customerB): int {
return $customerB->getOrders()->count() - $customerA->getOrders()->count();
});
$customerWithMostOrders = $sortedCustomers[0];
echo "Customer with the most orders = [{$customerWithMostOrders->getId()}] {$customerWithMostOrders->getName()}\n";
{
"customers" : [
{
"id": "CUST-0001",
"created_date": "2018-05-12",
"email": "yoda@example.com",
"name": "Yoda"
},
{
"id": "CUST-0002",
"created_date": "2018-09-23",
"email": "spock@example.com",
"name": "Spock"
},
{
"id": "CUST-0003",
"created_date": "2019-03-30",
"email": "thedoctor@example.com",
"name": "Doctor Who"
},
{
"id": "CUST-0004",
"created_date": "2019-04-03",
"email": "frodo@example.com",
"name": "Frodo"
},
{
"id": "CUST-0005",
"created_date": "2019-04-21",
"email": "hpotter@example.com",
"name": "Harry Potter"
},
{
"id": "CUST-0006",
"created_date": "2019-12-12",
"email": "tanderson@example.com",
"name": "Neo"
}
],
"orders": [
{
"id": "ORDER-0001",
"customer_id": "CUST-0001",
"created_date": "2018-05-12",
"fulfilled_date": "2018-05-15",
"items": [
{
"name": "ITEM-0294",
"price": 100.00
},
{
"name": "ITEM-0145",
"price": 100.00
}
],
"total_price": 200.00
},
{
"id": "ORDER-0002",
"customer_id": "CUST-0002",
"created_date": "2018-09-23",
"fulfilled_date": "2018-09-27",
"items": [
{
"name": "ITEM-0025",
"price": 50.00
},
{
"name": "ITEM-0027",
"price": 25.00
}
],
"total_price": 75.00
},
{
"id": "ORDER-0003",
"customer_id": "CUST-0001",
"created_date": "2019-02-14",
"fulfilled_date": "2019-02-18",
"items": [
{
"name": "ITEM-0389",
"price": 100.00
}
],
"total_price": 100.00
},
{
"id": "ORDER-0004",
"customer_id": "CUST-0003",
"created_date": "2019-03-30",
"fulfilled_date": "2019-04-04",
"items": [
{
"name": "ITEM-0002",
"price": 400.00
},
{
"name": "ITEM-0389",
"price": 100.00
}
],
"total_price": 500.00
},
{
"id": "ORDER-0005",
"customer_id": "CUST-0005",
"created_date": "2019-04-21",
"fulfilled_date": "2019-04-22",
"items": [
{
"name": "ITEM-0089",
"price": 20.00
}
],
"total_price": 20.00
},
{
"id": "ORDER-0006",
"customer_id": "CUST-0002",
"created_date": "2019-05-01",
"fulfilled_date": "2019-05-08",
"items": [
{
"name": "ITEM-0003",
"price": 50.00
}
],
"total_price": 50.00
},
{
"id": "ORDER-0007",
"customer_id": "CUST-0005",
"created_date": "2019-09-13",
"fulfilled_date": "2019-09-15",
"items": [
{
"name": "ITEM-0092",
"price": 20.00
},
{
"name": "ITEM-0093",
"price": 20.00
}
],
"total_price": 40.00
},
{
"id": "ORDER-0008",
"customer_id": "CUST-0006",
"created_date": "2019-12-12",
"fulfilled_date": "2020-01-05",
"items": [
{
"name": "ITEM-0312",
"price": 50.00
}
],
"total_price": 50.00
},
{
"id": "ORDER-0009",
"customer_id": "CUST-0001",
"created_date": "2019-12-13",
"fulfilled_date": "2020-01-11",
"items": [
{
"name": "ITEM-0212",
"price": 100.00
}
],
"total_price": 100.00
},
{
"id": "ORDER-0010",
"customer_id": "CUST-0006",
"created_date": "2020-01-03",
"fulfilled_date": "2020-01-15",
"items": [
{
"name": "ITEM-0077",
"price": 20.00
}
],
"total_price": 20.00
}
]
}
Show Comments