Одностраничный магазин с корзиной на Phalcon + AngularJS + Zurb Foundation

    Введение


    Всем привет! Завтра у меня дедлайн по проекту, который я делаю для местной Камчатской компании по доставки еды. И поэтому у меня есть две причины написать эту статью, первая — прокрастинация перед дедлайном, а вторая — я не нашёл на Хабре какого-либо обучающего мануала по написанию корзины товаров на AngularJS.

    Я нашёл статью на стороннем блоге, которая частично помогла мне решить пару задач, которые стояли передо мной. Но оформление статьи оставляло желать лучшего, да и за 5 лет я уже отвык от кода в блокноте, без подсветки синтаксиса, поэтому нужно было как-то структурировать и сделать более читабельной эту полезную информацию.



    Почему был выбран формат одностраничного магазина?



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

    И соответственно чтобы исключить потери клиентов, переходящих по разным категориям блюд, из-за нестабильного канала, была выбрана подобная модель. То есть человеку достаточно один раз загрузить сайт и получить всю необходимую информацию, и сделать заказ.

    Так же такой формат позволил избежать хранения товаров корзины в сессиях, localStorage или же в базе данных. Так как мы точно знаем, что человек никуда не уйдёт с этой страницы, мы храним данные корзины в объекте javascript. Ещё одним плюсом стало уменьшение времени заказа, так как нет нужды перемещаться по категориям, и загружать новые страницы. И так как позиций блюд не слишком много, нам даже не пришлось делать Ajax-подгрузку данных при нажатии на категорию, всё подгружалось из кеша базы данных.

    База данных


    После того, как был готов и свёрстан дизайн одностраничного сайта, пришло время создать структуру базы данных категорий и товаров. Это наверно самый быстрый и самый простой этап, учитывая наши потребности и направленность на простоту работы системы и взаимодействия с пользователями. У меня уже был набросок админки на Phalcon PHP Framework, поэтому поправить его для работы с двумя таблицами category и products, не составило особого труда.

    Таблица category


    Таблица products


    Вот так выглядит админка сайта


    Получаем категории и блюда из базы данных


    Для работы с базой данных использовалась стандартная ORM система фреймворка Phalcon, если вы с ней не работаете, можете пропустить этот раздел, он для общего развития, чтоб дальше было понятно откуда ноги растут, точнее откуда и как берутся блюда на сайте.

    В основном контроллере IndexController.php в модуле frontend, я написал функцию, которая сформирует данные нужным нам образом и выведет их на единственную нашу страницу.
    public function indexAction()
        {
             //Получаем все категории в массив
            $category = Category::find()->toArray();
            $help = new \Lib\Url();
            Перебираем массив и для каждой категории подгружаем блюда
            foreach ($category as $key => $val)
            {
                $category[$key]['url'] = $help->translit_url($val['name']);
                //Это не родительская категория? Тогда удаляем из массива
                if ($val['pid'] != 0)
                {
                    unset($category[$key]);
                }
                else
                {
                    //Подгружаем массив подкатегорий, правда в нашем случае только одна категория имеет подкатегории
                    $category[$key]['sub'] = Category::find("pid = '".$val['id']."'")->toArray();
                    //Если есть подкатегории, цепляем к ним блюда, если нет, то цепляем блюда к основном категории
                    if(count($category[$key]['sub']) > 0)
                    {
                        foreach($category[$key]['sub'] as $i => $cat)
                        {
                            $category[$key]['sub'][$i]['products'] = Products::find("category = '" . $cat['id'] . "'")->toArray();
                        }
                    }
                    else
                    {
                        $category[$key]['products'] = Products::find("category = '" . $val['id'] . "'")->toArray();
                    }
                }
            }
            $this->view->setVar('items',$category);
        }
    


    Возможно есть более изящные решения этой задачи, но у нас не будет больше 200 посетителей за день, и мы подключим кеширование запросов к базе данных, и в принципе не будет такой сильной нагрузки, тем более это Phalcon — «Самый быстрый PHP фреймворк». Но вопрос сейчас не о производительно и оптимизации, это пока рано, главное что пора выводить товар на странице.

    Кому доверить рендеринг товаров на странице? AngularJS или Phalcon?


    Сначала я реализовал всё на AngularJS, ну это было как-то изящнее и красивее, но потом задумался о СЕО-оптимизации, и индексации поисковыми системами, и подумал что лучше наверно не рисковать, и рендеринг доверить нашему старому доброму любимому PHP.

    Приводить код выводящий товары на странице я тут не буду, как из соображений величины этого кода, так и из этических соображений, всё таки это продукт нашего заказчика. Да и думаю, кто читает Хабр знает как пользоваться функцией foreach в php.

    Ну ладно, покажу как я выводил категории блюд, а там уже по примеру каждый разберётся и с товарами.

    <div class="row">
            <div class="large-12 columns">
                <ul class="tabs small-block-grid-2 medium-block-grid-4 large-block-grid-6" data-tab>
                    <?php foreach ($items as $item): ?>
                        <li>
                            <a href="#<?= $item['url'] ?>">
                                <img ng-src="/uploads/<?= $item['images'] ?>" alt="<?= $item['name'] ?>">
                            </a>
                            <p><?= $item['name'] ?></p>
                        </li>
                    <?php endforeach; ?>
                </ul>
            </div>
        </div>
    


    Выглядит это всё дело очень и очень аппетитно.


    Добавление товара в корзину


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



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

    В основном для понимания основы вам нужно видеть только этот отрезок кода, который выводит кнопку «Добавить» с полем, куда можно ввести количество штук блюда.

    <div class="add-cart">
          <input type="number" ng-model="num<?=$p['id']?> value="1" min="1" max="50">
         <button type="button" ng-click="addCart(<?=$p['id']?>, num<?=$p['id']?>, '<?=$p['title']?>', <?=$p['price']?>)"></button>
    </div>
    


    Как видно из исходного кода, функция addCart() выполняет добавление данных в объект javascript, давайте посмотрим исходный код на AngularJS.

        $scope.carts = [];
        $scope.addCart = function(id, num, title, price)
        {
            var nums = num || 1;
            $scope.carts.push({
                id : id,
                num : nums,
                title : title,
                price : price
            });
        };
    


    Я думаю тут нечего объяснять, всё предельно просто и понятно, а главное — работает! После того, как мы добавили данные в $scope.carts, нужно их куда-то вывести. В нашем случае, дизайнер решил сделать это в плавающее окошко справа, вот так.


    И соответственно код.

    <div class="cart ng-cloak" ng-cloak ng-show="carts.length > 0">
        <div class="cart-items">
            <h3 class="text-center">Корзина</h3>
            <div class="row items" ng-repeat="item in carts">
                <div class="small-5 columns text-right">
                    {{item.title}}
                </div>
                <div class="small-2 columns">
                    <input type="number" ng-model="item.num" min="1" max="50">
                </div>
                <div class="small-4 columns">
                    {{item.price}} руб.
                </div>
                <div class="small-1 columns">
                    <a href ng-click="removeItem(carts,item)">X</a>
                </div>
            </div>
        </div>
        <div class="cart-results text-center">
            <div class="row">
                <div class="small-12 columns">
                    <select ng-model="delivery" ng-init="delivery = 0">
                        <option value="0">Октябрьский район</option>
                        <option value="1">Ленинский район</option>
                        <option value="2">Долиновка, Завойко</option>
                        <option value="3">Самовывоз (-10%)</option>
                    </select>
                </div>
            </div>
            <h3>{{total() | number : 0}} руб.</h3>
            <button class="new_btn" data-reveal-id="order">Заказать</button>
        </div>
    </div>
    


    Кстати, функция ng-cloak просто выручает в условиях Камчатского интернета, если её не использовать, пока человек будет ждать загрузку страницы, ему будет видна пустая корзина со страшными символами. Для тех кто не знаком с AngularJS, укажу на несколько ключевых моментов.
    Это нужно, чтобы показывать корзину, только в случае если в ней есть товары.
    ng-show="carts.length > 0"
    

    Этот код выводит конечную сумму заказа, форматируя число, и убирая копейки, которые могут получится, при высчитывании 10% скидки, например в случае самовывоза.
    {{total() | number : 0}}
    

    У меня часто возникала задача удалить элемент ассоциативного массива, я каждый раз забывал как это делать, и обращался к Google, но надеюсь после этой публикации я наконец-то запомню, а те кто не знал, узнают.
    $scope.removeItem = function(carts, item) {
            carts.splice(item, 1);
    };
    

    А что, кто-то рассчитывал что будет больше кода? Можно кстати даже в одну строчку написать. Но не будем изгаляться, нам главное читабельность кода. И наверно последнюю функцию которую я хочу привести в этой статье — это подсчёт конечной суммы заказа. Он был выполнен исходя из условий и способов доставки.
    $scope.total = function() {
            var total = 0;
            angular.forEach($scope.carts, function(item) {
                total += item.num * item.price;
            });
            var delivery = 0;
            if($scope.delivery == 0)
            {
                if(total >= 600)
                {
                    delivery = 0;
                }
                else
                {
                    delivery = 130;
                }
            }
            if($scope.delivery == 1)
            {
                if(total >= 1500)
                {
                    delivery = 0;
                }
                else
                {
                    delivery = 130;
                }
            }
            if($scope.delivery == 2)
            {
                delivery = 300;
            }
            if($scope.delivery == 3)
            {
                delivery = 10/total*100*(-1);
            }
            return total + delivery;
        };
    

    Чем объяснять откуда растут цифры, я просто покажу блок с информацией о доставке, а вы уже сами разгадаете откуда появились разные цифры в коде. Тем более у каждого эти данные будут разные, и будет разное количество районов, поэтому не вижу смысла заострят на этом внимание, тем более статья и так уже получилась достаточно объёмная.


    Послесловие


    Главная задача моего поста решена, теперь на Хабре есть материал, о том как сделать добавление товаров в корзину и подсчёт суммы заказа на AngularJS, а в интернете есть хорошо оформленный материал об этом, который дополнит запись из стороннего блога, ссылку на который я привёл в начале статьи. Ну а так же, я уже достаточно прокрастинировал, поэтому пора приступать снова к работе, и заканчивать отправку заказа нашему заказчику. Надеюсь статья поможет таким же как я. Если у вас всё же возникнут вопросы по приведённому исходному коду, или что-то будет не понятно, я с удовольствием отвечу в комментариях. К сожалению опыт работы с AngularJS всего пол года, поэтому чем смогу, тем помогу. Спасибо за ваше внимание.

    Обновление
    Спасибо всем за комментарии, я постарался исправить недочёты и описал проделанную работу в следующей публикации — «Одностраничный магазин на Phalcon PHP + AngularJS. Работа над ошибками».
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 45

      +19
      image
      Совсем незачем юзеру говорить будет страница перезагружена или нет, ему это фиолетово. Не усложняйте.
        +9
        Действительно, это выглядит как демонстрация: «Ура! Пользователь, ты представляешь? Мы, наконец, осилили Ангуляр!» :)
          +1
          Хотя может быть и правда на Камчатке всё так плачевно. Средний пользователь с вечера ставит на загрузку десятки страниц во вкладках, чтобы потом в течение следующих суток их почитать.
            +2
            Так было 5 лет назад, 1мб GPRS связи 7 рублей. Сейчас лучше, 3500 рублей за скорость 400 кбит/сек.
            0
            Спасибо за фидбэк, но как дать пользователю визуально понять, что не будет загружена новая страница при клике?
              +7
              Я думаю, что это совершенно ненужная для пользователя информация, следовательно мусор. Средний пользователь просто не поймёт, о чём вы ему хотите сказать, из оставшихся только единицы смогут оценить. Тем более 99,9% интернета как-то прекрасно обходились загрузкой страниц даже во времена диалапа, и никто при этом не страдал.
                0
                может быть ховер на картинку какойнибудь. возможно чтото вроде этого.
                ну или подчеркивание на заголовках из точек. последнее правда не столь очевидно будет на фоне картинки.
                  0
                  Точечное подчёркивание, обычно, означает вызов диалога, вместо навигации. В данном случае примерно это и происходит, так что если для автора это настолько сильно важно, то это самый верный способ.
                    0
                    + отъезжание иконок вверхв-низ, под которой видно, что что-то есть более подробное
                    + подчеркнуть названия пунктиром?
                    +2
                    Вы же делаете магазин одностраничным не для того, что бы пользователь порадовался, что он одностраничный, а для того что бы пользователь ощутил скорость работы, отсутствие тех самых признаков перезагрузки типа белого моргнувшего экрана. И вот когда он нажмет, то сам заценит. Он не поймет перезагрузилась страница или нет, но ему понравится)
                +6
                Так же такой формат позволил избежать хранения товаров корзины в сессиях, localStorage или же в базе данных. Так как мы точно знаем, что человек никуда не уйдёт с этой страницы, мы храним данные корзины в объекте javascript.


                Я правильно понял, что если я случайно закрою вкладку, то все мои товары в корзине будут навсегда потеряны? Придётся оформлять заказ заново? Не слишком ли опрометчивый подход?

                Подозреваю, что на самом деле вам было банально лень делать человеческую корзину.
                  +6
                  Кстати да, зачем избегать localStorage мне не совсем понятно. Туда можно кэшировать стили, в которых «мелкая» графика в base64. И опять таки в localStorage можно собирать объект, который пригодится для аналитики.

                    0
                    В нашем случае нет, средний пользователь не открывает больше 5 вкладок. У меня сейчас открыто 10, потому что я их не закрываю, чтобы потом снова не загружать. А вообще цель статьи была описать общий процесс работы с корзиной, если кто-то захочет развить тему, это его решение. Мы зная наш рынок стараемся делать проще, и конечно же если АБ тесты и вебвизор покажет что 20% пользователей случайно закрывают вкладки, то сделаем с localStorage.
                      +1
                      Дело не только в случайно закрытой вкладке. Я могу начать заказывать, затем отвлечься, закрыть браузер, потом захочу вернуться и тут меня ожидает сюрприз. Это просто некрасиво, я считаю, сегодня обманывать ожидания пользователя. А пользователи привыкли к персистентному состоянию корзины, потому что это абсолютная норма. Даже не знаю, почему вы решили иначе. Но дело ваше, конечно, ваши пользователи.
                        0
                        Никакие тесты не покажут случайность закрытия вкладки. Даже не знаю, к счастью это, или к сожалению :)
                          0
                          Если корзина где-то сохраняется между заходами пользователя, — то это небольшой такой плюсик в карму магазина, = повышение лояльности. Вот логинюсь я на чайник, а там в корзине с лета лежат комплектующие.
                            0
                            Представьте, что 3% купивших бы товар случайно закрыли вкладки и посчитайте сколько потеряет магазин. А потом посчитайте сколько строчек кода вам понадобится что бы внедрить хранение корзины :)
                              –1
                              Можно же в куки положить всю корзину
                                0
                                Зачем если в localStorage до первой зачистки может жить «жирный» объект со статистикой, чтобы напомнить о незакрытом заказе или предложить акцию если после нескольких раз человек так и не решился сделать заказ.

                                0
                                Слишком много «но» и «если», чтобы можно было с такой уверенностью отказываться от абсолютного не требующего затрат полезного инструмента. Но конверсия почти точно будет лучше при условии хранения списка/истории заказа.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                Если бы я писал такую статью, то убрал бы в главной картинке название фирмы и телефон заказа, а то администраци Хабра посчитает это как реклама или убираем все рубрики WEB разработки, ООП и Добро пожаловать в «Я ПИАРЮСЬ!».
                                  –2
                                  Главная задача моего поста решена, теперь на Хабре есть материал, о том как сделать добавление товаров в корзину и подсчёт суммы заказа на AngularJS
                                  Эта задача была бы решена, если бы ты оформил все в ввиде модуля библиотеки и выложил это на гитхаб. А как сделать карзину товаров, знает любой WEB разработчик, проработавший более 6 мес.
                                    +2
                                    <a href="#<?= $item['url'] ?>">
                                    про шаблоны — ничего не знаем?
                                      –1
                                      Вообще php изначально в общем-то язык-шаблонизатор. Зачем в нем использовать еще один шаблонизатор, который будет написан на каком-то еще мета-языке?
                                        0
                                        Чтобы не писать постоянно
                                        <?= isset($item['url'])? htmlspecialchars($item['url']): '';?>
                                        ?

                                        Тем более, что у phalcon есть volt.
                                          –1
                                          php был шаблонизатором (причем убогим) для c/c++ очень давно, во времена php1/2. Вспомните JS лет так 8-10 назад.
                                        +1
                                        Моё небольшое ИМХО:

                                        добавление в корзину через вставку инлайн PHP скриптов — плохо. Т.к. в идеале, Angular — MVC фреймворк, и в его виды, никто лезть не должен.

                                        Когда контроллеры Angular пухнут, становится сложновато разобрать структуру. Лично для себя, выводил такие предопределенные структуры:

                                        $scope.models = {
                                           someModel:0
                                        }
                                        
                                        $scope.actions = {
                                           someAction:function() { }
                                        }
                                        


                                        Для всех Agnular-тэгов, лучше подставлять префикс data-, data-ng-model, data-ng-click. Так валиднее, привет опять таки IE.

                                        А еще есть https://github.com/johnpapa/angularjs-styleguide стайлгайд, к которому в принципе можно привыкнуть.

                                        Большинство выводимых данных, лучше выводить через ng-bind или ng-bind-template или ng-bind-html (при наличии модуля $sce).
                                          –2
                                          Обучающий материал по написанию корзины товаров на AngularJS?! Вы это серьезно сейчас?
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              А вообще любой «самый быстрый фреймворк» можно замедлить построением дерева категорий больше, чем за 1 запрос к бд.
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                  +1
                                                  или заюзать nested set/matherialized path
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                +6
                                                Так как у нас тут пока ещё свобода слова, то пожалуй оставлю каплю своего негатива.
                                                Никаких обид, говорю только по делу. Статью надо было назвать: «ура, я начал изучать angular js, смотрите что он умеет». Carts в переводе на русский будет тележки. Я не специалист по Phalcon/php, но SQL-запросы в циклах? Не эксперт по деревьям, но думаю тут лучше подошла бы MongoDB для приложения в целом. Больно смотреть на php внутри angular шаблонов, однако если вы настолько обеспокоены своей сео-оптимизацией, то может проще было взглянуть на решения вроде prerender.io? Если бы это был мой магазин, я бы сео-оптимизацией не беспокоил бы себя в принципе. Гораздо проще и выгоднее купить рекламу. А дальше сайт, если хороший — раскрутит себя самостоятельно. Задача сео в данном контексте как раз таки выдать сайт по запросу вроде «заказать пиццу онлайн караганда». Ну и к слову — надеюсь, что вы хотя бы проверяете на сервере что прислал клиент, и перезваниваете ему. Используя мощь AngularJS + localStorage можно чисто на стороне клиента, создать многофункциональный лендинг, который будет делать всё настолько красиво, что серверу останется лишь отдать ему товары (rest/rpc/inline-js) и принять заказ, предварительно его проверив. Какой у вас тут смысл от angular? На Vanilla JS можно то же самое написать и работать будет намного быстрее. Хранить картинки в базе идея плохая на мой взгляд. Да и если я правильно понимаю админов вы «angular'ом» обделили, оставив им «перезагружающиеся страницы». Админка к слову плохая. Внутри не дерево, а просто список аля «проследи кто у кого предок».
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                    0
                                                    Видимо товарищ никогда не работал в команде еще. Отсюда и такие грабли. И все эти магические цифры

                                                         if($scope.delivery == 3)
                                                            {
                                                                delivery = 10/total*100*(-1);
                                                            }
                                                    

                                                    которые потом он сам же, спустя полгода, будет мучительно разбирать, когда понадобится добавить еще одно условие доставки или поменять скидку
                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                    0
                                                    Вопрос по prerender.io

                                                    Получается он прокликивает страницу и сохраняет html-снапшоты всех возможных состояний, которые и отдает в итоге поисковикам?
                                                      0
                                                      Нет, не прокликивает.
                                                      prerender.io отдаёт поисковому боту HTML, «сфотографированный» после того, как закончатся все AJAX запросы или по установке специального флага.
                                                      А вот поисковый бот потом уже «кликает» по ссылкам.

                                                      Мы тоже им пользовались почти полгода. Для проекта с 150 000 страниц он стал работать на редкость гадостно.
                                                      Взяли исходники, которые лежат на github в упрощённом виде, без веб-интерфейса и очередей, изучили и сделали свой github.com/icons8/impresser
                                                      Документация — не пинайте ногами, пожалуйста :( — нет вообще, даже в коде комментариями
                                                      Сейчас отрисовывает полмиллиона страниц, по 3-5 секунд на каждую, но делает это регулярно и поисковым ботам выдаёт результат за миллисекунды.
                                                      Требует выделенный сервер, потому что фантом очень жрёт процессор и на VDS упирается именно в производительность виртуальных процессоров. Мы взяли один в США, чтобы было максимально близко от серверов, на которых работают гуглоботы.

                                                      Зачем prerender/impresser, если гуглобот умеет исполнять Javascript?
                                                      Во-первых, гуглобот не понимает, что страница ждёт данных через AJAX — в результате в поисковый индекс попало много пустых страниц с крутящийся картинкой-загрузчиком. У prerender есть специальный флаг в javascript, чтобы подождать, пока страница не готова.
                                                      Во-вторых, Yandex, Bing, Yahoo и много других поисковых систем не умеют исполнять JS и видят вообще пустые страницы.
                                                      В-третьих, боты социальных сетей не умеют исполнять Javascript. В результате, например, Facebook не видит название страницы и иллюстрацию к ней, если теги Open Graph динамически заполняются через Angular.

                                                      Попробуйте сравнить результаты:
                                                      Инструкция take.ms/BzPOr черех Google Chrome.
                                                      Откройте страницу ic8.link/very_basic в одной обычной вкладке браузера и в другой вкладке в режиме эмуляции устройств с UserAgent = «googlebot».
                                                      HTML страницы загружаются быстрее и вообще не содержат никакого JS. Их можно шарить через соц сети.
                                                    –2
                                                    А почему был выбран AngularJS? JQuery уже ведь грузится с Zurb Foundation
                                                      +1
                                                      Я не понимаю, почему раньше чтобы набрать 9 плюсов надо было классную статью написать, а сейчас можно написать что угодно в любом качестве.
                                                        +2
                                                        Тема не раскрыта.
                                                        Почему Phalcon + AngularJS + Zurb Foundation?
                                                        Корзина по хорошему, в рамках подобного проекта, пишется на javascript в несколько строк (можно вообще без ооп). В чем преимущество использовать связки эти и т.п.? Зачем в таком простом проекте использовать ORM?

                                                        В общем тема поста не соответствует действительности, после прочтения больше вопросов чем ответов. На недели делал одностраничный магазин, на голом php+mysql+javascript по времени ушло часов 10 (с версткой и доп.правками которых не было в макете, сам программинг часа 3-4 ).

                                                        Так в чем приемущество? Не будет ли проще, быстрей, понятней и удобней в мелких работах без фреймворков обойтись? Может и не пришлось бы запихивать запрос в цикл.
                                                          0
                                                          Можно и без фреймворков вообще и без ORM. Вот сравнение «с фреймворком или без».
                                                          Я бы сказал — чем владеет, на том и делает.
                                                          В данном случае ORM не страшен, потому его в меру. А та часть Фалькона, которая реалиует микросервисы — очень хорошо подошла бы для Reach Client Application, которые данные с сервера забирают только через AJAX — код получается компактным, изящным.
                                                          –1
                                                          Опять куча фреймворков и как обычно получается «велик»…
                                                          А не проще ли использовать готовые решения. К примеру opencart.
                                                          Будет гораздо проще, а функционал на много порядков больше.

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

                                                          Самое читаемое