• Czas czytania ~8 min
  • 23.03.2023
Image by Annie Ruygt

Fly pobiera obraz platformy Docker, konwertuje go na maszynę wirtualną i uruchamia tę maszynę wirtualną w dowolnym miejscu na świecie. Uruchom aplikację Laravel w kilka minut!

Koszyki nie opróżniają się, gdy użytkownik opuszcza stronę. Dlaczego? Cóż, w tym celu muszę zabrać cię w czasie. Rozpocznij falujące retrospekcje!

Dawno, dawno temu żył sobie codzienny, zwykły, normalny facet o imieniu Jack. Jack uwielbiał zakupy online i często spędzał godziny na przeglądaniu różnych stron internetowych w poszukiwaniu najlepszych ofert. Dodawał przedmioty do koszyka, ale nigdy ich nie zamawiał. Zamiast tego często sprawdzał koszyk, podziwiając wybrane przedmioty, ale nigdy nie kliknął przycisku "kup". Jack zamawiał je tylko wtedy, gdy był całkowicie w stu procentach pewien, że ich chce.

Czy znam Jacka?

Tak, mam zwyczaj wypełniania koszyków rzeczami, które zamówię kilka miesięcy później lub po prostu zapomnę. Na bok generowane przez ChatGPT bajki o moich wadach charakteru, jesteśmy tutaj, aby porozmawiać o prawdziwych bohaterach tej historii: wózkach na zakupy, które niewdzięcznie przechowują nasze możliwe przyszłe zakupy w różnych sklepach internetowych. Pamiętaj, aby kiedyś podziękować im za ich służbę.

Każdy, zbudujmy jeden, aby zobaczyć, jak działają!

Oto tematy, które

  • porusza ten artykuł: sesja http i fasada sesji w Laravel wzorzec
  • wzorzec
  • usługi pakiet Money
  • Kolekcje
  • 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 wzorzec 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.

Zbudujemy więc sklep internetowy typu barebone. Postaram się, aby było to krótkie i proste w dwóch krokach: dodanie produktów do koszyka i użycie koszyka do złożenia zamówienia.

Odprawa, budujmy!

The Models

W naszym sklepie internetowym typu barebone mamy 2 modele, o które musimy się martwić:

  • tabela produktów z identyfikatorem, znaczniki czasu, nazwa, opis, cena i rabat. Zawiera wiele zamówień.
  • tabela Zamówienia, która należy do jednego Użytkownika, ma identyfikator i znaczniki czasu oraz zawiera wiele Produktów.

Ponieważ jest to relacja wiele-do-wielu, będziemy potrzebować tabeli przestawnej. Będzie to zawierać zagraniczne identyfikatory dla produktu i zamówienia, a także pola rabatu (rabat produktu w momencie składania zamówienia) i kwoty (zamówionych produktów). Oto jak ustawione są modele Produkt i Zamów:

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 automatycznie przyjmie, że nazwa tabeli przestawnej to [alphabetically first model name]\_[alphabetically first model name], co jest order\_product w naszym przypadku. Laravel zakłada również, że zagraniczne identyfikatory zostaną wywołane product_id i order_id. Metoda withPivot umożliwia nam dodawanie dodatkowych kolumn w tabeli, takich jak i amount w naszym przypadku, jak discount pokazano w dokumentach.

Tak będą wyglądać nasze tabele:

Adding Products to the Shopping Cart

Building the Shopping Cart Service

Usługa koszyka na zakupy zajmie się przechowywaniem i pobieraniem danych do / z sesji http. W ten sposób możemy ponownie użyć kodu i wstrzyknąć go do wszystkich komponentów, w których jest potrzebny.

app/Services Utwórz folder i tam utwórz nową klasę php o nazwie CartService.php. Zanim zaczniemy dodawać produkty do koszyka, musimy porozmawiać o tym, jakie dane zamierzamy zapisać. Odkryłem, że najbardziej wydajnym i bezpiecznym sposobem jest:Zachowaj tylko to, czego potrzebujemy do wyświetlenia i / lub utworzenia zamówienia Zachowaj tylko typy skalarne (int, float, bool, string)

  • Oto, co zamierzamy zapisać w naszym CartService:

productId - do tworzenia kwoty order_product - do wyświetlania i tworzenia order_product cena produktu - wyświetlanie nazwy produktu - wyświetlanie

  • rabatu - wyświetlanie i tworzenie order_product

Aby ułatwić wyszukiwanie naszych danych, nadamy im również identyfikator produktu. Tak to będzie wyglądało:

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

Okej, mamy teraz wszystko, czego potrzebujemy. addToCart Utwórz funkcję w 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;
}

Ponieważ używamy identyfikatora produktu jako klucza, możemy łatwo sprawdzić, czy koszyk zawiera już produkt za pomocą isset($shoppingCart[$productId]) . Jeśli koszyk zawiera już produkt, możemy łatwo zwiększyć kwotę, w przeciwnym razie musimy pobrać produkt i zapisać wszystkie jego właściwości w nowym elemencie tablicy.

Usuwanie produktów z koszyka jest dość podobne: sprawdź, czy istnieje w koszyku (powinien!) i sprawdź ilość. Jeśli jest to 1, usuń element z tablicy całkowicie, a w przeciwnym razie po prostu zmniejsz kwotę. Oto jak to wygląda:

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;
}

Teraz użyjmy naszych zupełnie nowych metod, dobrze?

Firstly, for wzorzec components to use the CartService they will need the productId. In my example, I made a wzorzec 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 wzorzec 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);
}

Usługa CartService jest wstrzykiwana i gotowa do użycia wszędzie tam, gdzie jest dodawana w ten sposób. Łatwy w obsłudze,

cytrynowy wycisk! Mam również komponent przeglądu koszyka, który będzie musiał zaktualizować własne wartości, gdy użytkownik zwiększy lub zmniejszy kwoty. Oto jak mi się to udało:W mount haczyku cyklu życia Livewire pozwolę składnikowi pobrać zawartość koszyka:A zmieniając kwoty, użyję wartości zwracanej metod CartService:Występuje problem:

In the mount wzorzec 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);
}

Co zrobić, jeśli na ekranie są dwa komponenty, które wymagają aktualizacji? Na przykład przegląd koszyka i przycisk koszyka na pasku nawigacyjnym?

Refreshing wzorzec Views

Worry not, wzorzec'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() metoda po prostu zlicza wszystkie ilości wszystkich elementów tablicy w koszyku. Chodzi w zasadzie o to, ile sztuk jest w koszyku: 2 jabłka i 3 banany dają w sumie 5.

Jest to subTotal suma price x amount każdego produktu, więc 2 jabłka za 1 USD za każde i 3 banany za 10 USD otrzymałyby subTotal 32 USD. Aby ułatwić formatowanie, używam pakietu laravel-money. getCartSubtotal zwróci Money obiekt, który można sformatować w dowolny sposób za pomocą format() metody.

Bum, napełnianie koszyka jest zrobione i odkurzone! Teraz załóż nasze czapki z daszkiem i sprawdźmy proces płatności. Rozumiesz?

Umieść swoje serwery blisko użytkowników i podziwiaj szybkość bliskości. Wdrażaj globalnie w Fly w kilka minut!

Wdróż swoją aplikację Laravel!  →

Creating Orders With a Many-to-many Relationship

Okay, nasi klienci mają teraz cyfrowego asystenta, który trzyma wszystkie wybrane produkty podczas dalszego przeglądania. Rozumiesz?

Następnie: faktyczne tworzenie zamówienia z koszyka produktów. Po pierwsze, wyślij zawartość koszyka jako dane wejściowe formularza:

<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>

Wystarczy json_encode tablicę i wysłać ją jako ciąg znaków. Wyprostuj swój kapelusz zaplecza i dołącz do mnieStoreOrderRequest, aby spojrzeć na walidację.

Validating a FormRequest With an Array

Możesz po prostu użyć json reguły sprawdzania poprawności, ale zachęcam do dekodowania jej w FormRequest i sprawdzania poprawności jako tablicy. Jest bardziej elastyczny w ten sposób:Reguły sprawdzania poprawności:

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)]);
}

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

Oznacza orderProducts.*. to, że reguła sprawdzania poprawności musi być stosowana do każdego elementu tablicyorderProducts. W ten sposób możemy zweryfikować właściwości pojedyncze.

Dobra, home stretch: zapisanie Zamówienia w bazie danych!

Attaching Intermediate Tables to a Model

Racja, otrzymaliśmy dane i wszystko wygląda dobrze: sprawdzono przeszłość, a wykrywacze metalu nie wydały na nas sygnału dźwiękowego. Stwórzmy nasze zamówienie już teraz!

Jak zawsze, Laravel ułatwia nam to: możemy użyć metody relacjibelongsToMany, aby dodać wiersz w tabeli pośredniej, w następujący sposób:

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

Spowoduje to dodanie tabeli pośredniej między $order a produktem z identyfikatorem $productId.attach() Dodanie rabatu i jego kwoty również nie jest trudne: po prostu dodaj pary key=>value po $productId:

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

Now, musimy to zrobić dla każdego order_product, który otrzymujemy z koszyka. To tablica, pamiętasz? Oto jak wygląda ostateczna wersja:

$validated = $request->validated();

$order = new Order();

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

Teraz użytkownik może zamówić dobrze, ale pozostaje jeden szczegół do naprawienia: koszyk nie opróżnia się po złożeniu zamówienia. Pozwolę ci to rozgryźć na własną rękę, ale jestem pewien, że session→forget() metoda się przyda

. Jak zawsze, dzięki za przeczytanie!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297