Здравствуйте, дорогие читатели! Судя по вашим комментариям к предыдущим статьям, многим из вас очень интересно, как именно мы будем использовать AngularJS в связке с нашим приложение на Yii2 фреймворке. В этой статье я подниму занавес и опишу процесс подключения фреймворка AngularJS и способы его применения.
Я планирую использовать Yii2 для основной массы приложения, но Angular я буду использовать для таких частей приложения, как модальное окно добавления объявления, поиск по сайту, карбилдер (часть функциональности сайта, позволяющая собрать машину своей мечты и отправить запрос на нее администратору сайта и дилерам партнерам) и т.д. Ничего такого, что бы изменило текущий роут, но подобный подход придаст нашему приложению отзывчивости и простоты взаимодействия с посетителем.
В перспективе мы будем переводить проект в одностраничное приложение на AngularJS, которое будет взаимодействовать с бэкендом Yii2 REST. Но на данный момент, я не хочу это делать, потому что тогда нам нужно будет обрабатывать маршрутизацию, валидацию, авторизацию, права доступов и тому подобное как в Angular.js, так и на бэкенде.
Структура RESTful API Yii предоставляет нам чистый API, который может обмениваться данными со встроенным AngularJS приложением, или же, с нашим мобильным приложением в будущем.
Так как мы заботимся о производительности, мы будем обеими ногами идти в сторону REST. Хорошо структурированное RESTful приложение является большим плюсом, особенно если там проработана хорошая система кэширования с гибкой стратегией. Также, мы планируем разместить бэкенд и базу данных на сервере Amazon EC2 и предоставлять только JSON данные для минимального использования полосы пропускания. Тем более, что Amazon EC2 обладает такой особенностью, как автоматическое масштабирование, которое позволяет ему автоматически адаптировать вычислительные мощности в зависимости от трафика сайта. А что касается нашего статического контента, мы будем хранить его на оптимизированной CDN Amazon S3, которая имеет самую низкую стоимость и более быструю скорость ответа. Об этом я буду писать более подробно в своих следующих статьях.
Yii2 фреймворк предоставляет целый набор инструментов для упрощения реализации и внедрения RESTful API. Сейчас я продемонстрирую пример, как можно построить набор RESTful
API, с минимальными затратами усилий на написание кода.
Для начала, создадим контроллер для API в модуле
Lease
, он будет находиться по пути modules/lease/controllers/frontend
файл ApiController.php
и в нем пропишем следующее:<?php
namespace modules\lease\controllers\frontend;
use yii\rest\ActiveController;
class ApiController extends ActiveController
{
public $modelClass = 'modules\lease\models\frontend\Lease';
}
Класс контроллера наследуется от yii\rest\ActiveController, который реализует общий набор RESTful действий. Указав
modelClass
, как modules\lease\models\frontend\Lease
, контроллер знает, какая модель может быть использована для извлечения и манипулирования данными.Для того, чтобы предоставить возможность пользоваться нашим API посредством ajax-запросов с других доменов, можно добавить поведение
cors
в контроллер, если это необходимо:...
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['corsFilter' ] = [
'class' => \yii\filters\Cors::className(),
];
// В это место мы будем добавлять поведения (читай ниже)
return $behaviors;
}
...
Обратите внимание, что мы хотим сохранить конфигурацию поведения из yii\rest\Controller, вот почему мы сохраняем его в первой строке
$behaviors = parent::behaviors();
и продолжаем добавлять к нему поведения.Yii2 REST API-интерфейс способен отдавать ответы в XML и JSON форматах. Мы будем использовать только формат JSON, — сообщим об этом фреймворку:
...
$behaviors['contentNegotiator'] = [
'class' => \yii\filters\ContentNegotiator::className(),
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
];
...
Мы также легко можем конфигурировать различные права доступов к действиям контроллера API c помощью класса yii\filtersAccessControl. Укажем, что создавать, редактировать и удалять листинги могут только авторизованные пользователи:
...
$behaviors['access'] = [
'class' => \yii\filters\AccessControl::className(),
'only' => ['create', 'update', 'delete'],
'rules' => [
[
'actions' => ['create', 'update', 'delete'],
'allow' => true,
'roles' => ['@'],
],
],
];
...
Также, для обеспечения контроля доступа к действиям можно переопределить метод
CheckAccess()
, для того, чтобы проверить, имеет ли текущий пользователь привилегию на выполнение определенного действия над указанной моделью данных. Например, напишем проверку, чтобы пользователь мог редактировать или удалять только свои листинги:…
public function checkAccess($action, $model = null, $params = [])
{
// проверяем может ли пользователь редактировать или удалить запись
// выбрасываем исключение ForbiddenHttpException если доступ запрещен
if ($action === 'update' || $action === 'delete') {
if ($model->user_id !== \Yii::$app->user->id)
throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s lease that you\'ve created.', $action));
}
}
...
Этот метод будет вызываться автоматически для действий класса yii\rest\ActiveController. Если вы создаете в контроллере новое действие, в котором также необходимо выполнить проверку доступа, вы должны вызвать этот метод явным образом в новых действиях.
Следующим этапом мы изменим конфигурацию компонента
urlManager
в бутстрэппинг классе модуля в файле modules/lease/Bootstrap.php
...
$app->getUrlManager()->addRules(
[
[
'class' => 'yii\rest\UrlRule',
'controller' => ['leases' => 'lease/api'],
'prefix' => 'api'
]
]
);
…
Приведенная выше конфигурация в основном добавляет URL правило для контроллера API таким образом, что данные могут быть доступны и ими можно будет манипулировать с использованием короткого URL-адреса и соответствующего HTTP заголовка. Мы добавили
prefix
, для того, чтобы все ссылки для обращения к API сервисам начинались с /api/*
, таким образом, по этому паттерну этот контент можно будет запросто скрыть от поисковых машин в файле robots.txt. В дальнейшем, в каждом модуле, который будет участвовать в RESTful API-интерфейсе, мы таким же образом будем добавлять контроллер для API.Написанным выше минимальным количеством кода, мы уже выполнили свою задачу по созданию RESTful API-интерфейса для доступа к данным из модели
Lease
. Мы создали API-интерфейсы, которые включают в себя: - GET /api/leases: Получить все листинги
- HEAD /api/leases: Получить заголовок ответа на запрос GET /api/leases
- POST /api/leases: Создать новый листинг
- GET /api/leases/3: Получить данные листинга с id=3
- HEAD /api/leases/3: Получить заголовок ответа GET /api/leases/3
- PATCH /api/leases/3 и PUT /api/leases/3: Изменить данные листинга с id=3
- DELETE /api/leases/3: Удалить листинг id=3
- OPTIONS /api/leases: Получить список доступных методов запроса для /api/leases
- OPTIONS /api/leases/3: Получить список доступных методов запроса для /api/leases/3
С помощью механизма RESTful API фреймворка Yii2, мы реализовали API эндпоинты в виде действий контроллера и используем эти действия для одного типа данных (для одной модели).
На данный момент, мы реализовали часть бэкенда для приложения и теперь можем приступить к реализации фронтенда – начнем с того, что подключим AngularJS. Делать это будем используя composer, нам просто нужно будет добавить следующие строки в секцию
“require”
в файле composer.json
....
"bower-asset/angular": "^1.5",
"bower-asset/angular-animate": "^1.5",
"bower-asset/angular-bootstrap": "^2.2"
…
И выполнить команду в командной строке
php composer.phar update
.Теперь давайте сделаем
AssetBundle
в файле так, чтобы мы могли с легкостью подцепить AngularJS файлы к лейауту. Вот так будет выглядеть файл \frontend\assets\AngularAsset.php
<?php
namespace frontend\assets;
use yii\web\AssetBundle;
use yii\web\View;
class AngularAsset extends AssetBundle
{
public $sourcePath = '@bower';
public $js = [
'angular/angular.js',
'angular-animate/angular-animate.min.js',
'angular-bootstrap/ui-bootstrap.min.js',
'angular-bootstrap/ui-bootstrap-tpls.min.js'
];
public $jsOptions = [
'position' => View::POS_HEAD,
];
}
public $jsOptions = [ ‘position’ => View::POS_HEAD, ];
сообщает Yii, что необходимо подключить JavaScript файлы в секции head нашего лейаута вместо того, чтобы размещать их в самом конце секции body, таким образом, ядро AngularJS загрузится настолько быстро, насколько это возможно. Мы также должны добавить этот Bundle в зависимости главного asset bundle
приложения и там же подключить еще один файл ‘js/app.js’
, в котором будет находится экземпляр AngularJS приложения.<?php
namespace frontend\assets;
use yii\web\AssetBundle;
/**
* Main frontend application asset bundle.
*/
class AppAsset extends AssetBundle
{
public $basePath = '@webroot';
public $baseUrl = '@web';
public $css = [
'css/site.css',
];
public $js = [
'js/app.js',
'js/controllers.js',
'js/directives.js',
'js/services.js',
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
'frontend\assets\AngularAsset'
];
}
Давайте заодно добавим файл
controllers.js
в этот же бандл. В дальнейшем, мы пропишем в нем контроллеры нашего фронтенда, directives.js
и services.js
для описания директив и сервисов соответственно.Начнем имплементировать Angular во вьюхи приложения с добавления директивы
ng-app
к элементу html – это будет запускать наше AngularJS приложение, которое мы назовем “app”
....
<html lang="<?= Yii::$app->language ?>" data-ng-app="app" >
...
Обратите внимание, что я написал директиву
data-ng-app
именно так, иначе, наша html разметка не пройдет W3C валидацию. На работу директив это никакого влияния не оказывает, а верстка у нас должна проходить валидацию согласно спецификации по проекту. Поэтому для всех нестандартных директив мы будем дописывать приставку data-
.Код для Angular приложения будет располагаться в
frontend/web/js/app.js
. Там мы определим приложение и подключим к нему модули, которые мы собираемся использовать.'use strict';
var app = angular.module('app', [
'ngAnimate',
'ui.bootstrap',
'controllers' //наш модуль frontend/web/js/controllers.js
]);
В файле
frontend/web/js/services.js
напишем сервис LeaseService
, который будет выполнять CRUD операции для листингов посредством общения с REST API бэкенда: 'use strict';
var app = angular.module('app');
app.service('LeaseService', function($http) {
this.get = function() {
return $http.get('/api/leases');
};
this.post = function (data) {
return $http.post('/api/leases', data);
};
this.put = function (id, data) {
return $http.put('/api/leases/' + id, data);
};
this.delete = function (id) {
return $http.delete('/api/leases/' + id);
};
});
На сегодняшний день, наш модуль
controllers
будет содержать в себе очень простой контроллер LeaseController
. Он будет запрашивать данные от /api/leases
и передавать данные в отображение.'use strict';
var controllers = angular.module('controllers', []);
controllers.controller('LeaseController', ['$scope', 'LeaseService',
function ($scope, LeaseService) {
$scope.leases = [];
LeaseService.get().then(function (data) {
if (data.status == 200)
$scope.leases = data.data;
}, function (err) {
console.log(err);
})
}
]);
Используем этот контроллер в файле
\modules\lease\views\frontend\default\index.php
указав через директиву ng-controller
и выводим в таблицу данные, которые нам предоставил API:<div class="lease-default-index" data-ng-controller="LeaseController">
<div>
<h1>All Leases</h1>
<div data-ng-show="leases.length > 0">
<table class="table table-striped table-hover">
<thead>
<th>Year</th>
<th>Make</th>
<th>Model</th>
<th>Trim</th>
</thead>
<tbody>
<tr data-ng-repeat="lease in leases">
<td>{{lease.year}}</td>
<td>{{lease.make}}</td>
<td>{{lease.model}}</td>
<td>{{lease.trim}}</td>
</tr>
</tbody>
</table>
</div>
<div data-ng-show="leases.length == 0">
No results
</div>
</div>
</div>
Теперь, когда мы просмотрим страницу по пути
/lease/default/index
, мы увидим список всех листингов, записанных в базу данных. Надеюсь, вы убедились, насколько быстро и легко можно создать RESTful API-интерфейс на базе фреймворка Yii2. Весь описанный в этой и предыдущих статьях код, доступен для вас в репозитории.
Материал подготовили: greebn9k(Сергей Грибняк), pavel-berezhnoy(Павел Бережной)