Правильно ли я делаю?

Обсуждаем, как правильно строить приложения
Ответить
mashinis
Сообщения: 24
Зарегистрирован: 2016.03.19, 15:17

Правильно ли я делаю?

Сообщение mashinis »

Доброго времени суток, уважаемые форумчане!
В Yii делаю робкие шаги и нужна конструктивная критика.
Будьте добры, объясните мне, правильно ли я делаю модели, контроллеры и вид? Если не правильно, то как бы вы сделали.
Код 100% рабочий, но хотелось бы его оптимизировать.

Контроллер

Код: Выделить всё

<?php
     
namespace app\controllers;

use yii\web\Controller;        
use app\models\Posts;
     
class PostsController extends Controller
{
    public function actionIndex()
    {

        $query = Posts::find()->select('post_id, post_title, post_short, post_img_prev, post_img')->orderBy('post_id DESC');
        $pages = new \yii\data\Pagination(['totalCount' => $query->count(), 'pageSize' => 2, 'pageSizeParam' => false, 'forcePageParam'=> false]);
        $posts = $query->offset($pages->offset)->limit($pages->limit)->all();
        return $this->render('index', compact('posts', 'pages'));
    }
    
    public function actionView()
    {
        $id = \Yii::$app->request->get('id');

        $post = Posts::findOne($id);

        $images = $post->images;
        if(empty($post)) throw new \yii\web\HttpException(404, 'Такой страницы нет...');
        return $this->render('view', compact('post', 'images'));
    }
} 
Модель Posts

Код: Выделить всё

<?php

namespace app\models;

use Yii;
use \yii\db\ActiveRecord;

/**
 * This is the model class for table "posts".
 *
 * @property integer $post_id
 * @property string $post_title
 * @property string $post_short
 * @property string $post_text
 * @property integer $post_category_id
 * @property string $post_img_prev
 * @property string $post_img
 * @property string $post_data
 * @property string $post_url
 */
class Posts extends ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'posts';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['post_title', 'post_short', 'post_text', 'post_category_id', 'post_img_prev', 'post_img', 'post_data', 'post_url'], 'required'],
            [['post_text'], 'string'],
            [['post_category_id'], 'integer'],
            [['post_data'], 'safe'],
            [['post_title', 'post_img_prev', 'post_img', 'post_url'], 'string', 'max' => 100],
            [['post_short'], 'string', 'max' => 500],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'post_id' => 'Post ID',
            'post_title' => 'Post Title',
            'post_short' => 'Post Short',
            'post_text' => 'Post Text',
            'post_category_id' => 'Post Category ID',
            'post_img_prev' => 'Post Img Prev',
            'post_img' => 'Post Img',
            'post_data' => 'Post Data',
            'post_url' => 'Post Url',
        ];
    }
    
    public function getImages()
    {
        return $this->hasMany(Images::className(), ['image_post_id' => 'post_id']);
    }
} 
Модель Images

Код: Выделить всё

<?php

namespace app\models;

use Yii;

/**
 * This is the model class for table "images".
 *
 * @property integer $image_id
 * @property integer $image_post_id
 * @property string $image_url
 * @property string $image_preview_url
 * @property string $image_alt_post_title
 */
class Images extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'images';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['image_post_id', 'image_url', 'image_preview_url', 'image_alt_post_title'], 'required'],
            [['image_post_id'], 'integer'],
            [['image_url', 'image_preview_url', 'image_alt_post_title'], 'string', 'max' => 100],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'image_id' => 'Image ID',
            'image_post_id' => 'Image Post ID',
            'image_url' => 'Image Url',
            'image_preview_url' => 'Image Preview Url',
            'image_alt_post_title' => 'Image Alt Post Title',
        ];
    }
    
    public function getPosts()
    {
        return $this->hasOne(Posts::className(), ['post_id' => 'image_post_id']);
    }    
} 
actionIndex

Код: Выделить всё

<?php if (!empty($posts)): ?>
    <?php foreach ($posts as $post): ?>
        <div class="panel panel-default">
          <div class="panel-heading">
            <h3 class="panel-title">
                <a href="<?= yii\helpers\Url::to(['posts/view', 'id' => $post->post_id])?>"><?= $post->post_title ?></a>
            </h3>
          </div>
          <div class="panel-body">
            <a href="<?= yii\helpers\Url::to(['posts/view', 'id' => $post->post_id])?>">
                <img src="<?= $post->post_img_prev ?>" alt="<?= $post->post_title ?>" class="img-responsive img-thumbnail"> 
            </a>   
                </br>
            <?= $post->post_short ?>     
          </div>
        </div>
    <?php endforeach; ?>
    <?= \yii\widgets\LinkPager::widget(['pagination' => $pages]) ?>
<?php endif; ?>
actionView

Код: Выделить всё

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">
            <?=$post->post_title?></a>
        </h3>
    </div>
    <div class="panel-body">
    <div class="row">
    
    <?php foreach ($images as $image): ?>
    <!--  -->
    <div class="col-xs-6 col-sm-4 col-md-3 col-lg-2">
        <a href="#" data-toggle="modal" data-target="#myModal" data-text="<?= $image->image_url ?>" >
            <img src="<?= $image->image_preview_url ?>" alt="<?= $post->post_title ?>" class="img-responsive img-thumbnail thumbnail"> 
        </a>
    </div>
  
    <?php endforeach; ?>  
     
    </div>   
        <p>
            <?= $post->post_text ?> 
        </p>
<!-- HTML-код модального окна -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true" id="myModal">

  <div class="modal-dialog">
    <div class="modal-content">
              <!-- Заголовок модального окна -->
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
        <h4 class="modal-title"><?= $post->post_title ?></h4>
      </div>
      <div class="modal-body">     
        <div id="result">Загрузка...</div>
      </div>
    </div>
  </div>
</div>
    </div>
</div>
bootstrap.js

Код: Выделить всё

$('#myModal').on('shown.bs.modal', function (event) {
  data = $( event.relatedTarget ).data( "text" );
  data2 = '<img src="'+data+'" '+'class="img-responsive img-thumbnail"'+'>';
  $( "#result" ).html( data2 );
  
});

$('#myModal').on('hidden.bs.modal', function (event) {
  data = $( event.relatedTarget ).data( "text" );
  $( "#result" ).html( '' );
});
kwasti
Сообщения: 262
Зарегистрирован: 2016.01.28, 16:14

Re: Правильно ли я делаю?

Сообщение kwasti »

В идеале из контроллера убрать работу с полями
query = Posts::find()->select('post_id, post_title, post_short, post_img_prev, post_img')->orderBy('post_id DESC');
не дай бог вы поменяете в будущем имя поля, удалите его или еще что сделаете со структурой, вы замучаетесь искать и вносить изменения по всем контроллерам.

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

уже не раз на форуме затрагивалась идея MVC
Модель должна полностью работать с запросами/данными и в приложение передавать уже конечный запрос/данные.
в контроллере допускается дополнительная рабюота с готовыми данными типа пагинации, лимита данных и т.д. без привлечения специфичных элементов типа имена полей, допускается обращение к полю через метод модели. Такой способ обращения к полю не потребует изменения во всем коде приложения, а только в модели.

во View рекомендую заставить себя использовать хелперы типа ArrayHelper, Html
значительно облегчат вам работу и исключат человеческий фактор по закрытию html тега.
mashinis
Сообщения: 24
Зарегистрирован: 2016.03.19, 15:17

Re: Правильно ли я делаю?

Сообщение mashinis »

kwasti писал(а):Во View рекомендую заставить себя использовать хелперы типа ArrayHelper, Html
значительно облегчат вам работу и исключат человеческий фактор по закрытию html тега.
Спасибо большое за ответ! Хелперы только учу. Понимаю, что с ними проще. Но с настоящим богажом знаний, пока нет возможности их использовать. Спасибо и за остальные советы.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Правильно ли я делаю?

Сообщение slavcodev »

А я советую для начала познать что такое PSR-1,2. Это прежде чем начать изучать любой фреймворк и писать вопросы на форуме.
Это поможет комьюнити, от которого ты ждешь помощи, легче/быстрее прочитать и понять твой код.

> Код 100% рабочий

Это ты так думаешь :) В `actionView` у тебя баг (оставлю нахождение его тебе практическим заданием)
Жду Yii 3!
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Правильно ли я делаю?

Сообщение slavcodev »

Как уже было сказано, лучше убрать все чт связано с БД из контролера и шаблона (даже название полей, хотя это сложнее, и не сразу)

Код: Выделить всё

    public function actionIndex()
    {
        $paginationOptions = [
          'totalCount' => 2,
          'pageSizeParam' => false,
          'forcePageParam' => false,
        ];

        $posts = Posts::getAllDataProvider($paginationOptions);

        return $this->render('index', compact('posts'));
    }
   
    public function actionView($id)
    {
        $post = Posts::getById($id);

        if (!$post) {
           throw new \yii\web\HttpException(404, 'Такой страницы нет...');
        }

        return $this->render('view', compact('post'));
    }
 
В примере выше:

- getAllDataProvider - возвращает провайдер данных (поищи в документации, это сразу микс пагинация, сортировка, фильтрация и т.д)
- actionView($id) - используется именные параметры, Yii сам вытащит его из запроса
- Posts::getById - используются собственные методы, логические по смыслу, по поставленной задаче, а не стандартные вроде `findOne`, т.к. эти тоже относятся к БД и по хорошему не должны использоваться в контролерах. Плюс "getById" может содержать собственную логику.

Вместо `Posts::className()` переходи на `Posts::class` если версия PHP позволяет.

PS: Кажется раздел форума выбран не подходящим, все это вопросы по Yii, а не по архитектуре и дизайну. Думаете нужно перенести?
Жду Yii 3!
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Правильно ли я делаю?

Сообщение ElisDN »

slavcodev писал(а):- getAllDataProvider - возвращает провайдер данных (поищи в документации, это сразу микс пагинация, сортировка, фильтрация и т.д)
Так всю модель провайдерами и статическими методами завалит.

Для CRUD провайдер формируется в Search-классе. Логично остальные провайдеры вынести в свои аналогичные классы для выборок с методами getAllDataProvider и подобными.

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

Код: Выделить всё

public function actionIndex()
{
    $dataProvider = new ActiveDataProvider([
        'query' => Posts::find()->preview()->latest(),
    ]);
    return $this->render('index', compact('dataProvider'));
}
slavcodev писал(а):- Posts::getById - используются собственные методы, логические по смыслу, по поставленной задаче, а не стандартные вроде `findOne`, т.к. эти тоже относятся к БД и по хорошему не должны использоваться в контролерах. Плюс "getById" может содержать собственную логику.
Критерии выборок лучше помещать в ActiveQuery-классы в виде:

Код: Выделить всё

$post = Posts::find()->published()->byId($id)->one();
Последний раз редактировалось ElisDN 2016.07.13, 14:44, всего редактировалось 2 раза.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Правильно ли я делаю?

Сообщение zelenin »

slavcodev писал(а):PS: Кажется раздел форума выбран не подходящим, все это вопросы по Yii, а не по архитектуре и дизайну. Думаете нужно перенести?
я уже тянулся к кнопке переноса) вопрос косвенно об архитектуре, но слишком о "низкой".
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Правильно ли я делаю?

Сообщение slavcodev »

ElisDN писал(а):Так всю модель провайдерами завалит.
И что? Тем более что по задаче не видно сколько там провайдеров нужно.
ElisDN писал(а):Для CRUD провайдер формируется в Search-классе. Логично остальные провайдеры вынести в свои аналогичные классы для выборок с методами getAllDataProvider и подобными.
Мой ответ был, что в контролере держать логику построения запроса - не лучшее решение. Можно и в Search-классе если он есть.
ElisDN писал(а):Или можно оставить в контроллере, инкапсулировав только выборки в PostsQuery
Тут не соглашусь. Т.к. построение провайдера, это еще и пагинации и сортировка, что заставит писать названия столбцов в контролере.
Так же ка ки использование query builder'а (включая scopes) в контролере.

Если в приложении относительно постов две задачи: посмотреть список (сортировнаый, с пагинаций) и посмотреть отдельно один пост,
я бы сделал два метода, это может быть в моделе, или вынести в Search-класс (хотя в этом решении Yii2 я сомневаюсь и не использую).

Добавится появится новая задача по постам, будут новые решения.
Жду Yii 3!
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Правильно ли я делаю?

Сообщение ElisDN »

slavcodev писал(а):И что? Тем более что по задаче не видно сколько там провайдеров нужно.
То, что завалит слабо относящейся к модели функциональностью.
slavcodev писал(а):Мой ответ был, что в контролере держать логику построения запроса - не лучшее решение. Можно и в Search-классе если он есть.
В модели - такое же не лучшее, как и в контроллере.
slavcodev писал(а):Тут не соглашусь. Т.к. построение провайдера, это еще и пагинации и сортировка, что заставит писать названия столбцов в контролере. Так же как и использование query builder'а (включая scopes) в контролере.
Да, для провайдеров к ActiveRecord из коробки удобен Search или подобный компонент.

А scopes в контроллере имена столбцов писать не заставляет, так что запросы более-менее инкапсулирует, если нет необходимости вводить подобные сервисы для выборок.
slavcodev писал(а):Если в приложении относительно постов две задачи: посмотреть список (сортировнаый, с пагинаций) и посмотреть отдельно один пост, я бы сделал два метода, это может быть в моделе, или вынести в Search-класс (хотя в этом решении Yii2 я сомневаюсь и не использую).
Ну я, как сказал в начале, статикой модель не заваливаю. Для большей гибкости и меньших проблем с чтением кода.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Правильно ли я делаю?

Сообщение slavcodev »

ElisDN писал(а):То, что завалит слабо относящейся к модели функциональностью.
В модели - такое же не лучшее, как и в контроллере.
Мы же ActiveRecord обсуждаем? Можно ли считать что работа с коллекцией записей это часть модели?
Повторюсь, можно это вынести и в отдельный класс (Search-класс), что является чем-то похожим на репозиторий.
Но если таких методов для работы с коллекцией постов, не много, то статические метода вполне подойдут.
Именно статическими, т.к. статический метод это действие над всеми сущностями данного класса.
ElisDN писал(а):А scopes в контроллере имена столбцов писать не заставляет, так что запросы более-менее инкапсулирует, если нет необходимости вводить подобные сервисы для выборок.
scopes - не заставит, а вот настройка сортировки провайдера вроде да (хотя провайдеры Yii2 не использовал, а Yii1 уже подзабыл).
scopes - инкапсулирует SQL, а вот цепочка таких scopes, что тоже является логикой построение запроса,
я считаю тоже логика лишняя для контролера. Как минимум по одной причине:
бизнес логика выбора коллекции постов показаной на странице может меняться.
ElisDN писал(а):Ну я, как сказал в начале, статикой модель не заваливаю. Для большей гибкости и меньших проблем с чтением кода.
Я тоже не заваливаю, так же как не использую ActiveRecord.
Но два статических метода против нового класса с двумя не статическими методами, не уступают по гибкости и количеству проблем.
Жду Yii 3!
Ответить