Собственно попробовал я повторить все вышеупомянутое на примере и возникло еще больше вопросов, чем было.
Попробую все подробно расписать. Пример начал делать работая над блогом и рассматриваем сущность Article.
Архитектура следующая:
Код: Выделить всё
- controllers
- backend
- frontend
- interfaces
- migrations
- models
- backend
- ArticleSearch.php
- ArticleForm.php
- frontend
- ArticleForm.php
- ArticleSearch.php
Article.php
ArticleQuery.php
ArticleRepository.php
ArticleService.php
- views
- backend
- frontend
- widgets
Bootstrap.php
Module.php
В любом случае архитектуру лучше всего всегда делать модульной и сразу предполагаем, что модуль будет отвечать как за frontend, так и за backend
Идем дальше, контроллер выходит примерно вот такого содержимого:
Код: Выделить всё
/**
* Создать
* @return string
*/
public function actionCreate()
{
$model = new ArticleForm();
if (Yii::$app->request->isPost) {
$service = new ArticleService();
$model->load(Yii::$app->request->post());
if ($model->validate()) {
$service->create($model);
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Обновить
* @param $id
* @return string
* @throws NotFoundHttpException
*/
public function actionUpdate($id)
{
$model = new ArticleForm();
$modelRepository = ArticleRepository::findById($id);
if (!$modelRepository) {
throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
}
$model->setAttributes($modelRepository->getAttributes());
if (Yii::$app->request->isPost) {
$service = new ArticleService();
$model->load(Yii::$app->request->post());
if ($model->validate()) {
$service->update($model, $modelRepository);
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Удалить
* @param $id
* @return mixed
* @throws NotFoundHttpException
*/
public function actionDelete($id)
{
$modelRepository = ArticleRepository::findById($id);
if (!$modelRepository) {
throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
}
$service = new ArticleService();
$service->delete($modelRepository);
// Redirect
}
Как мы договорились, основная модель ничего не содержит кроме стандартных методов (Article.php):
Код: Выделить всё
class Article extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return '{{%articles}}';
}
/**
* @return ArticleQuery
*/
public static function find()
{
return new ArticleQuery(get_called_class());
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => '№',
'slug' => 'Слаг',
'title' => 'Заголовок',
'short_text' => 'Краткое описание',
'full_text' => 'Полное описание',
'meta_title' => 'Заголовок (Title)',
'meta_description' => 'Описание (Description)',
'meta_keywords' => 'Ключевые слова (Keywords)',
'visible' => 'Отображение',
'time_create' => 'Дата создания',
'time_update' => 'Дата редактирования',
];
}
}
Кастомные квери (ArticleQuery.php):
Код: Выделить всё
class ArticleQuery extends ActiveQuery
{
/**
* @param int $state
* @return $this
*/
public function visible($state = 1)
{
$this->andWhere([Article::tableName() . '.visible' => $state]);
return $this;
}
}
Репозиторий, который отвечает за выборку (ArticleRepository.php)
Код: Выделить всё
class ArticleRepository extends Article
{
/**
* Получить запись по идентификатору
* @param $id
* @param int $visible
* @return array|null|\yii\db\ActiveRecord
*/
public static function findById($id, $visible = 1)
{
return self::find()
->where(['id' => $id])
->visible($visible)
->one();
}
}
Сервис, который отвечает за сохранение/редактирование и удаление данных (ArticleService.php)
Код: Выделить всё
class ArticleService
{
/**
* Сохранить
* @param Model $article
* @return bool
*/
public function create(Model $article)
{
$model = new Article();
$model->setAttributes($article->getAttributes());
return $model->save(false);
}
/**
* Обновить
* @param Model $article
* @param Article $repository
* @return bool
*/
public function update(Model $article, Article $repository)
{
$repository->setAttributes($article->getAttributes());
return $repository->save(false);
}
/**
* Удалить
* @param Article $repository
* @return false|int
* @throws \Exception
*/
public function delete(Article $repository)
{
return $repository->delete();
}
}
Форма, которая получает и обрабатывает данные (ArticleForm.php)
Код: Выделить всё
class ArticleForm extends \app\modules\articles\models\Article
{
// Наследуемся от Article (ActiveRecord), чтобы избежать перечисления всех нужных свойств
/**
* @inheritdoc
*/
public function transactions()
{
return [
'user-create' => self::OP_ALL,
'user-update' => self::OP_ALL,
];
}
/**
* @inheritdoc
*/
public function scenarios()
{
return [
'user-create' => [],
'user-update' => [],
];
}
/**
* @inheritdoc
*/
public function beforeValidate()
{
if (parent::beforeValidate()) {
// Логика
return true;
}
return false;
}
/**
* @inheritdoc
*/
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
// Логика
return true;
}
return false;
}
}
Теперь еще раз описание моделей:
1)
Article.php - Основная Модель содержит только стандартные атрибуты ActiveRecord.
2)
ArticleQuery.php - Кастомные квери (Скопы). Для удобства.
3)
ArticleRepository.php - Наследуется от основной модели и содержит все необходимые методы для выборки.
4)
ArticleService.php - Сохраняет (редактирует и удаляет) данные в хранилище (например в базу данных).
5)
ArticleForm.php - Наследуется от основной модели, так как нам лень переопределять все нужные нам свойства для формы.
Обратите внимание: для админки и для сайта форма добавления статьи может быть разной, поэтому ArticleForm у нас несколько.
Теперь появляется список вопросов:
1) Походу я не много запутался с репозиторием и сервисом, возможно где-то ошибка ?
2) Теперь у нас у одной сущности появилось куча классов, это нормально ?
3) Что если появляется задача организовать комментарии и теги, поэтому есть необходимость в связанности, например в реляциях:
getUser(), getTags() и getComments(), хорошо было бы закатать это под интерфейсы, а вот где это рассположить ?
4) Если есть жирный модуль где одной сущностью не обойтись, например модуль объявлений (на доске объявлений).
Есть следующие сущности:
- Апартамент
- Объявление (к примеру квартира может сдаваться: на час, на день и на ночь)
- Изображения (у каждого апартамента есть изображения)
- Резерваци (есть возможность зарезервировать апартамент)
- Календарь (ведет статистику какой апартамент когда занят, а когда свободен)
Из выше представленного примера выходит, то что мы расплодим более 20 моделей на эти 5 сущностей. Так и нужно ?