Всем доброго времени суток. Сегодня мы продолжаем сборку ядра RS-MINI. Совсем недавно мы закончили реализацию абстрактного класса для контроллеров, а в предыдущем посте немного видоизменили некоторые классы. Надеюсь для тех кто в это вникает эти изменения, да и код вообще вопросов не вызывает.
Сегодня мы реализуем еще один абстрактный класс. Он будет базой для одной из составляющей схемы проектирования MVC — модели.
В отличии от контроллеров у модели будет всего три метода:
Прежде чем что-либо расписывать нужно сказать пару слов о том, что вообще представляет из себя модель. Ну было бы еще не плохо пояснить роль контроллеров во всем этом, а то я что-то как-то мимо этой темы пробежал. Давайте посмотрим на примере.
Предположим у нас есть страница
/post/1/edit/
Перейдя на нее нам будет позволено отредактировать статью с ID равной единице. Что будет происходить когда мы откроем данную страницу?
Что будет происходить когда администратор нажмет на кнопку «сохранить»?
Из выше сказанного понятно, что модель берет на себя все «грязную» работу. Именно она очень тесно работает с данными. Контроллер лишь дает ей задания (ну бывает что сам что-то проверяет).
Теперь вернемся к нашим методом.
Контроллер каждый раз поднимает новую копию объекта модели. А так как я уже отвык от юзанья ключевого слова new, у модели будет специальный статичный метод для этой операции. Это метод (его можно увидеть в листинге чуть ниже. называется create()) своего рада фабрика по производству объектов. Все завязано на функции get_called_class(). Она позволяет получить полное имя класса из которого был вызван этот метод. Нам остается лишь вернуть new этого имени, тем самым подняв объект.
Внутри модели будет не красиво писать каждый раз (ну может и красиво, это спорный момент)
\core\database::create();
поэтому я подумав решил, что будем писать вот так
$this->database();
Код этого метода ниже в листинге. В этом методе нет ничего сложного, поэтому — без комментариев
Вот тут самое интересное. Как было сказано в предисловии контроллер будет передавать данные в модель. Как она будет это делать? Например, если мы записываем нового пользователя в базу данных, то в модель, данные (этого пользователя) можно было бы передать вот так:
model::create()->registration('Логин', 'Пароль');
т.е. в виде параметров. Я вместо этого предлагаю передавать эти данные вот так:
model::create()
->setLogin('Логин')
->setPassword('Пароль')
->registration()
;
а в модели, получить их вот так:
$login = $this->getLogin();
$password = $this->getPassword();
Но согласитесь, создавать методы setLogin(), getLogin(), setPassword() и getPassword() и еще множество таких методов будет очень и очень нудно. Вот тут на помощь нам придет магический метод __call() который позволяет с имитировать эти методы не создавая их.
Дело в том, что тело этого метода отрабатывает до того как php ругнется на нас за то, что мы пытаемся вызвать несуществующий метод класса. В __call() приходя два параметра
Небольшими манипуляциями (смотрите листинг, этот метод очень хорошо закомментирован) с именем, и мы понимаем, что нам нужно
Этот файл лежит в папке /rs-mini/core/ и называется abstractmodel.class.php. Вот код абстрактного класса
<?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;
if(!defined('RS-MINI')) die();
/*
* abstractmodel v 1.0
*
* абстрактный класс для моделей
*
*/
abstract class abstractmodel
{
/*
* хранит значение посланные в метод __call()
*
* @var - массив
* @access - private
*/
private $call = array();
/*
* создание объекта
* функция get_called_class() позволяет получить полное имя текущего
* класса (тоесть того класса из которого была запущена данная функция)
* если вызываемая модель будет лежать в папке
* "/project/modules/pub/test/model/a.class.php", сам класс будет
* называться "a" а неймспейс у класса будет "pub\test\model" то функция
* get_called_class() выдаст вот такой результат: "pub\test\model\a"
*
* нам остается только вызвать полученную строчку через спец слово new
* тем самым поднять объект вызываемой модели
*
* например для того что бы поднять объект класса "a" достаточно написать
* вот так:
* $modelA = \pub\test\model\a::create();
*
* @access - public
*
*/
public static function create()
{
// получаем полное имя вызываемого класса
$who = get_called_class();
// поднимаем объект этого класса
return new $who();
}
/*
* метод позволяем вызвать несуществующие методы класса
* данный метод обрабатывает не все подрят а только
* те методы которые начинаются на get (получить) или set (записать).
*
* суть проста, когда разработчик юзает метод setЧтоНибудь(значение)
* в свойстве call создается ключ массива "чтонибудь" (имя метода
* приводится к нижнему регистру а "set" из имени вырезается) с значением
* "значение"
*
* когда разработчик юзает метод getЧтоНибудь(), метод __call из свойства
* call вытаскивает значение ключа "чтонибудь" (имя метода приводится к
* нижнему регистру а "get" из имени вырезается). если нет ключа "чтонибудь"
* метод __call (в примере getЧтоНибудь()) вернет NULL
*
* метод необходим что бы из контроллеров можно было передать в модель
* значения для работы этой модели. Например если у нас есть некая модель "a"
* которая призвана что-то вставить в базу данных, и есть таблица БД с полем
* name, то контроллер, для того что бы что-то вставить в поле name через
* модель "a", может передать значения (в модель) для этого поля следующим образом:
* (метод insert() в модели отвечает за вставку строк в БД)
*
* a->setName('имя')->insert();
*
* В методе insert() модели "a" имя можно будет получить вот так:
*
* $name = $this->getName();
*
*
*
* @access - public
* @return - сам себя или сохраненное значение (все зависит от
* того set или get метод был вызван)
*
* @param string name - имя вызываемого метода
* @param array arguments - присланные аргументы в вызываемый метод
*/
public function __call($name, $arguments)
{
// ниже в комментариях приведен конкретный пример с методами getName() и setName().
// предаваемое значение - "Алексей"
// приводим имя вызываемого метода к нижнему регистру
// в случае если вызван метод setName(), то получим setname
// в случае если вызван метод getName(), то получим getname
$name = mb_strtolower($name);
// делим имя вызываемого метода на части
// в случае если вызван метод setName(), то получим set и name
// в случае если вызван метод getName(), то получим get и name
// в $action помещаем set или get
$action = mb_substr($name, 0, 3);
// в $argument помещаем часть имени метода (при вызове
// getName() или setName() частью будет - name)
$argument = mb_substr($name, 3, mb_strlen($name)-1);
// в зависимости от того что в $action
switch($action) {
case 'get':
// возвращаем из свойства call значение (в нашем примере из ключа name
// получим значение "Алексей", если это значение записали путем вызова
// метода setName() с значением "Алексей")
return (isset($this->call[$argument])) ? $this->call[$argument] : NULL;
break;
case 'set':
// сохраняем в свойстве call значение (в нашем примере в ключ name
// свойства call будет передано значение "Алексей")
$this->call[$argument] = $arguments[0];
// возвращаем объект модели
return $this;
break;
}
}
/*
* метод поднимает объект базы данных
*
* @access - protected
* @return - объект базы данных
*
* @param string name - имя таблицы
*/
protected function database($name)
{
return database::create($name);
}
}
Тут ничего сложного нет, но тем не менее если есть какие либо вопросы, то пишите их в комментарии, постараюсь на них ответить.
Результат проделанной работы можно скачать в конце этой статьи
Всего Вам наилучшего, на сегодня все!