СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H Ну очень простой чат на Yii2 + pjax в черновиках Из песочницы

Здравствуйте, уважаемые Хабровчане! Решил поделиться с вами довольно простым криворуким приёмом, как за 10 минут "поднять" чатик на "голом" yii2 с помощью pjax. Кому интересно про что речь, добро пожаловать под кат.


Думаю что большинство читателей, знакомых с yii2, также знакомы и с технологией pjax.


БД




От БД нам нужна всего 1 таблица с сообщениями, создадим её в свойственной yii манере, с помощью миграции:


$ ./yii migrate/create init

app/migrations/m160923_115323_init.php
use yii\db\Migration;

class m160923_115323_init extends Migration
{
    public function up()
    {
        $this->createTable('message', [
            'id' => $this->primaryKey(),
            'from' => $this->integer()->notNull(),
            'to' => $this->integer()->notNull(),
            'text' => $this->text()->notNull()
        ]);
    }

    public function down()
    {
        $this->dropTable('message');
    }
}

На данном этапе можно сразу описать модель сообщения с довольно важным методом, с помощью которого мы в дальнейшем будет получать экземпляр ActiveQuery для формирования самого чата:


app/models/Message.php
namespace app\models;

use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
/**
 * @property int $id
 * @property int $from
 * @property int $to
 * @property string $text
*/
class Message extends ActiveRecord
{
    public function rules()
    {
        return [
            [['from', 'to', 'text'], 'required'],
            [['from', 'to'], 'integer'],
            ['text', 'string']
        ];
    }

    /**
     * @param int $from
     * @param int $to
     * @return ActiveQuery
     */
    public static function findMessages($from, $to)
    {
        return self::find()
            ->where(['from' => $from])
            ->orWhere(['from' => $to, 'to' => $from]);
    }
}

вот собственно и всё, что нам потребуется от БД.


Контроллер




Контроллер у нас будет состоять всего из одного "экшна":


app/controllers/ChatController.php
namespace app\controllers;

use app\models\Message;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\VarDumper;
use yii\web\Controller;

class ChatController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => ['@'],
                    ]
                ],
            ]
        ];
    }

    public function actionIndex($id)
    {
        $currentUserId = Yii::$app->user->identity->getId();
        $messagesQuery = Message::findMessages($currentUserId, $id);
        $message = new Message([
            'from' => $currentUserId,
            'to' => $id
        ]);
        if ($message->load(Yii::$app->request->post()) && $message->validate()) {
            $message->save();
            $message = new Message([
                'from' => $currentUserId
            ]);
            if (Yii::$app->request->isPjax) {
                return $this->renderAjax('_chat', compact('messagesQuery', 'message'));
            }
        }
        if (Yii::$app->request->isPjax) {
            return $this->renderAjax('_list', compact('messagesQuery', 'message'));
        }

        return $this->render('chat', compact('messagesQuery', 'message'));
    }
}

Постараюсь пояснить, что происходит в "экшне":


  1. При переходе на страницу загружается полностью контент вместе с layout
  2. После загрузки страницы запускается setInterval, который обновляет список сообщений
  3. При отправке сообщения в чат обновляется список совместно с формой

Вью




А вот тут, по моему мнению, самое интересное. Для начала постараюсь пояснить логику обновления "чатика" при помощи pjax:


image

В нашем случае нам нужно:


  1. Обернуть список с формой в pjax-контейнер для обновления всего списка сообщений и формы в случае отправки сообщения собеседнику;
  2. Обернуть только список в pjax-контейнер для периодического обновления списка сообщений.

app/views/chat/chat.php
<?php
/**
 * @var \yii\web\View $this
 * @var \app\models\Message $message
 * @var \yii\db\ActiveQuery $messagesQuery
 */
?>
<?php \yii\widgets\Pjax::begin([
    'timeout' => 3000,
    'enablePushState' => false,
    'linkSelector' => false,
    'formSelector' => '.pjax-form'
]) ?>
<?= $this->render('_chat', compact('messagesQuery', 'message')) ?>
<?php \yii\widgets\Pjax::end() ?>
<?php $this->registerJs(<<<JS
function updateList() {
  $.pjax.reload({container: '#list-messages'});
}
setInterval(updateList, 1000);
JS
) ?>

app/views/chat/_chat.php
<?php
/**
 * @var \yii\web\View $this
 * @var \app\models\Message $message
 * @var \yii\db\ActiveQuery $messagesQuery
 */
?>
<?php \yii\widgets\Pjax::begin([
    'id' => 'list-messages',
    'enablePushState' => false,
    'formSelector' => false,
    'linkSelector' => false
]) ?>
<?= $this->render('_list', compact('messagesQuery')) ?>
<?php \yii\widgets\Pjax::end() ?>
<?php \yii\widgets\ActiveForm::begin(['options' => ['class' => 'pjax-form']]) ?>
<?= \yii\bootstrap\Html::activeTextarea($message, 'text') ?>
<?= \yii\helpers\Html::submitButton('Отправить') ?>
<?php \yii\widgets\ActiveForm::end() ?>

app/views/chat/_list.php
<?php
/**
 * @var \yii\web\View $this
 * @var \yii\db\ActiveQuery $messagesQuery
 */
?>
<?= \yii\widgets\ListView::widget([
    'itemView' => '_row',
    'layout' => '{items}',
    'dataProvider' => new \yii\data\ActiveDataProvider([
        'query' => $messagesQuery,
        'pagination' => false
    ])
]) ?>

app/views/chat/_row.php
<?php
/**
 * @var \yii\web\View $this
 * @var \app\models\Message $model
 */
?>
<div class="row">
    <div class="col-md-3"><?= $model->from ?></div>
    <div class="col-md-9"><?= $model->text ?></div>
</div>

Результат


комментарии (7)

+4
limonte ,   * (был изменён)
Чат, основанный на setInterval() в 2016м году, серьезно? Вы отстали на пару лет от сегодняшнего уровня, постарайтесь наверстать.
0
+1 –1
Alexufo ,   * (был изменён)

есть одно весомое преимущество старого как мир setInterval() — не требует демона. Во всем остальном… чем больше народу — тем меньше кислороду :-)

+1
shelestov ,  
Зачем делиться криворуким (как вы сказали) приемом? Какая от этого практическая польза? В остальном согласен с предыдущим комментатором.
+4
lizarge ,  
хабрахабр 2016©
+3
kalobyte ,  
ну вот, очередной быдлокодинг

Юрий sluchainiyznak
PHP Backend — разработчик

то же что ли себя как-то называть?
ладно, с этого дня буду reuckende entwickler aller sprachen
— а я еще подумал: причем тут фреймворк для сайтов и где автор будет брать сервер вебсокетов? или может быть статья как раз про то, как на этом фреймворке сделать демон пхп для вебсокетов
0
kroshanin ,  
В защиту автора: за последние полгода передо мной уже дважды ставили задачу «сделать простенький чат» на их сайте, причем сайт на обычном хостинге, где нет возможности использовать websocket. Поэтому использование подобного подхода в некоторых случаях вполне оправдано.
Про «быдлокод» тоже весьма странное утверждение: в приведенных примерах кода ничего критично-плохого я не увидел.
Другой вопрос: для хабра статья, действительно, слабовата. По моему мнению, начинающих не следует вводить в заблуждение использованием «setInterval», а опытные разработчики решат подобную задачу и так без особого труда.
+1
kalobyte ,  
быдлокод тут в том, что используется фреймворк с кучей файлов для решения довольно простой задачи, да еще и база данных
16 лет назад такие чаты делались на файлах без всяких фреймворков