<?php


bootstrap();
function bootstrap()
{
    // Инициализируем всё.
    $users = new UserStore();
    $userCarts = new UserCartStore();
    $products = new ProductStore();
    $controller = new CartController(
        $users,
        $userCarts,
        $products
    );
    $httpHandler = new HttpCartHandler(
        $controller
    );
    $router = new Router();
    
    // Настраиваем роутер.
    $router->onNothing(static function () {
        print 'Unexpected route';
    });
    $router->addRoute('/cart/add', static function () use ($httpHandler) {
        $httpHandler->addProductToCart();
    });
    
    // Запускаем роутер, он вызовет хендлер, тот контроллер, а тот базы данных.
    $router->route($_SERVER['REQUEST_URI']); // REQUEST_URI - это не совсем то, я для примера написал.
}

class Router
{
    protected $routes = [];
    protected $onNothing;

    public function addRoute($path, callable $callback)
    {
        $this->routes[$path] = $callback;
    }

    public function onNothing(callable $callback)
    {
        $this->onNothing = $callback;
    }

    public function route($path)
    {
        if (!empty($this->routes[$path])) {
            $this->routes[$path]();
        } elseif (!empty($this->onNothing)) {
            ($this->onNothing)();
        } else {
            throw new Exception('No "nothing" callback was configured');
        }
    }

}


class HttpCartHandler
{
    
    /** @var CartController */
    protected $cartController;

    public function __construct(CartController $cartController)
    {
        $this->cartController = $cartController;
    }

    public function addProductToCart()
    {
        $userId = $_POST['user_id'] ?? null;
        $productId = $_POST['product_id'] ?? null;
        
        try {
            $this->cartController->addProductToCart(
                $userId,
                $productId
            );
            print 'Successfully added'; // Для принтов по идее нужен отдельный класс-Renderer;
        } catch (InvalidArgumentException $e) {
            print 'Invalid argument: ' . $e->getMessage();
        } catch (\Throwable $e) {
            error_log((string) $e);
            print 'Internal error';
        }
    }
}

class CartController
{
    // В реальном коде тут были бы интерфейсы, я же пока захардкодил сразу классы.
    /** @var UserStore */
    protected $users;
    /** @var UserCartStore */
    protected $userCarts;
    /** @var ProductStore */
    protected $products;

    /**
     * @param UserStore $users
     * @param UserCartStore $userCarts
     * @param ProductStore $products
     */
    public function __construct($users, $userCarts, $products)
    {
        $this->users = $users;
        $this->userCarts = $userCarts;
        $this->products = $products;
    }

    public function addProductToCart($userId, $productId)
    {
        $user = $this->users->load($userId);
        if ($user === null) {
            throw new InvalidArgumentException('invalid user id');
        }
        $product = $this->products->load($productId);
        if ($product === null) {
            throw new InvalidArgumentException('invalid product id');
        }
        
        $this->userCarts->addProduct($userId, $productId);
    }
}

class UserCartStore
{
    public function addProduct($userId, $productId)
    {

    }
}

class UserStore
{
    public function load($userId)
    {

    }
}

class ProductStore
{
    public function load($productId)
    {

    }
}
