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