Версия №2
Контроллер
Код: Выделить всё
/**
* Создать
* @return string
*/
public function actionCreate()
{
$model = new ArticleForm(['scenario' => 'user-create']);
if (Yii::$app->request->isPost) {
$model->load(Yii::$app->request->post());
if ($model->validate()) {
if ($model->createArticle()) {
Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
} else {
Yii::$app->session->setFlash('danger', 'Возникла ошибка');
}
return $this->refresh();
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Обновить
* @param $id
* @return string
* @throws NotFoundHttpException
*/
public function actionUpdate($id)
{
/** @var ArticleForm $model */
$model = new ArticleForm();
$model = $model->repository->findById($id);
if (!$model) {
throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
}
$model->setScenario('user-update');
if (Yii::$app->request->isPost) {
$model->load(Yii::$app->request->post());
if ($model->validate()) {
if ($model->updateArticle()) {
Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
} else {
Yii::$app->session->setFlash('danger', 'Возникла ошибка');
}
return $this->refresh();
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Удалить
* @param $id
* @return string
* @throws NotFoundHttpException
*/
public function actionDelete($id)
{
/** @var ArticleForm $model */
$model = new ArticleForm();
$model = $model->repository->findById($id);
if (!$model) {
throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
}
if ($model->deleteArticle()) {
Yii::$app->session->setFlash('success', 'Данные успешно удалены');
} else {
Yii::$app->session->setFlash('danger', 'Возникла ошибка');
}
return $this->redirect('index');
}
Основная модель
Код: Выделить всё
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' => 'Дата редактирования',
];
}
/**
* @return ArticleRepository
*/
public function getRepository()
{
$model = new ArticleRepository($this);
return $model;
}
}
Репозиторий
Код: Выделить всё
class ArticleRepository
{
/**
* @var Article
*/
protected $model;
/**
* @param Article $model
*/
public function __construct(Article $model)
{
$this->model = $model;
}
/**
* Получить запись по идентификатору
* @param $id
* @param int $visible
* @return array|null|\yii\db\ActiveRecord
*/
public function findById($id, $visible = 1)
{
$model = $this->model;
return $model::find()
->where(['id' => $id])
->visible($visible)
->one();
}
}
Кастомные квери
Код: Выделить всё
class ArticleQuery extends ActiveQuery
{
/**
* @param int $state
* @return $this
*/
public function visible($state = 1)
{
$this->andWhere([Article::tableName() . '.visible' => $state]);
return $this;
}
}
Форма
Код: Выделить всё
<?php
namespace app\modules\articles\models\frontend;
use app\modules\articles\models\Article;
use Yii;
/**
* Форма сущности "Article"
* @package app\modules\articles\models\frontend
*/
class ArticleForm extends \app\modules\articles\models\Article
{
/**
* @inheritdoc
*/
public function behaviors()
{
return [
'TimestampBehavior' => [
'class' => \yii\behaviors\TimestampBehavior::className(),
'attributes' => [
\yii\db\ActiveRecord::EVENT_BEFORE_INSERT => 'time_create',
\yii\db\ActiveRecord::EVENT_BEFORE_UPDATE => 'time_update',
],
]
];
}
/**
* @inheritdoc
*/
public function transactions()
{
return [
'user-create' => self::OP_ALL,
'user-update' => self::OP_ALL,
];
}
/**
* @inheritdoc
*/
public function scenarios()
{
return [
'user-create' => ['title', 'short_text', 'full_text'],
'user-update' => ['title', 'short_text', 'full_text'],
];
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['title', 'short_text', 'full_text'], 'required'],
[['title', 'short_text', 'full_text'], 'filter', 'filter' => 'trim'],
[['title', 'short_text', 'full_text'], 'string'],
[['title'], 'string', 'max' => 100],
];
}
/**
* @inheritdoc
*/
public function beforeValidate()
{
if (parent::beforeValidate()) {
// Логика
return true;
}
return false;
}
/**
* @inheritdoc
*/
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
$this->meta_title = $this->title;
$this->meta_description = $this->title;
$this->meta_keywords = $this->title;
return true;
}
return false;
}
/**
* Создать
* @inheritdoc
*/
public function createArticle()
{
$this->slug = md5(uniqid(true));
$this->visible = 1;
return $this->save(false);
}
/**
* Обновить
* @return bool
*/
public function updateArticle()
{
return $this->save(false);
}
/**
* Удалить
* @return bool
*/
public function deleteArticle()
{
return $this->delete();
}
}
Теперь попробую расписать логику
Контроллер
DI - почему я не хочу использовать DI и передавать классы в конструкторе, все потому, что их может быть несколько, поэтому нужно создавать дополителньые переменные и мусорить в конструкторе, куда удобнее просто импортировать нэймспейс.
+ класс будет отрабатывать только тогда, когда действительно это нужно.
Основная модель
Содержит только методы AR и геттер на репозиторий
Репозиторий
Это не совсем паттерн репозиторий (его не обязательно наследовать от интерфейса), он нужен скорее для того, чтобы вынести все выборки в отдельный класс и не мешать их с прочим делом.
Кастомные квери
Ну тут все понятно, не какой магии.
Форма
Для каждой формы предлагаю создавать класс (например ArticleForm), который будет содержать сю логику работы с формой.
Он должен наследоваться от основной модели.
Аргументы:
- transactions()
- свойства
- избавляемся от лишней сущности (сервисы) и делаем удобное сохранение через $this. Я разбирал эти сервисы и понял, что в простых и средних вариантах, а именно: получить данные - обработать - записать в базу, нет никакого смысла создавать еще 1 слой. Оставим сервис для более сложных задач.
Результат:
Все более менее структурировано, на 1 сущность можент быть около 2 форм (frontend и backend).
Вся логика (получить данные - обработать - сохранить) реализована в файле формы.
Модель не содержит 100500 строк кода, а работат только с дефолтными данными AR
В репозиторий инкапсулированы все выборки, котоыре нам нужны.
Кастомные квери помогают избавится от дублей в запросах.
В общем правила игры yii2 приняты и получаем удобную архитектуру.
Вопросы:
Вопрос пока только 1. Что делать с геттреами реляций ?
Есть задачи, когда например какой-то метод для фронтенда не такой как для бэкенда, поэтому реляции переопределены, а выборка идет через static (позднее статическое связывание), например метод выборки лежит в common, а получаем модель из frontend. Если это инкапсулировать в репозиторий, будет вопрос как это будет работать.
Собственно вопрос актуален: куда девать геттры на реляции, оставлять в основной моделе ?