Пример MVC в php. Третья статья. Модели. Элементарные действия с записями

Содержание цикла статей:

Введение

Пример MVC в php. Третья статья. Модели. Элементарные действия с записямиВ предыдущей статье мы с вами написали каркас сайта, который использует в своей основе 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качать исходники и дамп тестовой таблицы можно архивом тут.

Рассказать друзьям:


Пример MVC в php. Третья статья. Модели. Элементарные действия с записями: 12 комментариев

  1. Отличная статья! А Вы не могли бы выложить исходники по этой статье?

    Сейчас ломаю голову, как построить свою CMS. Просмотрел кучу примеров реализации mvc, везде что то да не так, а знания пока не позволяют прям полностью самому построить структуру)

    Ваш метод нравится, Вы не описываете совсем уж простые вещи. Буду рад продолжению цикла статей.

    1. Добрый день, я рад, что статьи кому-то оказались действительно полезными!
      Исходники и дамп тестовой таблицы я выложил — в конце статьи.
      Новые статьи будут выходить примерно раз в неделю.

      1. Здравствуйте! Я вот сейчас делаю по 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

        Что скажете?
        Вот у меня просто вопрос. Для каждой страницы по сути, свой контроллер, ну или для группы страниц. А как в таком случае сделать, чтобы какая нибудь функция выполнялась на всех страничках сайта. Например, я хочу, чтобы меню, которое создается в админке, отображалось на всех страничках. Где мне запросом его вытягивать лучше?
        Заранее благодарю Вас за ответ))

        1. Добрый день!
          Для удобства, стоит вынести все служебные файлы, которые относятся к фреймворку, в отдельную папку — так будет проще переносить их с проекта на проект.
          Чтобы использовать функции во всех контроллерах, можно создать вспомогательный класс и в нем писать общие для всех страниц методы.
          Для решения проблемы с меню, можно писать логику формирования и вывода в шаблоне. Если смотреть по моей структуре, то в файле first_layouts.php, который используется на всех страницах. Но хочу заметить, что писать запросы в сомом шаблоне не совсем красиво и правильно, поэтому в шаблоне старайтесь минимизировать логику — сделайте там просто вызов вспомогательного класса и просто выведите, то что он вернет. А уже в самом классе опишите обращение к моделям(к бд) и формирование меню.
          Написание вспомогательных классов и вынос фреймворка в отдельную папку я планирую описать чуть позже — в следующих статьях, но поскольку Вы спросили могу даль ссылку на репозиторий с исходниками, в которых я уже реализовал вынос папки с фреймворком, сделал разделение на модули — админка и сайт находятся в разных папках, а также написал вспомогалтельный для использования в любом мест сайла — класс называется service_isf.php, находится в папке с фреймворком. Вызывать его очень просто, например вызвать редирект можно вот так: ISF::redirect(‘index/index’);.
          Вот ссылка на репозиторий, некоторые моменты еще «сырые», но в целом все рабочее, я постоянно обновляю репозиторий — можете переодически смотреть обновления:
          https://github.com/Ipatov/example-blog Исходники лежат в папке www

  2. Блин, структуру не получилось передать. Вот так правильнее будет уж:

    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

  3. Ваши статьи оказались более чем полезны. Спасибо вам великое за колоссальный труд. Ваш подкаст вдохновил и подтолкнул меня наконец то слезть с проце_дурки. Конечно еще учить и учить, читать и читать. Во второй главе присутствовали небольшие ошибки в коде, разобрав и устранив которые я проникся в структуру до мозга костей и вник в суть MVC. И если бы там все было гладко, красиво и в белом пальто — я бы так и не понял смысла работы. Спасибо еще раз, бро.

    PS Может и бойан но хочу тоже поделиться чем то полезным для ООП-шника. phpclasses.org — шикарный репозиторий классов php. И это не жалкий скриптовый варезник. Джа, бро!

    1. Спасибо за добрые слова! После таких комментариев хочется писать и писать :) Жаль только сейчас со временем туговато

  4. Спасибо за статью, внятно написана. Залил исходники на сервер, не работает. Если есть возможность, объсните, где косяк:
    Warning: include(/config.php): failed to open stream: No such file or directory in /srv/http/temp2/index.php on line 5

    Warning: 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

    1. Извиняюсь за столь долгое ожидание.
      Проблема в подключении файла конфигураций, достаточно подправить путь в индексом файле:

      // подключаем конфиг
      include (‘config.php’);

      Я выложил пятую статью, в которой поправил все неточности, которые присутствовали ранее.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*