AJAX в CakePHP



Подготовка

Установка и настройка фреймворка CakePHP

В первую очередь выполним установку и настройку фреймворка CakePHP. Как установить фреймворк на локальный сервер вы можете посмотреть тут (классический способ установки) и тут (альтернативный способ установки). В примере будет использована папка локального сервера copy.loc к которой мы будем обращаться из адресной строки браузера посредством протокола http (http://copy.loc).

Я воспользовался альтернативным способом (через файл oven.php) и установил вместе с фреймворком CakePHP – менеджер плагинов «Mixer». Так же, параллельно с этим, создал базу данных под названием mydatabase, подключение к которой за меня выполнил «oven.php» (если устанавливаете фреймворк классическим способом, то вам будет необходимо внести параметры своей базы данных в массив Datasources файла \config\app.php).

Генерация кода MVC и наполнение базы данных через SQL-запрос

Для быстрого создания всех необходимых таблиц в нашей базе данных воспользуемся веб-интерфейсом phpMyAdmin и выполним соответствующий sql-запрос (приведен ниже). Чтобы кейк нам всячески помогал и делал за нас всю «грязную работу» не забываем про принятые в нем соглашения для названий таблиц:

CREATE TABLE articles(
   id INT AUTO_INCREMENT PRIMARY KEY,
   title VARCHAR(255) NOT NULL,
   body TEXT,
   created DATETIME,
   modified DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 

CREATE TABLE comments(
   id INT AUTO_INCREMENT PRIMARY KEY,
   article_id INT NOT NULL,
   body text COLLATE utf8_unicode_ci,
   created DATETIME,
   modified DATETIME,
  FOREIGN KEY article_key (article_id) REFERENCES articles(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

INSERT INTO articles (title, body, created, modified)
VALUES
('First Post', 'This is the first post.', now(), now());

INSERT INTO articles (title, body, created, modified)
VALUES
('Second Post', 'This is the second post.', now(), now());

Далее, при помощи плагина Mixer (по адресу http://copy.loc/mixer) выполним генерацию всех необходимых моделей, контроллеров, и видов нашего приложения. Если вы устанавливали CakePHP классическим способом, то генерацию кода можно выполнить через консоль, при помощи команды bin\cake bake all.

Ну и наконец, в файле маршрутизаторе \config\routes.php пропишем соответствующий маршрут, для того, чтобы при обращении к главной страницы сайта http://copy.loc, мы попадали на страницу списка статей:

use Cake\Core\Plugin;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\Routing\Route\DashedRoute;

Router::defaultRouteClass(DashedRoute::class);

Router::scope('/', function (RouteBuilder $routes) {
    
    $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
    $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
    
    $routes->fallbacks(DashedRoute::class);
});

Plugin::routes();

Ниже на изображении приведено то, что вы должны увидеть, перейдя по адресу вашего приложения. В моем случае это http://copy.loc

AJAX-в-CakePHP

Подключаем jQuery и Bootstrap

Скачиваем поочередно дистрибутивы Bootstrap и jQuery, после чего размещаем их в соответствующих папках нашего приложения: файлы javascript в папке \webroot\js\, файлы css в папке \webroot\css\, а шрифты в папке \webroot\font\. Дополнительно, в папке приложения \webroot\js\, создаем файл scripts.js (\webroot\js\scripts.js), где позже будем писать наш javascript код.

Далее, в основном файле-шаблоне default.ctp (\src\Template\Layout\default.ctp) выполняем подключения вышеуказанных библиотек и файла scripts.js:

<!-- В тэге head приложения, следующие строки:-->
<?= $this->Html->css('base.css') ?>
<?= $this->Html->css('cake.css') ?>
<!-- Меняем на: -->
<?= $this->Html->css(['bootstrap.css', 'base.css', 'cake.css']) ?>

<!-- Так же, в тэге head добавляем:-->    
<?= $this->Html->script(['jquery-3.2.1', 'bootstrap', 'scripts'], ['block' => 'scriptBottom']); ?>

<!--Перед закрытием тэга body подключаем вышеуказанный блок скриптов:-->
<?= $this->fetch('scriptBottom') ?>

Конечно, после подключения bootstrap.css дизайн нашего сайта немного поехал, но это не главное (для того чтобы изменения были минимальными bootstrap.css в массиве следует подключить первым, как показано выше), так как его использование в примере обусловлено возможностью простого и быстрого запуска модального окна. Так же, обратите внимание, что сначала мы подключаем библиотеку jQuery ('jquery-3.2.1' в массиве стоит первым), а уж потом все остальное.

Создание нового комментария к статье

Перейдем к просмотру, например, первой статьи (http://copy.loc/articles/view/1). На данный момент комментарии к ней отсутствуют. Слева в сайт-баре имеется ссылка на создание нового комментария, давайте добавим к ней обработчик события onclick, который заблокирует переход по самой ссылке и будет открывать модальное окно с формой добавления комментария. Для этого скорректируем код соответствующего шаблона (\src\Template\Articles\edit.ctp) следующим образом:

<!--Код ссылки в сайт-баре:-->
<li><?= $this->Html->link(__('New Comment'), ['controller' => 'Comments', 'action' => 'add'], ['onclick' => "return viewModalComment()"]) ?> </li>
...
<!-- В самом низу файла добавляем код модального окна:-->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <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">Комментарий к статье: <?= h($article->title) ?></h4>
            </div>
            <div class="modal-body">
                <?php echo $this->Form->control('body', [
                    'type' => 'text',
                    'label' => 'Текст комментария',
                    'id' => 'commentBody'
                ]);?>
            </div>
            <div class="modal-footer">
                <button type="button" id="addComment" onclick="return addComment(<?=$article->id?>)" data-loading-text="Идет сохранение..." class="btn btn-primary">Сохранить комментарий</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
            </div>
        </div>
    </div>
</div>

В контроллере CommentsController (\src\Controller\CommentsController.php) создадим экшен addajax() на который мы будем посылать json запрос с данными из браузера:

public function addajax()
    {
        if ($this->request->is('json')) {
            $rezult = false;
            $comments = [];
            $comment = $this->Comments->newEntity();
            $comment = $this->Comments->patchEntity($comment, $this->request->getData());
            if ($this->Comments->save($comment)) {
                $rezult = 'Комментарий успешно сохранен';
                $comments = $this->Comments->find()->where(['article_id' => $this->request->getData(['article_id'])]); //если комментарий сохранен успешно, получаем все комментарии для нашей статьи
            }
            $this->set(compact('comments', 'rezult'));
        }
        else throw new NotFoundException('Страницы не существует, либо у Вас отсутствуют права доступа к данному разделу!');
    }

Для того, чтобы сервер, в лице нашего фреймворка, мог принимать наши json запросы, а так же формировать и отсылать обратно в браузер ответы, необходимо в файле-маршрутизаторе \config\routes.php прописать возможность использования расширения .json. Давайте изменим область маршрутизации '/' следующим образом:

Router::scope('/', function (RouteBuilder $routes) {
    
    $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
    $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
    $routes->extensions(['json']); //добавленная строка
    
    $routes->fallbacks(DashedRoute::class);
});

Формирование ответа сервера после сохранения комментария

Так же, важным моментом является то, что для формирования ответа мы будем использовать шаблон вида, который назовём addajax.ctp - так же, как и экшен контроллера CommentsController. Чтобы использование шаблона стало доступным, необходимо в AppController (\src\Controller\AppController.php) у функции beforeRender(Event $event) удалить возможность автоматической сериализации переменных видов или присвоить ключу '_serialize' значение false:

public function beforeRender(Event $event)
    {
        if (!array_key_exists('_serialize', $this->viewVars) &&
            in_array($this->response->type(), ['application/json', 'application/xml'])
        ) {
            $this->set('_serialize', false); //значение true заменено на false, либо можно просто закомментировать эту строку
        }
    }

Далее, давайте создадим вышеуказанный шаблон вида (\src\Template\Comments\json\addajax.ctp), в нем мы сформируем ответ, в виде двух переменных, которые сервер отправит в браузер. Одна из переменных будет содержать строку html кода (все комментарии для текущей статьи), используя который мы будем обновлять комментарии на странице при помощи jQuery. Вторая переменная вспомогательная - необходима для понимания, что сохранение комментария прошло успешно:

//\src\Template\Comments\json\addajax.ctp
<?php
    $comments_string = '';
    $comments_string .= '<h4>'. __('Related Comments') . '</h4>';
    if ($comments->count()){ //переменные $comments и $rezult переданы в шаблон из экшена addajax()
        $comments_string .= '<table cellpadding="0" cellspacing="0">';
        $comments_string .= '<tr>';
        $comments_string .= '<th scope="col">'. __('Id').'</th>';
        $comments_string .= '<th scope="col">'. __('Article Id').'</th>';
        $comments_string .= '<th scope="col">'. __('Body').'</th>';
        $comments_string .= '<th scope="col">'. __('Created').'</th>';
        $comments_string .= '<th scope="col">'. __('Modified').'</th>';
        $comments_string .= '<th scope="col" class="actions">'.__('Actions').'</th>';       
        $comments_string .= '</tr>';
            foreach ($comments as $comment){
                $comments_string .= '<tr>';
                $comments_string .= '<td>'.h($comment->id).'</td>';
                $comments_string .= '<td>'.h($comment->article_id).'</td>';
                $comments_string .= '<td>'.h($comment->body).'</td>';
                $comments_string .= '<td>'.h($comment->created).'</td>';
                $comments_string .= '<td>'.h($comment->modified).'</td>';
                    $comments_string .= '<td class="actions">';
                        $comments_string .= $this->Html->link(__('View'), ['controller' => 'Comments', 'action' => 'view', $comment->id]);
                        $comments_string .= $this->Html->link(__(' Edit'), ['controller' => 'Comments', 'action' => 'edit', $comment->id]);
                        $comments_string .= $this->Form->postLink(__(' Delete'), ['controller' => 'Comments', 'action' => 'delete', $comment->id], ['confirm' => __('Are you sure you want to delete # {0}?', $comment->id)]);
                    $comments_string .= '</td>';
                $comments_string .= '</tr>';
            }
        $comments_string .= '</table>';
    }
    
    $comments = $comments_string;
    
    echo json_encode(compact('comments', 'rezult'));  
?>

Отправка json запроса, получение и обработка ответа

Ну и в завершение всего в файле scripts.js создаем две функции. Одна - вызывает нам модальное окно с формой ввода комментария, а другая срабатывает при нажатии кнопки «Сохранить комментарий» и отправляет на сервер запрос, ждет ответ? который после получения обрабатывает:

function viewModalComment(){
    $('#myModal').modal('show');
    return false;
}

function addComment(article_id){
    var commentBody = $('#commentBody').val(); //Получаем текст комментария
    var btn = $('#addComment');
    btn.button('loading') //Запускаем анимацию кнопки: "Идет сохранение..." 
            $.ajax({
                type: 'post',   
                url: "/comments/addajax.json",
                data: {body: commentBody, article_id: article_id},
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                },
                success: function(data){
                    if(data.rezult) { //Проверяем вспомогательную переменную
                        alert(data.rezult); //Выводим сообщение об успешном сохранении комментария
                        $('.related').html(data.comments); //обновляем комментарии в блоке div с классом related
                        $('#myModal').modal('hide'); //Закрываем модальное окно
                        $('#commentBody').val(''); //Очищаем поле ввода в модальном окне
                    }
                    else alert('Не удалось сохранить комментарий, попробуйте выполнить операцию позже');
                },
                error: function(e) {
                    alert("Произошла ошибка запроса к серверу, попробуйте выполнить операцию позже или обновить страницу");
                    console.log(e); //Выводим ошибки, если есть, в консоль
                } 
            }).always(function () {
              btn.button('reset') //Останавливаем анимацию кнопки
            });
    return false;
}

Заключение

Если вы все сделали правильно, то при нажатии на ссылку «New Comment» на странице просмотра статьи, будет открываться модальное окно с предложением создать новый комментарий. После ввода в поле текста комментария и нажатия на кнопку «Сохранить комментарий» на сервер отправляется запрос (файл javascript.js) с данными (id статьи и текст комментария). Сервер, приняв наш запрос (экшен addajax()), проводит проверку данных (валидацию в соответствии с правилами, прописанными в файле модели \src\Model\Table\CommentsTable.php) и если данные валидны, сохраняет новый комментарий в базе данных. Далее, в этом же экшене, получаются все комментарии, относящиеся к текущей статье (включая новый комментарий), которые заносятся в переменную $comments и, вместе с переменной $rezult, передаются в шаблон вида addajax.ctp. В шаблоне происходит формирование ответа который, при помощи функции json_encode, выдается в виде json-представления.

Далее, ответ с сервера принимается браузером и обрабатывается функцией addComment файла scripts.js, а именно: обновляется код блока div с классом related (

Теги: AJAX, json

Поделиться: