Решение использовать mysql

 

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

В общем взгрустнул я и решил, пока мы еще не начали пользоваться базой данных, перекинуться с файловой, на mysql

Что для этого нам понадобится?

  • Ну первым делом нам нужно написать обертку для использования функций mysql
  • Далее (правда начнем мы с этого) нам понадобится класс конфига системы RS-MINI. Мы ведь должны где-то хранить настройки подключения к базе данных
  • Так же нам придется подправить классы:
    • request.class.php — мы не делали экранирование спецсимволы. Для работы с mysql это очень важно. Так что придется переписать метод toString() и написать еще один — toHtml()
    • abstractmodel.class.php — необходимо переписать метод database(). Точнее мы его вообще удалим, и напишем кое что другое
    • application.class.php — именно тут будет стартовать подключение к базе данных

Реализуем конфигурационный класс

Класс конфига — config.class.php

Как я писал выше, начнем мы с конфига, так как он используется в обертке. Что из себя представляет этот класс? Все очень просто, у нас будет статичный метод setConfig() который при запуске напичкает статичные приватные свойства необходимой информацией. Запускаться этот метод будет из файла /rs-mini/init.php. Далее нам понадобится статичные методы для получение данных из приватных свойств. На данном этапе у нас будет лишь одно свойство db и метод getDB() для обслуживания этого самого свойства.

Давайте я Вам продемонстрирую листинг этого класса (класс называется config.class.php и лежит в папке /project/conf/)

<?php

/*
 * @package RS-MINI
 * @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
 * @license GNU General Public License version 2; see LICENSE.txt
 *
 */

namespace conf;

if(!defined('RS-MINI')) die();

/*
 * config v 1.0
 *
 * класс с настройками движка
 *
 */

class config
{
   /*
    * массив с настройками базы данных
    *
    * @var    - array
    * @access - private
    */
    
    private static $db = array();
    
    /*
     * установка настроек движка
     *
     * @access - public
     */
    
    public static function setConfig()
    {
        define('CMS',               'RS-MINI');  // имя движка
        define('VERSION',           '0.0.1');    // версия движка
        
        self::$db = array(
            'server'  => 'localhost',
            'user'    => 'root',
            'pass'    => '123456',
            'name'    => 'rs_mini'
        );                                       // настройки базы
    }
    
    /*
     * метод получае настройку базы данных
     *
     * @access - public
     * @return - настройка
     *
     * @param string param - ключ в массиве настроек
     */
    
    public static function getDB($param)
    {
        return (isset(self::$db[$param])) ? self::$db[$param] : FALSE;
    }
}

Думаю вопросов этот маленький класс вызвать не должен. Если Вы внимательно смотрели на методы, то могли заметить создание двух констант про которые я ничего не сказал. Эти две константы:

  • CMS
  • VERSION

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

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

Правка в init.php

Тут ничего сложного нет, нам нужно всего лишь вызвать метод setConfig(). Вот новый код (напомню, файл называется init.php и лежит в /rs-mini/core/)

<?php

/*
 * @package RS-MINI
 * @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
 * @license GNU General Public License version 2; see LICENSE.txt
 *
 */
 

if(!defined('RS-MINI')) die();

require_once(DIR_RSMINI . '/core/loader.class.php');      // подключаем лоадер классов

loader::register();                                       // регистрируем загрузчик классов
\conf\config::setConfig();                                // запускаем конфиг проекта
\core\application::run();                                 // стартуем систему

Обертка для функций mysql

Нам понадобится три глобальных метода:

  • подключение к базе
  • исполнение sql запросов
  • получение выборки

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

После исполнения sql запросов мы сохраним кое какие данные. К ним относятся (подробнее об этих данных ниже):

  • кол-во выбранных строк с помощью SELECT
  • кол-во затронутых строк последним INSERT, UPDATE, REPLACE или DELETE запросом
  • автоматически генерируемый ID, последнего запроса

Выборки мы будет получать с помощью функции mysqli_fetch_assoc() которая возвращает ассоциативный массив, тем самым наша выборка будет массивом вот такой структуры:

[0]
    [поле в таблице]    => значение,
    [поле в таблице]    => значение,
    [поле в таблице]    => значение
[1]
    [поле в таблице]    => значение,
    [поле в таблице]    => значение,
    [поле в таблице]    => значение
[2]
    [поле в таблице]    => значение,
    [поле в таблице]    => значение,
    [поле в таблице]    => значение

Я практически всегда начиная писать подобные классы, прикидываю как им буду пользоваться. Предлагаю ответить на вопрос, как делать SELECT с помощью данной обертки? Думаю что вот так:

<?php

$rowList = sql::query('селект запрос')->fetch();

этот вызов вернет нам либо ассоциативный массив с данными либо false, если скуль запрос выбрал ноль строк.

Теперь давайте ответить на вопрос, как исполнить INSERT, UPDATE или DELETE запрос? Я предлагаю делать это вот так:

<?php

$result = sql::query('insert, update, delete запрос');

Вроде красиво Улыбаюсь

Теперь давайте подумаем как определять успешно ли прошел тот или иной запрос, или нет. Как мы видим и для селект запроса и для других используется тот же самый метод query(). Оно и понятно, ведь для выполнения запроса используется одна функция — mysqli_query().

В случае с селкет запросами ответ на поставленный вопрос очевиден, ведь метод fetch(), как было сказано выше, вернет либо массив либо false. На основе этого можно наверняка сказать удачно прошел запрос или нет. А вот как узнать результат INSERT запроса или например DELETE запроса? Мы ведь кроме метода query() ничего больше не использовали. Для того что бы определить удачный запрос или нет, мы в методе query() объявим спец свойство — affectedRows (получается с помощью функции mysqli_affected_rows())

  • Если в этом свойстве будет ноль — это скажет нам о том, что ни одна строка не была затронута. Кроме пожалуй случаев полного удаление строк из таблицы. Если верить документации запрос DELETE без указания условий удаление (то есть без WHERE) вернет ноль.
  • Если в этом свойстве будет -1 — это говорит о том что произошла какая-то ошибка
  • Если же в этом свойстве будет число больше нуля — это говорит о том, что запрос успешно затронул n-ое количество строк в базе.

В общем если это свойство не равно -1 — это значит запрос прошел успешно.

В методе query() так же будет еще два свойства (я о них уже говорил):

  1. insertID — будет хранить автоматически генерируемый ID, последнего INSERT запроса. Получается с помощью функции mysqli_insert_id()
  2. numRows — кол-во выбранных строк с помощью SELECT запроса. Получается с помощью функции mysqli_num_rows()

Как запрашивать данные из этих свойств? Очень просто:

<?php

$result = sql::query('insert, update, delete запрос');
echo $result->affectedRows . "<br>";
echo $result->insertID     . "<br>";
echo $result->numRows;

В общем то это все , что нужно от обертки. Давайте уже смотреть на готовый класс Улыбаюсь (файл называется sql.class.php и лежит в папке /rs-mini/core/)

<?php

/*
 * @package RS-MINI
 * @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
 * @license GNU General Public License version 2; see LICENSE.txt
 *
 */
 
namespace core;

// создаем псевдоним для неймспейса конфига
use \conf\config AS config;

if(!defined('RS-MINI')) die();

/*
 * sql v1.0
 *
 * класс по работе с sql запросами
 *
 */

class sql
{
    /*
     * число рядов в результирующей выборке
     *
     * @var    - integer
     * @access - public
     */
     
    public $numRows = 0;

    /*
     * число строк, затронутых последним INSERT, UPDATE, REPLACE или DELETE запросом
     * > 0 все ОК. == -1 ошибка, или значение по умолчанию. == 0 запрос UPDATE ничего не обновил
     *
     * @var    - integer
     * @access - public
     */
     
    public $affectedRows = -1;

    /*
     * автоматически генерируемый ID, последнего запроса
     *
     * @var    - integer
     * @access - public
     */

    public $insertID = 0;

    /*
     * указатель на выборку
     *
     * @var    - integer
     * @access - private
     */
     
    private $resourceQuery = FALSE;

    /*
     * кол-во сделанных селект запросов
     *
     * @var    - integer
     * @access - public
     */
     
    public static $selectCount = 0;

    /*
     * указатель на соединение с MySQL
     *
     * @var    - resource
     * @access - private
     */
     
    private static $resource = FALSE;
    
    /*
     * подключаемся к БД
     *
     * @access - public
     *
     */
    
    public static function setConnect()
    {
        // подключаемся к БД
        self::$resource = @mysqli_connect(
            config::getDB('server'),    // сервер
            config::getDB('user'),      // юзер
            config::getDB('pass'),      // пароль юзера
            config::getDB('name')       // база данных
        );

        // если подключение прошло с ошибкой
        if (!self::$resource)
            die('Ошибка ' . CMS . ' v' . VERSION . ': При подключение к базе данных произошла ошибка');

        // устанавливаем кодировку базы
        mysqli_set_charset(self::$resource, "utf8");
    }

    /*
     * создание объекта и исполнение sql запроса
     * \core\sql::query('запрос')
     *
     * @access - public
     * @return - объект sql класса
     *
     * @param sql - SQL запрос
     */
    
    public static function query($sql)
    {
        // поднимаем объект класса sql
        $obj = new sql();
        
        // исполняем запрос
        $obj->resourceQuery = mysqli_query(self::$resource, $sql);
        
        // наполняем данные
        // кол-во выбранных строк с помощью SELECT
        $obj->numRows       = @mysqli_num_rows($obj->resourceQuery);
        // кол-во затронутых строк последним INSERT, UPDATE, REPLACE или DELETE запросом
        $obj->affectedRows  = @mysqli_affected_rows(self::$resource);
        // автоматически генерируемый ID, последнего запроса
        $obj->insertID      = @mysqli_insert_id(self::$resource);
        
        // вертаем объект (для того что бы можно было
        // исполнить метод fetch(), или достучаться
        // до свойств с информацией)
        return $obj;
    }
    
    /*
     * метод получает выборку запроса
     * \core\sql::query('запрос')->fetch()
     *
     * @access - public
     * @return - ассоциативный массив с выбранными строками
     *           из БД или FALSE если ничего небыло выбранно
     *
     */
    
    public function fetch()
    {
        // массив для хранения строк выборки
        $resultList = array();
        // ключ для формирование строки
        $i          = 0;

        // если ресурс запроса пуст
        if ($this->resourceQuery === FALSE)
            // выводим false
            return FALSE;
        
        // +1 к кол-ву select запросов сделанных системой RS-MINI
        self::$selectCount++;
        
        // листаем строки выбокри
        while ($row = mysqli_fetch_assoc($this->resourceQuery)) {
            // листаем поля строки
            foreach ($row as $key => $value)
                // заполняем массив
                $resultList[$i][$key] = $value;
            
            // переключаем строку
            $i++;
        }
        
        // если выборка пустая
        if (empty($resultList)) {
            // пытаемся получить ошибку
            $error = mysqli_error(self::$resource);
            
            // если нет ошибок, то селект запрос просто вернул 0 строк
            if (empty($error))
                // вертаем false
                return FALSE;
            
            // если ошибка есть, то показываем ее на экран
            echo $error;
            
            // закрываем подключение
            self::close();
            
            // убиваем скрипт
            die();
        }
        
        // возвращаем выборку
        return $resultList;
    }
    
    /*
     * метод закрывает подключение к БД
     *
     * @access - public
     * @return - true или false
     *
     */
    
    public static function close()
    {
        return mysqli_close(self::$resource);
    }
}

Как видно из кода появились еще несколько свойств, два из которых статичные. Но тут ничего сложного нет, свойство resource нужно для хранения единственного подключения к базе данных. Если внимательно посмотреть, можно заметить, что метод query() каждый раз поднимает новый объект класса sql. В каждом таком объекте свойства insertID, affectedRows и numRows имеют свои значения (они зависят от скуль запроса), а вот свойство resource всегда только одно. (свойство содержит результат работы функции mysqli_connect())

Что касается свойства resourceQuery. Оно не статичное, и тоже для каждого объекта свое. Содержит результат работы функции mysqli_query() и необходимо для получения информации о SELECT запросе.

А вот свойство selectCount предназначено для подсчета сделанных SELECT запросов и несет исключительно информативный характер.

Ну так то вроде все Улыбаюсь

Подправляем классы request.class.php, abstractmodel.class.php и application.class.php

Класс request.class.php

Начнем с класса для получение безопасных значений из глобальных массивов. Учитывая то что он не маленький, я продемонстрирую лишь два метода который нужно дописать/написать. Вот они (полный код класса смотрите в архиве, класс называется request.class.php и лежит в папке /rs-mini/core/)

    /*
     * обрабатывает данные в свойстве self::$httpData как стороку
     *
     * @access - public
     * @return - обработанные данные или null если ничего не найдено
     *
     */
    
    public function toString()
    {
        return addslashes(htmlspecialchars(self::$httpData));
    }
    
    /*
     * обрабатывает данные в свойстве self::$httpData как html
     *
     * @access - public
     * @return - обработанные данные или null если ничего не найдено
     *
     */
    
    public function toHtml()
    {
        return addslashes(self::$httpData);
    }

метод toString() теперь не просто преобразует специальные символы в HTML сущности но еще и экранирует спецсимволы в строке.

А метод toHtml() оставляет html код, но тоже экранирует спецсимволы.

Класс abstractmodel.class.php

Тут я тоже продемонстрирую лишь один метод. Он придет на смену уже существующему методу database(). Суть query() в том, что бы исполнить скуль запрос, и запомнить (передать в свойство call через магический метод __call()) вспомогательные данные этого запроса (я про свойства numRows, affectedRows и insertID объекта класса sql). В общем вот код: (полный код класса смотрите в архиве, класс называется abstractmodel.class.php и лежит в папке /rs-mini/core/)

    /*
     * метод поднимает объект базы данных и исполняет запрос
     *
     * @access - protected
     * @return - объект класса sql
     *
     * @param string sql - скуль запрос
     */
    
    protected function query($sql)
    {
        // исполняем запрос, и помещаем объект в свойство
        $obj = sql::query($sql);
        
        // запоминаем результат запроса
        $this->setNumRows($obj->numRows)
             ->setAffectedRows($obj->affectedRows)
             ->setInsertID($obj->insertID)
        ;
        
        // возвращаем объект
        return $obj;
    }

Класс application.class.php

Ну здесь совсем просто. Нам необходимо подключиться к базе данных вызвав метод setConnect() класса sql, и я предлагаю это сделать в методе run() класса application. Вот код метода: (файл называется application.class.php и лежит в папке /rs-mini/core/)

    /*
     * главный метод. запуск системы
     *
     * @access  - public
     *
     */
     
    public static function run()
    {
        // поднимаем некоторые классы ядра
        // объект прослойка между контроллерами и вьюшкой
        self::$dom = new dom();
        
        // стартуем подключение к БД
        sql::setConnect();
        
        // тест класса по работе с глобальными переменнами.
        // переходим вот по такому урлу http://mini.test.ru/?name=<p>Алексей</p>
        ?><xmp><?
        var_dump(request::getHttpGet('name')->toString());
        ?></xmp><?
    }

Заключение

Вроде ничего не забыл. Тестировать новую обертку будет, скорее всего, в следующей статье.

Если есть вопросы, то пишите комментарии, постараюсь на них ответить. Результат работы можете скачать в конце статьи. Кстати, перед тем как запускать содержимое архива, обязательно не забудьте создать базу данных (свою я назвал rs_mini). Она пока будет пустой, но без нее, при подключение, вылезет ошибка.

Всего Вам наилучшего, у меня на сегодня все!

Прикрепленные файлы

 

Возможно Вам будут интересны следующие заметки

Комментарии (11)

Ваше имя *
Сайт
Ваш E-mail *
Ваше сообщение *
 
CWTeam, 14 Августа 2015 г. 10:31 пишет:
Читатель
Скажите мне всего 1 вещь, на кой хрен использовать mysqli если давно придумали PDO?
Алексей, 14 Августа 2015 г. 10:58 пишет:
Автор

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

p.s. у меня к Вам вопрос, зачем Вы читаете эти статьи, если Ваши знания явно на моем (а то и выше моего) уровне?

Ответ для пользователя: CWTeam
CWTeam, 14 Августа 2015 г. 11:09 пишет:
Читатель
Я давно слежу за вашими новостями, зачем и почему не знаю, но привычка осталась))))
Раньше Вадим по вашим статьям делал NiunCMS потом вы перестали постить новости, он ее забросил я у него разработку забирал и делал niuncms 2.0, но в связи с нехваткой времени я ее забросил, а потом отдал назад Вадиму и он ее похоронил окончательно

А с PDO советую познакомиться
Ответ для пользователя: Алексей
Алексей, 14 Августа 2015 г. 11:20 пишет:
Автор
Я думал niuncms преобразовалась в uDiscuz а после в elementary CMS, разве нет? Над элементари сейчас не идет работа?
Ответ для пользователя: CWTeam
CWTeam, 15 Августа 2015 г. 06:02 пишет:
Читатель
Нет, uDiscuz и elementary CMS это уже другие разработки от того же Вадима, а ниун так и ушла в небытие. Кстати он свои разработки снова забросил
Ответ для пользователя: Алексей
Алексей, 15 Августа 2015 г. 13:41 пишет:
Автор
Жаль. Может еще появится энтузиазм =)
Ответ для пользователя: CWTeam
CWTeam, 16 Августа 2015 г. 05:11 пишет:
Читатель
Возможно, у вас же появляется время от времени =)
Ответ для пользователя: Алексей
dlegame, 18 Августа 2015 г. 08:03 пишет:
Читатель
Доброго вам времени коллеги!
Ваня, я активно разрабатываю elementary CMS но хочу сменить ее название на Plugun CMS ибо сильно длинное.
Просто были семейные проблемы и сейчас устроился на работу и буду по вечерам дописывать CMS, позже могу показать движок.
Ответ для пользователя: CWTeam
CWTeam, 18 Августа 2015 г. 09:06 пишет:
Читатель
Привет! Я рад, что ты ее не забросил. по возможности напиши мне в вк
Ответ для пользователя: dlegame
dlegame, 19 Августа 2015 г. 05:09 пишет:
Читатель
Хорошо, а ты что сейчас CMS не какую не пишешь?
Ответ для пользователя: CWTeam
CWTeam, 19 Августа 2015 г. 14:07 пишет:
Читатель
Пишу закрытую биллинг систему, ну и думаю возродить NiunCMS, если ты не против :)
Ответ для пользователя: dlegame