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:

  1. The highest order amount.
  2. The order totals for each of the past three (3) years.
  3. 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
        }
    ]
}
Tagged in: