Компьютерные уроки

Ускоряем запросы в БД с помощью PDO и итераторов Перевод. Как установить параметры ORDER BY с помощью подготовленной инструкции PDO? Лучшие товары, по лучшим ценам












У PDO свой собственный хитровыдуманный способ соединения, называемый . Плюс во время коннекта можно задать хренову тучу опций, некоторые из которых чрезвычайно полезны. Полный список можно найти , но важными из них являются только несколько.

Пример правильного соединения:

$host = "127.0.0.1" ;
$db = "test" ;
$user = "root" ;
$pass = "" ;
$charset = "utf8" ;

$dsn = "mysql:host= $host ;dbname= $db ;charset= $charset " ;
$opt = [
PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION ,
PDO :: ATTR_DEFAULT_FETCH_MODE => PDO :: FETCH_ASSOC ,
PDO :: ATTR_EMULATE_PREPARES => false ,
];
$pdo = new PDO ($dsn , $user , $pass , $opt );

Что здесь происходит?

В $dsn задается тип БД, с которым будем работать (mysql), хост, имя базы данных и чарсет.
- затем идут имя пользователя и пароль
- после которого задается массив опций, про который ни в одном из руководств не пишут.

При том что этот массив - чрезвычайно полезная, как уже говорилось выше, штука. Самое главное - режим выдачи ошибок надо задавать только в виде исключений.
- Во-первых, потому что во всех остальных режимах PDO не сообщает об ошибке ничего внятного,
- во-вторых, потому что исключение всегда содержит в себе незаменимый stack trace,
- в-третьих - исключения чрезвычайно удобно обрабатывать.

Плюс очень удобно задать FETCH_MODE по умолчанию, чтобы не писать его в КАЖДОМ запросе, как это очень любят делать прилежные хомячки.
Также здесь можно задавать режим pconnect-а, эмуляции подготовленных выражений и много других страшных слов.

В результате мы получаем переменную $pdo, с которой и работаем далее на протяжении всего скрипта.

Для выполнения запросов можно пользоваться двумя методами.
Если в запрос не передаются никакие переменные, то можно воспользоваться функцией query(). Она выполнит запрос и вернёт специальный объект - PDO statement. Очень грубо можно его сравнить с mysql resource, который возвращала mysql_query(). Получить данные из этого объекта можно как традиционным образом, через while, так и через foreach(). Также можно попросить вернуть полученные данные в особом формате, о чем ниже.
$stmt = $pdo -> query ("SELECT name FROM users" );
while ($row = $stmt -> fetch ())
{
}

Если же в запрос передаётся хотя бы одна переменная, то этот запрос в обязательном порядке должен выполняться только через подготовленные выражения . Что это такое? Это обычный SQL запрос, в котором вместо переменной ставится специальный маркер - плейсхолдер. PDO поддерживает позиционные плейсхолдеры (?), для которых важен порядок передаваемых переменных, и именованные (:name), для которых порядок не важен. Примеры:
$sql = ;
$sql = ;
Чтобы выполнить такой запрос, сначала его надо подготовить с помощью функции prepare(). Она также возвращает PDO statement, но ещё без данных. Чтобы их получить, надо исполнить этот запрос, предварительно передав в него переменные. Передать можно двумя способами:
Чаще всего можно просто выполнить метод execute(), передав ему массив с переменными:
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt -> execute (array($email ));

$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = :email" );
$stmt -> execute (array("email" => $email ));
Как видно, в случае именованных плейсхолдеров в execute() должен передаваться массив, в котором ключи должны совпадать с именами плейсхолдеров.

Иногда, очень редко, может потребоваться второй способ, когда переменные сначала привязывают к запросу по одной, с помощью bindValue() / bindParam(), а потом только исполняют. В этом случае в execute() ничего не передается. Пример можно посмотреть в мануале
Используя этот метод, всегда следует предпочесть bindValue()? поскольку поведение bindParam() не очевидно для новичков и будет приводить к проблемам.

После этого можно использовать PDO statement теми же способами, что и выше. Например, через foreach:
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt ->
foreach ($stmt as $row )
{
echo $row [ "name" ] . "\n" ;
}

ВАЖНО: Подготовленные выражения - основная причина использовать PDO, поскольку это единственный безопасный способ выполнения SQL запросов, в которых участвуют переменные.

Также prepare() / execute() могут использоваться для многократного выполнения единожды подготовленного запроса с разными наборами данных. На практике это бывает нужно чрезвычайно редко, и особого прироста в скорости не приносит. Но на случай, если понадобится делать много однотипных запросов, то можно писать так:

$data = array(
1 => 1000,
5 => 300,
9 => 200,
);

$stmt = $pdo -> prepare ("UPDATE users SET bonus = bonus + ? WHERE id = ?" );
foreach ($data as $id => $bonus )
{
$stmt -> execute ([ $bonus , $id ]);
}

Здесь мы один раз подготавливаем запрос, а затем много раз выполняем.

Мы уже выше познакомились с методом fetch(), который служит для последовательного получения строк из БД. Этот метод является аналогом функции mysq_fetch_array() и ей подобных, но действует по-другому: вместо множества функций здесь используется одна, но ее поведение задается переданным параметром. В подробностях об этих параметрах будет написано позже, а в качестве краткой рекомендации посоветую применять fetch() в режиме FETCH_LAZY :
$stmt = $pdo -> prepare ("SELECT name FROM users WHERE email = ?" );
$stmt -> execute ([ $_GET [ "email" ]]);
while ($row = $stmt -> fetch (PDO :: FETCH_LAZY ))
{
echo $row [ 0 ] . "\n" ;
echo $row [ "name" ] . "\n" ;
echo $row -> name . "\n" ;
}
В этом режиме не тратится лишняя память, и к тому же к колонкам можно обращаться любым из трех способов - через индекс, имя, или свойство.

Также у PDO statement есть функция-хелпер для получения значения единственной колонки. Очень удобно, если мы запрашиваем только одно поле - в этом случае значительно сокращается количество писанины:
$stmt = $pdo -> prepare ("SELECT name FROM table WHERE id=?" );
$stmt -> execute (array($id ));
$name = $stmt -> fetchColumn ();

Но самой интересной функцией, с самым большим функционалом, является fetchAll(). Именно она делает PDO высокоуровневой библиотекой для работы с БД, а не просто низкоуровневым драйвером.

FetchAll() возвращает массив, который состоит из всех строк, которые вернул запрос. Из чего можно сделать два вывода:
1. Эту функцию не стоит применять тогда, когда запрос возвращает много данных. В таком случае лучше использовать традиционный цикл с fetch()
2. Поскольку в современных РНР приложениях данные никогда не выводятся сразу по получении, а передаются для этого в шаблон, fetchAll() становится просто незаменимой, позволяя не писать циклы вручную, и тем самым сократить количество кода.

Получение простого массива.
Вызванная без параметров, эта функция возвращает обычный индексированный массив, в котором лежат строки из бд, в формате, который задан в FETCH_MODE по умолчанию. Константы PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_OBJ могут менять формат на лету.

Получение колонки.
Иногда бывает нужно получить простой одномерный массив, запросив единственное поле из кучи строк. Для этого используется режим PDO::FETCH_COLUMN
$data = $pdo -> query ("SELECT name FROM users" )-> fetchAll (PDO :: FETCH_COLUMN );
array (
0 => "John" ,
1 => "Mike" ,
2 => "Mary" ,
3 => "Kathy" ,
)

Получение пар ключ-значение.
Также востребованный формат, когда желательно получить ту же колонку, но индексированную не числами, а одним из полей. За это отвечает константа PDO::FETCH_KEY_PAIR.
$data = $pdo -> query ("SELECT id, name FROM users" )-> fetchAll (PDO :: FETCH_KEY_PAIR );
array (
104 => "John" ,
110 => "Mike" ,
120 => "Mary" ,
121 => "Kathy" ,
)

Получение всех строк, индексированных полем.
Также часто бывает нужно получить все строки из БД, но также индексированные не числами, а уникальным полем. Это делает константа PDO::FETCH_UNIQUE
$data = $pdo -> query ("SELECT * FROM users" )-> fetchAll (PDO :: FETCH_UNIQUE );
array (
104 => array (
"name" => "John" ,
"car" => "Toyota" ,
),
110 => array (
"name" => "Mike" ,
"car" => "Ford" ,
),
120 => array (
"name" => "Mary" ,
"car" => "Mazda" ,
),
121 => array (
"name" => "Kathy" ,
"car" => "Mazda" ,
),
)
Следует помнить, что первой в колонкой надо обязательно выбирать уникальное поле.

Всего различных режимов получения данных в PDO больше полутора десятков. Плюс ещё их можно комбинировать! Но это уже тема для отдельной статьи.

Работая с подготовленными выражениями, следует понимать, что плейсхолдер может заменять только строку или число. Ни ключевое слово, ни идентификатор, ни часть строки или набор строк через плейсхолдер подставить нельзя. Поэтому для LIKE надо сначала подготовить строку поиска целиком, а потом ее подставлять в запрос:

$name = "% $name %" ;
$stm = $pdo -> prepare ("SELECT * FROM table WHERE name LIKE ?" );
$stm -> execute (array($name ));
$data = $stm -> fetchAll ();

Ну, вы поняли. Тут тоже всё плохо. PDO не предоставляет вообще никаких средств для работы с идентификаторами, и их надо форматировать по-старинке, вручную (или посмотреть, все-таки, в сторону SafeMysql , в которой этот, как и многие другие вопросы, решены просто и элегантно).
Следует помнить, что правила форматирования идентификаторов отличаются для разных БД.

В mysql для ручного форматирования идентификатора необходимо выполнить два действия:
- заключить его в обратные одинарные кавычки (backticks, "`").
- проискейпить эти символы внутри идентификатора внутри путём удвоения.

$field = "`" . str_replace ("`" , "``" , $_GET [ "field" ]). "`" ;
$sql = $field " ;

Однако, здесь есть один нюанс. Одного форматирования может быть недостаточно. приведенный выше код гарантирует нас от классической инъекции, но в некоторых случаях враг все равно может записать что-то нежелательное, если мы бездумно подставляем имена полей и таблиц прямиком в запрос. К примеру, есть в таблице users поле admin. Если входящие имена полей не фильтровать, то в это поле, при автоматическом формировании запроса из POST-а, любой дурак запишет любую гадость.

Поэтому имена таблиц и полей, приходящие от юзера, желательно проверять на допустимость, как в приведённом ниже примере

Любой код для вставки, который можно увидеть в многочисленных туториалах, навевает тоску и желание убиться апстену. Многокилометровые построения с повторением одних и тех же имен - в идексах $_POST-а, в именах переменных, в именах полей в запросе, в именах плейсхолдеров в запросе, в именах плейсходеров и именах переменных при привязке.
Глядя на этот код, хочется кого-нибудь убить, или, по крайней мере, сделать его немного короче.

Это можно сделать, если принять соглашение, по которому имена полей в форме будут соответствовать именам полей в таблице. Тогда эти имена можно будет перечислить только один раз (в целях защиты от подмены, о которой говорилось выше), и использовать небольшую функцию-хелпер для сборки запроса, которая, в силу особенностей mysql, годится как для INSERT, так и UPDATE запросов:

function pdoSet ($allowed , & $values , $source = array()) {
$set = "" ;
$values = array();
if (! $source ) $source = & $_POST ;
foreach ($allowed as $field ) {
if (isset($source [ $field ])) {
$set .= "`" . str_replace ("`" , "``" , $field ). "`" . "=: $field , " ;
$values [ $field ] = $source [ $field ];
}
}
return substr ($set , 0 , - 2 );
}

Соответственно, для вставки код будет

$allowed = array("name" , "surname" , "email" ); // allowed fields
$sql = "INSERT INTO users SET " . pdoSet ($allowed , $values );
$stm = $dbh -> prepare ($sql );
$stm -> execute ($values );

А для апдейта - такой:

$allowed = array("name" , "surname" , "email" , "password" ); // allowed fields
$_POST [ "password" ] = MD5 ($_POST [ "login" ]. $_POST [ "password" ]);
$sql = "UPDATE users SET " . pdoSet ($allowed , $values ). " WHERE id = :id" ;
$stm = $dbh -> prepare ($sql );
$values [ "id" ] = $_POST [ "id" ];
$stm -> execute ($values );

Не слишком эффектно, но зато очень эффективно. Напомню, кстати, что если использовать Класс для безопасной и удобной работы с MySQL , то это всё делается в две строчки.

PDO и ключевые слова
Здесь кроме фильтрации ничего придумать невозможно. поэтому тупо прогонять все не прописанные в запросе напрямую операторы через белый список:

$dirs = array("ASC" , "DESC" );
$key = array_search ($_GET [ "dir" ], $dirs ));
$dir = $orders [ $key ];
$sql = "SELECT * FROM `table` ORDER BY $field $dir " ;

Страница оформления плагина woocommerce имеет определенную структуру и функционал. Но данная структура не совсем удобна. Доработаем страницу оформления, разбив на логические блоки: Заказ, Доставка, Форма оплаты, Информация о заказчике и т.д.

Оформление заказа (базовая структура)

Шаблон form-checkout.php
form name=»checkout»

woocommerce_checkout_billing (информация о заказчике)
woocommerce_checkout_shipping (информация о доставке)

h3 Заказ /h3
div id=»order_review» Order-review /div

Шаблон review-order.php (Order-review)
table
позиции товара к покупке
итого
shipping (cart-shipping.php, ajax зависимость от введенной в woocommerce_checkout_billing информации, подробнее про доставку)
/table

div id=»payment» Форма оплаты /div (подключена хуком)

Кнопка Оформить заказ

Подготовил более наглядную схему за какую область оформления отвечает какой шаблон Woocommerce

Блок «Выбор способа доставки»

Шаблон который отвечает за вывод блока с выбором вариантов доставки находиться (почему-то) здесь: cart/cart-shipping.php .

Надпись Доставка, которая изначально там присутствует выводиться этой строкой:

Echo wp_kses_post($package_name);

Добавить заголовок «Выберите способ доставки» и общий вес заказа непосредственно в блок с доставкой, можно хуком

Function action_woocommerce_review_order_before_shipping() { echo "Выбор способа доставки"; global $woocommerce; echo "Общий вес: "; $total_weight = $woocommerce->cart->cart_contents_weight; $total_weight .= " ".get_option("woocommerce_weight_unit"); echo $total_weight; echo ""; }; add_action("woocommerce_review_order_before_shipping", "action_woocommerce_review_order_before_shipping", 10, 0);

Дорабатываем элементы

Если мы не используем функционал расчета доставки и применения купонов — можно отключить ajax обновление. Если отключать ajax не нужно — ниже есть способ без отключения.

// Отключить Ajax на странице оформления jQuery(document.body).on("update_checkout", function(e){ //e.preventDefault(); //e.stopPropagation(); e.stopImmediatePropagation(); //console.log(e); });

Переместить блок Формы оплаты

Remove_action("woocommerce_checkout_order_review", "woocommerce_checkout_payment", 20); add_action("woocommerce_after_order_notes", "woocommerce_checkout_payment", 20); либо подключить к собственному хуку

Отключение расчета доставки

Если доставка довольно сложна и невозможно прописать ее в автоматическом режиме (например: если мы не сможем реально подсчитать какая нужна газель), то мы отключаем расчет доставки: просто оставляем в методах доставки Самовывоз и Доставка (с 0 ценой). При этом из формы информации о заказчике удаляем поля относящиеся доставки и наоборот из формы доставки удаляем поля касающиеся заказчика. И создаем скрипт при нажатии на пункт Доставка раскрывающий форму доставки.

Id поля с пунктом Доставка

$(document).ready(function() { if($(«#shipping_method_0_flat_rate-6 «).is(‘:checked’)) { $(‘.shipping_address’).show(); } $(‘.shipping_method’).click(function(){ if($(«#shipping_method_0_flat_rate-6 «).is(‘:checked’)) { $(‘.shipping_address’).show(); $(‘.shipping_address’).html(‘

Информация о доставке

‘); } else { $(‘.shipping_address’).html(‘

‘); $(‘.shipping_address’).hide(); } }); });

Данный вариант реализации не является полноценным решением, т.к. имеет ряд недостатков, и в своей основе имеет довольно примитивный подход

Взаимосвязь варианта доставки и полей

Метод для 2-х вариантов доставки (для 3-х и более не подойдет)

Если у нас есть варианты доставки и самовывоз, то было бы неплохо синхронизировать вариант доставки и предлагаемые для заполнения поля. При самовывозе необходимо чтобы поля доставки скрывались, а при выборе варианта доставки появлялись.

Проблема woocommerce проявляется в том, что форма Детали доставки раскрывается при нажатии заголовка Доставка по другому адресу? (#ship-to-different-address) и вместе с сокрытием поля отключается верификация полей доставки. При двух вариантах доставки (Самовывоз и Доставка) при их переключении можно поставить с помощью jquery событие (click) на id ship-to-different-address. При этом необходимо сделать, чтоб одно из полей изначально было активным, в зависимости от необходимости.

$("#shipping_method_0_flat_rate-2").attr("checked",true); $("input:radio").on("change", function () { $("#ship-to-different-address-checkbox").click(); });

Для того чтобы поля доставки были изначально открытыми необходимо установить настройку доставки в опциях woocommerce
Назначение доставки > По умолчанию для адреса доставки клиента

В поле город можно по-умолчанию сделать автоподставление
$(«#shipping_city»).val(«Ростов-на-Дону»);

Метод для 3-х и более вариантов доставки

Изначально делаем доставку (один из вариантов) и поля доставки активными. Далее jquery-магия

$("input:radio").on("change", function () { if($("#shipping_method_0_local_pickup-2").is(":checked")) { if($("#ship-to-different-address-checkbox").is(":checked")) { $("#ship-to-different-address-checkbox").click(); } else { } } else { if($("#ship-to-different-address-checkbox").is(":checked")) { } else { $("#ship-to-different-address-checkbox").click(); } } });

В этом случае мы проверяем активно ли поле Самовывоз (#shipping_method_0_local_pickup-2) и в зависимости от этого проверяем скрытый (я его скрываю) checkbox (Доставка по другому адресу? #ship-to-different-address).

Без отключения ajax

Ajax на странице оформления заказа так или иначе нужен (для тех же купонов). Поэтому немного изменим (изменения коснулись первой строки, т.к. элементы DOM были еще не созданы) универсальный скрипт отключения доставочных полей. #shipping_method_0_local_pickup-3 — id поля с самовывозом.

$("body").on("change", "input:radio", function(e) { if($("#shipping_method_0_local_pickup-3").is(":checked")) { if($("#ship-to-different-address-checkbox").is(":checked")) { $("#ship-to-different-address-checkbox").click(); } else { } } else { if($("#ship-to-different-address-checkbox").is(":checked")) { } else { $("#ship-to-different-address-checkbox").click(); } } });

И на всякий случаем включаем изначальный выбор на какую-либо доставку

$("#shipping_method_0_flat_rate-4").attr("checked",true);

Напоминание: в настройках доставки Назначение доставки должно быть выбрано - По умолчанию для адреса доставки клиента

Варианты оформления для физ. лица и юр. лица

Задача сделать 2 варианта оформления заказа для физ. лица и для юр. лица. Создание этого функционала описано в статье

«Какая корзина? И почему вдруг корзина?» — возможно удивитесь вы, прочитав заголовок. Так вот, корзина самая обычная — та, которую все мы используем, покупая что-нибудь в интернет-магазине. А опубликовать статью о ее создании, я решила по одной, единственной причине — не смогла устоять перед замечательным и красивым решением.

Не верите? можете посмотреть результат . А если вам интересно как это делается, читайте далее.

Вступление

В этой статье, мы собираемся создать корзину, работающую на основе технологии Ajax. Все товары будут записываться в базу данных MySQL, данные будут обрабатываться с помощью PHP.

JQuery, будет запускать Ajax на странице, кроме этого, плагин simpletip , добавит всему процессу интерактивности.

Итак, давайте начнем, скачайте демо-файлы, и начинайте чтение.

Шаг 1 – База данных MySQL

Если вы хотите получить рабочую демо-версию, вам понадобится выполнить следующий SQL-запрос в панели управления базой данных (то есть в phpMyAdmin). Этот запрос создаст таблицу, и внесет несколько продуктов. Этот код запроса, также доступен в файле table.sql, в демо-файлах.

table.sql

CREATE TABLE IF NOT EXISTS internet_shop (id int(6) NOT NULL auto_increment, img varchar(32) collate utf8_unicode_ci NOT NULL default "", name varchar(64) collate utf8_unicode_ci NOT NULL default "", description text collate utf8_unicode_ci NOT NULL, price double NOT NULL default "0", PRIMARY KEY (id), UNIQUE KEY img (img)) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=7 ; INSERT INTO internet_shop VALUES(1, "iPod.png", "iPod", "The original and popular iPod.", 200); INSERT INTO internet_shop VALUES(2, "iMac.png", "iMac", "The iMac computer.", 1200); INSERT INTO internet_shop VALUES(3, "iPhone.png", "iPhone", "This is the new iPhone.", 400); INSERT INTO internet_shop VALUES(4, "iPod-Shuffle.png", "iPod Shuffle", "The new iPod shuffle.", 49); INSERT INTO internet_shop VALUES(5, "iPod-Nano.png", "iPod Nano", "The new iPod Nano.", 99); INSERT INTO internet_shop VALUES(6, "Apple-TV.png", "Apple TV", "The new Apple TV. Buy it now!", 300);

После этого, вам нужно заполнить данные вашей учетной записи MySQL, в файле connect.php .

Шаг 2 – XHTML

Сначала мы создадим основную разметку.

demo.php

Корзина Лучшие товары, по лучшим ценам Товары // php-код, который генерирует товары Корзина Оформить

Как вы могли заметить, из вышеприведенного кода, мы расположили наш контент в двух основных секциях, полностью идентичных с точки зрения XHTML-разметки. В первой колонке мы отображаем все наши продукты, вторая колонка выступает в качестве корзины.

Ниже, вы можете увидеть детальное представление структуры нашей секции товаров.

Товары, генерируются с помощью нашего PHP-кода, как можно увидеть в строке 18. Мы разберем это подробнее, через несколько минут. Теперь, давайте взглянем, как мы обработаем XHTML-разметку, для получения финального дизайна.

Шаг 3 – CSS

В этот раз, CSS-код получился очень длинный, поэтому я разделил его на две части.

demo.css

Body,h1,h2,h3,p,td,quote,small,form,input,ul,li,ol,label{ /* сброс первоначальных стилей, для совместимости браузеров */ margin:0px; padding:0px; font-family:Arial, Helvetica, sans-serif; } body{ color:#555555; font-size:13px; background-color:#282828; } .clear{ /* clear-fix хак для чистки потока от флоатов */ clear:both; } #main-container{ /* это основной контейнер, содержащий две секции */ width:700px; margin:20px auto; } .container{ /* основной контейнер для секций контента - товаров и корзины */ margin-bottom:40px; } .top-label{ /* внешний span, включающий в себя название секции*/ background: url(img/label_bg.png) no-repeat; /* отображение левой части label_bg.png - широкого изображения с закругленными краями */ display:inline-block; margin-left:20px; position:relative; margin-bottom:-15px; /* название секции прилегает к верхнему краю секции товаров*/ } .label-txt{ /* внутренний span - обведен красной рамкой, на рисунке выше*/ background: url(img/label_bg.png) no-repeat top right; /* отображение правой части изображения label_bg.png */ display:inline-block; font-size:10px; height:36px; margin-left:10px; /* слева оставлено пустое пространство, чтобы отображить фон внешнего span"а */ padding:12px 15px 0 5px; text-transform:uppercase; } .content-area{ /* Верхняя часть изображения с закругленными краями, смотрите на рисунке выше */ background:url(img/container_top.png) no-repeat #fcfcfc; padding:15px 20px 0 20px; } .content{ /* общий отступ для обеих секций */ padding:10px; } .drag-desired{ /* индивидуально назначенные свойства */ background:url(img/drag_desired_label.png) no-repeat top right; padding:20px; } .drop-here{ /* не предназначено для других секций */ background:url(img/drop_here_label.png) no-repeat top right; } .bottom-container-border{ /* нижняя часть закругленной картинки, которая завершает секцию */ background:url(img/container_bottom.png) no-repeat; height:14px; } .product{ /* стили для товаров */ border:2px solid #F5F5F5; float:left; margin:15px; padding:10px; } .product img{ cursor:move; } p.descr{ padding:5px 0; } small{ display:block; margin-top:4px; } .tooltip{ /* тултипы, которые создаются с помощью плагина simpletip */ position: absolute; top: 0; left: 0; z-index: 3; display: none; background-color:#666666; border:1px solid #666666; color:#fcfcfc; padding:10px; -moz-border-radius:12px; /* закругленные углы */ -khtml-border-radius: 12px; -webkit-border-radius: 12px; border-radius:12px; }

Обратите внимание на класс tooltip. Он создается автоматически, плагином simpletip , но не имеет никаких стилей, по умолчанию. Вот почему, мы назначаем ему стиль здесь. Я использовал свойство border-radius , которое еще не поддерживается всеми браузерами, но не принесет сильного ущерба, тем, кто его не поддерживает.

Теперь, давайте взглянем на CSS-стили, для секции корзины.

#cart-icon{ /* div, который содержит иконку корзины */ width:128px; float:left; position:relative; /* устанавливаем относительное позиционирование, так, чтобы ajax-загрузчик позиционировался по отношению к div*/ } #ajax-loader{ position:absolute; /* абсолютное позиционирование располагает элемент на странице, относительно его родительского элемента, которому назначено относительное позиционирование */ top:0px; left:0px; visibility:hidden; } #item-list{ /* содержимое корзины будет расположено в этом блоке */ float:left; width:490px; margin-left:20px; padding-top:15px; } a.remove,a.remove:visited{ /* Удаление ссылки */ color:red; font-size:10px; text-transform:uppercase; } #total{ /* блок, с общей суммой */ clear:both; float:right; font-size:10px; font-weight:bold; padding:10px 12px; text-transform:uppercase; } #item-list table{ /* каждый товар в корзине, позиционируется внутри блока item-list*/ background-color:#F7F7F7; border:1px solid #EFEFEF; margin-top:5px; padding:4px; } a.button,a.button:visited{ /* Кнопка оформления заказа */ display:none; height:29px; width:136px; padding-top:15px; margin:0 auto; overflow:hidden; color:white; font-size:12px; font-weight:bold; text-align:center; text-transform:uppercase; background:url(img/button.png) no-repeat center top; /* отображаем только верхнюю часть фонового изображения */ } a.button:hover{ background-position:bottom; /* при наведении, мы показываем нижнюю часть фоногового изображения */ text-decoration:none; } /* Несколько менее интересных стилей */ a, a:visited { color:#00BBFF; text-decoration:none; outline:none; } a:hover{ text-decoration:underline; } h1{ font-size:28px; font-weight:bold; font-family:"Trebuchet MS",Arial, Helvetica, sans-serif; } h2{ font-weight:normal; font-size:20px; color:#666666; text-indent:30px; margin:20px 0; } .tutorialzine h1{ color:white; margin-bottom:10px; font-size:48px; } .tutorialzine h3{ color:#F5F5F5; font-size:10px; font-weight:bold; margin-bottom:30px; text-transform:uppercase; } .tutorial-info{ color:white; text-align:center; padding:10px; margin-top:-20px; }

Любой разработчик скажет нам, что здесь мы кое-что упустили. Как вы, наверное, догадались – специальные процедуры лечения для IE6.

Лично я, планирую в скором времени прекратить поддержку IE6 во всех своих проектах – если бы не IE6, вышеприведенный код, был бы на четверть короче, и мне не пришлось бы тратить столько времени на его отладку.

Но, в любом случае, вот, что нам нужно, чтобы IE6 понимал, то, что мы от него хотим:

demo.php

.pngfix { behavior: url(pngfix/iepngfix.htc);} /* this is a special htc file that fixes the IE6 transparency issues */ .tooltip{width:200px;}; /* provide a default width for the tooltips */

Отлично. Теперь, давайте взглянем на окончательный вариант PHP.

Шаг 4 – PHP

Мы используем PHP несколькими способами и в разных местах. Для начала, давайте посмотрим, как формируется список товаров на главной странице.

demo.php