Содержание цикла статей:
- 1. Вводная статья
- 2. Маршрутизация, контролеры, экшены, шаблоны
- 3. Модели. Элементарные действия с базой данных
- 4. Проектируем блог
- 5. Исправление неточностей структуры
Введение
В предыдущей статье мы с вами написали каркас сайта, который использует в своей основе MVC подход. На данный момент у нас работает маршрутизация, сделано разделение на модели, контроллеры и отображения(view). Но поскольку предыдущая статья получилась большая, я затронул работу с моделями вскользь, в этой статье мы это наверстаем.
Модели в большинстве случаев используются для работы с базой данных, то есть все запросы для получения, обновления, удаления или записи в БД будут осуществляться в моделях. В контролерах не должно быть работы с базой, вместо запросов должно быть обращение к методам моделей.
Структура моделей
Для удобства работы с базой данных, принято делать для каждой таблицы отдельную модель – класс, а в методах каждой модели уже описывать взаимодействие с таблицами. Давайте это реализуем. Как известно, для обращения к бд, необходимо делать подключение, но так как мы уже создаем соединение в файле index.php будем его использовать и в моделях. Так же для удобства можно создать абстрактный класс, от которого наши модели будут наследовать типовые методы. К таким методам можно отнести элементарные действия с базой данных – получение одной записи по id, получение всех записей таблицы и тд.
Давайте создадим абстрактный класс models_base.php и расположим его в папке classes. Код класса будет выглядеть так:
Abstract Class Model_Base {
protected $db;
protected $table;
private $dataResult;
public function __construct($select = false) {
// объект бд коннекта
global $dbObject;
$this->db = $dbObject;
// имя таблицы
$modelName = get_class($this);
$arrExp = explode('_', $modelName);
$tableName = strtolower($arrExp[1]);
$this->table = $tableName;
// обработка запроса, если нужно
$sql = $this->_getSelect($select);
if($sql) $this->_getResult("SELECT * FROM $this->table" . $sql);
}
// получить имя таблицы
public function getTableName() {
return $this->table;
}
// получить все записи
function getAllRows(){
if(!isset($this->dataResult) OR empty($this->dataResult)) return false;
return $this->dataResult;
}
// получить одну запись
function getOneRow(){
if(!isset($this->dataResult) OR empty($this->dataResult)) return false;
return $this->dataResult[0];
}
// извлечь из базы данных одну запись
function fetchOne(){
if(!isset($this->dataResult) OR empty($this->dataResult)) return false;
foreach($this->dataResult[0] as $key => $val){
$this->$key = $val;
}
return true;
}
// получить запись по id
function getRowById($id){
try{
$db = $this->db;
$stmt = $db->query("SELECT * from $this->table WHERE id = $id");
$row = $stmt->fetch();
}catch(PDOException $e) {
echo $e->getMessage();
exit;
}
return $row;
}
// запись в базу данных
public function save() {
$arrayAllFields = array_keys($this->fieldsTable());
$arraySetFields = array();
$arrayData = array();
foreach($arrayAllFields as $field){
if(!empty($this->$field)){
$arraySetFields[] = $field;
$arrayData[] = $this->$field;
}
}
$forQueryFields = implode(', ', $arraySetFields);
$rangePlace = array_fill(0, count($arraySetFields), '?');
$forQueryPlace = implode(', ', $rangePlace);
try {
$db = $this->db;
$stmt = $db->prepare("INSERT INTO $this->table ($forQueryFields) values ($forQueryPlace)");
$result = $stmt->execute($arrayData);
}catch(PDOException $e){
echo 'Error : '.$e->getMessage();
echo '<br/>Error sql : ' . "'INSERT INTO $this->table ($forQueryFields) values ($forQueryPlace)'";
exit();
}
return $result;
}
// составление запроса к базе данных
private function _getSelect($select) {
if(is_array($select)){
$allQuery = array_keys($select);
array_walk($allQuery, function(&$val){
$val = strtoupper($val);
});
$querySql = "";
if(in_array("WHERE", $allQuery)){
foreach($select as $key => $val){
if(strtoupper($key) == "WHERE"){
$querySql .= " WHERE " . $val;
}
}
}
if(in_array("GROUP", $allQuery)){
foreach($select as $key => $val){
if(strtoupper($key) == "GROUP"){
$querySql .= " GROUP BY " . $val;
}
}
}
if(in_array("ORDER", $allQuery)){
foreach($select as $key => $val){
if(strtoupper($key) == "ORDER"){
$querySql .= " ORDER BY " . $val;
}
}
}
if(in_array("LIMIT", $allQuery)){
foreach($select as $key => $val){
if(strtoupper($key) == "LIMIT"){
$querySql .= " LIMIT " . $val;
}
}
}
return $querySql;
}
return false;
}
// выполнение запроса к базе данных
private function _getResult($sql){
try{
$db = $this->db;
$stmt = $db->query($sql);
$rows = $stmt->fetchAll();
$this->dataResult = $rows;
}catch(PDOException $e) {
echo $e->getMessage();
exit;
}
return $rows;
}
// уделение записей из базы данных по условию
public function deleteBySelect($select){
$sql = $this->_getSelect($select);
try {
$db = $this->db;
$result = $db->exec("DELETE FROM $this->table " . $sql);
}catch(PDOException $e){
echo 'Error : '.$e->getMessage();
echo '<br/>Error sql : ' . "'DELETE FROM $this->table " . $sql . "'";
exit();
}
return $result;
}
// уделение строки из базы данных
public function deleteRow(){
$arrayAllFields = array_keys($this->fieldsTable());
array_walk($arrayAllFields, function(&$val){
$val = strtoupper($val);
});
if(in_array('ID', $arrayAllFields)){
try {
$db = $this->db;
$result = $db->exec("DELETE FROM $this->table WHERE `id` = $this->id");
foreach($arrayAllFields as $one){
unset($this->$one);
}
}catch(PDOException $e){
echo 'Error : '.$e->getMessage();
echo '<br/>Error sql : ' . "'DELETE FROM $this->table WHERE `id` = $this->id'";
exit();
}
}else{
echo "ID table `$this->table` not found!";
exit;
}
return $result;
}
// обновление записи. Происходит по ID
public function update(){
$arrayAllFields = array_keys($this->fieldsTable());
$arrayForSet = array();
foreach($arrayAllFields as $field){
if(!empty($this->$field)){
if(strtoupper($field) != 'ID'){
$arrayForSet[] = $field . ' = "' . $this->$field . '"';
}else{
$whereID = $this->$field;
}
}
}
if(!isset($arrayForSet) OR empty($arrayForSet)){
echo "Array data table `$this->table` empty!";
exit;
}
if(!isset($whereID) OR empty($whereID)){
echo "ID table `$this->table` not found!";
exit;
}
$strForSet = implode(', ', $arrayForSet);
try {
$db = $this->db;
$stmt = $db->prepare("UPDATE $this->table SET $strForSet WHERE `id` = $whereID");
$result = $stmt->execute();
}catch(PDOException $e){
echo 'Error : '.$e->getMessage();
echo '<br/>Error sql : ' . "'UPDATE $this->table SET $strForSet WHERE `id` = $whereID'";
exit();
}
return $result;
}
}
Теперь приведу код модели для таблицы с пользователями (в таблице три столбца – id, first_name и last_name).
):
Class Model_Users Extends Model_Base {
public $id;
public $name;
public $last_name;
public function fieldsTable(){
return array(
'id' => 'Id',
'first_name' => 'First name',
'last_name' => 'Last name'
);
}
}
Теперь подробно рассмотрим код модели и абстрактного класса. С моделью все просто – мы наследуем все методы у Model_Base, создаем свойства с такими же именами, как и имена столбцов в таблице с пользователями. И создаем метод, при обращении к которому возвращается массив с именами всех полей таблицы. Имена полей таблицы нам необходимы при выполнении методом в классе Model_Base – при обращении к базе данных.
Абстрактная модель у нас получилась большая, но и работу она выполняет не маленькую, возможно, что всех ее методов нам будет достаточно для создания блога, но если чего-то будет не хватать, мы всегда можем расширить класс. Этим и хорош ООП – всегда можно без проблем и без вреда для сайта расширять функционал.
На данный момент наши модели умеют получать, обновлять, удалять и создавать записи в таблицах базы данных. Давайте рассмотрим несколько примеров того, что можно делать с бд, используя наши модели.
Получение записей из бд
Получение нескольких строк из таблицы:
// создаем запрос $select = array( 'where' => 'id >= 1 AND id <= 5', // условие 'group' => 'first_name', // группируем 'order' => 'id DESC', // сортируем 'limit' => 10 // задаем лимит ); $model = new Model_Users($select); // создаем объект модели $usersInfo = $model->getAllRows(); // получаем все строки var_dump($usersInfo); // выводим данные
Получение одной строки из таблицы:
$select = array( 'where' => 'id = 2' ); $model = new Model_Users($select); $usersInfo = $model->getOneRow(); var_dump($usersInfo);
Помимо получения строк, мы можем получать значения конкретных столбцов:
// запрос $select = array( 'where' => 'id = 2' ); $model = new Model_Users($select); $model->fetchOne(); // извлекаем данные // получаем значения столбцов $firstName = $model->first_name; $lastName = $model->last_name; // выводим var_dump($firstName); var_dump($lastName);
Создание записи в бд
Также просто, как и получать, мы можем и записывать данные:
// создаем объект $model = new Model_Users(); // задаем значения для полей таблицы $model->id = 10; // id можно и пропустить, если для этого поля настроен авто инкремент $model->first_name = 'Иван'; $model->last_name = 'Иванов'; $result = $model->save(); // создаем запись var_dump($result); // проверяем результат: true или false
Обновление записи в бд
Обновление записей в таблице тоже не составит проблем, выглядеть это будет так:
// запрос $select = array( 'where' => 'id = 10' ); // модель $model = new Model_Users($select); // извлекаем данные $model->fetchOne(); // задаем новые значения $model->first_name = 'Петр'; $model->last_name = 'Петров'; // обновляем запись $result = $model->update(); var_dump($result); // проверяем результат: true или false
Удаление записи в бд
И последнее элементарное действие с базой данных – удаление записей.
// модель $model = new Model_Users(); // условие удаления $select = array( 'where' => 'id > 10' ); // удаляем $result = $model->deleteBySelect($select); var_dump($result); // проверяем результат. Вернется количество удаленных строк
И еще один доступный вариант удаления, для одной записи:
// запрос $select = array( 'where' => 'id = 10' ); // модель $model = new Model_Users($select); // извлекаем данные $model->fetchOne(); // удаляем строку $result = $model->deleteRow(); var_dump($result);
Я думаю, что на этом стоит закончить сегодняшнюю статью, информации для размышления и осмысления пока достаточно. В следующей статье уже начнем проектировать блог.
Cкачать исходники и дамп тестовой таблицы можно архивом тут.

(4 оценок, среднее: 4,00 из 5)
Отличная статья! А Вы не могли бы выложить исходники по этой статье?
Сейчас ломаю голову, как построить свою CMS. Просмотрел кучу примеров реализации mvc, везде что то да не так, а знания пока не позволяют прям полностью самому построить структуру)
Ваш метод нравится, Вы не описываете совсем уж простые вещи. Буду рад продолжению цикла статей.
Добрый день, я рад, что статьи кому-то оказались действительно полезными!
Исходники и дамп тестовой таблицы я выложил — в конце статьи.
Новые статьи будут выходить примерно раз в неделю.
Здравствуйте! Я вот сейчас делаю по MVC что-то типа фреймворка, чтобы можно было не подправляя код основных классов, сделать из сайта-визитки интернет-магазин или корпоративный портал.
Вот моя текущая структура папок:
application // находится само приложение
backend // администраторская панель
orders // Модуль «Заказы»
shop // Модуль «Товары»
categories // Модуль «Категории» и т.д.
frontend // внешняя часть сайта
controllers
models
bootstrap.php
system // системные классы и все необходимые элементы
classes // системные классы
View.php
Controller.php
Model.php
Route.php и т.д.
themes // Темы сайта
default
css
js
images
views
functions.php // Класс с методами текущей темы
.htaccess
config.php
index.php
Что скажете?
Вот у меня просто вопрос. Для каждой страницы по сути, свой контроллер, ну или для группы страниц. А как в таком случае сделать, чтобы какая нибудь функция выполнялась на всех страничках сайта. Например, я хочу, чтобы меню, которое создается в админке, отображалось на всех страничках. Где мне запросом его вытягивать лучше?
Заранее благодарю Вас за ответ))
Добрый день!
Для удобства, стоит вынести все служебные файлы, которые относятся к фреймворку, в отдельную папку — так будет проще переносить их с проекта на проект.
Чтобы использовать функции во всех контроллерах, можно создать вспомогательный класс и в нем писать общие для всех страниц методы.
Для решения проблемы с меню, можно писать логику формирования и вывода в шаблоне. Если смотреть по моей структуре, то в файле first_layouts.php, который используется на всех страницах. Но хочу заметить, что писать запросы в сомом шаблоне не совсем красиво и правильно, поэтому в шаблоне старайтесь минимизировать логику — сделайте там просто вызов вспомогательного класса и просто выведите, то что он вернет. А уже в самом классе опишите обращение к моделям(к бд) и формирование меню.
Написание вспомогательных классов и вынос фреймворка в отдельную папку я планирую описать чуть позже — в следующих статьях, но поскольку Вы спросили могу даль ссылку на репозиторий с исходниками, в которых я уже реализовал вынос папки с фреймворком, сделал разделение на модули — админка и сайт находятся в разных папках, а также написал вспомогалтельный для использования в любом мест сайла — класс называется service_isf.php, находится в папке с фреймворком. Вызывать его очень просто, например вызвать редирект можно вот так: ISF::redirect(‘index/index’);.
Вот ссылка на репозиторий, некоторые моменты еще «сырые», но в целом все рабочее, я постоянно обновляю репозиторий — можете переодически смотреть обновления:
https://github.com/Ipatov/example-blog Исходники лежат в папке www
Отлично! Спасибо Вам!
Блин, структуру не получилось передать. Вот так правильнее будет уж:
application // находится само приложение
backend // администраторская панель
orders // Модуль "Заказы"
shop // Модуль "Товары"
categories // Модуль "Категории" и т.д.
frontend // внешняя часть сайта
controllers
models
bootstrap.php
system // системные классы и все необходимые элементы
classes // системные классы
View.php
Controller.php
Model.php
Route.php и т.д.
themes // Темы сайта
default
css
js
images
views
functions.php // Класс с методами текущей темы
.htaccess
config.php
index.php
Ваши статьи оказались более чем полезны. Спасибо вам великое за колоссальный труд. Ваш подкаст вдохновил и подтолкнул меня наконец то слезть с проце_дурки. Конечно еще учить и учить, читать и читать. Во второй главе присутствовали небольшие ошибки в коде, разобрав и устранив которые я проникся в структуру до мозга костей и вник в суть MVC. И если бы там все было гладко, красиво и в белом пальто — я бы так и не понял смысла работы. Спасибо еще раз, бро.
PS Может и бойан но хочу тоже поделиться чем то полезным для ООП-шника. phpclasses.org — шикарный репозиторий классов php. И это не жалкий скриптовый варезник. Джа, бро!
Спасибо за добрые слова! После таких комментариев хочется писать и писать :) Жаль только сейчас со временем туговато
Спасибо за статью, внятно написана. Залил исходники на сервер, не работает. Если есть возможность, объсните, где косяк:
Warning: include(/config.php): failed to open stream: No such file or directory in /srv/http/temp2/index.php on line 5Warning: include(): Failed opening '/config.php' for inclusion (include_path='.:/usr/share/pear') in /srv/http/temp2/index.php on line 5
Notice: Use of undefined constant DB_HOST - assumed 'DB_HOST' in /srv/http/temp2/index.php on line 8
Notice: Use of undefined constant DB_NAME - assumed 'DB_NAME' in /srv/http/temp2/index.php on line 8
Notice: Use of undefined constant DB_USER - assumed 'DB_USER' in /srv/http/temp2/index.php on line 8
Notice: Use of undefined constant DB_PASS - assumed 'DB_PASS' in /srv/http/temp2/index.php on line 8
Warning: PDO::__construct(): php_network_getaddresses: getaddrinfo failed: Name or service not known in /srv/http/temp2/index.php on line 8
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known' in /srv/http/temp2/index.php:8 Stack trace: #0 /srv/http/temp2/index.php(8): PDO->__construct('mysql:host=DB_H...', 'DB_USER', 'DB_PASS') #1 {main} thrown in /srv/http/temp2/index.php on line 8
У Вас какая версия php? Должно работать на 5.4
5.5.15
Извиняюсь за столь долгое ожидание.
Проблема в подключении файла конфигураций, достаточно подправить путь в индексом файле:
// подключаем конфиг
include (‘config.php’);
Я выложил пятую статью, в которой поправил все неточности, которые присутствовали ранее.