Модели данных
- Теория моделей
- Нотация проектирования базы данных
- Менеджер моделей Model
- Общее описание
- Выборки (get methods)
- Добавление (add methods)
- Изменение (update methods)
- Удаление (delete methods)
- Импорт (delete methods)
- Связи (link methods)
- Теория и типы связей
- Генерация связей по базе данных
- Добавление своих связей
- Объекты сущности Entity
- Типы данных
- Getters
- Объект коллекции Collection
-
Фильтры
- Теория фильтрации
- Значения по-умолчанию
- Каскад данных при фильтрации
- Как добавить свои фильтры в модель
- Как отфильтровать входные данные
- Как отфильтровать отдельное поле (filterValue)
- Как запретить фильтрацию
- Как работает фильтрация (вид изнутри)
- Фильтры, которые прописываются автоматически
- Список фильтров
-
Валидаторы
- Теория валидации
- Как добавить свои валидаторы в модель?
- Как проверить отдельное поле (validateValue)
- Схема инициализации работы валидатора
- Объект результата выполнения операции
-
Объекты условий Cond
- Общее описание
Chapter 1. Теория моделей
Chapter 1.1. Что это такое?
- ЭТО НЕ ORM, как его понимают программисты PHP :)
- ЭТО ORM, так как иммет связи;
- Тут нет и не будет ActiveRecord;
- Основной принцип - договоренности и условности;
- Жестка формальность наименований и полей БД;
- Упрощенные объектные связи;
- Упрощенное управление данными в БД;
- Выделение сущностей и действий над ними.
Так что же это такое?! Модели - базируются на договоренностях наименования таблиц, полей и ключей в базе данных. В моделях мы выделяем сущности (Entity) и связи между сущьностями. Модель позволяет управлять данными в базе., (*1)
Chapter 1.2. Цели моделей?
При проектировании моделей преследовалась цель дать инструмент для работы с базой данных для пользователей разного уровня квалификации:, (*2)
-
Начинающий - знает основы, может пользоваться только интерфейсом (тем что уже есть), не может сильно накосячить;
-
Обычный уровень - понимает как работает, как работают связи и основные части системы, может строить сложные выборки и обработки, не может сильно накосячить;
-
Гуру - понимает архитектуру, может дописывать плагины, модифицировать сами модели, может очень сильно накосячить;
От этих трех целей строится вся идея моделей., (*3)
Пример
Есть комманда разработчиков разного уровня:, (*4)
- Женя - разработчик, только пришел в компанию;
- Петя - разработчик, работает в компании долго, примерно знает как все устроено;
- Вася - проектирует все новые разработки.
Жене дали задание сделать сложный раздел. Он накидывает базу данных по всем правилам.
Так же не забывает, прописать все фильтры и валидаторы. Система моделей автоматически генерит ему работающий CRUD-код.
Женя его использует и незадумывается над правильностью этого кода. Но, Жене нужно сделать сложную выборку или обработку в моделе,
по-правилам, ему запрещено это делать самостоятельно. Он обращается к более профессиональному коллеге., (*5)
Петя, знает все принципы работы моделей, он умеет делать сложные выборки, хорошо понимает связи сущностей. Он может помочь Жене
с его проблемой, совместно они строят сложную выборку, даже не сломав и не нагнув базу. Но в какой-то момент они понимают,
что стандартный функционал не может решить их проблему. Нужно модифицировать саму систему (да, такое бывает), (*6)
Вася написал уже кучку плагинов и пофиксил несколько багов. Женя и Петя приходят к нему. Вася анализирует ситуацию.
Если решение единоразовое, то он не будет его вносить в систему, он предложит обойти проблему. Если проблема
глобального масштаба, то нужно либо дописать плагин к генератору моделей, либо изменить модели. Он вносит правки., (*7)
Таким образом мы отгораживаем разработчиков от чистого SQL и контролируем связи и запросы к базе отвечающие за
выбор данных. Только гуру может внести в процесс изменение и изменить логику выборки данных из базы. При этом
непрофессиональные пользователи "бездумно" оперируют связями без боязни сильно "накосячить"., (*8)
Chapter 1.3. Как достигаются цели?
Небольшое количество атомарных элементов:, (*9)
- Объект (Entity)
- Набор объектов (Collection)
- Объект условий для действий (Cond)
- Менеджер объектов (Model)
Четко типизированная (константы) структура, не позволит сделать лишнего. В то же время, структура легко изменяется. Можно с легкостью модифицировать практически любые элементы, но это больше присуще архитекторам системы., (*10)
В основе лежат простые принципы проектирования базы данных. Нотация, по-которой создается база данных, достаточно хорошо выверена и логична. Многие методы имеют жестко-определенные названия, что позволяет писать функционал и не искать название метода., (*11)
Код моделей и методов работы с ней получается легко-читаемым и логичным. Можно посмотреть на переменную и четко сказать, что за модель ее сгенерила и какого типа переменная. Что бы это было, нужно всег-лишь соблюдать нотаци и следовать рекомендациям из этого описания., (*12)
Вся основная структура связей/отношений объектов строится автоматически, на основе базы данных. Главное в этой операции - это создать базу данных с правильными ключами и связями. Генератор моделей сделает всю основную работу., (*13)
Простота структуры и управления данными - основной принцип. Все операции с данными - очень просты и легки в понимани. Отношения объектов заложены в архитектуру и неподдаются изменению незатрагивая архитектуру приложения. Это накладывает определенные ограничения, но в то же время является преимуством - простота понимания., (*14)
Chapter 1.4. Основа моделей
В основе всего лежит сущность. Точнее ее содержимое. Любая сущность имеет свои свойства и связанные сущности. В моделях используется многомерный массив, связанные сущности которого помечаются нижним подчеркиванием в начале ключа (поэтому поля в базе с нижним подчеркиванием в начале имени колонки - запрещены)., (*15)
12,
'name' => 'Товар',
'price' => 12.98,
'brand_id' => 12,
'_brand' => array(
'id' => 12,
'name' => 'levis'
'_brand_alias_collection' => array(
array('id' => 1
'name' => 'levi\'s'),
array('id' => 2
'name' => 'levi\'strauses co'),
)
)
)
?>
Нужно запомнить три следующих утверждения:
1. Поля начинающиеся не с _ - это свойства сущности;
2. Поля, которые начинаются с _ - это связанная сущность или набор сущностей
3. Если поле начинается с _ и заканчивается на _collection - это набор сущностей, (*16)
Эти утверждения основа, но так же можно встретить связанные данные, которые не являются ни сущностью, ни набором. Это так называемые кешируемые данные или агрегатные., (*17)
12,
'name' => 'Товар',
'price' => 12.98,
'brand_id' => 12,
'_brand_name' => 'levis',
'_brand' => array(
'id' => 12,
'name' => 'levis'
'_brand_alias_collection' => array(
array('id' => 1
'name' => 'levi\'s'),
array('id' => 2
'name' => 'levi\'strauses co'),
)
)
)
?>
Обратите внимание на _brand_name - это либо агрегатные данные либо кешируемые, то есть этот параметр может появиться в выборке:, (*18)
getCond()
->join(ProductModel::JOINT_BRAND) // Присоединяем бренд
->column(array('*', '_brand_name' => 'brand.name')) // _brand_name - агрегатное свойство
$product = ProductModel::getIntance()->get($cond);
?>
Но так же это может появиться в Entity., (*19)
has('_brand_name')) {
return $this->get('_brand_name');
} elseif ($this->getBrand()) {
$this['_brand_name'] = $this->getBrand()->getName();
}
return $this->get('_brand_name');
}
}
?>
Такая схема достаточно проста, понятна и логична. Абсолютно, все методы опираются на такую структуру массива., (*20)
Chapter 2. Нотация проектирования базы данных
Введение
Система моделей строилась с учетом персональных предпочтений работы с базой данных. Достаточно большая стабильность проектировки и слаженность в работе комманды достигается за счет введения общих договоренностей. Приведенные ниже, договоренности позволяют недопускать простые ошибки при разработке и проектировании различных сервисов. Мы нарабатываем свои методики, которые используем в повседневной работе., (*21)
Правила наименования табилц и полей
При наименовании полей не используем различных префиксов базы данных и прочей ереси, которая повелась в времен, когда в одну базу данных впирали и форум и чатик и цмс с магазинчиком. Итак, одна полная модель базы - это все таблицы из этой базы., (*22)
Запрет на использование системных переменных в именах полей
Внимание: Наименование полей моделей берется из названия поле базы данных. Настроить псевдонимы - нельзя. Возможности настроить в дальнейших версиях не будет., (*23)
Так же крайне не рекомендуется использовать любые названия полей, которые необходимо экранировать: count, as, from и т.д., (*24)
Пропускаемые таблицы
Так уж повелось, но таблицы начинающиеся на "_" или заканчивающиеся на этот символ пропускаются и модели по ним не строятся. Так же не строится модель для таблиц с суффиксом "_index" в дальнейшем суффиксы и префиксы, которые не нужно индексировать можно будет настроить в конфигурационном файле., (*25)
Правила наименования таблиц
При проектировании мы всегда опираемся на базовые сущности. Эти сущности лежат в основе каждого проекта. Например, мы проектируем магазин и хотим сделать теги для магазинов, в этом случае таблицы желательно назвать так:
* product
* tag
* product_tag_link
* product_tag_info (информация по тегу связанному с продуктом), (*26)
Но, если вы строите социальную сеть, где теги основа системы, к которым вяжется куча всего, то все должно выглядеть наоборот:, (*27)
- product
- tag
- tag_product_link
- tag_product_info (информация по продуктом связанному с тегом)
то есть в начало мы ставим базовую сущность на второе место второстепенную, пример актуальной базы:
* product
* tag
* tag_alias
* tag_stat
* tag_info
* comment
* product_info
* product_rubric
* product_rubric_tag_info (таблица для описательных параметров тега для рубрики продукта)
* product_rubric_info
* product_rubric_stat
* product_rubric_index (неиндексируемая моделями таблица: суффикс _index)
* product_product_rubric_link
* product_image
* product_product_image_link
* product_comment_link
* product_tag_link
* и т.д., (*28)
Chapter 2. Менеджер моделей Model
Chapter 2.1. Добавление данных add
У каждой модели есть метод добавления. В процессе выполнения происходят следующие действия:, (*29)
Результат метода это объект \Model\Result\Result, (*30)
Chapter 5. Фильтры
Chapter 5.1. Теория фильтрации
!!!ВСЕ ДАННЫЕ ДОЛЖНЫ ФИЛЬТРОВАТЬСЯ!!!, (*31)
Данные которые мы получаем в моделе, по-умолчанию считаются грязными. Их нужно отмыть, накормить, проверить и поробовать запихать в базу. Дальше расскажу об этапах добавления данных и как можно настроить модель что бы все было по фен-шую., (*32)
Представьте, что на входе мы имеем ассоциативный массив с данными, где ключ - это значение поля, а значение собственно само значение этого поля. Прежде чем попасть в базу каждое поле должно быть обработано по следующей схеме:, (*33)
-
Значения по-умолчанию - наложение занчений по-умолчанию, кастомим через setupDefaultsRules
-
Каскад полей - если поле пустое, возможно значение можно взять с другого поля, кастомим через setupFilterCascadeRules
-
Фильтрация данных - на каждое поле натравливаются фильтры, которые можно кастомить через методы setupFilterRules
-
Проверка данных - теперь мы все это проверям, кастомить через метод setupValidatorRules
Как проектируем
При создании базы данных мы прописываем поля. При прописывании поле в базе данных, нужно думать, какие данные должны попасть в базу. Другими словами, мы должны отвечать на несколько важных вопросов для каждого поля:, (*34)
- Что должно быть в поле;
- Что может прийти (прийти может что угодно и еще чуть-чуть);
- Как я буду это фильтровать;
Пример
В базу данных нужно добавить поле price, помня ошибки прошлого мы делаем его decimal(9, 2), что бы не было проблем с
точностью вычислений., (*35)
- Что должно быть в поле:
- Что может прийти:
- 1 200 (пробел)
- 1,200 (число с разделителем)
- 1.200 (число с разделителем)
- 1,200.00 (число с разделителем и копейками)
- 1.200,00 (число с разделителем и копейками)
- 1,200.000.00 (неправильное число с разделителем и копейками)
- 1.200,00.00 (неправильное число с разделителем и копейками)
- $1.200,00.00 dollars (мусор)
- Как будем фильтровать:
- Подчистим от левых данных;
- Float наложить точно не получится;
- Нужно делать разбор числа
Вывод: нужно написать свой фильтр, например, \App\Filter\Price, (*36)
Chapter 5.2. Значения по-умолчанию
При добавлении мы учитываем данные по-умолчанию. Это значит что в модельках есть сгенеренный массив в котором мы прописываем, что при создании новой записи, если неопределено поле, то ему нужно присвоить определенное значени. Настраиваются свои значения так:, (*37)
<?php
public function setupDefaultsRules()
{
$this->defaultRules['test_field'] => 'Значение по-умолчанию';
}
?>
Chapter 5.3. Каскад данных при фильтрации
Представьте, что у вас есть сущность в которой есть поля:
* name - название чего-то
* h1 - заголовок
* title - название где-то еще
* meta_title - название для заголовка браузера
* slug - часть от URL, (*38)
но на входе есть только name, так почему же его нельзя использовать для формирования других полей?!, (*39)
addFilterCascadeRules['name'] => array('h1', 'title', 'meta_title');
$this->addFilterCascadeRules['h1'] => array('name', 'title', 'meta_title');
$this->addFilterCascadeRules['title'] => array('name', 'h1', 'title');
$this->addFilterCascadeRules['meta_title'] => array('name', 'h1', 'meta_title');
$this->addFilterCascadeRules['slug'] => array('name', 'h1', 'title', 'meta_title');
}
?>
Chapter 5.4. Как добавить свои фильтры в модель
В объекте модели нужно добавить метод:, (*40)
<?php
public function setupFilterRules()
{
$this->filterRules['field'][] = Filter::getFilterInstance('\App\Filter\MyFilter');
}
?>
Chapter 5.5. Как отфильтровать входные данные
filterOnAdd($inputData);
?>
Chapter 5.6. Как отфильтровать отдельное поле (filterValue)
filterValue($input, 'field_name');
?>
Chapter 5.7. Как запретить фильтрацию
Методы filterOnAdd и filterOnUpdate вторым параметром принимают объект условий, за флаг фильтрации отвечают следующие константы:, (*41)
Chapter 5.8. Как работает фильтрация (вид изнутри)
Каждая модель имеет список правил фильтрации для полей:, (*42)
<?php
$this->filterRules = array(
'id' => array(
Filter::getFilterInstance('\App\Filter\Id')
),
'slug' => array(
Filter::getFilterInstance('\App\Filter\Slug')
),
'create_date' => array(
Filter::getFilterInstance('\App\Filter\Date'),
Filter::getFilterInstance('\Zend\Filter\Null')
),
'modify_date' => array(
Filter::getFilterInstance('\App\Filter\Date')
),
'status' => array(
Filter::getFilterInstance('\App\Filter\EnumField')
)
);
?>
Эти правила инициализируются методом:, (*43)
<?php
$this->getFilterRules();
?>
После инициализации основных правил фильтрации запускается метод в котором разработчик может управлять текущими правилами фильтрациями:, (*44)
<?php
$this->setupFilterRules();
?>
На эти данные опираются все методы фильтрации. Дальше при добавлении/измении запускается механизм фильтрации:
* filterOnAdd
* filterOnUpdate, (*45)
Работают они по одному принципу, различие только в отношении к неопределенным полям (update их пропускает)., (*46)
Схема работы этих методов:, (*47)
- Применяем каскад данных;
- Получаем правила фильтрации;
- Фильтруем поля;
- Поля для которых нет фильтров, остаются неизменными;
Chapter 5.9. Фильтры, которые прописываются автоматически
Поле в базе |
Что проверяем |
Какие фильтры |
@type - tinyint
@type - smallint
@type - mediumint
@type - int
@type - bigint
|
- Число без плавающей точки
|
App\Filter\Int
App\Filter\Null (if nullable)
|
@type - enum
|
- Латинские буквы, цифры и символ подчеркивания
|
App\Filter\EnumField
App\Filter\Null (if nullable)
|
@type - char
@type - varchar
@type - enum
@type - tinyblob
@type - tinytext
@type - blob
@type - text
@type - mediumblob
@type - mediumtext
@type - longblob
@type - longtext
@type - timestamp
|
- Отсутствие пробельных символов по краям
|
App\Filter\StringTrim
App\Filter\Null (if nullable)
|
id
_id$
|
- Число без плавающей точки
- Положительное
- Не ноль
|
App\Filter\Id
|
name[_translate или _alias]$
title[_translate или _alias]$
h1[_translate или _alias]$
meta_title[_translate или _alias]$
|
- Строка без пробельных симоволов по краям
- Без тегов
- Без двойных пробельных символов
- Начинается с большой буквы
- Html Entity заменены на UTF-8 символы
|
App\Filter\Name
|
slug
|
- Только латиница и цифры
- Остутствуют пробельные символы
- Начинается и заканчивается либо на букву, либо на цифру
- Теги и недопустимые символы вырезаются
- Из русского текста делаем транслит
- Ограничен длинной в 255 символов
|
App\Filter\Slug
|
description
_description
text
_text
|
- Накладывается вырезатель опасных тего
- Разрешены все теги кроме опасных
- Накладывается типограф
|
App\Filter\Text
|
url
_url$
|
- Валидные данные url
- Запрещены теги
- Запрещены символы не подходящие для url
- Не хранится schema URL
|
App\Filter\Url
|
level
pos
|
- Только число
- Только целое
- Только положительное
|
App\Filter\Abs
|
email
_email$
|
- Символы разрешенные в E-Mail
|
App\Filter\Email
|
price
_price$
|
- На выходе float число
|
App\Filter\Price
|
^is_
|
- Может содержать только y или n
|
App\Filter\IsFlag
|
_hash$
|
- Может содержать только [A-H0-9] только в верхнем регистре
- Максимальная длинна 40 символов
|
App\Filter\Hash
|
_stem$
|
- Стем слов
|
App\Filter\Stem
|
_count$
|
- На выходе int число (положительное)
|
App\Filter\Abs
App\Filter\Abs
|
_date$
|
- На выходе дата в формате Y-m-d H:i:s
|
App\Filter\Date
|
Chapter 5.10. Список существующих фильтров
Класс фильтра |
Опции |
Что делает |
App\Filter\Abs |
нет |
Делает значение абсолютным |
App\Filter\Date |
нет |
Пытается создать дату в формате Y-m-d H:i:s из разных данных |
App\Filter\Email |
нет |
Удаляет символы запрещенные в E-Mail |
App\Filter\EntityDecode |
нет |
Заменяет HTML-entities на обычные символы |
App\Filter\EnunField |
нет |
Оставляет символы разрешенные для значений enum-полей |
App\Filter\Float |
нет |
Приводит значение к Float |
App\Filter\Hash |
нет |
Удаляет символы не относящиеся к hash, оставляет первые 40 символов |
App\Filter\Id |
нет |
Приводит к абсолютному числу |
App\Filter\IsFlag |
нет |
Очищает все что не Y и не N
|
App\Filter\Md5 |
нет |
Делает из строки md5 |
App\Filter\Name |
нет |
Фильтрует имя, вырезает теги, лишнее пробелы и тд |
App\Filter\PlainText |
нет |
Удаляет теги и немного подчищает. Декодит HTML-Entity |
App\Filter\Price |
нет |
Из мусора умеет делать цену товара, например |
App\Filter\Slug |
нет |
Все что не английский - в транслит, заменяет пробелы на -, оставляет символы разрещенные для использования в URL |
App\Filter\Stem |
нет |
Берет основания от всех слов |
App\Filter\StemHash |
нет |
Делает hash текста с использованием stem |
App\Filter\Stopword |
нет |
Фильтрует нежелательные слова |
App\Filter\StringTrim |
нет |
Тупо, trim |
App\Filter\Truncate |
length = 80 // длина строки
etc = '…' // что добавить в конце обрезанной строки
break_words = true | false; // разрешить "рвать" слова
middle = true | false; // сделать обрезку текста по-центру
|
Обрезка текста |
App\Filter\Truncate40 |
нет |
Взять первые 40 символов |
App\Filter\Truncate128 |
нет |
Взять первые 128 символов |
App\Filter\Truncate255 |
нет |
Взять первые 255 символов |
App\Filter\Ucfirst |
нет |
Делает первый символ в верхнем регистре, остальные в нижнем |
App\Filter\Url |
все сложно, смотри код |
Фильтрует URL |
App\Filter\Xml |
нет |
Чистит код от нечистой силы, что ломает XML |
Chapter 6. Валидаторы
Chapter 6.1. Теория валидации?
Все данные которые попадают в базу данных должны быть обязательно проверены. Если данные не проверять перед вставкой, можно попасть с XSS или просто упадет приложение, так как база не ожидает таких данных., (*48)
Chapter 6.2. Как добавить свои валидаторы в модель?
В объекте модели нужно добавить метод:, (*49)
validatorRules['required'];
// В эту переменную складываем валидаторы, которые будут работать при изменении
// $this->validatorRules['not_required'];;
$this->validatorRules['not_required']['field'][] = Validator::getValidatorInstance('\App\Validator\MyValidator');
}
?>
Chapter 6.3. Как проверить отдельное поле (validateValue)
validatorRules
/** @var $validator boolean|Validator **/
$validator = SomeModel::getInstance()->validateValue($input, 'field_name');
// Работать так
if ($validator !== true) {
// Ошибка
$messages = $validtor->getMessages();
}
?>
Chapter 6.4. Схема инициализации работы валидатора
initValidatorRules();
}
}
/**
* Самый-самый родитель
*/
class AbstractGeneratedModel
{
public function initValidatorRules($reqiured)
{
/** тут правила сгенеренные генератором **/
$this->setupValidatorRules();
}
}
/**
* Модель куда разработчик вносит изменения
*/
class Model
{
public function setupValidatorRules($reqiured)
{
/** тут правила прописанные пользователем **/
}
}
?>
Chapter 7. Объект результата выполнения операции
Некоторые операции, требуют в ответе вернуть не только результат, но и валидатор и другие параметры. Специально для этого придуман объект \Model\Result\Result
Схема работы очень проста, он фиксирует текущий результат, а так же вложенные (например, при импорте данных) Объект имеет вложенную структуру. То есть внутри одного Result может содержать несколько других Result, (*50)
Ниже перечень основных методов по работе с Result, (*51)
add(/** data **/);
// При вставке не было ошибок
if ($result->isValid()) {
// do something
}
// Идентификатор добавленной записи/записей (может быть массивом)
$insertId = $result->getResult();
// Объект валидатора (ошибки брать тут)
$validator = $result->getValidator();
// Получить перечень ошибок с учетом вложенных результатов
$error = $result->getErrors();
?>
Chapter 8. Объект условий Cond
Chapter 8.1. Общее описание
Объект условий - это объект простого типа, который хранит в себе какие связи нужно подключить к выборке и как отсортировать и отфильтровать данные., (*52)
Так же объект условий содержит внутри себя следующие важные данные:, (*53)
- Имя сущности для которой создан
- Имя Entity - нужна что бы сообщить модели, какого типа данные мы ожидаем
- Имя Collection - нужна что бы сообщить модели, какого типа данные мы ожидаем
Chapter 8.2. Method from
Говорит из какой таблицы будет производиться выборка., (*54)
Синтаксис:, (*55)
Cond::init()->from('table_name'); // SELECT FROM table_name
Cond::init()->from(array('table_alias' => 'table_name')); // SELECT FROM table_name as table_alias