Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
vp-webdev
7 лет назад

Создание сайта на фреймворке Yii2, часть 2 - Модели

Прежде чем что-либо программировать - нужно чётко понимать какие данные вы собираетесь хранить, как их показывать пользователю, по каким критериям извлекать из БД и т.д. От этого будет зависеть структура таблиц в базе данных, а также модели, которые будут описывать эти данные.





Я буду работать над клиентом https://www.vp-golos.ml , задуманным для помощи тематическим и локальным сообществам Vox-Populi.
Подробнее о vp-golos.ml

Создание модели

Одна из основных задач проекта - показывать ленту материалов не просто по тэгу или автору, а в виде более сложной выборки. Так в одной выборке могут участвовать сразу несколько авторов и тэгов. По одним контент будет показываться, а по другим исключаться.
Например: показывать материалы трёх авторов по тэгу "мысли", ещё у одного автора показывать "человек" и "любовь", ещё у двоих авторов показывать посты с тэгом "путешествия" и у всех шести авторов исключить посты с тэгом "эстафетацвета".
Суть задачи, думаю, ясна.
Теперь нужно решить как хранить посты и тэги. В таких ситуациях руки сами тянутся использовать третью нормальную форму. Такой подход очень удобен, когда нужно извлечь материалы по тэг(тэгам). Для этого выборка идёт по таблице-связке постов с тэгами и к ней джоиним таблицу постов.
Сейчас я представлю это схематично и напишу запрос.

select p.post_id, p.title, pt.tag_id
 from post_tags pt
 left join posts p on p.post_id=pt.post_id
 where pt.tag_id in (12,15,16,9)
 group by p.post_id


Тут всё легко, но как быть, если нужно исключить из выборки посты по тэгу?
Если пост прикреплён к пяти тэгам, и нужно исключить пост пост по наличию одного из них - то в выборке будет этот же пост с остальными тэгами. Сейчас я покажу что имею ввиду.
Посты по тэгам номер 12,15,16,9 (без группировки по post_id)

select p.post_id, p.title, pt.tag_id
 from post_tags pt
 left join posts p on p.post_id=pt.post_id
 where pt.tag_id in (12,15,16,9)


Обращаем внимание на Post 6 - он рубрицирован тэгами 15 и 16.
Теперь попробуем достать посты по тэгам номер 12,15,16,9 за исключением тэга 15

select p.post_id, p.title, pt.tag_id
 from post_tags pt
 left join posts p on p.post_id=pt.post_id
 where pt.tag_id in (12,15,16,9) and pt.tag_id!=15


Как видите - пост "Post 6" остался. Так как его связка с тэгом 16 удовлетворяет условию tag_id!=15
В этом случае нужно дописывать условие по id тэга в join части запроса. Если тэгов будет много - джоин очень раздуется. Если честно - я незнаю насколько этот запрос будет тяжелым. Я его потом проверю, а пока я немного изменю структуру таблицы и запроса. Я просто скопирую все id тэгов в таблицу постов. Это не есть гуд, но если в плане производительности будет выйгрыш - значит так всё и останется.

Одно из решений(без JOIN`ов)

Можно в таблице постов сделать 5 колонок - для каждого тэга по колонке. Но в этом случае в запросах будет куча OR-ов, поэтому не будем рассматривать этот вариант.

Предлагаю остановиться на следующем: сложить все 1-5 тэгов в одну строку и поместить это в одну колонку в таблице постов.
Выглядеть это будет так.

post_idtitlecreatedtags
1Мой пост1502888759*tag1*tag2*tag22*

Между каждым тэгом и с краёв строки я поместил знак звёздочки(можно любой символ, не входящий в список допустимых символов в именах тэгов - это всё кроме букв, цифр, тире и знака подчёркивания)
Сделал я это для того, чтобы фильтр по тэгу tag2 не зацепил тэг tag22
Теперь в запросе я буду писать:

where tags like '%*tag2*%' and tags not like '%*tag22*%'

Имейте ввиду, что такой подход допустим только из-за того, что пост имеет максимум 5 тэгов. Если у поста будет 20 тэгов - так делать конечно же не стоит.

Пишем код

Теперь, когда мы чётко понимаем что нужно делать - можно начинать кодить.
Все таблицы моделей должны быть описаны в миграции.
Если вкратце - то миграция - это описание таблиц в файлах и создание их в БД в соответствии с ними. Работать с миграцией надо в консоли.
В корне сайта есть файл yii, просто, без расширения.
Попробуйте в консоли перейти в папку проекта и запустить его:

cd /var/www/site-path
./yii


Если вы видите ошибку Permission denied - сделайте файл yii исполнительным.

Создадим файл миграции для таблицы постов, назовём её posts.
Поля таблицы:

  • post_id - первичное поле
  • title - заголовок
  • created - дата создания
  • image - главное изображение поста
  • author - автор
  • tags - строка тэгов
  • parent_permlink - раздел
  • permlink - ссылка
  • votes_count - количество голосов
  • comments_count - количество комментариев
  • reblogs_count - количество репостов

Выполняем команду:

./yii migrate/create posts

На вопрос о создании файла отвечаем yes
Готово.
У меня в корне сайта появилась папка migrations и в ней файл для таблицы posts

Создание таблицы описывается в методе up. Я написал следующий код:

    public function up()
    {
      $this->createTable('posts', [
        'post_id' => $this->primaryKey(),
        'title' => $this->string(255),
        'created' => $this->integer(),
        'image' => $this->string(255),
        'author' => $this->string(255),
        'tags' => $this->string(255),
        'parent_permlink' => $this->string(255),
        'permlink' => $this->string(255),
        'votes_count' => $this->integer()->defaultValue(0),
        'comments_count' => $this->integer()->defaultValue(0),
        'reblogs_count' => $this->integer()->defaultValue(0),
      ]);
    }

Теперь нужно запустить миграцию. Для этого сперва зададим хост, логин, пароль и имя БД в конфиге. Хранится он в файле config/db.php. Затем выполняем команду:

./yii migrate/up

У меня таблица успешно создалась.

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

./yii migrate/create tags
./yii migrate/create post_tags

Описание таблицы tags

      $this->createTable('tags', [
        'tag_id' => $this->primaryKey(),
        'name' => $this->string(255),
      ]);

Описание таблицы post_tags

public function up()
    {
      $this->createTable('post_tags', [
        'id' => $this->primaryKey(),
        'post_id' => $this->integer(),
        'tag_id' => $this->integer(),
        'delta' => $this->integer(),
      ]);
    }
    $this->createIndex('post_tag', 'post_tags', ['post_id', 'tag_id'], true);

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

Повторно запускаем ./yii migrate/up - обе новые таблицы создадутся.

Заполнение таблиц данными

Данные для этих таблиц, будем брать с SQL сервера от @arcange.

Я наверное не буду сейчас показывать как я их стягиваю, там ничего сложного - один select из SQL-сервера и один insert в свою базу, образно говоря. Да и сильно раздувать пост не хочется. Если кому не до конца ясен этот момент - пожалуйста дайте знать.

Извлечение данных

Так как в этом уроке сделан упор на модели - пока не будем разбираться с возможностями контроллеров, а просто создадим action в SiteController.

    public function actionTest() {
      echo 'Test page';
    }

После этого у нас появится страница с адресом /index.php?r=site/test

Выведем на ней последние 10 постов

Для того, чтобы удобно работать с моделями - нужно описать их в классах.
Давайте создадим класс для модели post. Создать его можно через консоль, а можно и при помощи утилиты Gii. Она доступна в том числе и через браузер. Чтобы её запустить - откройте файл config/web.php и найдите там следующий код:

$config['modules']['gii'] = [
        'class' => 'yii\gii\Module',
        // uncomment the following to add your IP if you are not connecting from localhost.
        'allowedIPs' => ['127.0.0.1', '195.100.101.102', '::1'],
    ];

В переменную allowedIPs я уже вписал свой IP адрес 195.100.101.102. Если вы работаете локально - просто расскоментите эту строчку.
Теперь открывайте страницу /index.php?r=gii, на ней первым пунктом как раз будет Model Generator. Запускайте его.

В поле Table Name пишите posts.
В поле Model Class пишите Post.
Namespace и Base Class оставляем как есть.
Model Generator.png

Жмите Preview, а затем Generate.

Если у вас ничего не работает - проверьте права на запись в папку runtime, они должны быть у пользователя www-data. Тоже относится и к папке models.

Если у вас всё получилось - то в папке models появится файл Post.php с начальным описанием модели.

Если у вас не работает gii - то вы можете создать ровно такой же файл вручную, просто это займёт больше времени.

Точно так же создайте модель Tag.

Теперь извлечём последние 5 постов одного автора:

//в шапке файла
use app\models\Post;

//метод actionTest в теле класса SiteController
    public function actionTest() {
      echo 'Test page<br />';
      $q = Post::find()
        ->where(['author' => 'tristamoff'])
        ->orderBy(['created' => SORT_DESC])
        ->limit(5);
      $posts = $q->all();
      foreach ($posts as $post) {
        echo $post->title . '<br>';
        echo date('Y.m.d H:i', $post->created) . '<br>';
        echo '<hr />';
      }
    }

Код достаточно простой. Класс Post - это обращение к нашей модели. Метод find - сделает выборку из таблицы модели. Методы where, orderBy и limit интуитивно понятны.
Запускаем.

У меня всё получилось. В дальнейшем поговорим о взаимосвязях между моделями(пост - тэг) и конечно о темизации

Заглавное фото с сайта kwork.ru

Первая часть - вступление

30
1057.100 GOLOS
На Golos с August 2017
Комментарии (11)
Сортировать по:
Сначала старые