Всем доброго времени суток. После продолжительных раздумий и кучи тестов я пришел к выводу, что создавать интернет-магазин на системе, которая в качестве базы данных использует файлы, будет очень грустно. Я проводил тесты, пытался оперировать с 5-тью таблицами с 1000 строк в каждой. Полет был нормальным, до тех пор пока не понадобилось изменить что-нибудь в этих 1000 строк. Оно не удивительно, ведь для update необходимо было запрашивать и сохранять файл аж 1000 раз.
В общем взгрустнул я и решил, пока мы еще не начали пользоваться базой данных, перекинуться с файловой, на mysql
Что для этого нам понадобится?
Как я писал выше, начнем мы с конфига, так как он используется в обертке. Что из себя представляет этот класс? Все очень просто, у нас будет статичный метод 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;
}
}
Думаю вопросов этот маленький класс вызвать не должен. Если Вы внимательно смотрели на методы, то могли заметить создание двух констант про которые я ничего не сказал. Эти две константы:
нужны лишь для удобства. Везде по проекту, вместо имени движка и версии мы будет писать вот эти константы, и в случае чего мы очень быстро может изменить и версию, и если понадобится — имя
Кстати, что касается самих настроек для подключения. Если Ваши настройки буду совпадать с моими, то Вам не придется каждый раз (после скачивания архива в конце статьи) менять их. Если же они отличны, то просто не забывайте их менять
Тут ничего сложного нет, нам нужно всего лишь вызвать метод 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(); // стартуем систему
Нам понадобится три глобальных метода:
Так же понадобится метод для закрытия подключения, но он будет состоять всего из одной строчки, так что о нем и сказать то нечего
После исполнения sql запросов мы сохраним кое какие данные. К ним относятся (подробнее об этих данных ниже):
Выборки мы будет получать с помощью функции 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())
В общем если это свойство не равно -1 — это значит запрос прошел успешно.
В методе query() так же будет еще два свойства (я о них уже говорил):
Как запрашивать данные из этих свойств? Очень просто:
<?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 и лежит в папке /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 код, но тоже экранирует спецсимволы.
Тут я тоже продемонстрирую лишь один метод. Он придет на смену уже существующему методу 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;
}
Ну здесь совсем просто. Нам необходимо подключиться к базе данных вызвав метод 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). Она пока будет пустой, но без нее, при подключение, вылезет ошибка.
Всего Вам наилучшего, у меня на сегодня все!
В данном проекте нет большой разницы что использовать PDO или mysqli. Выбор пал на mysqli потому что работаю с ним уже довольна давно, а PDO знаю лишь теоретически и на практике не применял.
p.s. у меня к Вам вопрос, зачем Вы читаете эти статьи, если Ваши знания явно на моем (а то и выше моего) уровне?
Раньше Вадим по вашим статьям делал NiunCMS потом вы перестали постить новости, он ее забросил я у него разработку забирал и делал niuncms 2.0, но в связи с нехваткой времени я ее забросил, а потом отдал назад Вадиму и он ее похоронил окончательно
А с PDO советую познакомиться
Ваня, я активно разрабатываю elementary CMS но хочу сменить ее название на Plugun CMS ибо сильно длинное.
Просто были семейные проблемы и сейчас устроился на работу и буду по вечерам дописывать CMS, позже могу показать движок.