Yii2 RESTful API-интерфейс для AngularJS


    Здравствуйте, дорогие читатели! Судя по вашим комментариям к предыдущим статьям, многим из вас очень интересно, как именно мы будем использовать 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(Павел Бережной)
    • +14
    • 24,6k
    • 3
    Поделиться публикацией
    Комментарии 3
      0
      Возможно вам понравится расширение «Restangular», оно позволяет делать сервисы на своей основе и вам не придётся расписывать базовые операции…
        +1
        Расскажите — как у вас устроена пагинация в листингах?
          0
          yii\rest\ActiveController действительно хорош, пока клиент работает напрямую с данными из БД. Мы тоже с него начинали, но в итоге 90% методов пришлось кастомизировать.
          Если используете Yii2 RBAC, его можно удобно подружить с angularjs-rbac
          Будет интересно почитать продолжение, про одностраничное приложение на AngularJS + Yii2 REST.

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

          Самое читаемое