tworzenie aplikacji server-sideprojektowanie sklepów internetowychwykonywanie aplikacji internetowych

Strona główna Publikacje "Sklepy internetowe" Software 2.0 3/2002 część 2
Programowanie przy użyciu smarty + PHP i Postgresql lub MySQL

Strona główna
O mnie
Projekty
Publikacje
Linki


- Mój Anty blog

- Statystyki #php.pl

 


Sklepy internetowe
Autor: Adam Major

Wersja do druku
Artykul został pierwotnie opublikowany w magazynie Software 2.0 3/2002 - "Programowanie dla Internetu". Software 2.0
www.software.com.pl

część 1 | część 2 > | część 3 >


Przechowywanie danych o produktach

Tego typu dane najlepiej zgrupować w dwie tabele: prod i prod2. Pierwsza z nich będzie przechowywać informacje potrzebne do transakcji (tj. nazwa, cena netto, cena brutto itp.) oraz przyporządkowanie do działu i kategorii. Druga tabela będzie zawierać dłuższe opisy produktów. Podział na dwie tabele jest korzystny ponieważ znacznie zmniejszamy ilość danych, które musi przetworzyć baza dany w przypadku wyszukiwania produktu np. wg ceny.

Jednym z najważniejszych kryteriów profesjonalnego tworzenia sklepów jest zapewnienie dużej szybkości działania, dlatego w tabeli prod zapisujemy nazwę produktu w polu typu char oraz zakładamy indeksy na wszystkie pola, które będą przeszukiwane. Również ze względów wydajnościowych nie przechowujemy w bazie zdjęć produktów a jedynie informacje o nich. Pola mini* zawierają dane dotyczące miniaturki zdjęcia produktu.
Mini_t - format zdjęcia (0 - brak, 1 - GIF, 2-JPEG, 3 - PNG), ten sposób przechowywania tej informacji pozwala w razie potrzeby na dodanie kolejnego formatu grafiki np. 4-SWF. Mini_w i Mini_h to odpowiednio szerokość i wysokość obrazka. Dane te uzupełniamy przy dodawaniu nowego produktu lub jego edycji.
Rid_ct i Rid_sc zawierają identyfikator działu i kategorii.
      Aby wyświetlić wszystkie informacje o danym produkcie możemy się posłużyć następującym zapytaniem:

select * from prod, prod2 where id_pr='$HTTP_GET_VARS[id]' AND id_pr2=id_pr

 
CREATE TABLE prod
(
 id_pr       smallint unsigned not null
             auto_increment,
 pr_act	     tinyint unsigned not null,
 rid_ct	     tinyint unsigned not null,
 rid_sc	     tinyint unsigned not null,
 name        char(40) not null,
 price_n     float(4,2) not null,
 price_b     float(4,2) not null,
 vat         tinyint,
 mini_t      tinyint unsigned,
 mini_w      tinyint unsigned,
 mini_h      tinyint unsigned,
 key         pr_act (pr_act),
 key         rid_ct (rid_ct),
 key         rid_sc (rid_sc),
 key         name (name),
 key         price_n (price_n),
 key         price_b (price_b),
 primary key (id_pr)
);

CREATE TABLE prod2
(
 id_pr2      smallint unsigned not null
             auto_increment,
 big_t       tinyint unsigned,
 big_w       tinyint unsigned,
 big_h       tinyint unsigned,
 info        text,
 primary key (id_pr2)
);
Listing 3. Polecenia SQL tworzące strukturę tabel prod i prod2.

Składowanie zamówień

Jest praktycznie najistotniejszym elementem działania sklepu. W tej grupie danych musimy przechowywać wszystkie informacje konieczne do zrealizowania zamówienia oraz przydatne do generowania wszelkiego rodzaju statystyk np. dynamiki sprzedaży itp. Również tutaj najkorzystniejsze jest stworzenie dwóch tabel. W jednej będziemy przechowywać dane teleadresowe klienta w drugiej spis produktów, które zakupił.
W przypadku drugiej tabeli oprócz identyfikatora produktu, przechowujemy również jego cenę oraz wartość podatku VAT jaka była w czasie składania zamówienia. Jest to bardzo istotne ze względu na stosunkowo szybką zmianę cen czy też okresowe promocje, które mogą cechować projektowany sklep. Klient powinien płacić tyle ile wynosiła cena produktów w chwili zakupu, a nie realizacji zamówienia.

W tabeli buy oprócz danych teleadresowych przechowujemy: identyfikator zamówienia id_b, status - określa w jakim stadium realizacji jest dane zamówienie, można np. przyjąć trzy poziomy realizacji: oczekiwanie, pakowanie, wysyłka (zrealizowane). Jeśli zamierzamy udostępnić użytkownikom stronę z ich "historią zamówień" bardzo istotnym elementem jej byłaby informacja o postępach w realizacji zamówienia. Po zmianie stanu zamówienia (przez operatora sklepu) na zrealizowany, powinna zostać ustawiona data wysyłki (send_date).
      Oprócz wyżej wymienionych pól istotne są także pola: total, zawierające wartość zamówienia - można co prawda ją wyliczyć na podstawie tabeli buy2, ale jest to dość czasochłonne w przypadku generowania np. statystyk miesięcznych - i bad_post, które przechowuje informacje o liczbie błędnych prób wysłania formularza zamówieniowego. Na podstawie tej informacji operator sklepu może zdecydować czy warto wysłać produkt do klienta.
Więcej o sposobach zabezpieczenia się przed nieuczciwymi klientami w dalszej części artykułu.
W tabeli buy2 przechowywane są dane dotyczące poszczególnych produktów w zamówieniu. Id_z to identyfikator zamówienia, qt ilość sztuk produktu o identyfikatorze id_p.
 
CREATE TABLE buy
(
 id_b        smallint unsigned not null
             auto_increment,
 status      tinyint unsigned not null,
 total       float (6,2) not null,
 firm        varchar (40),
 fname       varchar (20),
 lname       varchar (30) not null,
 street      varchar (40),
 post        char (6),
 city        varchar (30),
 region      tinyint unsigned not null,
 phone       varchar (22),
 email       varchar (40),
 pesel       varchar (11),
 nip         varchar (13),
 bad_post    tinyint unsigned,
 buy_date    timestamp not null,
 send_date   timestamp not null default '0',
 host        varchar (200),
 key         status (status),
 key         total (total),
 key         firm (firm),
 key         lname (lname),
 key         region (region),
 key         buy_date (buy_date),
 key         send_date (send_date),
 primary key (id_b)
);

CREATE TABLE buy2
(
 id_b2       mediumint unsigned not null
             auto_increment,
 id_z        smallint unsigned not null,
 id_p        smallint unsigned not null,
 qt          smallint unsigned,
 price_n     float (4,2),
 price_b     float (4,2),
 vat         tinyint,
 key         id_z (id_z),
 key         id_p (id_p),
 primary key (id_b2)
);
Listing 4. Polecenia SQL tworzące tabele buy i buy2.

Funkcje administracyjne

Ta grupa danych odpowiada za logowanie się opiekunów sklepu oraz nadanie im odpowiednich uprawnień do zarządzania poszczególnymi częściami sklepu. W najprostszym przypadku, kiedy wszyscy administratorzy są sobie równi możemy zastosować następującą strukturę bazy.  
CREATE TABLE admin
(
 id_a        tinyint unsigned not null
             auto_increment,
 login       char(12) not null,
 pass        char(14) binary,
 primary key (id_a)
);
Listing 5. Polecenia SQL tworzące tabelę admin

Mamy już zaprojektowaną strukturę bazy danych teraz przychodzi czas na zaplanowanie sposobu przechowywania informacji o tym co dany klient chce kupić, czyli

Koszyk

Istnieje wiele możliwości przechowywania informacji o stanie koszyka klienta. Można zapisywać informacje po stronie klienta np. w ciastkach lub ukrytych polach formularza, jednak nie jest to bezpieczne, ponieważ użytkownik może manipulować przy tych danych. Część rozwiązań opiera się o bazę danych lub pliki tymczasowe jednak taki sposób choć o wiele bezpieczniejszy jest dość nie wygodny oraz w większości w/w rozwiązań silnie obciąża serwer. Najlepszym rozwiązaniem łączącym bezpieczeństwo i prostotę użytkowania jest zastosowanie sesji. Wszelkie informacje o stanie koszyka są przechowywane po stronie serwera, standardowo w plikach. Dane o ilości egzemplarzy produktu o danym identyfikatorze można zapisywać w tablicy dwuwymiarowej lub skorzystać z dość powszechnie znanej klasy koszyka na zakupy.
My skorzystamy właśnie z pewnej odmiany takiej klasy.

Dla części czytelników zastanawiający może być fragment od if (ini_get... oraz linia $HTTP_SESSION_VARS[cart] = $cart =...
Ze względów bezpieczeństwa powinniśmy się odwoływać do zmiennych przez tablice asocjacyjne $HTTP_*_VARS, pozwala nam to jednoznacznie określić skąd pochodzą dane.
Abyśmy mogli korzystać z globalnej przestrzeni zmiennych (wtedy wszystkie zmienne również są widoczne jako $zmienna) należy mieć włączoną w pliku php.ini opcję register_globals. PHP ewoluuje w kierunku w którym w/w opcja będzie domyślnie wyłączona. Jeśli chcemy zapewnić działanie w/w funkcji w przypadku kiedy register_globals jest włączone lub wyłączone należy posłużyć się właśnie takim trikiem, ponieważ gdy ta opcja jest włączona i koszyk nie jest zainicjowany to nie można się odwołać do metody add koszyka, którego nie ma jeszcze w sesji.
Musimy więc sprawdzić czy PHP jest skonfigurowane do tworzenia globalnej przestrzeni zmiennych jeśli tak to odwołujemy się do zmiennej globalnej, jeśli nie to odwołujemy się normalnie poprzez zmienną $HTTP_SESSION_VARS[cart].
Przynajmniej mi się nie udało tego obejść w wersji 4.0.6 inaczej [ przyp. autora po opublikowaniu tego artykułu zauważyłem bardzo proste rozwiązanie problemu. Należy sprawdzić czy istnieje zmienna sesyjna koszyk jeśli nie to zarejestrować koszyk i przekierować (wywołać ponownie) skrypt tak aby nastąpiło dodanie produktu. Jeśli natomiast jest już zarejestrowana zmienna sesyjna wystarczy wykonać metodę add i dodać produkt do koszyka], oczywiście po za skorzystaniem z funkcji
ini_set ('register_globals', '0').
      Kod z listngu 6 zapisujemy do pliku o nazwie np. dokoszyka.php, odpowiednie wywołanie tego pliku spowoduje
 
<?php

class cart
{
 var $za;
 function add ($element) { $this->za[$element]++; }
 function del ($element)
          { unset($this->za[$element]); }
 function edit ($element, $val)
          { $this->za[$element] = $val; }
 function show_cart() { return $this->za; }
 function drop_cart() { unset($this->za); }
}

function add_cart()
{
 global $HTTP_GET_VARS, $HTTP_SESSION_VARS, $cart,
  $c_total, $c_bad;

 $conn = pconnect_to_mysql();
 $sql = "select id_pr from prod where id_pr =
 '$HTTP_GET_VARS[id_p]'";
 if (!($res = @mysql_query($sql,$conn)))
   {$err_ln=__LINE__; include('db_error.php'); }
 if (@mysql_num_rows($res) == 0) return 0;

 if (!$HTTP_SESSION_VARS[cart])
  {
   session_register(cart, c_total, c_bad);
   $HTTP_SESSION_VARS[cart] = $cart = new cart;
   $HTTP_SESSION_VARS[c_total] = 0;
   $HTTP_SESSION_VARS[c_bad] = 0;
   if (ini_get ('register_globals'))
      $cart->add($HTTP_GET_VARS[id_p], 1);
      else $HTTP_SESSION_VARS[cart]->
        add($HTTP_GET_VARS[id_p], 1);
  }
  else $HTTP_SESSION_VARS[cart]->
       add($HTTP_GET_VARS[id_p], 1);
}

if ($HTTP_GET_VARS[id_p] > 0 AND
   is_numeric($HTTP_GET_VARS[id_p])) add_cart();
header('location: koszyk.php');

?>
Listing 6. Klasa cart, sposób inicjalizacji i dodania produktu do koszyka.
zainicjowanie koszyka (jeśli to konieczne), wprowadzenie nowego produktu do kosza (lub zwiększenie ilości danego produktu) oraz przekierowanie do skryptu koszyk.php. Zastosowanie pliku pośredniczącego (dokoszyka.php) dodawanie produktów do koszyka powoduje uodpornienie go na przeładowywanie strony (odśwież, reload) przez użytkownika, które mogłoby prowadzić do zwiększania się ilości danego produktu przy każdym odświeżeniu strony.

część 1 | część 2 > | część 3 >

sklepy_lst.zip - Wszystkie listingi zawarte w artykule (4 KB).



planowanie, projektowanie, programowanie, wdrażanie projektów informatycznych
Copyright (c) 2002-06 by