В этом уроке мы собираемся создать приложение для знакомств для iOS, подобное Tinder . Для передачи голоса и сообщений мы будем использовать платформу Sinch , используя ее мощный SDK.
В первой части мы сосредоточимся на разработке RESTful API для хранения и извлечения информации о пользователях. Во второй части клиент iOS подключится к этому API, чтобы найти соседних пользователей на основе текущего местоположения пользователя.
Мы будем использовать Laravel 5.0 для службы RESTful и будем охватывать основные понятия, такие как маршруты и контроллеры. Мы также собираемся определить пользовательские модели для поддержки интеграции MongoDB в стиле ActiveRecord. Давайте начнем.
1. Базовая настройка
Я предполагаю, что вы уже установили Composer и последний установщик Laravel . Если нет, следуйте официальной документации Laravel 5.0 . Процесс установки не должен занять больше пары минут.
В командной строке перейдите в папку, в которой вы хотите создать приложение для службы RESTful, и выполните следующую команду:
1
|
laravel new mobilesinch
|
Через пару секунд команда сообщит вам, что приложение mobilesinch успешно создано. Перейдите в новую папку и выполните следующую команду:
1
|
php artisan fresh
|
Любое приложение Laravel 5.0 по умолчанию поставляется с некоторыми базовыми платформами для регистрации и аутентификации пользователей. Эта команда позаботится об удалении, поскольку мы хотим начать с чистого листа.
Есть еще одна вещь, о которой мы должны позаботиться, прежде чем писать реальный код для нашего сервиса RESTful. По умолчанию Laravel 5.0 поставляется с промежуточным программным обеспечением для защиты от подделки межсайтовых запросов (CSRF) . Однако, поскольку мы создаем не веб-сайт, а API-интерфейс RESTful, не имеет смысла его использовать. Кроме того, это может вызвать некоторые проблемы на этом пути.
Для этого приложения лучше всего его удалить. В корне папки приложения перейдите в app / Http . Внутри этой папки есть файл с именем Kernel.php . Откройте его и удалите следующую строку:
1
|
‘App\Http\Middleware\VerifyCsrfToken’,
|
Вы также можете удалить WelcomeController.php , расположенный в app / Http / Controllers , а также представление по умолчанию welcome.blade.php в папке resources / views . Мы не будем их использовать, но вы можете оставить их там, если хотите. Просто убедитесь, что вы оставили представление 503.blade.php на месте, так как это полезно для отладки приложения.
2. Base
модель
Приложение знакомств, которое этот учебник стремится создать, имеет функцию, похожую на Tinder, в которой вы можете найти пользователей рядом с вашим текущим местоположением. Чтобы это работало, API должен выполнить поиск на основе местоположения пользователя, известного как геопространственный запрос. В то время как мы могли бы сделать это с MySQL, отраслевой стандарт ориентирован на MongoDB, и мне лично это нравится гораздо больше.
Вместо использования фасада БД Laravel, мы создадим наш собственный класс, который будут расширены моделями приложения для выполнения запросов в MongoDB.
Это будет простой класс, и он не будет интегрирован в модель Laravel Eloquent, хотя мы могли бы, пока я хотел бы сделать его простым.
Шаг 1: Конфигурация MongoDB
Прежде чем писать класс для выполнения запросов MongoDB, нам нужно настроить информацию о базе данных так же, как мы это сделали бы для MySQL, PostgreSQL или любого другого сервера базы данных.
Внутри корневой папки конфигурации создайте новый файл и назовите его mongodb.php . Добавьте следующий код к нему:
1
2
3
4
5
6
7
8
9
|
<?php
return [
‘host’ => ‘localhost’,
‘port’ => 27017,
‘user’ => »,
‘pass’ => »,
‘db’ => ‘mobilesinch’
];
|
Мы устанавливаем хост и порт для нашего сервера MongoDB, если таковые имеются, задаем имя пользователя и пароль для подключения и определяем базу данных, которую мы будем использовать, mobilesinch .
Поскольку MongoDB — это документно-ориентированная база данных, и она не содержит схем, нам не требуется дополнительная настройка, определение миграции или что-либо еще для структурирования таблиц. Это просто работает.
Шаг 2: подключение к базе данных
У нас есть файл конфигурации, и теперь пришло время создать реальный класс, который будет обрабатывать взаимодействия с базой данных.
Этот класс будет выполнять запросы к MongoDB с использованием ActiveRecord-подобного синтаксиса. Внутри приложения / Http папку, создайте новую модель и добавьте в нее файл Base.php . Добавьте следующий код к нему:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
<?php namespace App\Http\Models;
use Illuminate\Support\Facades\Config;
class Base {
private $_config = null;
private $_conn = null;
private $_db = null;
public function __construct() {
$this->_config = Config::get( ‘mongodb’ );
$this->_connect();
}
private function _connect() {}
}
|
Это скелет для нашей Base
модели. Он не расширяется ни от чего и использует только фасад Config
Laravel для получения параметров конфигурации, которые мы создали ранее.
Далее нам нужно создать соединение с базой данных. Добавьте следующий код в закрытый метод _connect
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
$conn = ‘mongodb://’.$this->_config[‘host’];
if( ! empty( $this->_config[‘port’] ) ) {
$conn .= «:{$this->_config[‘port’]}»;
}
$options = array();
if( ! empty( $this->_config[‘user’] ) && ! empty( $this->_config[‘pass’] ) ) {
$options[‘username’] = $this->_config[‘user’];
$options[‘password’] = $this->_config[‘pass’];
}
try {
$this->_conn = new \MongoClient( $conn, $options );
$this->_db = $this->_conn->{$this->_config[‘db’]};
return true;
} catch( \MongoConnectionException $e ) {
$this->_conn = null;
return false;
}
|
В этом методе мы создаем строку подключения и устанавливаем имя пользователя и пароль, если таковые имеются. Затем мы используем PHP-драйвер MongoDB для создания соединения и устанавливаем базу данных, указанную в файле конфигурации.
Если вы знакомы с синтаксисом MongoDB из командной строки, этот метод эквивалентен входу в консоль mongo и use mobilesinch
. Обратитесь к официальной документации PHP MongoDB для получения дополнительной информации.
Шаг 3: вспомогательные методы
Прежде чем продолжить операции CRUD с базой данных, есть несколько методов, которые должен реализовать наш Base
класс. Они используются для установки фильтров, операторов выбора и других переменных запроса, которые используются для выполнения операций с базой данных. Давайте начнем с добавления необходимых переменных-членов. Над конструктором класса добавьте следующий код:
1
2
3
4
5
6
7
|
private $_ws = array();
private $_sls = array();
private $_lmt = 99999;
private $_ost = 0;
|
Это держатели для запросов where , select , limit и offset . Чтобы установить эти переменные-члены, создайте следующие методы установки:
1
2
3
4
5
|
protected function _limit( $limit, $offset = null ) {}
protected function _select( $select = «» ) {}
protected function _where( $key, $value = null ) {}
|
Метод _limit
будет полезен для разбиения на страницы результатов операции READ. Пользователь может установить параметр limit
чтобы указать количество записей для извлечения и, необязательно, параметр offset
чтобы указать страницу для чтения. Добавьте следующий код в метод _limit
:
1
2
3
4
5
6
|
if ( $limit !== NULL && is_numeric( $limit ) && $limit >= 1 ) {
$this->_lmt = $limit;
}
if ( $offset !== NULL && is_numeric( $offset ) && $offset >= 1 ) {
$this->_ost = $offset;
}
|
Метод _select
будет использоваться для определения того, какие поля записи должен возвращать запрос READ. Оператор select должен быть представлен в виде строки, разделенной запятыми.
1
2
3
4
|
$fields = explode( ‘,’, $select );
foreach ( $fields as $field ) {
$this->_sls[trim( $field )] = true;
}
|
Наконец, метод _where
будет использоваться для фильтрации результатов запроса и может быть либо массивом, либо парой ключ / значение.
1
2
3
4
5
6
7
|
if ( is_array( $key ) ) {
foreach( $key as $k => $v ) {
$this->_ws[$k] = $v;
}
} else {
$this->_ws[$key] = $value;
}
|
Теперь у нас есть поддержка для ограничения и фильтрации запросов, но мы должны добавить некоторые другие вспомогательные методы. Первый будет использоваться для объединения любого оператора where перед выдачей запроса с параметром where запроса.
В тот момент, когда мы напишем наши методы CRUD, это будет иметь больше смысла. В нижней части класса добавьте следующий закрытый метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private function _set_where( $where = null ) {
if ( is_array( $where ) ) {
$where = array_merge( $where, $this->_ws );
foreach ( $where as $k => $v ) {
if ( $k == «_id» && ( gettype( $v ) == «string» ) ) {
$this->_ws[$k] = new \MongoId( $v );
} else {
$this->_ws[$k] = $v;
}
}
} else if( is_string( $where ) ) {
$wheres = explode( ‘,’, $where );
foreach ( $wheres as $wr ) {
$pair = explode( ‘=’, trim( $wr ) );
if ( $pair[0] == «_id» ) {
$this->_ws[trim( $pair[0] )] = new \MongoId( trim( $pair[1] ) );
} else {
$this->_ws[trim( $pair[0] )] = trim( $pair[1] );
}
}
}
}
|
Это выглядит немного пугающе, но на самом деле все довольно просто. Сначала проверяется, является ли параметр where
массивом. Если это так, он объединяет данные значения с существующими, используя вспомогательный метод _where
.
Этот метод, однако, также поддерживает строку для установки того, что возвращается операцией READ. Эта строка должна иметь следующий формат:
1
|
name=John,last_name=Smith
|
В этом примере будет выполнен запрос и возвращены поля, в которых для поля name
установлено значение John
а для поля last_name
указано значение Smith
.
Обратите внимание, что для массива или строки мы проверяем наличие поля _id
. Если это так, и это строка, мы создаем новый объект MongoId
из него. Идентификаторы — это объекты в MongoDB, и сравнение их со строкой вернет false
, поэтому это преобразование необходимо.
Еще одна вещь, которую мы должны сделать, это сбросить все параметры запроса после выполнения операции, чтобы они не влияли на последующие запросы. Метод _flush
позаботится об этом.
1
2
3
4
5
6
|
private function _flush() {
$this->_ws = array();
$this->_sls = array();
$this->_lmt = 99999;
$this->_ost = 0;
}
|
Шаг 4: Операции CRUD
Теперь у нас есть все необходимые функции для фильтрации и ограничения результатов наших запросов. Пришло время для реальных операций с базой данных, которые будут опираться на PHP-драйвер MongoDB. Если вы не уверены в чем-то, обратитесь к документации .
СОЗДАТЬ Операцию
Первая операция, которую мы собираемся поддерживать, — это создание записи в системе. Добавьте следующий код в Base
класс:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
protected function _insert( $collection, $data ) {
if ( is_object( $data ) ) {
$data = ( array ) $data;
}
$result = false;
try {
if ( $this->_db->{$collection}->insert( $data ) ) {
$data[‘_id’] = ( string ) $data[‘_id’];
$result = ( object ) $data;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}
|
Хотя драйвер PHP ожидает, что вставленные данные будут массивом, наш класс будет поддерживать как массивы, так и объекты. Сначала мы проверяем, что передано нам, и разыгрываем его соответствующим образом. Затем мы пытаемся вставить запись в базу данных и вернуть вставленную запись как объект, включая _id
.
ЧИТАЙТЕ Операцию
Мы собираемся реализовать два метода чтения, один из которых будет использоваться для извлечения одной записи, а другой — для получения списка записей. Начнем с первого.
1
2
3
4
5
6
7
|
protected function _findOne( $collection, $where = array() ) {
$this->_set_where( $where );
$row = $this->_db->{$collection}->findOne( $this->_ws, $this->_sls );
$this->_flush();
return ( object ) $row;
}
|
Мы определяем предложение where для запроса и используем PHP-драйвер MongoDB для выполнения операции findOne
. Затем мы сбрасываем параметры запроса и возвращаем запись как объект.
PHP драйвер MongoDB возвращает результат в виде массива, а я лично предпочитаю объекты. Это истинная причина для актеров.
Далее мы реализуем метод _find
для получения списка записей.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
protected function _find( $collection, $where = array() ) {
$this->_set_where( $where );
$docs = $this->_db->{$collection}
->find( $this->_ws, $this->_sls )
->limit( $this->_lmt )
->skip( $this->_ost );
$this->_flush();
$result = array();
foreach( $docs as $row ) {
$result[] = ( object ) $row;
}
return $result;
}
|
В методе _find
мы используем метод find
драйвера, устанавливающий limit
запросов и параметры skip
для поддержки разбиения на страницы.
Однако этот метод возвращает объект MongoCursor
, который мы затем перебираем для получения реальных записей. Как и прежде, мы приводим каждую запись как объект, добавляя ее в массив результатов.
ОБНОВЛЕНИЕ Операция
У нас уже есть поддержка для создания и чтения записей из базы данных. Теперь мы должны иметь возможность редактировать эти записи и добавлять или изменять данные записи. Создайте новый метод _update
и реализуйте его следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
protected function _update( $collection, $data, $where = array() ) {
if ( is_object( $data ) ) {
$data = ( array ) $data;
}
$this->_set_where( $where );
if ( array_key_exists( ‘$set’, $data ) ) {
$newdoc = $data;
} else {
$newdoc = array( ‘$set’ => $data );
}
$result = false;
try {
if( $this->_db->{$collection}->update( $this->_ws, $newdoc ) ) {
$result = ( object ) $data;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}
|
Как и в случае операции CREATE, мы поддерживаем и массивы, и объекты, что означает, что мы проверяем и приводим соответственно Мы объединяем предложения where, переданные методу, используя вспомогательный метод. Остальное ничем не отличается от уже созданного метода _insert
.
Однако стоит отметить особую вещь. Когда мы обновим документ MongoDB и передадим данные в метод _update
, документ будет заменен. Если мы обновляем только одно поле и передаем данные для этого поля, документ становится этим полем. Вот почему нам нужно создать массив с ключом $set
и добавленной информацией. В результате наша запись не будет заменена новой информацией.
УДАЛЕНИЕ Операция
Наконец, драйвер должен поддерживать операцию DELETE для удаления документов из базы данных.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
protected function _remove( $collection, $where = array() ) {
$this->_set_where( $where );
$result = false;
try {
if ( $this->_db->{$collection}->remove( $this->_ws ) ) {
$result = true;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}
|
Как и прежде, мы устанавливаем условие where для операции удаления и полагаемся на PHP-драйвер MongoDB для выполнения операции remove
в базе данных.
И это все для нашей Base
модели. Это много кода, но теперь мы можем выполнять операции в MongoDB для моделей, которые наследуются от Base
класса.
3. Модель Session
Модель Session
будет отвечать за создание, удаление и поиск сеанса в базе данных. Создайте новый файл в папке Models приложения, назовите его Session.php и добавьте в него следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<?php namespace App\Http\Models;
class Session extends Base {
private $_col = «sessions»;
public function create( $user ) {
$this->_where( ‘user_id’, ( string ) $user->_id );
$existing = $this->_findOne( $this->_col );
if ( !empty( ( array ) $existing ) ) {
$this->_where( ‘user_id’, ( string ) $user->_id );
$this->_remove( $this->_col );
}
$session = new \stdClass();
$session->user_id = ( string ) $user->_id;
$session->user_name = $user->name;
$session = $this->_insert( $this->_col, $session );
return $session;
}
public function find( $token ) {
$this->_where( ‘_id’, $token );
return $this->_findOne( $this->_col );
}
public function remove( $token ) {
$this->_where( ‘_id’, $token );
return $this->_remove( $this->_col );
}
}
|
Эта модель расширяется от Base
класса, который мы создали ранее, для поддержки операций MongoDB. Он также устанавливает коллекцию для sessions
.
Метод create
используется для создания записи сеанса пользователя. Прежде чем пытаться создать его, метод проверяет, есть ли у пользователя активный сеанс. Если это так, он удаляет его из базы данных и создает новую запись с переданной пользовательской информацией.
Метод find
используется для извлечения записи сеанса из базы данных с использованием токена сеанса. Обратите внимание, что он просто устанавливает предложение where для запроса и делегирует задачу поиска записи методу _findOne
Base
класса.
Чтобы завершить сеанс пользователя, мы реализуем метод remove
. Используя маркер сеанса, он делегирует тяжелую _remove
методу _remove
Base
класса. Обратите внимание, что модель не проверяет переданный токен сеанса. Это должно быть обработано контроллером. Единственная проблема для модели — манипулирование данными.
4. Модель User
Другая модель, в которой нуждается наш REST API, — это модель для взаимодействия с пользователем. В папке Models приложения создайте новый файл User.php и добавьте в него следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php namespace App\Http\Models;
use App\Http\Models\Base as Model;
class User extends Model {
private $_col = «users»;
private $_error = null;
public function get( $where ) {}
public function get_error() {}
public function create( $user ) {}
public function remove( $id ) {}
public function retrieve( $id, $distance, $limit = 9999, $page = 1 ) {}
public function update( $id, $data ) {}
}
|
Модель User
немного сложнее. Давайте начнем с методов получения пользователей. Метод get
будет отвечать за получение одной записи, используя идентификатор пользователя. Добавьте следующий код в метод get
:
1
2
3
4
5
6
|
if ( is_array( $where ) ) {
return $this->_findOne( $this->_col, $where );
} else {
$this->_where( ‘_id’, $where );
return $this->_findOne( $this->_col );
}
|
Мы предполагаем, что в случае, where
параметр where
не является массивом, это идентификатор пользователя. Затем метод get
делегирует задачу поиска записи методу _findOne
Base
класса.
Метод get_error
— это вспомогательный метод, который даст контроллеру больше информации о сбое в модели.
1
|
return $this->_error;
|
Последней операцией чтения в модели User
является метод для метода retrieve
. Это приведет к получению списка пользователей. Добавьте следующий код в метод retrieve
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
if ( !empty( $id ) && !empty( $distance ) ) {
$this->_where( ‘_id’, $id );
$this->_select( ‘location’ );
$user = $this->_findOne( $this->_col );
if ( empty( ( array ) $user ) ) {
$this->_error = «ERROR_INVALID_USER»;
return false;
}
$this->_where( ‘$and’, array(
array(
‘_id’ => array( ‘$ne’ => new \MongoId( $id ) )
),
array(
‘location’ => array(
‘$nearSphere’ => array(
‘$geometry’ => array(
‘type’ => «Point»,
‘coordinates’ => $user->location[‘coordinates’]
),
‘$maxDistance’ => ( float ) $distance
)
)
)
) );
}
$this->_limit( $limit, ( $limit * —$page ) );
return $this->_find( $this->_col );
|
Этот метод поддерживает пагинацию и геопространственные запросы. Если передаются параметры id
и distance
, он пытается выполнить поиск ближайших пользователей на основе местоположения пользователя.
Если id
не совпадает ни с одной записью, он возвращает false
. Если пользователь существует, он подготавливает геопространственный запрос, используя индекс 2dsphere MongoDB.
Обратите внимание, что мы также устанавливаем запрос, чтобы не возвращать пользователя, совпадающего с _id
пользователя, выполняющего поиск. Наконец, он устанавливает параметры ограничения и смещения запроса, делегируя задачу методу _find
Base
класса.
Чтобы удалить пользователей, нам нужно реализовать метод remove
. Добавьте следующий код в метод remove
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
$this->_where( ‘_id’, $id );
$user = $this->_findOne( $this->_col );
if ( empty( ( array ) $user ) ) {
$this->_error = «ERROR_INVALID_ID»;
return false;
} else {
$this->_where( ‘_id’, $id );
if ( !$this->_remove( $this->_col ) ) {
$this->_error = «ERROR_REMOVING_USER»;
return false;
}
}
return $user;
|
Мы проверяем, что данный _id
соответствует существующему пользователю, и _remove
удалить его, используя метод _remove
Base
класса. Если что-то пошло не так, мы устанавливаем свойство модели _error
и возвращаем false
.
Еще одна операция, которую должна поддерживать наша модель, — создание пользовательских записей. Добавьте следующий код в метод create
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
if ( is_array( $user ) ) {
$user = ( object ) $user;
}
$this->_where( ‘$or’, array(
array(
«email» => $user->email
),
array(
«mobile» => $user->mobile
)
)
);
$existing = $this->_findOne( $this->_col );
if ( empty( ( array ) $existing ) ) {
$user = $this->_insert( $this->_col, $user );
} else {
$user = $existing;
}
$user->_id = ( string ) $user->_id;
return $user;
|
В этом методе мы удостоверяемся, что еще нет пользователя, связанного с данным email
или mobile
. Если это правда, мы возвращаем соответствующего пользователя. Если это не так, мы делегируем задачу создания пользователя методу _insert
Base
класса.
Прежде чем мы вернем запись пользователя, мы приводим _id
к строке. Это почему? Возвращаемый нам объект определяет поле _id как объект MongoId . Однако клиентскому приложению этот объект не нужен.
Модель User
также должна поддерживать обновление пользовательских записей. Добавьте следующий код в метод update
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
if ( is_array( $data ) ) {
$data = ( object ) $data;
}
if ( isset( $data->email ) || isset( $data->mobile ) ) {
$this->_where( ‘$and’, array(
array(
‘_id’ => array( ‘$ne’ => new \MongoId( $id ) )
),
array(
‘$or’ => array(
array(
’email’ => ( isset( $data->email ) ) ?
),
array(
‘mobile’ => ( isset( $data->mobile ) ) ?
)
)
)
)
);
$existing = $this->_findOne( $this->_col );
if ( !empty( ( array ) $existing ) && $existing->_id != $id ) {
$this->_error = «ERROR_EXISTING_USER»;
return false;
}
}
$this->_where( ‘_id’, $id );
return $this->_update( $this->_col, ( array ) $data );
|
Как и в Base
классе, метод update
принимает как массивы, так и объекты в качестве данных для пользователя. Это делает метод намного более гибким.
Прежде чем обновить запись о пользователе, мы должны убедиться, что email
и mobile
не используются другим пользователем. Если это так, мы устанавливаем ошибку в EXISTING_USER
и возвращаем false
. В противном случае мы делегируем операцию обновления Base
классу.
5. BaseController
Class
Так же, как модели приложения наследуются от Base
класса, контроллеры также наследуются от общего родительского класса, отличного от класса Controller
Laravel. Этот класс BaseController
, тем не BaseController
, не BaseController
со сложностью Base
модели.
Этот класс будет использоваться только для решения нескольких простых задач. Чтобы создать класс, мы используем команду ремесленника Laravel. В командной строке перейдите в корень приложения и выполните следующую команду:
1
|
php artisan make:controller BaseController —plain
|
Это создаст файл с именем BaseController.php в папке Controllers приложения в папке app / Http . Так как мы используем --plain
flag, у контроллера не будет никаких методов, чего мы и хотим.
Этот контроллер не будет использовать класс Request
поэтому вы можете удалить следующую строку:
1
|
use Illuminate\Http\Request;
|
Поскольку нам нужен доступ к модели Session
, добавьте следующую строку в объявление класса BaseController
:
1
|
use App\Http\Models\Session as SessionModel;
|
Теперь мы готовы реализовать методы класса BaseController
. Начните с добавления следующих методов внутри объявления класса:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
protected function _check_session( $token = «», $id = «» ) {
$result = false;
if ( !empty( $token ) ) {
$SessionModel = new SessionModel();
$session = $SessionModel->find( $token );
if ( !empty( ( array ) $session ) ) {
if ( !empty( $id ) ) {
if ( $session->user_id == $id ) {
$result = $session;
}
} else {
$result = $session;
}
}
}
return $result;
}
protected function _response( $result ) {
if ( is_object( $result ) && property_exists( $result, «status» ) ) {
return response()->json( $result, $result->status );
} else {
return response()->json( $result );
}
}
|
Метод _check_session
используется для проверки токена сеанса, который передается в качестве первого аргумента. Некоторые задачи в приложении требуют, чтобы пользователь вошел в систему. Например, при обновлении пользовательской записи пользователь, соответствующий активному сеансу, должен соответствовать _id
записи, которую необходимо обновить.
Реализация довольно проста. Мы выбираем сеанс для токена сеанса и, если идентификатор пользователя, который соответствует сеансу, совпадает с идентификатором, переданным в качестве второго аргумента, мы возвращаем сеанс. В противном случае мы возвращаем false
.
Другой вспомогательный метод заботится об отправке результата обратно клиенту, который использует API. На данный момент мы поддерживаем только JSON. Если возвращаемый результат является объектом и имеет параметр состояния, мы устанавливаем его с помощью вспомогательного метода response
Laravel. В противном случае мы просто возвращаем результат.
6. Класс SessionController
Следующий контроллер, который мы реализуем, — это тот, который обрабатывает запросы на ресурс Sessions
. В командной строке перейдите в корень приложения и выполните следующую команду:
1
|
php artisan make:controller SessionController —plain
|
Это создаст новый файл с именем SessionController.php в папке Controllers приложения в папке app / Http . Прежде чем мы реализуем этот класс, нам нужно позаботиться о нескольких вещах.
Класс SessionController
настоящее время наследуется от класса Controller
от Laravel. Нам нужно установить это для использования нашего класса BaseController
. Это означает, что мы должны заменить
1
|
use App\Http\Controllers\Controller;
|
с
1
|
use App\Http\Controllers\BaseController;
|
Нам также нужно изменить предложение extends
класса. Вместо расширения из Controller
убедитесь, что ваш класс расширяет класс BaseController
. Нам также необходимо включить модели, используемые в контроллере. Под последним объявлением добавьте следующие строки:
1
2
|
use App\Http\Models\Session as SessionModel;
use App\Http\Models\User as UserModel;
|
Обычно мы просто использовали бы SessionModel
, но вы поймете, почему мы также используем UserModel
в одно мгновение. Что касается самого класса контроллера, добавьте следующий код:
1
2
3
4
5
6
7
8
9
|
private $_model = null;
public function __construct() {
$this->_model = new SessionModel();
}
public function create( Request $request ) {}
public function destroy( $token ) {}
|
Мы устанавливаем объект model
контроллера в конструкторе и объявляем несколько методов, которые являются действиями, поддерживаемыми ресурсом Sessions
.
Шаг 1: Удаление сеанса
Для удаления пользовательского сеанса мы просто используем токен сеанса, который задается в качестве параметра в URL-адресе ресурса. Мы объявим об этом позже в маршрутах приложения. Внутри метода destroy
добавьте следующий код:
1
2
3
4
5
6
7
|
$result = new \stdClass();
if ( !$this->_model->remove( $token ) ) {
$result->error = «ERROR_REMOVING_SESSION»;
$result->status = 403;
}
return $this->_response( $result );
|
Метод использует метод SessionModel
и возвращает результат, используя метод BaseController
класса BaseController
. Если удаление успешно завершено, мы возвращаем пустой объект. Если произошла ошибка, мы возвращаем ошибку с кодом состояния 403
.
Шаг 2: Создание сессии
Метод создания сеанса немного сложнее. Обратите внимание, что в объявлении метода мы используем объект Request
Laravel. Мы используем этот объект для доступа к параметрам POST запроса. Внутри метода create
добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
|
$email = $request->get( ’email’ );
$mobile = $request->get( ‘mobile’ );
$fbId = $request->get( ‘fbId’ );
$result = new \stdClass();
if ( ( empty( $email ) && empty( $mobile ) ) || empty( $fbId ) ) {
$result->error = «ERROR_INVALID_PARAMETERS»;
$result->status = 403;
} else {}
return $this->_response( $result );
|
Мы еще не создали объект сеанса, потому что сначала нам нужно кое-что обсудить. Приложение будет использовать только логин Facebook. Из SDK Facebook мы получаем информацию о пользователе при выполнении операции входа в систему. В обработчике POST ресурса Session
API нам нужно поддерживать две вещи:
- начать сеанс для пользователя
- создание пользователя, когда он не существует, а затем начало сеанса
Это также причина для включения UserModel
в контроллер. В приведенном выше пустом предложении else
добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
$UserModel = new UserModel();
$where = ( !empty( $email ) ) ?
$user = $UserModel->get( $where );
if ( empty( ( array ) $user ) ) {
} else {
if ( $fbId != $user->fbId ) {
$result->error = «ERROR_INVALID_CREDENTIALS»;
$result->status = 403;
}
}
if ( !property_exists( $result, «error» ) ) {
$result = $this->_model->create( $user );
$result->token = $result->_id;
unset( $result->_id );
}
|
Сначала мы проверяем существующего пользователя с помощью email
или mobile
. Если пользователь существует, мы проверяем, что данный идентификатор Facebook совпадает с идентификатором Facebook для записи пользователя. Если это так, мы создаем объект сеанса. Если это не так, метод возвращает ошибку INVALID_CREDENTIALS
с кодом состояния 403
.
Начало сеанса завершено. Обратите внимание, что это не очень безопасно. Тем не менее, для целей этого урока, он будет работать просто отлично.
Для случая, когда нет пользователя, связанного с переданным по email
или mobile
, мы хотим создать новую запись. В приведенном выше пустом предложении if
добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
name = $request->get( ‘name’ );
$gender = $request->get( ‘gender’ );
$location = $request->get( ‘location’ );
if ( empty( $name ) || empty( ( array ) $location ) || empty( $gender ) ) {
$result->error = «ERROR_INVALID_PARAMETERS»;
$result->status = 403;
} else {
if ( gettype( $location ) == «string» ) {
$location = json_decode( $location );
}
$locObj = new \stdClass();
$locObj->type = «Point»;
$locObj->coordinates = array( $location->lon, $location->lat );
$user->name = $name;
$user->fbId = $fbId;
$user->email = $email;
$user->mobile = $mobile;
$user->gender = $gender;
$user->location = $locObj;
$user = $UserModel->create( $user );
}
|
Сначала мы извлекаем остальные обязательные параметры из запроса, а затем проверяем, задан ли параметр location
как объект JSON или закодированный объект JSON (строка). Метод ожидает, что этот параметр будет в следующем формате:
1
2
3
4
|
{
«lat» : 37.427208696456866,
«lon» : -122.17097282409668
}
|
Затем мы преобразуем это местоположение в местоположение MongoDB 2dSphere . Для выполнения геопространственных запросов это поле должно иметь следующий формат:
1
2
3
4
|
{
«type» : «Point»
«coordinates» : [ -122.17097282409668, 37.427208696456866 ]
}
|
Мы могли бы попросить клиента отправить местоположение в этом формате. Однако лучше, чтобы мы не обременяли клиента переформатированием местоположения пользователя, поскольку это зависит от нашей реализации.
После установки объекта местоположения мы проверяем, что параметры, необходимые для пользователя, существуют, и, если это так, мы создаем новый объект пользователя, используя метод create
класса UserModel
.
Вот и все. Даже если мы можем начать сеанс, отправив только параметры email
и параметры fbId
или параметры mobile
и fbId
, если будет предоставлена остальная информация о пользователе, наш обработчик позаботится о создании нового пользователя при необходимости и запуске сеанса.
7. Класс UserController
Последний контроллер, который требуется приложению, — это тот, который отвечает за обработку ресурса Users
. Еще раз, мы используем команду ремесленника Laravel. В командной строке перейдите в корень приложения и выполните следующую команду:
1
|
php artisan make:controller UserController —plain
|
Это создаст файл UserController.php в папке Controllers приложения в папке app / Http . Как и в SessionController
классом SessionController
, убедитесь, что класс UserController
наследуется от BaseController
и включает в себя класс UserModel
. Внутри объявления класса добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private $_model = null;
public function __construct() {
$this->_model = new UserModel();
}
public function create( Request $request ) {}
public function get( Request $request, $id ) {}
public function remove( Request $request, $id ) {}
public function retrieve( Request $request ) {}
public function update( Request $request, $id ) {}
|
Как и в SessionController
классом SessionController
, мы инициализируем объект модели и объявляем методы, которые будут поддерживаться ресурсом Users
. Начнем с тех, что для операций GET. В методе get
добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
|
$token = $request->get( ‘token’ );
$result = new \stdClass();
if ( !$this->_check_session( $token ) ) {
$result->error = «PERMISSION_DENIED»;
$result->status = 403;
} else {
$result = $this->_model->get( $id );
}
return $this->_response( $result );
|
Чтобы получить запись из системы, мы требуем, чтобы у пользователя был активный сеанс. Он не должен совпадать с идентификатором полученного пользователя. Если у пользователя нет действительного сеанса, мы возвращаем ошибку PERMISSION_DENIED
с кодом состояния 403
. В противном случае мы возвращаем запись пользователя в виде объекта JSON.
Далее, для списка пользователей, нам нужно реализовать метод retrieve
. Добавьте следующий код в метод retrieve
:
01
02
03
04
05
06
07
08
09
10
11
12
|
$token = $request->get( ‘token’ );
$distance = $request->get( ‘distance’ );
$session = $this->_check_session( $token );
$result = $this->_model->retrieve( ( isset( $session->user_id ) ? $session->user_id : «» ), $distance, $request->get( ‘limit’ ), $request->get( ‘page’ ) );
if ( !is_array( $result ) && !$result ) {
$result = new \stdClass();
$result->error = $this->_model->get_error();
$result->status = 403;
}
return $this->_response( $result );
|
Мы начнем с выборки параметров запроса, в частности токена сеанса пользователя и параметров расстояния. Этот метод, однако, не требует активного сеанса. Если сеанс действителен, мы передаем идентификатор пользователя в метод retrieve
класса UserModel
.
Если передан параметр distance
, выполняется геопространственный запрос. Если нет, то выполняется обычный запрос find
. В случае ошибок мы извлекаем ошибку из модели и возвращаем ее пользователю с кодом состояния 403
. В противном случае мы возвращаем массив, содержащий найденных пользователей.
Создание пользователя будет сопоставлено с операцией POST ресурса Users
. Добавьте следующий код в метод create
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
$email = $request->get( ’email’ );
$fbId = $request->get( ‘fbId’ );
$gender = $request->get( ‘gender’ );
$location = $request->get( ‘location’ );
$mobile = $request->get( ‘mobile’ );
$name = $request->get( ‘name’ );
if ( gettype( $location ) == «string» ) {
$location = json_decode( $location );
}
$locObj = new \stdClass();
$locObj->type = «Point»;
$locObj->coordinates = array( $location->lon, $location->lat );
$result = new \stdClass();
if ( empty( $name ) || empty( ( array ) $location ) || empty( $fbId ) || empty( $gender ) || ( empty( $email ) && empty( $mobile ) ) ) {
$result->error = «ERROR_INVALID_PARAMETERS»;
$result->status = 403;
} else {
$user = array(
«email» => $email,
«fbId» => $fbId,
«gender» => $gender,
«location» => $locObj,
«mobile» => $mobile,
«name» => $name
);
$result = $this->_model->create( $user );
}
return $this->_response( $result );
|
Сначала мы извлекаем необходимую информацию для пользователя и, как в обработчике создания сеанса, преобразуем местоположение пользователя в соответствующий формат.
После этого мы проверяем, что требуемая информация передается. Обратите внимание, что хотя поля email
и mobile
являются необязательными, по крайней мере одно должно присутствовать.
После этих проверок мы вызываем метод create
класса UserModel
для вставки нового пользователя в базу данных. Наконец, мы возвращаем нового пользователя или ошибку.
Чтобы удалить пользователя, нам нужно реализовать метод remove
. Добавьте следующий код в метод remove
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
$token = $request->get( ‘token’ );
$result = new \stdClass();
if ( !$this->_check_session( $token, $id ) ) {
$result->error = «PERMISSION_DENIED»;
$result->status = 403;
} else {
$result = $this ->_model->remove( $id ); if ( !$result ) {
$result = new \stdClass(); $result ->error = $this ->_model->get_error(); $result ->status = 403; }
}
return $this ->_response( $result ); |
Это один из тех методов, в которых мы хотим _id
удалить пользователя, чтобы он соответствовал _id
активному сеансу пользователя. Это первое, что мы проверяем. Если это так, мы делегируем метод модели remove
. В противном случае мы устанавливаем ошибку PERMISSION_DENIED
и отправляем результат обратно пользователю.
Наконец, давайте реализуем операцию обновления пользователя. Внутри update
метода добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
$token = $request ->get( 'token' ); $data = new \stdClass(); if ( ! empty ( $email = $request ->get( 'email' ) ) ) { $data ->email = $email ; }
if ( ! empty ( $fbId = $request ->get( 'fbId' ) ) ) { $data ->fbId = $fbId ; }
if ( ! empty ( $gender = $request ->get( 'gender' ) ) ) { $data ->gender = $gender ; }
if ( ! empty ( $location = $request ->get( 'location' ) ) ) { if ( gettype ( $location ) == "string" ) { $location = json_decode( $location ); }
$locObj = new \stdClass(); $locObj ->type = "Point" ; $locObj ->coordinates = array ( $location ->lon, $location ->lat ); $data ->location = $locObj ; }
if ( ! empty ( $mobile = $request ->get( 'mobile' ) ) ) { $data ->mobile = $mobile ; }
if ( ! empty ( $name = $request ->get( 'name' ) ) ) { $data ->name = $name ; }
$result = new \stdClass(); if ( ! $this ->_check_session( $token , $id ) ) { $result ->error = "PERMISSION_DENIED" ; $result ->status = 403; } else {
$result = $this ->_model->update( $id , $data ); if ( !$result ) {
$result = new \stdClass(); $result ->error = $this ->_model->get_error(); $result ->status = 403; }
}
return $this ->_response( $result ); |
Мы проверяем переданные данные и устанавливаем соответствующий объект для обновления. В случае location
параметра мы сначала переформатируем его.
Опять же, этот метод должен быть доступен только пользователям с активным сеансом, который соответствует их собственному _id
. Это означает, что мы сначала проверим, что это так.
Затем мы вызываем update
метод UserModel
класса и возвращаем результат клиенту.
8. Маршрутизатор приложений
С этим последним фрагментом кода наш API завершен. У нас есть наши контроллеры и модели на месте. Последнее, что мы должны сделать, — сопоставить входящие запросы с соответствующими конечными точками.
Для этого нам нужно отредактировать файл route.php нашего приложения . Он находится внутри папки app / Http . Если вы откроете его, вы должны увидеть что-то вроде этого:
1
|
Route::get( '/' , 'WelcomeController@index' ); |
Когда приложение получает запрос GET без указания какого-либо ресурса, index
метод WelcomeController
класса должен его обработать. Тем не менее, вы, вероятно, уже удалили WelcomeController
в начале этого урока. Если вы попытаетесь перейти к этой конечной точке в браузере, вы получите ошибку. Давайте заменим эту последнюю строку следующим кодом:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Route::post( 'sessions' , 'SessionController@create' ); Route:: delete ( 'sessions/{token}' , 'SessionController@destroy' ); Route:: delete ( 'users/{id}' , 'UserController@remove' ); Route::get( 'users' , 'UserController@retrieve' ); Route::get( 'users/{id}' , 'UserController@get' ); Route::post( 'users' , 'UserController@create' ); Route::put( 'users/{id}' , 'UserController@update' ); |
Мы сопоставляем запросы API с методами, ранее добавленными в наши контроллеры. Например, следующий вызов
1
|
[ DELETE ] - http: //YOUR_API_URL/sessions/abc |
переводит в запрос DELETE по указанному URL. Это означает , что в SessionController
в delete
методе , с которым будет знамением abc
.
Вывод
Вот и все для RESTful API, использующего Laravel 5.0. У нас есть поддержка управления пользователями и сессиями, и это именно то, что нам нужно для реализации клиента iOS.
In the next part of this tutorial, Jordan will be showing you how to integrate this API in an iOS application. He will also show you how to integrate the Sinch SDK for messaging and voice calls.