• Час читання ~9 хв
  • 23.03.2023
Image by Annie Ruygt

Fly робить зображення Docker, перетворює його на віртуальну машину і запускає цю віртуальну машину в будь-якій точці світу. Запустіть програму Laravel за лічені хвилини!

Кошики для покупок не спустошуються, коли користувач залишає сторінку. Чому? Ну, для цього мені потрібно буде повернути вас у минуле. Киньте хвилястий флешбек-перехід!

Колись давно був повсякденний, звичайний, нормальний хлопець на ім'я Джек. Джек любив робити покупки в Інтернеті і часто годинами переглядав різні веб-сайти, щоб отримати найкращі пропозиції. Він додавав товари до свого кошика для покупок, але, здавалося, ніколи не замовляв їх. Натомість він часто перевіряв свій кошик, милуючись вибраними предметами, але насправді ніколи не натискав кнопку «купити». Джек замовив би їх лише тоді, коли був повністю стовідсотково впевнений, що хоче їх.

Чи знаю я Джека?

Так, у мене є звичка наповнювати візки для покупок речами, які я замовлю через місяці, або просто відверто забути. Згенеровані чатом казки про недоліки мого персонажа осторонь, ми тут, щоб поговорити про справжніх героїв історії: кошики для покупок, які невдячно тримають наші можливі майбутні покупки в різних веб-магазинах. Не забудьте колись подякувати їм за службу.

У будь-якому випадку, давайте побудуємо його, щоб побачити, як вони працюють!

Це теми, які торкається ця стаття :

  • http сесія, і фасад сесії в Laravel шаблон сервісу
  • шаблон сервісу
  • Грошовий пакет
  • Laravel
  • колекції

The Goal

Our end goal will be to create a new order with all the products that the user has in their shopping cart. To enable that, we'll need some шаблон сервісу components for updating the values without reloading the page as well as a shopping cart that keeps track of the products of our users (the users's products?). Quick spoiler, we'll use a Service class for that and in there we'll use Laravel's Session facade.

Отже, ми побудуємо дуже голий веб-магазин. Я постараюся, щоб він був коротким і простим у два кроки: додавання товарів у кошик і використання кошика для оформлення замовлення.

Брифінг закінчено, давайте будувати!

The Models

У нашому інтернет-магазині barebones у нас є 2 моделі, про які можна турбуватися:

  • таблиця товару з ідентифікатором, позначками часу, назвою, описом, ціною та знижкою. Це містить кілька замовлень.
  • таблиця "Замовлення", яка належить одному Користувачеві, має ідентифікатор і позначки часу та містить кілька Продуктів.

Оскільки це зв'язок «багато-до-багатьох», нам знадобиться зведена таблиця. Це буде містити іноземні ідентифікатори для Товару та Замовлення, а також поля для знижки (знижки товару на момент замовлення) та суми (замовлених товарів). Ось як налаштовані моделі продуктів і замовлень:

class Product extends Model
{
    //other stuff here

    public function orders()
    {
        return $this->belongsToMany(Order::class)->withPivot('discount', 'amount');
    }
}

class Order extends Model
{
    //other stuff here

    public function products()
    {
        return $this->belongsToMany(Product::class)->withPivot('discount', 'amount');
    }
}

Laravel автоматично вважатиме назву зведеної таблиці [alphabetically first model name]\_[alphabetically first model name], що в order\_product нашому випадку. Ларавель також вважатиме, що іноземне посвідчення особи буде називатися product_id і order_id. Метод withPivot дозволяє нам додавати додаткові стовпці в таблицю, як і amount в нашому випадку, як discount показано в документах.

Ось як виглядатимуть наші таблиці:

Adding Products to the Shopping Cart

Building the Shopping Cart Service

служба кошика для покупок оброблятиме все зберігання та отримання даних у/з сеансу http. Таким чином, ми можемо повторно використовувати код і вводити його в усі компоненти, де це необхідно.

Створіть папку app/Services і там зробіть новий клас php під назвою CartService.php. Перш ніж ми почнемо додавати продукти в кошик, нам потрібно поговорити про те, які дані ми збираємося зберегти. Я знайшов найефективніший і безпечний спосіб - це:Зберігати лише те, що нам потрібно для відображення та / або створення замовлення Зберігати лише скалярні типи (int, float, bool, string)

  • Отже, ось що ми збираємося зберегти в нашому CartService:

productId - для створення суми order_product - для відображення та для створення order_product ціни товару - для відображення назви товару - для відображення знижки - для відображення

  • та для
  • створення order_product

Щоб наші дані були легко доступні для пошуку, ми також надамо їм ключ productId. Ось як це буде виглядати:

749 => [
  'productId' => 749,
  'amount' => 5,
  'price' => 1499, //always save price as an int!
  'name' => 'Creative product name here',
  'discount' => 0,
]

Гаразд, у нас зараз є все, що нам потрібно. Зробіть addToCart функцію в CartService:

public function addToCart(int $productId): array
{
    // get data from session (this equals Session::get(), use empty array as default)
    $shoppingCart = session('shoppingCart', []);

    if (isset($shoppingCart[$productId]))
    {
        // product is already in shopping cart, increment the amount
        $shoppingCart[$productId]['amount'] += 1;
    }
    else
    {
        // fetch the product and add 1 to the shopping cart
        $product = Product::findOrFail($productId);
        $shoppingCart[$productId] = [
            'productId' => $productId,
            'amount'    => 1,
            'price'     => $product->price->getAmount(),
            'name'      => $product->name,
            'discount'  => $product->discount
        ];
    }

    // update the session data (this equals Session::put() )
    session(['shoppingCart' => $shoppingCart]);
    return $shoppingCart;
}

Оскільки ми використовуємо ідентифікатор продукту як ключ, ми можемо легко перевірити, чи кошик для покупок уже містить продукт, який використовує isset($shoppingCart[$productId]) . Якщо кошик для покупок вже містить продукт, ми можемо легко збільшити суму, інакше нам потрібно отримати продукт і зберегти всі його властивості в новому елементі масиву.

Вилучення товару з кошика досить схоже: перевірте, чи є воно в кошику (слід!) і перевірте суму. Якщо це 1, видаліть елемент з масиву повністю, а в іншому випадку просто зменшіть суму. Ось як це виглядає:

public function removeFromCart(int $productId): array | null
{
    $shoppingCart = session('shoppingCart', []);

    if (!isset($shoppingCart[$productId]))
    {
        // should not happen, and should throw an error.
        return null;
    }
    else
    {
        if ($shoppingCart[$productId]['amount'] == 1){
            unset($shoppingCart[$productId]);
        }
        else
        {
            $shoppingCart[$productId]['amount'] -= 1;
        }
    }

    session(['shoppingCart' => $shoppingCart]);
    return $shoppingCart;
}

Тепер давайте використовувати наші абсолютно нові методи, чи не так?

Firstly, for шаблон сервісу components to use the CartService they will need the productId. In my example, I made a шаблон сервісу component for the 'add to cart' button and gave it the productId while I was looping over the products to display. It looked something like this:

@foreach($products as $product)
    <tr>
        <td>{{$product->name}}</td>
        <td>
            <livewire:add-to-cart-button :productId="$product->id"></livewire:add-to-cart-button>
        </td>
    </tr>
@endforeach

The second thing the components will need is the CartService itself, which is where Laravel's dependency injection comes in handy: just pass the CartService as a parameter in a шаблон сервісу component method, and Laravel will take care of business. Here's how my addToCart method looks:

public function addToCart(CartService $cartService)
{
    $cartService->addToCart($this->productId);
}

CartService вводиться і готовий до використання, де б він не був доданий таким чином. Легко-горе лимон-писк!

У мене також є компонент огляду кошика для покупок, який повинен буде оновлювати власні значення, коли користувач збільшує або зменшує суми. Ось як мені це вдалося:На гачку життєвого циклу Livewire я дозволю компоненту mount отримувати вміст кошика для покупок:І при зміні сум я буду використовувати значення повернення методів CartService:Тут є проблема:

In the mount шаблон сервісу lifecycle hook , I'll let the component fetch the shopping cart contents:

public function mount(CartService $cartService)
{
    $this->shoppingCart = $cartService->getShoppingCart();
}

public function incrementAmount(int $productId, CartService $cartService)
{
    $this->shoppingCart = $cartService->addToCart($productId);
}

Що робити, якщо на екрані є два компоненти, які потрібно оновити? Наприклад, огляд кошика для покупок і кнопка кошика для покупок на панелі навігації?

Refreshing шаблон сервісу Views

Worry not, шаблон сервісу's got you covered! Just emit an event (I'd suggest 'updateShoppingCart' or something similar) and listen to it on the components that will need updating. Like this:

// in the 'incrementAmount' method
$this->emit('updateShoppingCart');

// in the shoppingCartButton component:
class ShoppingCartButton extends Component
{
    //public properties here...
    protected $listeners = ['updateShoppingCart' => 'updateShoppingCart'];

  public function updateShoppingCart(CartService $cartService)
    {
        $this->shoppingCart = $cartService->getShoppingCart();
        $this->cartAmount = $cartService->getCartAmount();
        $this->subTotal = $cartService->getCartSubTotal()->format();
    }

getCartAmount() метод просто підраховує всі суми всіх елементів масиву в кошику для покупок. В основному це те, скільки штук у кошику для покупок: 2 яблука та 3 банани загалом

становить 5. Це subTotal сума price x amount кожного продукту, тому 2 яблука за 1 долар кожне і 3 банани за 10 доларів отримають subTotal 32 долари. Щоб форматування було легким, я використовую пакет laravel-money. getCartSubtotal поверне Money об'єкт, який можна відформатувати так, як вам подобається, використовуючи format() метод.

Бум, заповнення кошика для покупок зроблено і припорошено! Тепер давайте одягнемо наші внутрішні капелюхи та перевіримо процес оформлення замовлення. Зрозуміли?

Перелітайте свої сервери поблизу своїх користувачів і дивуйтеся швидкості близькості. Розгортайтеся глобально на Fly за лічені хвилини!

Розгорніть свій додаток Laravel!  →

Creating Orders With a Many-to-many Relationship

Гаразд, тепер наші клієнти мають цифрового помічника, який тримає всі вибрані продукти, поки вони продовжують переглядати. Зрозуміли?

Далі: фактично створення Замовлення з кошика товарів. По-перше, надішліть вміст кошика для покупок разом як вхід форми:

<form method="post" action="{{route('orders.store')}}">
    @csrf
    <input type="hidden" name="orderProducts" id="orderProducts" value="{{json_encode($shoppingCart)}}">
    <button type="submit">{{__('Order')}}</button>
</form>

Просто json_encode масив і надішліть його рядком. Поправте свій задній капелюх і приєднайтеся до мене, StoreOrderRequest щоб подивитися на перевірку.

Validating a FormRequest With an Array

Ви можете просто скористатися правилом json перевірки, але я закликаю вас розшифрувати його у FormRequest і перевірити як масив. Таким чином, це більш гнучко:Правила перевірки:Означає,

protected function prepareForValidation()
{
    /**
     * We need to decode the JSON, so we can validate it as an array.
     * This is how the original input data looks:
     * $productId => ['amount' => $amount, 'price' => $price, 'name' => $name, 'discount' => $discount, 'productId' => $productId]
     */
    $this->merge(['orderProducts' => json_decode($this->orderProducts, true)]);
}

що правило перевірки має застосовуватися до кожного елемента масивуorderProducts.orderProducts.*.

return [
    'orderProducts.*.productId' => ['required', 'integer'],
    'orderProducts.*.amount' => ['required', 'integer', 'min:1'],
    'orderProducts.*.discount' => ['required', 'numeric', 'between:0,100'],
];

Таким чином, ми можемо перевірити до сингулярних властивостей.

Гаразд, домашня розтяжка: збереження Замовлення в базі даних!

Attaching Intermediate Tables to a Model

Правильно, ми отримали дані, і все це виглядає добре: були проведені перевірки фону, і металошукачі не подали на нас звуковий сигнал. Давайте створимо наш Орден зараз!

Як і завжди, Laravel робить це досить легко для нас: ми можемо використовувати метод по belongsToMany зв'язку, щоб додати рядок в проміжну таблицю, наприклад

$order->products()->attach($productId);

: Це додасть проміжну таблицю між $order і Product with ID $productId.attach() Додати знижку та суму до неї також не складно: просто додайте пари ключ=>значення після $productId:

$order->products()->attach($productId, ['amount' => $amount], 'discount' => $discount]]);

Тепер нам просто потрібно зробити це для кожного order_product, який ми отримуємо з кошика для покупок. Це масив, пам'ятаєте? Ось як виглядає остаточна версія:Тепер користувач може замовити просто чудово, але залишилося виправити одну деталь:

$validated = $request->validated();

$order = new Order();

foreach ($validated['orderProducts'] as $orderProduct)
{
    $order->products()->attach($orderProduct['productId'], ['amount' => $orderProduct['amount'], 'discount' => $orderProduct['discount']]);
}
$order->save();

кошик для покупок не спустошується після замовлення. Дозволю розібратися самостійно, але впевнений, що session→forget() спосіб стане в нагоді.

Як завжди, дякую за увагу!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297