FFCMS Wiki

Официальная документация разработчика и администратора

Инструменты пользователя

Инструменты сайта


core:mvc:model

Модели

Модели в FFCMS являются частью MVC-архитектуры и по большей степени являются объектами с набором свойств и методов, которые позволяют хранить, передавать и обрабатывать бизнес-логику приложения, правила валидации и методы взаимодействия.

Базовая реализация модели, Ffcms\Core\Arch\Model позволяет :

  • Назначать и присваивать значения свойствам модели (атрибутам)
  • Присваивать, обрабатывать и быстро генерировать значения форм
  • Обозначать правила валидации данных атрибутов
  • Экспортировать и использовать данные модели в иных реализациях

Атрибуты

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

Пример простейшей реализации модели:

file: /Apps/Model/Front/Welcome.php

<?php
namespace Apps\Model\Front;
 
use Ffcms\Core\Arch\Model;
 
class Welcome extends Model 
{
    public $text;
}

В дальнейшем, в контроллере или в представлении может быть присвоено значение для параметра $text:

$model = new \Apps\Model\Front\Welcome();
$model->text = "Have a nice day!";
 
echo $model->text; // will output "Have a nice day!"

Кроме того, атрибуты модели могут быть получены в виде key ⇒ value массива:

$model = new \Apps\Model\Front\Welcome();
$model->text = "Have a nice day!";
 
foreach ($model->getAllProperties() as $property => $value) {
    echo "Property " . $property . " has value: " . $value;
}

Динамические глобальные атрибуты

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

Глобальные атрибуты будут доступны везде, после их инициации - в контроллере, модели и представлении. Например мы можем определить глобальный атрибут itemGlobal при помощи выше описанной модели Welcome:

$model = new \Apps\Model\Front\Welcome();
$model->itemGlobal = "Hello, i am a GLOBAL attribute!";

После такого определения данный атрибут будет доступен во всех слоях текущего приложения, при помощи вызова $this:

echo $this->itemGlobal; // will display "Hello, i am a GLOBAL attribute!"

Пример такого использования глобальных атрибутов легко найти в представлениях, к примеру в layouts/main.php используется:

<title><?= \App::$Security->strip_tags($this->title); ?></title>

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

Валидация атрибутов

Валидация атрибутов на основе предопределенных правил является шагом для ускорения разработки приложений и стандартизации методов проверки входящих данных. После того, как пользовательские данные были переданы на модель мы можем выполнить проверку входящих данных путем предопределенных правил.

Все правила должны быть представлены в виде массива и определены в методе rules() вашей модели. Проверка на соответствие входящих данных правилам валидации выполняется методом validate(). Стоит отметить, что вызов метод валидации желательно делать в месте, где инициирована модель, а не посредством создания метода или использования магических методов before(), after.

Приведем простой пример модели, в которой реализован механизм назначения правил валидации:

file: /Apps/Model/Front/LoginForm.php

namespace Apps\Model\Front;
 
use Ffcms\Core\Arch\Model;
 
class LoginForm extends Model 
{
    public $login;
    public $password;
 
    public function rules()
    {
        return [
            [['login', 'password'], 'required'],
            ['login', 'length_min', 3],
            ['login', 'length_max', 12],
            ['password', 'length_min', 4]
        ];
    }
}

В дальнейшем, к примеру в действии контроллера данная модель может быть валидирована следующим образом:

public function actionLogin()
{
    $model = new Apps\Model\Front\LoginForm;
 
    if ($model->send()) { // data was sended via submit post method
        if ($model->validate()) {
            // validation is passed! Make you actions
        } else {
            // validation is failed! User data is wrong!
        }
    }
}

В данном примере выполняется проверка, отправлены ли данные пользователем send() и соответствуют ли эти данные правилам, описанным в модели validate().

Правило представляет собой массив не менее чем из 2х элементов:

[1, 2, ..., n]
  • Первый элемент - название атрибута или массив из названия атрибутов, которые будут подвергнуты проверке
  • Второй элемент - название направления валидации или статический указатель метода валидации
  • Третий и последующие элементы - прочие параметры, необходимые для валидации

К примеру, выше обозначенное правило валидации:

[['login', 'password'], 'required'],

говорит о том, что атрибуты login и password НЕОБХОДИМЫ для заполнения и не могут быть пустыми полями.

Аналогично, правило:

['password', 'length_min', 4]

требует того, чтобы атрибут password был не короче, чем 4 символа.

В случае УСПЕШНОЙ ПРОВЕРКИ СОБЛЮДЕНИЯ ВСЕХ ПРАВИЛ, метод validate() вернет true, а в случае, если ХОТЯ БЫ ОДНО ПРАВИЛО НЕ ВЫПОЛНЕНО - false.

Список стандартных правил валидации

В FFCMS уже реализован небольшой набор стандартных правил валидации для того чтобы ускорить процесс описания моделей и валидации их данных.

Название фильтра Краткое описание Пример использования
required Значение атрибута должно быть обязательно передано от пользователя путем GET,POST,PUT или др. [['login', 'password'], 'required']
used Значение атрибута может быть передано из пользовательской среды, а может и нет ['nickname', 'used']
length_min Минимальная длина значения атрибута БОЛЬШЕ чем аргумент фильтра ['login', 'length_min', 2]
length_max Максимальная длина значения атрибута МЕНЬШЕ чем аргумент фильтра ['login', 'length_max', 32]
in Значение атрибута должно находится в переданном массиве в фильтр в виде аргумента ['age', 'in', [18, 19, 20, 21]
notin Значение атрибута не должно находится в переданном массиве в фильтр в виде аргумента ['login', 'notin', ['reserved', 'admin', 'moderator']
string Значение атрибута должно быть «строкой» [['login', 'password'], 'string']
arr Значение атрибута должно быть «массивом» ['params', 'arr']
int Значение атрибута должно быть «целочисленным» ['age', 'int']
float Значение атрибута должно быть «числом с плавающей точкой» ['balance', 'float']
boolean Значение атрибута должно быть «булевого-типа» или подобного ему(1/0, true/fase) ['checkbox', 'boolean']
email Значение атрибута должно соответствовать стандарту именования адресов почтовых ящиков ['userMail', 'email']
phone Значение атрибута должно соответствовать стандарту телефонных номеров ['userPhone', 'phone']
url Значение атрибута должно соответствовать стандарту обозначения гиперссылки ['userPage', 'url']
equal Значение атрибута должно в точности соответствовать значению аргумента фильтра ['repeatPassword', 'equal', $this→getInput('password')]
reg_match Значение атрибута должно соответсвовать регулярному выражению, заданному в атрибуте фильтра ['userSkype', 'reg_match', '[A-Za-z0-9]']
intList Значение атрибута должно соответствовать формату целочисленного перечисления (1, 2, 5, 10, 25 …, n) ['happyYears', 'intList']
isFile Значение атрибута должно содержать массив значений $_FILE ['avatar', 'isFile']
sizeFile Размер файла, переданного в виде массива в значение атрибута, должно быть больше 1 элемента массива значения фильтра аргумента и меньше 2го(в байтах) ['avatar', 'sizeFile', [1024, 1024 * 1000]

Динамический вызов методов валидации

Кроме стандартных правил валидации вы можете использовать динамический вызов своих правил валидации по средствам динамического представления названия фильтра. Пользовательский фильтр может находится где угодно в вашей реализации классовой модели, единственное требование - ваш класс должен быть подвержен автозагрузке PSR-0. Общая структура реализации:

public static function myFilter($object, $param)
{
    // some actions
    return true;
}

Метод должен быть статичным и обязательно возвращать значение булевого типа. В случае возврата true валидация по данному фильтру будет считаться пройденной, в противном случае - будет выведена ошибка. В качестве аргументов метода обязательно должен быть определен первый - первым аргументом передается значение проверяемого атрибута, вторым (если определено) - значение аргумента фильтра в правилах валидации.

Предположим, что мы хотим реализовать свое правило валидации в классе \Apps\Model\Front\MyModel\EntityTest которое будет проверять, имеется ли в пользовательской сессии специфичное значение magic равное значению пользовательского поля sessionMagic.

public static function checkMagic($object)
{
    return App::$Session->get('magic') === $object;
}

Позже, данный фильтр может быть вызван из метода rules() любой классической модели по средством динамического обращения:

class MyEtcModel extends Model
{
    public $sessionMagic;
 
    public function rules()
    {
        return [
            ['sessionMagic', 'required'],
            ['sessionMagic', '\Apps\Model\Front\MyModel\EntityTest::checkMagic'] 
        ];
    }
 
}

Кроме подобного «динамического» вызова вы можете использовать динамический вызов в «точку входа» приложения App::$Object. Пример подобного вызова:

public function rules()
{
    return [
        ['captcha', 'App::$Captcha::validate'];
    ];
}

Атрибут в виде массива

Бывают случаи, когда использовать одиночные атрибуты для работы с значениями модели не целесообразно и достаточно сложно или значения являются логически едиными по какому-либо признаку, либо значений слишком много. В таком случае вам поможет объявление и валидация атрибутов в виде массива. Работа с массивными атрибутами практически ничем не отличается от классического объявления. Сам массив, как значение атрибута, будет представлен в виде key ⇒ value, где key - название вложенного атрибута, а value его значение.

Приведем простой пример: модель бизнес логики, которая отвечает за ввод пользователем данных «имя», «пароль», «подключения к компьютеру по ip:port» (выражение абстрактное, не стоит обращать на него внимание).

class UserCon extends Model
{
    // classic attributes
    public $login;
    public $pass;
 
    // our "array" attribute
    public $con = [];
 
    public function rules()
    {
        return [
            [['login', 'pass'], 'required'],
            // ... and more, look below: its array attribute usage
            [['con.ip', 'con.port'], 'required']
            ['con.ip', 'length_min', 7],
            ['con.ip', 'length_max', 16],
            ['con.port', 'int']
        ];
    }
 
    public function labels()
    {
        return [
            'login' => __('User login'),
            // look below: usage array attibute labels
            'con.ip' => __('IP address'),
            'con.port' => __('TCP Port')
        ];
    }
 
}

Обратите внимание на методы rules и labels, а именно то, как выполняется работа с массивным атрибутом con. Все множественные ключи атрибута могут быть получены путем использования синтаксиса Dotted Array. В классическом представлении, атрибут 'con' будет иметь следующую структуру:

class UserCon extends Model {
    public $con = [
        'ip' => 'value#1',
        'port' => 'value#2'
    ];
}

И как вы уже догадались, получить доступ ко вложенным ip и port в случае инициированной модели:

$model = new UserCon();

вы сможете путем классического обращения к значению массива через его ключ:

echo $model->con['ip'];

однако в правилах валидации и задании значений меток необходимо использовать синтаксис Dotted array и указывать ключи через точку - . в формате строки.

Источники атрибутов

Кроме использования автоматической модели получения значения атрибутов при работе с входящими данными вам возможно понадобиться использовать различные источники входящих данных для атрибутов. Так, при использовании моделей вместе с формами, нередко понадобиться указать источник входящих данных для того или иного атрибута. Источники должны быть указаны в методе sources() вашей модели. Пример:

public function sources()
{
    return [
        'avatar' => 'file',
        'name' => 'post',
        'age' => 'get'
    ];
}

Таким образом, атрибуты могут иметь различные источники. В этом примере, атрибут $avatar получит значение бинарного входящего файла (объект \Symfony\Component\HttpFoundation\FileBag), атрибут name будет принят из значений POST данных ($_POST), а атрибут age будет взят из данных параметров заголовка запроса ($_GET).

Фильтрация атрибутов

Очень часто модели будут использоваться вами для отображения бизнес-данных вашего приложения. Однако, при отображении данных, в случае если они каким-либо образом могут быть изменены пользователем вы должны учитывать безопасность их отображения, чтобы не допустить атаки межсайтового скриптинга (XSS). Для этих целей существует метод types(), в котором могут быть указаны типы фильтрации при назначении атрибутов из входящих данных.

Рассмотрим пример:

file: /Apps/Model/Front/Login.php

namespace Apps\Model\Front;
 
use Ffcms\Core\Arch\Model;
 
class Login extends Model 
{
    public $login;
    public $password;
    public $secureHtmlArea;
    public $fullHtmlArea;
 
    public function types()
    {
        return [
            'login' => 'text',
            'secureHtmlArea' => 'html',
            'fullHtmlArea' => '!secure'
        ];
    }
}

Как видите, в методе types в виде массива ключ-значение указаны атрибуты↔тип_фильтрации. Всего, существует 3 типа фильтров:

  1. text - приведение входящих данных к стандарту plain-text. Полностью удаляются все html теги
  2. html - приведение входящих данных к безопасному html. Используется библиотека Html Purifier
  3. !secure - отсутствие обработки входящих данных

Таким образом, в примере выше атрибут login примет только очищенную от всех тегов строку, secureHtmlArea - очищенный html-формат, а fullHtmlArea не будет подвержен фильтрации вовсе. Стоит так же упомянуть поле password, фильтрация которого не описана в функции types(): для него(и любых других не описанных атрибутов) по умолчанию будет применен самый строгий фильтр - text.

Ярлыки(метки)

Стандартная реализация моделей в FFCMS поддерживает алгоритм именования «ярлыков» (или меток), которые в последствии могут быть использованы для отображения названия элемента, которому соответствует значение, хранимое в атрибуте модели.

Ярлыки должны быть выставлены в методе labels() вашей модели. Пример:

file: /Apps/Model/Front/Login.php

namespace Apps\Model\Front;
 
use Ffcms\Core\Arch\Model;
 
class Login extends Model 
{
    public $login;
    public $password;
 
    public function labels()
    {
        return [
            'login' => 'You\'r login is: ',
            'password' => 'You\'r password is: '
        ];
    }
}

И позже, данные ярлыки могут быть использованы как подсказка, к примеру в контроллере будет вызвана модель и присвоены некоторые данные:

public action actionHello() {
    $model = Apps\Model\Front\Login();
    $model->login = 'Mihail';
    $model->password = 'simplePWD123';
 
    $this->response = App::$View->render('user_data', [
        'model' => $model->filter()
    ]);
}

А далее, в представлении user_data.php она будет обработана:

<h1>Welcome, new user!</h1>
<hr />
<?= $model->getLabel('login') . ': ' . $model->login ?> <br />
<?= $model->getLabel('password') . ': ' . $model->password ?>

Что приведет в конечном итоге к отображению следующей информации:

<h1>Welcome, new user!</h1>
<hr />
You'r login is: Mihail <br />
You'r password is: simplePWD123

* Обратите внимание, это лишь демонстрационный пример и он не в коем случае не призывает передавать пароли или прочие данные только таким образом!

Взаимодействие с формами

Одной из основных задач моделей в FFCMS является быстрая реализация динамических форм передачи данных между клиентом и сервером. FIXME описать взаимодействие, ссылка на forms, рассказать о csrf защите

Лучшие концепции

Модели, в любых своих реализациях, могут:

  • Содержать, изменять и предоставлять атрибуты и их значения
  • Обозначать правила для валидации атрибутов
  • Содержать собственные методы для реализации каких-либо задач бизнес-логики
  • Выполнять взаимодействие с базой данных по средствам использования реализаций Active Records

Модели не должны:

  • Обращаться к данным запроса, сессии или cookie - это задача контроллеров
  • Содержать HTML(или иные структурированные форматы отображения кода) представления или их части
  • Содержать размытую логику обработки различных по существу сценариев, лучше разбить сложную логику на несколько репрезентативных моделей
core/mvc/model.txt · Последние изменения: 2016/09/11 16:45 (внешнее изменение)