2010年10月11日星期一

php tutor shopping cart session

http://www.phpwebcommerce.com/shopping-cart-source-code.php

实例/example web:http://www.phpwebcommerce.com/plaincart/index.php?c=14

http://v3.thewatchmakerproject.com/journal/276/building-a-simple-php-shopping-cart

php shopping cart code

Building a simple PHP shopping cart
Nov 21 2005

Update 21/11: I’ve added a demo and downloadable .zip file at the end of the article.

Update 07/12: Thanks to Greg and Rob Shan Lone for spotting a bug in the cart update code – I’ve now fixed this and updated the demo and .zip file; thanks guys!

One of the most common requirements any web designer/developer will come across is likely to be that of the small business taking its first steps online; a client with an established widget-selling company now wants to move into the national market and sell their widgets to the world.

What they need to do this (apart from a great-looking, functional, usable and accessible website!) is some sort of shopping-cart system to allow their customers to add, remove and update the products they wish to buy.

This article will walk through the simple steps to achieve this, using the server-side language PHP and a MySQL database.
Setting up the database

Let’s take as our example a bookseller. They are not the best-stocked bookseller in the world, having as they do only three titles in stock, but it is enough for our example. Let’s create the database and add some stock:

1. CREATE TABLE books (
2. id int(6) unsigned NOT NULL auto_increment,
3. title varchar(100) NOT NULL default '',
4. author varchar(100) NOT NULL default '',
5. price decimal(3,2) NOT NULL default '0.00',
6. PRIMARY KEY (id)
7. ) TYPE=MyISAM;
8.
9. INSERT INTO books VALUES (1, 'Where God Went Wrong', 'Oolon Colluphid', '24.99');
10. INSERT INTO books VALUES (2, 'Some More of God\'s Greatest Mistakes', 'Oolon Colluphid', '17.99');
11. INSERT INTO books VALUES (3, 'Who Is This God Person Anyway?', 'Oolon Colluphid', '14.99');
12. Download this code: /code/php-cart-1.txt

Now that step is out of the way, let’s create our shopping cart.
Identify our requirements

The cart we are going to build should be pretty familiar to most internet users. It will provide a means of displaying a message on every page of the site (along the lines of “You have 5 items in your shopping cart”), which when clicked will take the customer to a list of the items in the cart; each item may be removed or have its quantity updated.

As all of our stock details are stored in the database, the only piece of information that we need to store about each customer is the id of each product they have added to their cart. To do this, we are going to use PHP’s built-in session handling capabilities.
Using sessions in PHP

PHP’s session handling is very easy to understand.

To ‘switch it on’, simply add session_start(); at the top of your code; you can then store values (or arrays) in session variables:
$_SESSION['name'] = 'Matthew'; $_SESSION['age'] = 31;

These $_SESSION variables will now be available to any PHP scripts on other pages (as long as you’ve included the session_start(); call first).
A short note on cookies

The default behaviour of PHP sessions is to store the session ID (a long string of numbers and letters that identifies you to the server) as a cookie on your computer; however, even if you have cookies disabled this functionality will still work – PHP will instead append the session ID to each link on the site (in the form ‘mypage.php?PHPSESSID=’) so that it can continue to accurately track visitors from page to page.
Creating the cart

We will store the contents of the shopping cart as a comma-separated list of product ids in a session named (unsurprisingly) ‘cart’ – for example, a cart containing “1,1,3,1,2” has four items; three of product #1, and one each of products #2 and #3.

Firstly, let’s create the code to display the “You have X items…” message on every page:

1. function writeShoppingCart() {
2. $cart = $_SESSION['cart'];
3. if (!$cart) {
4. return '

You have no items in your shopping cart

';
5. } else {
6. // Parse the cart session variable
7. $items = explode(',',$cart);
8. $s = (count($items) > 1) ? 's':'';
9. return '

You have '.count($items).' item'.$s.' in your shopping cart

';
10. }
11. }
12.
13. Download this code: /code/php-cart-2.txt

This function first gets the value of the ‘cart’ session variable into a local variable, mainly to save on typing. If the cart is empty, we return an appropriate message; if not, we use the explode() function to create an array of all the products in the cart, and then count() them and display the result (the $s variable is there to make plurals display correctly).

Now we can display the correct message anywhere on the site:
echo writeShoppingCart();

I hope that made sense; let’s move on to displaying the contents of the cart itself.
Show me the money

The shopping cart page itself (cart.php) can be arrived at in a couple of ways. The user may have clicked on the link created by the writeShoppingCart() function above; or, they may have clicked an ‘Add to cart’ link on a product page.

If it was the latter, we need to intercept that click and update our cart contents before displaying the new list of products – simple enough, as all we need to do is append the id of the clicked product to the ‘cart’ session variable. Our product links are going to look like this:
Add to cart

Don’t worry about the ‘action=add’ bit, we’ll get to that later. The id can now be extracted from $_GET[‘id’] and added to our cart:

1. $cart = $_SESSION['cart'];
2. if ($cart) {
3. $cart .= ','.$_GET['id'];
4. } else {
5. $cart = $_GET['id'];
6. }
7. $_SESSION['cart'] = $cart;
8. Download this code: /code/php-cart-3.txt

Now, on to the contents of our cart!
Multiple items, one product

As in our example above, it is entirely possible that a customer might have more than one of a certain product in their cart. As we don’t want to list duplicate items when we display the contents, we need to figure out a way to combine any of the same product into one entry (with a corresponding quantity attached).

1. $cart = $_SESSION['cart'];
2. if ($cart) {
3. $items = explode(',',$cart);
4. $contents = array();
5. foreach ($items as $item) {
6. $contents[$item] = (isset($contents[$item])) ? $contents[$item] + 1 : 1;
7. }
8. Download this code: /code/php-cart-4.txt

This time, after exploding the list of product ids, we have iterated through them to create a new array named $contents, which consists of key=>value pairs where the key is the product id and the value is the quantity of that product in the shopping cart. So to take our example above, a cart containing “1,1,3,1,2” would become an array where ‘1’=>3, ‘2’=>1, ‘3’=>1.

Now that we have an accurate count of unique products, let’s query the product database for each one and output its details into a table:

1. $total = 0;
2. $output[] = '';
3. foreach ($contents as $id=>$qty) {
4. $sql = 'SELECT * FROM books WHERE id = '.$id;
5. $result = $db->query($sql);
6. $row = $result->fetch();
7. extract($row);
8. $output[] = '';
9. $output[] = '';
10. $output[] = '';
11. $output[] = '';
12. $output[] = '';
13. $output[] = '';
14. $total += $price * $qty;
15. $output[] = '';
16. }
17. $output[] = '
Remove'.$title.' by '.$author.'£'.$price.'£'.($price * $qty).'
';
18. $output[] = '

Grand total: £'.$total.'

';
19.
20. Download this code: /code/php-cart-5.txt

(*Note*: I’m using a PHP class to handle my database connections, so your code may need to be slightly different).

Pretty simple stuff – we iterate through the new $contents array and query the database for each product id. Then we output the relevant details for each product; title, author and price, and at the bottom we show the grand total for the order. For each item, we’ve also included a link to remove the item from the cart, a textbox containing the current quantity of that item, and a total price (obviously this will only differ from the base price if the quantity is not 1).

The reason I’m using $output[] = ... is that I am buffering the output into an array to print to the screen later.

So far so good – now how about removing products from the cart?
Deleting a product

As shown above, the link to delete a product from the cart follows the same format as the ‘add a product’ link:
Remove

Let’s expand on the code from earlier by adding a switch() statement to handle the different things that might happen to our cart:

1. $cart = $_SESSION['cart'];
2. $action = $_GET['action'];
3. switch ($action) {
4. case 'add':
5. if ($cart) {
6. $cart .= ','.$_GET['id'];
7. } else {
8. $cart = $_GET['id'];
9. }
10. break;
11. case 'delete':
12. if ($cart) {
13. $items = explode(',',$cart);
14. $newcart = '';
15. foreach ($items as $item) {
16. if ($_GET['id'] != $item) {
17. if ($newcart != '') {
18. $newcart .= ','.$item;
19. } else {
20. $newcart = $item;
21. }
22. }
23. }
24. $cart = $newcart;
25. }
26. break;
27. }
28. $_SESSION['cart'] = $cart;
29. Download this code: /code/php-cart-6.txt

The new ‘delete’ case iterates through the ‘cart’ session, looking for any product ids that AREN’T the one we’re deleting, and adding them to a temporary variable called $newcart. When it’s finished, it puts the revised cart contents back into $cart.
Updating a product

Lastly, we are going to allow customers to update the contents of their shopping cart by manually changing the value in the quantity box for each product.

To make this work, we’ll wrap the shopping cart table in a
so that the ‘update cart’ button will submit the form:

1. $output[] = '';
2. $output[] = '';
3. foreach ($contents as $id=>$qty) {
4. $sql = 'SELECT * FROM books WHERE id = '.$id;
5. $result = $db->query($sql);
6. $row = $result->fetch();
7. extract($row);
8. $output[] = '';
9. $output[] = '';
10. $output[] = '';
11. $output[] = '';
12. $output[] = '';
13. $output[] = '';
14. $total += $price * $qty;
15. $output[] = '';
16. }
17. $output[] = '
Remove'.$title.' by '.$author.'£'.$price.'£'.($price * $qty).'
';
18. $output[] = '

Grand total: £'.$total.'

';
19. $output[] = '
';
20. $output[] = '
';
21. Download this code: /code/php-cart-7.txt

Note that, even though the form uses the POST method, its action includes a GET variable, “action=update”. Again, we can expand our previous code to process any quantity updates:

1. case 'update':
2. if ($cart) {
3. $newcart = '';
4. foreach ($_POST as $key=>$value) {
5. if (stristr($key,'qty')) {
6. $id = str_replace('qty','',$key);
7. $items = ($newcart != '') ? explode(',',$newcart) : explode(',',$cart);
8. $newcart = '';
9. foreach ($items as $item) {
10. if ($id != $item) {
11. if ($newcart != '') {
12. $newcart .= ','.$item;
13. } else {
14. $newcart = $item;
15. }
16. }
17. }
18. for ($i=1;$i<=$value;$i++) {
19. if ($newcart != '') {
20. $newcart .= ','.$id;
21. } else {
22. $newcart = $id;
23. }
24. }
25. }
26. }
27. }
28. $cart = $newcart;
29. break;
30.
31. Download this code: /code/php-cart-8.txt

This looks quite complicated, but it’s fairly straightforward; we interrogate the contents of the $_POST array (which holds all our quantity values) and extract the relevant id and value pairs. For each product, we then delete all the existing instances of it, and re-insert the new quantity.

There are a number of ways this could have been done – for example, by counting the existing number of each product present in the cart and figuring out whether we needed to add or remove items – but this seemed the easiest way to process the quantity updates.
Summary

And that’s about it! A functional and easy shopping cart script – here’s the final function to display the contents of the cart:

1. function showCart() {
2. $cart = $_SESSION['cart'];
3. if ($cart) {
4. $items = explode(',',$cart);
5. $contents = array();
6. foreach ($items as $item) {
7. $contents[$item] = (isset($contents[$item])) ? $contents[$item] + 1 : 1;
8. }
9. $output[] = '
';
10. $output[] = '';
11. foreach ($contents as $id=>$qty) {
12. $sql = 'SELECT * FROM books WHERE id = '.$id;
13. $result = $db->query($sql);
14. $row = $result->fetch();
15. extract($row);
16. $output[] = '';
17. $output[] = '';
18. $output[] = '';
19. $output[] = '';
20. $output[] = '';
21. $output[] = '';
22. $total += $price * $qty;
23. $output[] = '';
24. }
25. $output[] = '
Remove'.$title.' by '.$author.'£'.$price.'£'.($price * $qty).'
';
26. $output[] = '

Grand total: £'.$total.'

';
27. $output[] = '
';
28. $output[] = '
';
29. } else {
30. $output[] = '

You shopping cart is empty.

';
31. }
32. return join('',$output);
33. }
34. Download this code: /code/php-cart-9.txt

Note that if the cart is empty, a suitable message is displayed.

A working demo can be seen (excuse the lack of design!), or please feel free to download a .zip of all the files – any questions, comments or suggestions on how the code could be improved gratefully received.

Filed under: PHP, MySQL.

Technorati tags: php mysql php shopping cart

Digg this article

Bookmark this article with del.icio.us

Previously: Today I have mostly been learnin'...

Next: Random song lyrics: Update

Comments

Matthijs
1782 days ago
Nice article! A good explanation for how the basics of a shopping cart work. That’ll help us on our way. Thanks.
#1

Christian Montoya
1779 days ago
Awesome. I bookmarked this for later.
#2

Hamish Keith
1774 days ago
Very very good – prob the best i have seen on the web
#3

Andrew
1769 days ago
Thank you so much for this tutorial. It was exactly what I’d been looking for for some time. Great design as well. Beautifully done.
#4

Greg Hamilton
1769 days ago
First of all wicked article, very helpful. Whats the best way to attempt the checkout ? Thanks
#5

Matthew Pennell
1769 days ago
Greg: Funny you should ask that, as I wrote an article that kind of touches on the subject – it is specific to Textpattern, but there is a section on ‘Shopping Integration’ that covers using PayPal to handle payment.

As we’re doing the shoppng cart bit ourselves, we could use PayPal’s new API to simply send the payment details and receive back the response (or whatever other payment processor we’re using).
#6

Greg Hamilton
1769 days ago
Hey Matt thanks for the with the checkout help, quick question about the update function..when you try to update an id that isn’t qty1 it doesnt update…not sure if I messed up the demo file or not :( thanks
#7

Greg Hamilton
1769 days ago
Sorry Matt I mean if you have, lets say two products in yer cart and you try to update the top one it doesnt, but the bottom one will. I think it does it in the demo as well….thanks bud
#8

Matthew Pennell
1768 days ago
Thanks, Greg – I’ve fixed the problem and updated the demo and .zip.
#9

Greg Hamilton
1766 days ago
Hey again Matt
Quick question about quantities, is ti possible to add a function that also allows the user to add quantities inside the product view before adding to cart. Thanks bud
#10

Matthew Pennell
1766 days ago
It would be very easy, Greg, although I don’t have time to do it right now – perhaps a later expansion, unless you want to add it in and publish your changes?
#11

Greg Hamilton
1766 days ago
Ya I’m gonna give it a go. I’ll publish when I get it all set to go.
#12

Andrew
1764 days ago
Hi,

Thanks for the tutorial!

i have changed the cart.php page a little. on the cart.php page, if the user clicks re-fresh, an item is added to the the cart as the ID ifor the product is stored in the querystring. its easily fixed.

Just a heads up for some.

Once again thanks for the demo!
#13

Viktor
1761 days ago
Great tutorial. Most tutorials takes too many steps at the same time so you only end up comfused, but this is vert easy to follow and understand.
#14

hidayat
1744 days ago
Good job boss
it’s nice tutorial ,too nice
#15

Franco
1743 days ago
Hi Matthew,
I test your script
but when i add to cart
this is message page:
Please check quantities…
You shopping cart is empty
I don’t see any product choice.
Can you help me?

Regards and happy New year
Franco
#16

Matthew Pennell
1742 days ago
Hi Franco – I’d need a bit more information to work out what might be wrong; the first thing to try though would be to echo the $id and $cart variables as the code progresses through the ‘add’ routine to see what is happening.
#17

The Illumidigerati
1735 days ago
Matt, does this cart form allow enough security for actual business transactions in the form of moneyorder/personal check? I’ll be webmastering my girlfriends home business and starting out neither of us are really keen on dealing with credit or any online payment ventures, i.e. paypal. Is there a reason for anything more secure when moneyorders/personal checks are the only payment methods since only user information will be logged, not CC info or bank account info?
#18

Matthew Pennell
1735 days ago
This cart script doesn’t handle any kind of payment; it is only for keeping track of what is in the shopping cart.
#19

dreamer85
1734 days ago
good tutorial well done
if it explaines how to display image with the products will be greate tutorial
take care
#20

Momotaro
1725 days ago
Excellent tutorial.
I’d like to know how to display the result in a Form that could then be saved to .csv file server-side? Ultimately, even send me an email when order Form was submited. The tutorials I’ve come across in past were too full of tech-speak that I couldn’t understand.
#21

Aneurin
1720 days ago
Thank You for the tutorial…
i was wondering if i could add product option to this shopping cart..eg. same item(same product id) but different options (size,colour)...i would appreciate some ideas…thanks in advance…
#22

Matthew Pennell
1720 days ago
Aneurin: Yes, that’s certainly an option; the project that I developed this cart code for does just that.

I used certain characters to split the product that was added to the cart, so to take your example of something with a certain size and colour, I would store the sizes and colours in their own tables in the database, then add products in the format “123c6s3”, which would translate as “Product #123, colour #6, size #3”.

The show-cart page would then lookup that colour and size from the table as part of the SQL statement and display the correct description of the product.
#23

Matthew Pennell
1720 days ago
Momotaro: Sending an email when the order is submitted would be pretty simple � the PHP mail() function covers that in detail.
#24

Aneurin
1719 days ago
Thank You very much…...
#25

momotaro
1717 days ago
Thanks for the link
#26

junwei
1709 days ago
nice! It help me alot, u mind adding add to cart and login function . thank
#27

Cormac
1708 days ago
are there anyother api’s that could be intergrated fairly easily with this shopping cart other than PayPal’s ?
#28

Matthew Pennell
1708 days ago
Cormac: There are quite a few online payment mechanisms that are as easy to integrate with as PayPal. WorldPay is probably the next best known one, but there are plenty around.
#29

Matt Cooper
1702 days ago
Nice code, and I very much like your website too, very fresh design. One disappointment (only a small one!) is that you haven’t provided further code for a checkout…is this on the cards? Good luck…
#30

Tan
1700 days ago
Great Demo, the best on the web!
I have one problem, same as franco above, I select add item and it displays check quantities on the cart page, with no items passed through. I have had a look over every page to no avail, would it be a function issue? It passes the id through the url but does not display it.
#31

James
1695 days ago
Hi Matt,

I’m new to all this and am pulling my hair out! – it just won’t work for me – getting messages such as:

Query failed: Unknown column ‘Array’ in ‘where clause’ SQL: SELECT * FROM book WHERE id = Array

or…

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result

or…

Warning: mysql_num_rows(): supplied argument is not a valid MySQL

Where am I going wrong – from the other comments it seems to be pretty easy so it must be something I’m doing wrong!

Thanks for any help – very frstrated at the moment!

James
#32

Matthew Pennell
1695 days ago
James: That sounds as if the id is not being passed through in the querystring correctly. Check that you have populated the database correctly and have configured your database connection information properly.
#33

tan
1694 days ago
Just me again, why would it not display in the cart. Please note above note.
#34

Matthew Pennell
1694 days ago
Tan: It sounds as if the id variable in the querystring is not being picked up by the code (php-cart-3 in the code above) and added to the cart.

You could try adding a loop through all the items in the GET collection:

foreach ($_GET as $key => $value) {
echo $key . � contains � . $value . '
';
}

That will list everything all the variables in the querystring; if nothing is there, it may be that you�ve changed the form method to �post� instead of �get�? Or possibly you're using a very old version of PHP that doesn't support $_GET - you could try the above code with $HTTP_GET_VARS instead; if that works you'll have to change all instances of $_GET to $HTTP_GET_VARS (or upgrade to a host supporting a more modern version of PHP!)
#35

CHiNo4ReaL
1692 days ago
Nice tutorial, I’m gonna use it, but I have a question, is it possible to create catogory’s,
#36

Matthew Pennell
1692 days ago
Chino: Do you mean in the products listings, or in the cart session variable?

It depends how your database is set up whether you’d need any additional work to cope with categories.
#37

G
1692 days ago
Hello,

I have installed your cart on my website, but I have a problem. When I navigate between cart.php and index.php it appears this error message:
“Warning: session_start(): Cannot send session cache limiter – headers already sent (output started at ….global.inc.php:9) in cart.php on line 9”
“Warning: session_start(): Cannot send session cache limiter – headers already sent ”...
Can you please help me to solve it out?
Regards,
G.
#38

CHiNo4ReaL
1691 days ago
@Matthew

Yes, because i’m creating a webshop, with carparts, so there are a few catorgies, like exterior, frontbumpers, rims etc.
#39

CHiNo4ReaL
1691 days ago
Oh and another question, How can I finish the order?
#40

Matthew Pennell
1691 days ago
G: The session_start() command has to appear in the code before any output has been sent to the browser. As your error says, there is output being sent at line 9 of global.inc.php – you must have changed the source files, as my originals only have database connection variables in that file.
#41

Matthew Pennell
1691 days ago
Chino: Bear in mind that the id of the product that is held in the cart doesn’t have to include any reference to the category it is in.

You just have to alter the products database to include a category field; you can then use that when listing products, but just use the product id to hold it in the cart.

To finish the order, you’re going to have to decide what you want to do with it – either have it saved to a database, emailed to you, or processed by an online payment processor like PayPal. That’s beyond the scope of this tutorial, though.
#42

CHiNo4ReaL
1690 days ago
Ok, Do you have an example for the catorgyfield in the database?
#43

jatty5
1690 days ago
Hi all, I am new to this php and am trying to buid a shopping cart for my business myself. I have installed easyphp which has given me apache mysql and php, and am trying to work through this tutorial which I think is excellent for somebody like me a total novice! I have managed to create the database succesfully by copy the complete script and putting it through phpmyadmin. I tried to download the complete zip files, but where do i put them and how on earth do i set the database connection files up. Please help as I am getting alittle lost. I have also downloaded each script but agian where does it go, i know it would be in the WWW directory of my easyphp folder. Please help
#44

Matthew Pennell
1689 days ago
Chino: Just add a category field into your database using whatever MySQL admin tool you’re using.
#45

Matthew Pennell
1689 days ago
jatty5: If you have downloaded the zip you don’t need the individual scripts; just unpack the zip file to a folder under your www directory (e.g. “cart”). The database connection settings are in inc/global.inc.php if you need to change them, but if you’re running it locally the settings should be fine.

Then visit http://localhost/cart/ and it should all be working (you may have to start the PHP and MySQL services, depending on the package you are using).
#46

jatty5
1689 days ago
Hi Mathew, thanks for your swift reply, i have done what you said but still get the following reply:

I take it it has something to do with the database i created the database under phpmyadmin and called the database wtchproject and then run your script under that which created your tables under this database.

What do i change in the gobal file and is it possible for you to show where i can make those changes if possible.

Notice: Could not select database in c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php on line 80

Warning: session_start(): Cannot send session cookie – headers already sent by (output started at c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php:80) in c:\program files\easyphp1-8\www\cart-demo\index.php on line 9

Warning: session_start(): Cannot send session cache limiter – headers already sent (output started at c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php:80) in c:\program files\easyphp1-8\www\cart-demo\index.php on line 9
Your Shopping Cart

Notice: Undefined index: cart in c:\program files\easyphp1-8\www\cart-demo\inc\functions.inc.php on line 3

You have no items in your shopping cart
Books In Our Store

Notice: Query failed: No database selected SQL: SELECT * FROM books ORDER BY id in c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php on line 109

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php on line 151

Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in c:\program files\easyphp1-8\www\cart-demo\inc\mysql.class.php on line 167
#47

ScopAL
1689 days ago
Notice: Undefined index: cart in z:\home\cart\www\inc\functions.inc.php on line 3

You have no items in your shopping cart

:((((
#48

CHiNo4ReaL
1689 days ago
Jatty5
I think that you have selected the wrong database, your database is called wtchprojec, but in the script its selecting books
#49

Matthew Pennell
1689 days ago
jatty5: I would suggest that if you’re trying to learn to use PHP/MySQL, it would be a good idea to learn all about creating database connections, but here is how to fix your problem:

In global.inc.php, you have four variables that are used to create the database connection:

$host = ‘localhost’;
$user = ‘root’;
$pass = ‘’;
$name = ‘’;

These correspond to your host name (usually ‘localhost’), the username and password you are using, and the name of your database.

So in your case, just change the last line to $name=’wtchproject’; and the database connection class object will then be pointing at your database.
#50

CHiNo4ReaL
1687 days ago
If I want the order mailed to me, how can I do that?
I have tried to do that, but i cant find any good ones, can u help me?
#51

Matthew Pennell
1687 days ago
I suppose you’d have to build in some data capture for the customer’s details, and then use mail() to send everything.

I’m not building the whole site for you, though..! ;)
#52

CHiNo4ReaL
1686 days ago
ok, thanks anyway
#53

Greg Hamilton
1684 days ago
Hey again Matt, what are your feelings on using cookies instead of sessions to remember the items that are in the shopping cart, do you think its necessary at all ?
#54

Matthew Pennell
1684 days ago
Greg: The benefit would obviously be that you would be able to leave the site and come back to the same basket; an approach using both sessions and cookies would probably be a good idea (with a fairly short expiration period).
#55

Juilius
1683 days ago
Hi I am having problems with the cart.php, whenever I hit the refresh button it adds another quantity to the cart? Any ideas?
Oh by the way, great, great example!
#56

Matthew Pennell
1682 days ago
Juilius: Yes, refreshing the page will add another item to the cart due to its use of the querystring ($_GET collection) to handle addition.

You can get around it by adding an extra session variable and storing the last item added and the time it was added, then checking against that to see if it is a repeated entry within a certain timeframe.
#57

Steven
1657 days ago
I’ll be messing around with this on XAMPP soon! I’m a bit of a PHP beginner but I thinks it’s important to dive in at the deep end!
#58

Steven
1657 days ago
I’ll be messing around with this on XAMPP soon! I’m a bit of a PHP beginner but I thinks it’s important to dive in at the deep end!

PS. Edited to ask, does a shopping cart system like this have to be bolted onto a dynamic website or can I add it to static product pages?
#59

Matthew Pennell
1657 days ago
Steven: I’m not sure how you’d use it with static pages, as it relies on a database of product details – I suppose you could copy your static data into the database, but if you’re doing that you may as well be using the database to output your product info as well!
#60

Fred
1641 days ago
I’m just curious, since this code connect to the db and all. Is this vulnerable to sql injection at all?

Can users modify the input variables or the session cookie to inject special characters that could cause buffer overflow in the SQL sequence?

I don’ recall seeing anywhere that inputs are scrubbed, but I could be wrong or the way the code is written, input scrubbing might be a non-issue.

Any confirmation on this?
#61

Matthew Pennell
1640 days ago
Fred: You are correct that if you wanted to use this in a commercial setting it would be a good idea to add some validation of the session data when it is read to output the contents of the cart. This could be as simple as using preg_match to check that the ID of the product is numeric.
#62

Fred
1640 days ago
This was some amazingly great cart code which gave me a good jump on what I wanted to create. Thanks Matt.

I did add some basic sanitization code to keep the db safe from SQL injects. You can take from the following.

I added a sanitization function and then called it on certain variable inputs used in the cart program:

function sanitize_system_string($string, $min=’’, $max=’’) {
$pattern = ’/(;|\||`|>| $max)))
return FALSE;
return $string;
}

// these occur in various places
sanitize_system_string($_GET[‘id’],1,50)
$cart = sanitize_system_string($_SESSION[‘cart’]);
$action = sanitize_system_string($_GET[‘action’],1,50);

// assigned right underneath the update switch foreach loop
$key = sanitize_system_string($key,1,50);
#63

Alex
1640 days ago
Thanx for the code Fred very helpful…
#64

Fred
1639 days ago
Sorry the sanitize_system_string function got partially truncated by the comment submit form. The other examples of variable sanitization remained in tact though.

Anyway, folks can get the point. Find a sanitization function, insert it in the functions php document and then employ it throughout the cart code as needed.

I identified several good places in the last post :)

I love this cart code matt provided! It has provided me the basis to do some wonders with it!
#65

Ella
1638 days ago
Hi There,
I am fairly new to php etc and am trying to do a shopping cart as part of my final project for my degree. I am getting an error when I click the add to cart link it goes through to the cart page but has the following error:
Parse error: parse error in c:\LocalUser\fashuti\public_html\cart.php on line 174
it is erroring on the last line of code on the page which is obciously not the problem. I have adapted the code on your size so that i can distinguish by id, size and colour. My id’s are both letter and charactger based. I can understand all of the code apart from one line which confuses me and I wonder if it could relate to that?
It seems you are comparing qty which is created here to the id?

foreach ($contents as $id=>$qty) {

I wondered if this could be my problem?
I will post my cart code below:

Hi There,
I am fairly new to php etc and am trying to do a shopping cart as part of my final project for my degree. I am getting an error when I click the add to cart link it goes through to the cart page but has the following error:
Parse error: parse error in c:\LocalUser\fashuti\public_html\cart.php on line 174
it is erroring on the last line of code on the page which is obciously not the problem. I have adapted the code on your size so that i can distinguish by id, size and colour. My id’s are both letter and charactger based. I can understand all of the code apart from one line which confuses me and I wonder if it could relate to that?
It seems you are comparing qty which is created here to the id?

foreach ($contents as $id=>$qty) {

I wondered if this could be my problem?
I will post my cart code below:

$total = 0;
$output[] = ‘’;

$conn=odbc_connect(‘danum_fashu’,’’,’’);
foreach ($contents as $id=>$qty) {
$sql = ‘SELECT * FROM products, product_line WHERE products.product_id = ’.$id.’ AND products.product_id = product_line.product_id AND product_line.product_size= ’.$sz.’ AND product_line.product_colour = ’.$col;
$rs=odbc_exec($conn,$sql);

$pid=odbc_result($rs,”product_line.product_id”);
$size=odbc_result($rs,”product_line.product_size”);
$name=odbc_result($rs,”products.product_name”);
$colour=odbc_result($rs,”product_line.product_colour”);
$cost=odbc_result($rs,”products.product_costprice”);
$endcost = number_format($cost * 2.6, 2, ’.’, ’,’);

$output[] = ‘’;
$output[] = ‘’.$pid.’ by ’.$name.’’;
$output[] = ‘’.$size.’ ’.$colour.’’;
$output[] = ’£’.$endcost.’’;
$output[] = ‘’;
$output[] = ’£’.($endcost * $qty).’’;
$output[] = ‘Remove’;
$total += $endcost * $qty;
$output[] = ‘’;

}
$output[] = ‘’;
$output[] = ‘Grand total: £’.$total.’’;
odbc_close($conn);

Any help would be gratefully recieved

2010年10月9日星期六

SimpleXML介绍

SimpleXML提供了一种简单,直观的方法来处理XML。它只有一个单一类型的类,三个函数和六个方法。

使用SimpleXML

SimpleXMLElement 类是这个扩展中所有操作的核心类。可以用new关键字直接创建这种类,或是使用simplexml_load_file()或 simplexml_load_string()函数返回这种类。本文将使用清单7-1的XML文档来说明如何使用SimpleXML,将此文档命名为 sml.xml。

清单7-1 sml.xml

1.
2. 3. "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
4.
5.
6. SimpleXML in PHP 5
7.
8. Rob
9. Richards
10.

11.
12. 2005
13. Rob Richards
14.

15.

16.
17. Using SimpleXML
18. An example DOM Tree using DocBook.
19.

20.
21. Accessing Elements
22. Elements are accessed as properties
23.
24. 25. 26. $data = '
27. content';
28. $sxe = simplexml_load_string($data);
29. var_dump($sxe);
30. ?>
31. ]]>
32.

33.

34.


创建一个SimpleXMLElement对象

使用new关键字创建

1. $xml = "Content";
2. $sxe = new SimpleXMLElement($xml);//SimpleXMLElement Object ( [node1] => Content )

使用simplexml_load_string()创建

1. $xml = "Content";
2. $sxe = simplexml_load_string($xml);//SimpleXMLElement Object ( [node1] => Content )

如何选择这两种创建SimpleXMLElement的方法呢?simplexml_load_string()提供了更多的函数,比如控制解析选项的能力。如果不需要这些额外的函数的话就可以凭个人爱好选择一种方法。

使用simplexml_load_file()从一个URI创建

1. $sxe = simplexml_load_file("filename.xml");

simplexml_load_string()和simplexml_load_file()都有一个必需的参数和可选的参数。从PHP5.1开始simplexml_load_file()多了一个用来控制解析行为的第三个参数。

1. /* Prototype for PHP 5.0 */
2. simplexml_load_file(string data [, string class_name])
3. /* Prototype for PHP 5.1 */
4. simplexml_load_file(string data [, string class_name [, int options]])

保存XML数据

与DOM扩展一样,SimpleXML也提供了一个用来输出XML内容的方法asXML()。可以用这个方法以字符串或文件形式输出这个文档或文档中的某个节点。

1. $xml = "content";
2. $sxe = new SimpleXMLElement($xml);
3. print $sxe->asXML();
4. $sxe->asXML('test.xml');

输出:

1.
2. content

访问元素节点

在SimpleXML中,可以直接通过元素的名称来访问特定的元素。

访问元素

当一个文档被载入SimpleXML时,文档被看成是一个SimpleXML对象,文档中的所有元素都被看成是该对象的属性。

1. 2. $book = simplexml_load_file('sxml.xml');
3. /* Access the bookinfo child element of the book element */
4. $bookinfo = $book->bookinfo;
5. /* Access the title child element from the bookinfo element */
6. $title = $bookinfo->title;
7. ?>

如果使用DOM来访问title,代码如下

1. $dom = new DOMDocument();
2. $dom->load('sxml.xml');
3. $book = $dom->documentElement;
4. foreach($book->childNodes as $node) {
5. if ($node->nodeName == "bookinfo") {
6. foreach($node->childNodes as $child) {
7. if ($child->nodeName == "title") {
8. $node = $child;
9. break 2;
10. }
11. }
12. }
13. }
14. if ($node) {
15. $title = $node;
16. }

显然SimpleXML对的起它的名字。

访问内容

1. 2. $book = simplexml_load_file('sxml.xml');
3. $bookinfo = $book->bookinfo;
4. $title = $bookinfo->title;
5. /* Object examined with var_dump */
6. var_dump($title);
7. /* Using print with element containing text-only content */
8. print "Title: ".$title."\n";
9. $author = $bookinfo->author;
10. /* Object examined with var_dump */
11. var_dump($author);
12. /* Using print with element containing child elements */
13. print "Author: ".$author."\n";
14. ?>

这段代码检查了两个SimpleXMLElement对象,$author和$title。两者的区别是$author元素有包含子元素而$title元素只包含一个文本节点。
输出如下:
object(SimpleXMLElement)#4 (1) {
[0]=>
string(18) “SimpleXML in PHP 5″
}
Title: SimpleXML in PHP 5
object(SimpleXMLElement)#6 (2) {
[”firstname”]=>
string(3) “Rob”
[”surname”]=>
string(8) “Richards”
}
Author:

检查输出结果可以发现,$title是一个包含有文本内容的SimpleXMLElement对象,索引0表示元素的文本内容,当打印$title时,文本内容将以字符串形式返回。

$author元素有两个子元素,从输出结果可以看出,这些子元素被看成SimpleXMLElement对象的属性,这些属性的值是它们对应的节点包含的内容。用print输出$author时,输出结果是空格和换行符。

如果一个元素无子元素,只包含文本内容,那么可以将此元素所对应的SimpleXMLElement对象视为一个字符串来使用,有些情况下,为了获得以字符串形式返回文本内容,必须执行类型转换操作:

1. $titlecontent = (string) $title;

有子元素的元素所对应的SimpleXMLElement对象将返回该对象直属的文本节点,而不是任一子元素的内容。如果用print输出$author,将得到一个27字符长度,包含空格和换行符的字符。 Neither of the child elements,firstname or surname, nor their content is returned in the string.

为了理解最后一点,可以运行一些下面这段代码:

1. $doc = new SimpleXMLElement('somesubtextthing');
2. print $doc;

使用迭代对象

SimpleXMLElement 对象在大多数情况下是可迭代的,可以用这个特性来访问文档中多个元素名一样的节点,如清单7-1中的para元素。在使用元素名作为属性来访问一个元素时,SimpleXMLElement对象不是一个单一节点的存取器,它实际上是作为属性访问的元素名节点的集合。直接利用元素名作为属性访问实际上访问访问这个集合中的第一个元素。

看下这段代码

1. $book = simplexml_load_file('sxml.xml');
2. $para = $book->chapter->para;
3. print $para."\n";
4. foreach($para AS $node) {
5. print $node."\n";
6. }

这段代码中,$para变量就是para元素的集合,实际上包含了两个元素。如果直接访问$para的话实际上是访问第一个para元素,利用迭代可以看出 $para所包含的内容是两个元素。其中CDATA节点被看做是纯文本的内容,其中包含的空格和换行符都会被如实输出。

用迭代的方式来访问所有的元素显然不太实际,有时候我们希望访问结果集中的某个特定的元素,这时可以使用从0开始的索引来访问这个结果集,例如:

1. $book = simplexml_load_file('sxml.xml');
2. $para = $book->chapter->para[1];
3. print "Content: ".$para."\n";
4. foreach($para AS $node) {
5. print "Iter Content: ".$node."\n";
6. }

运行这段代码会发现foreach循环失效了,这是因为SimpleXML知道你只是在寻找结果集中某个特定的元素,这种情况下对象是不可迭代的。

Caution:使用索引方式访问一个SimpleXMLElement对象会返回一个不可以迭代的对象,因为它是一个单一的元素而不是一个元素集。

访问未知元素

在不知道XML文档的结构的情况下可以利用SimpleXML中的children()方法来返回一个可以迭代方式访问的某个元素的所有子元素的SimpleXML对象。如:

1. $book = simplexml_load_file('sxml.xml');
2. $author = $book->bookinfo->author;
3. $children = $author->children();
4. foreach($children AS $child) {
5. print $child."\n";
6. }

上述代码用children()方法返回了author节点下的所有子元素,然后用foreach循环输出。也可以使用索引方式访问返回的子元素,如echo $children[1];。

理解PHP对象函数

SimpleXMLElement对象的属性是动态的,因为这些属性是由对象实例决定的,而不是由类本身决定的。在PHP中,可以利用get_object_vars()函数来返回某个对象的所有属性,返回的结果是一个包含属性和值的数组,如:

1. $props = get_object_vars($author);
2. foreach ($props AS $name=>$value) {
3. print $name.": ".$value."\n";
4. }

输出:
firstname: Rob
surname: Richards
这段代码访问的子元素都只包含文本内容,所以返回的数组只包含属性名和值,对于一个包含许多子节点的元素,返回的结果稍微复杂点:

1. $props = get_object_vars($book->bookinfo);
2. var_dump($props);

输出:

array(3) {
[”title”]=>
string(18) “SimpleXML in PHP 5″
[”author”]=>
object(SimpleXMLElement)#4 (2) {
[”firstname”]=>
string(3) “Rob”
[”surname”]=>
string(8) “Richards”
}
[”copyright”]=>
object(SimpleXMLElement)#5 (2) {
[”year”]=>
string(4) “2005″
[”holder”]=>
string(12) “Rob Richards”
}
}

使用DOM互操作

另一种访问未知元素的方法是使用DOM,可以将一个节点导入DOM扩展,然后使用DOM的属性和方法来处理。

1. $book = simplexml_load_file('sxml.xml');
2. $author = $book->bookinfo->author;
3. $children = $author->children();
4. foreach($children AS $child) {
5. /* Import node into DOM, and get nodeName */
6. $element = dom_import_simplexml($child);
7. $name = $element->nodeName;
8. print $name.": ".$child."\n";
9. }

将节点导入DOM扩展时并没有创建一个节点的副本(copy),而是直接访问导入的节点(JIMMY注:这个概念很重要)。

修改内容

利用SimpleXML修改元素内容非常方便,你可以改变或移除树中的某个元素,但是不能直接在树中添加一个元素。要添加一个元素,可以使用DOM的互操作性:

1. $xml = "content";
2. $sxe = new SimpleXMLElement($xml);
3. $dom = dom_import_simplexml($sxe);
4. $dom->appendChild(new DOMElement("node2", "content2"));
5. print $sxe->asXML();

输出:

1.
2. contentcontent2

编辑文本内容

可以利用SimpleXML的属性赋值方法来直接编辑一个元素的内容,要主意的是如果文档中有多个元素名一样的元素,如果没有使用索引来指定要编辑哪个元素时PHP将发出一个警告。如:

1. $book = simplexml_load_file('sxml.xml');
2. /* Modify an unspecified para element where multiple para elements exist */
3. $book->chapter->para = "Removed CDATA";

输出:
Warning: main() [/phpmanual/function.main.html]: Cannot assign to an array of nodes
(duplicate subnodes or attr detected)

必须给para指定索引告诉程序你要编辑哪个元素:

1. $book = simplexml_load_file('sxml.xml');
2. $book->chapter->para[1] = "Removed CDATA";
3. print $book->chapter->asXML();

输出:

1.
2. Acessing Elements
3. Elements are accessed as properties
4. Removed CDATA
5.


这样,第二个para元素的内容被改为Removed CDATA。如果要编辑一个在文档中唯一存在的元素可不必指定索引,直接修改。如修改title:

1. $book = simplexml_load_file('sxml.xml');
2. $book->chapter->title = "New Title";
3. $book->chapter->para[1] = "Removed CDATA";
4. print $book->chapter->asXML();

输出:

1.
2. New Title
3. Elements are accessed as properties
4. Removed CDATA
5.


强烈建议使用索引来编辑元素,除非你对文档的结果非常确定。使用索引来编辑title元素会比较安全,如$book->chapter->title[0] = “New Title”;这行代码用索引[0]指定要编辑第一个title。

编辑有子树的元素

1. $book = simplexml_load_file('sxml.xml');
2. $cholder = $book->bookinfo->copyright->holder;
3. print $cholder->asXML()."\n";
4. $book->bookinfo = "No Book Info";
5. print $book->bookinfo->asXML()."\n";
6. print $cholder->asXML()."\n";

输出:

1. Rob Richards
2. No Book Info
3. Warning: SimpleXMLElement::asXML() [/phpmanual/function.asXML.html]: Node no
4. longer exists in N:\CVS Projects\php5\Debug_TS\booksxe.php on line 7

这段代码中,首先将文档中的holder元素赋值给$cholder变量,然后打印该变量。bookinfo元素包含有title,author和 copyright子树,它的内容被字符串No Book Info代替,从bookinfo的输出结果可以看出,它的子树被清空并且被字符串代替了。接着试图再次打印$cholder变量的XML内容,程序输出一个警告,这个变量依然是一个SimpleXMLElement对象,但它所属的节点在bookinfo元素改变时已经被破坏了。

另一种情况。将bookinfo元素中的子元素用字符串 代替。

1. $book = simplexml_load_file('sxml.xml');
2. $book->bookinfo = "SimpleXML in PHP 5";
3. print $book->bookinfo->asXML()."\n";

如果你认为上述代码将bookinfo中的内容清空后再给bookinfo创建了一个子节点title,那么你错了。输出结果是
SimpleXML in PHP 5
实际上bookinfo元素的子元素都被移除了,但是新赋值的XML数据被转义成文本内容,而不是一个新的子元素。

如果想用一个子树代替另一个子树,可以利用DOM扩展:

1. $book = simplexml_load_file('sxml.xml');
2. $bookinfo = dom_import_simplexml($book->bookinfo);
3. /* 移除bookinfo元素下的所有子元素*/
4. while ($bookinfo->firstChild) {
5. $bookinfo->removeChild($bookinfo->firstChild);
6. }
7. $bookinfo->appendChild(new DOMElement("title", "SimpleXML in PHP 5"));
8. print $book->bookinfo->asXML()."\n";

输出结果:

1.
2. SimpleXML in PHP 5
3.


移除元素

可以用PHP内置函数unset()来将一个元素从树中移除。unset()的参数必须是一个SimpleXMLElement,用属性方法来访问要移除的元素。例如,从chapter节点移除title元素:

1. $book = simplexml_load_file('sxml.xml');
2. $book->chapter->para[1] = "Removed CDATA";
3. unset($book->chapter->title);
4. print $book->chapter->asXML();

上述代码执行后,chapter的结构为:

1.
2.
3. Elements are accessed as properties
4. Removed CDATA
5.


将这个结果与下面代码执行的结果想比较:

1. $book = simplexml_load_file('sxml.xml');
2. $book->chapter->para[1] = "Removed CDATA";
3. $title = $book->chapter->title;
4. unset($title);
5. print $book->chapter->asXML();

输出结果

1.
2. Acessing Elements
3. Elements are accessed as properties
4. Removed CDATA
5.


title元素没有被移除,unset函数只对$title变量作用并没有将title元素从树中移除。

在移除一个元素时必须注意,用索引来指定的特定元素不会被移除:

1. $book = simplexml_load_file('sxml.xml');
2. $book->chapter->para[1] = "Removed CDATA";
3. unset($book->chapter->title[0]);
4. print $book->chapter->asXML();

输出:

1.
2. Acessing Elements
3. Elements are accessed as properties
4. Removed CDATA
5.


如果要移除所有的para元素时可以利用下面代码:

1. $book = simplexml_load_file('sxml.xml');
2. unset($book->chapter->para);
3. print $book->chapter->asXML();

输出:

1.
2. Acessing Elements
3.
4.
5.


问题是如果你只想移除其中的一个para元素时要怎么办。这时可以再次用到DOM扩展:

1. $book = simplexml_load_file('sxml.xml');
2. $chapter = dom_import_simplexml($book->chapter);
3. $node = $chapter->lastChild;
4. while($node) {
5. if ($node->nodeName == "para") {
6. $chapter->removeChild($node);
7. $node = NULL;
8. break;
9. }
10. $node = $node->previousSibling;
11. }
12. print $book->chapter->asXML();

输出:

1.
2. Acessing Elements
3. Elements are accessed as properties
4.
5.


所幸的是PHP5.2开始已经支持删除用索引指定的元素了:

1. $book = simplexml_load_file('sxml.xml');
2. unset($book->chapter->para[1]);
3. print $book->chapter->asXML();

访问属性

读取属性

下面的代码输出了book元素中的lang属性

1. $book = simplexml_load_file('sxml.xml');
2. print $book['lang'];

访问用索引指定的元素的属性:

1. $book = simplexml_load_file('sxml.xml');
2. print $book->chapter[0]['id'];

在不知道属性名的情况下可以用attributes()方法来输出属性:

1. $book = simplexml_load_file('sxml.xml');
2. foreach($book->chapter->attributes() AS $attribute) {
3. print $attribute."\n";
4. }

如果要获得位置的属性名,可以使用DOM扩展:

1. $book = simplexml_load_file('sxml.xml');
2. foreach($book->chapter->attributes() AS $attribute) {
3. $att = dom_import_simplexml($attribute);
4. print $att->nodeName."\n";
5. print $attribute."\n";
6. }

修改属性
修改属性的值跟修改元素的值一样,直接对其赋值就可以了:

1. $book = simplexml_load_file('sxml.xml');
2. $book['lang'] = "es";
3. print $book['lang'];

添加一个属性也很简单,如果对一个不存在的属性名进行赋值就给元素创建一个新属性

1. $book = simplexml_load_file('sxml.xml');
2. $book->bookinfo->author->firstname["prefix"] = "Mr.";
3. print $book->bookinfo->author->asXML();

输出:

1.
2. Rob
3. Richards
4.


移除属性

移除属性也用到unset()函数:

1. $book = simplexml_load_file('sxml.xml');
2. $book->bookinfo->author->firstname["prefix"] = "Mr.";
3. print $book->bookinfo->author->firstname->asXML()."\n\n";
4. unset($book->bookinfo->author->firstname["prefix"]);
5. print $book->bookinfo->author->firstname->asXML();

输出:

1. Rob
2.
3. Rob

扩展SimpleXMLElement类

1. class mySXE extends SimpleXMLElement {
2. function appendChild($name, $content) {
3. $dom = dom_import_simplexml($this);
4. $dom->appendChild($dom->ownerDocument->createElement($name, $content));
5. }
6. }

当实例化扩展的类时,文档的每个节点对象的类型都是扩展类的类型。

1. $sxe = new mySXE("");
2. $sxe->node1->appendChild("node2", "content");
3. print $sxe->asXML();

输出:

1.
2. content

使用new方法可以用来处理字符串类型的XML,如果XML保存在一个文件中,那么可以将扩展的类名作为第二个参数传给simplexml_load_string或simplexml_load_file

1. $sxe = simplexml_load_string("", "mySXE");
2. $sxe->node1->appendChild("node2", "content");
3. print $sxe->asXML();

输出结果与用new关键字输出的结果一样。

在SimpleXML中使用命名空间

将清单7-1的内容改为

1.
2.
3.
4. SimpleXML in PHP 5
5.
6. Rob
7. Richards
8.

9.
10. 2005
11. Rob Richards
12.

13.

14.


如果试图用普通的方法来访问元素或属性,你会分析这并不可行,例如:

1. $book = simplexml_load_file('sxmlns.xml');
2. print $book["lang"]."\n";
3. print $book->bookinfo->title."\n";

输出的结果是两行空白。

在访问命名空间节点前,必须使用children()和attributes()方法。这两个方法不仅可以在没有指定参数的时候使用,也可以在指定一个 URI命名空间作为参数使用。如果一个SimleXMLElement对象是从这两个方法返回的,那么你就可以像普通的元素和属性一样访问命名空间下的元素和属性:

1. $book = simplexml_load_file('sxmlns.xml');
2. /* 返回 http://www.example.com/ns2 命名空间下的所有属性 */
3. $bookatts = $book->attributes("http://www.example.com/ns2");
4. print $bookatts["lang"]."\n";
5. /* 返回 http://www.example.com/ns1 命名空间下的所有元素*/
6. $bookns = $book->children("http://www.example.com/ns1");
7. $bookinfo = $bookns->bookinfo;
8. /* 重置命名空间来访问非命名空间的元素 */
9. $nonsbkinfo = $bookinfo->children();
10. print $nonsbkinfo->title."\n";

children ()和attributes()方法可以被看做是过滤器,如果没有参数或者传递一个NULL作为参数,这两个方法将返回非命名空间下的元素或属性;否则将返回特定命名空间下的元素或属性。在重置之前,命名空间仍然起作用并且被子节点继承。例如,使用$bookinfo被设置为命名空间 http://www.example.com/ns1的对象,可以使用 print $bookinfo->author->firstname来但因author中的firstname元素。所有的元素都在命名空间下,因此你在创建$bookinfo对象时不必一直使用children()设置命名空间。

SimpleXML php操作xml的简单入门

DOM是Document Object Model文档对象模型的缩写。根据W3C DOM规范(http://www.w3.org/DOM/),DOM是一种与浏览器,平台,语言无关的接口,使得你可以访问页面其他的标准组件。

说到对xml文件的操作,简单的入门的话无非是: 读取文件,写文件,修改文件

首先说读取文件,我们可以使用两种方法,一种是dom方式,一种是php提供的SimpleXMLElement对象。

我的xml文件中的内容如下:



Great American Novel
This is a science fiction!
Science Novel


名字叫做test1.xml

$xmlstr = 'test1.xml';
//加载xml文件(load a xml file)
//$xml = simplexml_load_file($xmlstr); //这也是一种简单可行的方法

//the third parameter means the first parameter is url or not
$xml = new SimpleXMLElement($xmlstr,NULL,TRUE);
//以属性的方式读取
//echo $xml->book[0]->title;
//循环度读取元素
foreach ($xml->book as $book ){
//echo $book->plot."
";
}

//修改文本节点信息
$xml->book[0]->define = "self define!";
//插入节点
$mybook = $xml->addChild('book');
$mybook->addChild('title','Anna');
$mybook->addChild('plot','About Anna');
$mybook->addChild('define','added by me!');

echo $xml->asXML(); //输出xml文件内容
代码很清晰,不用更多解释了。下面的是使用dom对象读取的方法:

$dom = new DOMDocument('1.0');
$books = $dom->getElementsByTagName('book'); //注意这里获取的类型是DOMNodelist
$book = $books->item(0); //这里获取的类型是DOMNode
$title = $book->getElementsByTagName('title');
echo $title->item(0)->nodeValue; //输出节点信息

这里有点难理解,关键是DOMNodelist和DOMNode这两种类型。 nodelist就像一个数组,是我们用getElementsByTagName获取到的,它具有item方法,item后面可以跟上index来指明是访问那个node,有点类似与数组但是用法完全不一样。node的话,具有nodeValue这个属性,可是使用它来访问node的具体内容。 对于nodelist,一般都倾向与使用迭代来便利,用法如下:
foreach($books as $book){ //$books是nodelist类型的,$book是node类型的
//具体操作
}

读文件完成了,下面是写文件:

//创建一个dom对象
$dom = new DOMDocument('1.0');
//创建根节点
$books = $dom->appendChild($dom->createElement('books'));
//创建子节点
$book = $books->appendChild($dom->createElement('book'));
//创建子节点
$title = $book->appendChild($dom->createElement('title'));
//写入文本内容
$title->appendChild($dom->createTextNode('Great American Novel'));
//格式化输出
$dom->formatOutput = true;
//保存xml到文件
$dom -> save('test1.xml'); // save as file

最后是修改文件,其实也很简单,打开以后读取node的值,修改,然后保存就可以了。

//读取根节点
$root = $dom->getElementsByTagName('books');
//创建一个book节点
$mybook = $dom->createElement('book');
//添加到根节点
$root->item(0)->appendChild($mybook);
//创建title节点
$title = $dom->createElement('title');
//向title节点写入文本内容
$title->appendChild($dom->createTextNode('The deviche code'));
//修改文本内容
$title->nodeValue='my novel';
//添加到book节点
$mybook->appendChild($title);
//保存xml文件
$dom->save($xml_path);

修改文本的时候有两种方法,个人感觉第二种直接写nodeValue更好一些。 掌握了这三种操作,简单的xml操作基本就没有问题了。


这里有所有你想要的:
DOMNodelist讲解: http://www.phpchina.com/manual/php/function.dom-domnodelist-item.html
phpsimplexml官方帮助文档: http://us3.php.net/manual/en/book.simplexml.php

2010年10月2日星期六

举例说明数据库一、二、三及BCNF范式

数据库的设计范式是数据库设计所需要满足的规范,满足这些规范的数据库是简洁的、结构明晰的,同时,不会发生插入(insert)、删除(delete)和更新(update)操作异常。反之则是乱七八糟,不仅给数据库的编程人员制造麻烦,而且面目可憎,可能存储了大量不需要的冗余信息。

设计范式是不是很难懂呢?非也,大学教材上给我们一堆数学公式我们当然看不懂,也记不住。所以我们很多人就根本不按照范式来设计数据库。

实质上,设计范式用很形象、很简洁的话语就能说清楚,道明白。本文将对范式进行通俗地说明,并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。

范式说明

第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

例如,如下的数据库表是符合第一范式的:

字段1 字段2 字段3 字段4


而这样的数据库表是不符合第一范式的:

字段1 字段2 字段3 字段4
字段3.1 字段3.2



很显然,在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。

第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分),关键字为组合关键字(学号, 课程名称),因为存在如下决定关系:

(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)

这个数据库表不满足第二范式,因为存在如下决定关系:

(课程名称) → (学分)

(学号) → (姓名, 年龄)

即存在组合关键字中的字段决定非关键字的情况。

由于不符合2NF,这个选课关系表会存在如下问题:

(1) 数据冗余:

同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。

(2) 更新异常:

若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。

(3) 插入异常:

假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。

(4) 删除异常:

假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。

把选课关系表SelectCourse改为如下三个表:

学生:Student(学号, 姓名, 年龄);

课程:Course(课程名称, 学分);

选课关系:SelectCourse(学号, 课程名称, 成绩)。

这样的数据库表是符合第二范式的, 消除了数据冗余、更新异常、插入异常和删除异常。

另外,所有单关键字的数据库表都符合第二范式,因为不可能存在组合关键字。

第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:

关键字段 → 非关键字段x → 非关键字段y

假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话),关键字为单一关键字"学号",因为存在如下决定关系:

(学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话)

这个数据库是符合2NF的,但是不符合3NF,因为存在如下决定关系:

(学号) → (所在学院) → (学院地点, 学院电话)

即存在非关键字段"学院地点"、"学院电话"对关键字段"学号"的传递函数依赖。

它也会存在数据冗余、更新异常、插入异常和删除异常的情况,读者可自行分析得知。

把学生关系表分为如下两个表:

学生:(学号, 姓名, 年龄, 所在学院);

学院:(学院, 地点, 电话)。

这样的数据库表是符合第三范式的,消除了数据冗余、更新异常、插入异常和删除异常。

鲍依斯-科得范式(BCNF):在第三范式的基础上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。

假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

(仓库ID, 存储物品ID) →(管理员ID, 数量)

(管理员ID, 存储物品ID) → (仓库ID, 数量)

所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

(仓库ID) → (管理员ID)

(管理员ID) → (仓库ID)

即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况:

(1) 删除异常:

当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。

(2) 插入异常:

当仓库没有存储任何物品时,无法给仓库分配管理员。

(3) 更新异常:

如果仓库换了管理员,则表中所有行的管理员ID都要修改。

把仓库管理关系表分解为二个关系表:

仓库管理:StorehouseManage(仓库ID, 管理员ID);

仓库:Storehouse(仓库ID, 存储物品ID, 数量)。

这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。

范式应用

我们来逐步搞定一个论坛的数据库,有如下信息:

(1) 用户:用户名,email,主页,电话,联系地址

(2) 帖子:发帖标题,发帖内容,回复标题,回复内容

第一次我们将数据库设计为仅仅存在表:


用户名 email 主页 电话 联系地址 发帖标题 发帖内容 回复标题 回复内容

这个数据库表符合第一范式,但是没有任何一组候选关键字能决定数据库表的整行,唯一的关键字段用户名也不能完全决定整个元组。我们需要增加"发帖ID"、"回复ID"字段,即将表修改为:

用户名 email 主页 电话 联系地址 发帖ID 发帖标题 发帖内容 回复ID 回复标题 回复内容

这样数据表中的关键字(用户名,发帖ID,回复ID)能决定整行:

(用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容)

但是,这样的设计不符合第二范式,因为存在如下决定关系:

(用户名) → (email,主页,电话,联系地址)

(发帖ID) → (发帖标题,发帖内容)

(回复ID) → (回复标题,回复内容)

即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。

我们将数据库表分解为(带下划线的为关键字):

(1) 用户信息:用户名,email,主页,电话,联系地址

(2) 帖子信息:发帖ID,标题,内容

(3) 回复信息:回复ID,标题,内容

(4) 发贴:用户名,发帖ID

(5) 回复:发帖ID,回复ID

这样的设计是满足第1、2、3范式和BCNF范式要求的,但是这样的设计是不是最好的呢?

不一定。

观 察可知,第4项"发帖"中的"用户名"和"发帖ID"之间是1:N的关系,因此我们可以把"发帖"合并到第2项的"帖子信息"中;第5项"回复"中的"发 帖ID"和"回复ID"之间也是1:N的关系,因此我们可以把"回复"合并到第3项的"回复信息"中。这样可以一定量地减少数据冗余,新的设计为:

(1) 用户信息:用户名,email,主页,电话,联系地址

(2) 帖子信息:用户名,发帖ID,标题,内容

(3) 回复信息:发帖ID,回复ID,标题,内容

数据库表1显然满足所有范式的要求;

数据库表2中存在非关键字段"标题"、"内容"对关键字段"发帖ID"的部分函数依赖,即不满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常;

数据库表3中也存在非关键字段"标题"、"内容"对关键字段"回复ID"的部分函数依赖,也不满足第二范式的要求,但是与数据库表2相似,这一设计也不会导致数据冗余和操作异常。

由此可以看出,并不一定要强行满足范式的要求,对于1:N关系,当1的一边合并到N的那边后,N的那边就不再满足第二范式了,但是这种设计反而比较好!

对于M:N的关系,不能将M一边或N一边合并到另一边去,这样会导致不符合范式要求,同时导致操作异常和数据冗余。
对于1:1的关系,我们可以将左边的1或者右边的1合并到另一边去,设计导致不符合范式要求,但是并不会导致操作异常和数据冗余。

结论

满足范式要求的数据库设计是结构清晰的,同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的,在数据库表中存在1:1或1:N关系这种较特殊的情况下,合并导致的不符合范式要求反而是合理的。

在我们设计数据库的时候,一定要时刻考虑范式的要求。

第一范式,第二范式,第三范式,BCNF

第一范式 1NF

属性不可再分割,符合原子性。

没什么好解释的,地球人都明白

第二范式 2NF

在1NF的基础上:

不允许出现有field部分依赖于主键(或者说依赖于主键的一部分)

官方说法:数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于整组候选关键字。

Allen解释一下:比如一张表是(A, B, C, D),其中(A, B)是主键,如果存在B->C就违反了2NF,因为C只需要主键的一部分就可以被决定了

第三范式 3NF

在2NF的基础上:

不允许出现可传递的依赖关系(transitive dependencies)

官方说法:在第二范式的基础上,数据表中如果不存在非关键字段对关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A

很容易理解,Kiza同学说这是人类的常识。

Boyce-Codd范式 BCNF

在3NF的基础上:

不允许出现有主键的一部分被主键另一部分或者其他部分决定

另一种说法:Left side of every non-trivial FD must contains a key

每个非平凡FD的左边必须有一个主键

BCNF,表中的每个决定因子是候选键。如果只有一个候选键,则3NF和BCNF相同

网友们通常喜欢用的一个例子:

假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

   (仓库ID, 存储物品ID) →(管理员ID, 数量)

   (管理员ID, 存储物品ID) → (仓库ID, 数量)

所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

   (仓库ID) → (管理员ID)

   (管理员ID) → (仓库ID)

也就是说,(仓库ID, 存储物品ID)这个主键中的仓库ID可以被管理员ID决定,同样(管理员ID, 存储物品ID)中管理员ID也可以被仓库ID决定,所以此表应该拆分。

3NF->BCNF的步骤:

1. 一个表,从所给的FD中,找出一个FD左边没有key的

2. 给这个FD的右边补全属性(field)。

3. 此时得到两个表,一个是第2步中得到的FD中的表,另一个是第2步中FD的所有左边 及 其他所有没有在这个FD中出现的属性组成的表

4. 投影总的FD分别到两个表

如果仍然有表violation,继续分解

例如:

表(Name, Location, Application, Provider, FavAppl)

FD: Name->Location, Name->FavAppl, Application->Provider

Key: Name, Application

1. Name->Location的左边没有key。

2. 将其补充。Name->Location, Name->FavAppl,因此得到表(Name, Location, FavAppl)

3. 一个表是(Name, Location, FavAppl)。另一个表,”第2步中FD的所有左边“是指Name,“其他所有没有在这个FD中出现的属性”是指Application和

Provider。因此第二个表是(Name, Application, Provider)

4. 第二个表中,Application->Provider的左边没有key,继续分解第二个表

最终结果,原表分为了(Name, Location, Provider),(Application, Provider),(Application, Name)

总结:

1NF

| 消除非主属性对码的部分依赖

2NF

| 消除非主属性对码的传递依赖

3NF

| 消除主属性对码的部分和传递依赖

BCNF

参考资料:

http://blog.csdn.net/kkdelta/archive/2009/04/11/4064986.aspx

《Beginning Database Design》 By Wrox