В этой части мы начнем работать с интерфейсом REST. Создание REST Api на Laravel не очень сложно. Все, что нам нужно иметь в виду, это то, что мы имеем дело с EmberJS и что мы не хотим писать новый адаптер с нуля. Как обычно, вы можете найти исходный код этой части на github .
Когда начать?
Это сложный вопрос. У Ember свой рабочий процесс и логика. Если мы начнем писать наш REST с учетом этой логики, мы сэкономим некоторое время, у нас будет хорошая архитектура и что-то повторно используемое. Я думаю, что Ember сделал хороший выбор с их REST-архитектурой. Посмотрите, как Ember ожидает данные.
Давайте предположим, что мы хотим получить пользователя. Эмбер ожидает что-то вроде этого:
{
"user": {
"firstName": "firstName",
"lastName": "lastName"
}
}
Если мы хотим получить список пользователей, Ember будет ожидать такой json:
{
"users":
[
{
"firstName": "firstPersonsName",
"lastName": "lastname"
},
{
"firstName": "secondPersonName",
"lastName": "lastName"
}
]
}
Первый требует «пользователя», а второй требует «пользователей». Второй — во множественном числе. Эмбер тоже ввела некоторые правила для этого. Если вы сами не указали множественное число, используя:
Ember.Inflector.inflector.irregular('formula', 'formulae');
EmberJ сделает предположение и запросит «формулы». Иногда хорошо, что сам фреймворк предоставляет такие вещи, но, с другой стороны, вещи могут выйти из-под контроля, если вы забудете эти детали.
Прежде чем углубляться в Ember, предупреждаю: Ember сложный и мощный. Потратьте время, чтобы узнать, как это работает.
Если мы немного усложним вещи и установим отношения между объектами, например, мы скажем, что у пользователя есть несколько фотографий. Как бы мы это вывели?
{
"user": {
"id": 1,
"name": "firstName",
"lastname": "lastname,
"photos": [1, 2, 3]
},
"photos":
[
{
"id": 1,
"title": "Lorem Ipsum"
},
{
"id": 2,
"title": "Lorem Ipsum"
}
]
}
Это отношение один ко многим. Если мы запросим пользователя, его фотографии тоже будут извлечены. Мы уже установили некоторые отношения в Laravel, вы можете использовать их, если хотите, и использовать их также и в Ember.
Я начал с Ember, чтобы увидеть, как эта структура требует данных. Это проще, если вы знаете, как построить структуру. Проверка и получение данных из базы данных просты, но создать надежный и интеллектуальный интерфейс REST — сложная часть.
Готовимся к ОТДЫХУ
Когда вы что-то разрабатываете, макет может быть очень полезным. Даже если вы программист гуру и ненавидите работать с Photoshop или Gimp, есть хорошие инструменты для создания прототипов. Я использовал бальзамик, и мой прототип на первой странице был таким:
Давайте начнем строить его. Откройте /app/views/index.php
Это служит нашим одностраничным приложением. Мы создали этот файл в первой части этой серии.
<script type="text/x-handlebars">
<!-- The navigation top-bar -->
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<h1><a href="#">Photo Upload</a></h1>
</li>
</ul>
<section class="top-bar-section">
<!-- Left Nav Section -->
<ul class="left">
<li class="has-dropdown">
<a href="#">Categories</a>
<ul class="dropdown">
<li><a href="#">Category1</a></li>
<li><a href="#">Category2</a></li>
<li><a href="#">Category3</a></li>
<li><a href="#">Category4</a></li>
</ul>
</li>
</ul>
</section>
<div class="clearfix"></div>
</nav><!-- END Navigation -->
<!-- Content -->
<div style="margin-top: 50px;">
<!-- The content will be here -->
</div><!-- END Content -->
</script>
Позвольте мне объяснить это. Навигационный тег отвечает за навигацию. Тег nav
ul
title-area
Я также добавил раскрывающийся список со списком категорий. Отправляйтесь в Фонд 5 документов, если хотите узнать больше. В большинстве случаев это просто операции копирования / вставки, поэтому не беспокойтесь об этой части.
Также я использовал сеточную систему Foundation для области контента. Это будет заполнено всей информацией и будет изменено во время навигации. Все внутренние обновления будут обрабатываться Ember. Мы собираемся построить только 3 шаблона здесь. Один для пользователей, один для одной фотографии и один для целевой страницы.
Вы заметили, что весь наш код находится внутри тега скрипта? Ember использует руль в качестве языка шаблонов. Тип text/x-handlebars
Если вы некоторое время использовали Ember и Handlebars, вы, вероятно, использовали имена шаблонов. Я не указываю их в этом, потому что этот шаблон будет использоваться в качестве контейнера для всего приложения. Если вы не укажете имя, Ember использует его в качестве шаблона приложения.
Ресурсные контроллеры
Разрабатывая это очень простое приложение, я обнаружил, что контроллеры ресурсов пригодятся при разработке REST Apis. В этом суть архитектуры REST — все это ресурс. Для всех ресурсов может применяться HTTP-глагол: GET, POST, DELETE, PUT (обновление). Не все глаголы обязательны.
php artisan controller:make PhotoController --except=create,edit
Вот как мы создаем контроллер ресурсов через Artisan. Опция --except
Нам не нужны методы create
edit
Метод create
Поскольку мы создаем одностраничное приложение, нецелесообразно создавать вид за пределами Ember.
Создайте еще один контроллер ресурсов для категорий. Как видите, в этом контроллере доступны только методы show
index
Я думаю, что достаточно показать отдельную категорию и получить все категории.
php artisan controller:make CategoryController --only=show,index
Другим контроллером является контроллер изображений. Зачем нужен контроллер изображений, если он у нас уже есть? Потому что нам нужна конечная точка для обслуживания изображений. Dropbox хранит наши изображения, но мы не можем получить к ним доступ извне. Если вы хотите сделать папку общедоступной, вы должны заплатить. Это первая причина. Вторая причина в том, что я не хочу, чтобы каждое изображение было публичным. В двух словах, этот контроллер будет захватывать изображение из Dropbox и передавать его клиенту.
php artisan controller:make ImagesController --only=show
И последнее, но не менее важное, это UserController:
php artisan controller:make UserController --only=show,index
Маршрут
Теперь, когда у нас есть контроллеры, нам нужно связать эти контроллеры с их связанными маршрутами. Давайте обновим /app/routes.php
Сначала сгруппируйте их в пространстве имен url, используя Route::group
Route::group(array('prefix' => 'api/v1'), function()
{
});
Здесь мы указали префикс, пространство имен. Ко всему, что находится внутри этой группы, можно получить доступ следующим образом:
example.com/api/v1
Также мы можем указать фильтры внутри этой группы. Например, вы можете добавить фильтр Auth::onceBasic('username')
Вы также можете использовать другие аутентификации.
Добавьте три контроллера внутри этой группы. PhotoController, UserController и CategoryController.
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('photos', 'PhotoController');
Route::resource('users', 'UserController');
Route::resource('categories', 'CategoryController');
});
Добавьте ImagesController вне этой группы. Я не думаю, что этому контроллеру нужно пространство имен — изображения являются изображениями, и нет смысла давать им пространство имен.
Route::resource('files', 'ImagesController');
В конце файл /app/routes.php
Route::get('/', function()
{
return View::make('index');
});
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('photos', 'PhotoController');
Route::resource('users', 'UserController');
Route::resource('categories', 'CategoryController');
});
Route::resource('files', 'ImagesController');
Обратите внимание, что имена ресурсов множественного числа из-за требования Ember.
Заполнение этих контроллеров
Теперь мы можем начать что-то строить. Я не собираюсь рассказывать все о REST здесь, потому что очень сложно объяснить все вещи — узнать больше в этой серии . Начнем с фотоконтроллера.
Метод index()
Здесь мы могли бы сделать несколько страниц, но я не хочу, чтобы все стало слишком сложным. Если будет достаточно интереса к комментариям, мы обновим это приложение в следующей статье.
public function index()
{
try{
$statusCode = 200;
$response = [
'photos' => []
];
$photos = Photo::all()->take(9);
foreach($photos as $photo){
$response['photos'][] = [
'id' => $photo->id,
'user_id' => $photo->user_id,
'url' => $photo->url,
'title' => $photo->title,
'description' => $photo->description,
'category' => $photo->category,
];
}
}catch (Exception $e){
$statusCode = 400;
}finally{
return Response::json($response, $statusCode);
}
}
Позвольте мне объяснить это. Я вставил все внутри try
catch
finally
Если что-то пойдет не так, верните другой json с кодом состояния.
$photos = Photo::all()->take(9);
Это берет 9 фотографий из базы данных. Затем возьмите каждую фотографию и отобразите ее в отформатированном массиве, который позже будет преобразован в формат json.
Если все идет хорошо или если Eloquent не генерирует исключение, это выводит правильный вывод. Если вы хотите отобразить определенный код состояния, перехватите каждое исключение, которое может вызвать Eloquent, и отобразите правильный код состояния.
Давайте заполним метод show()
Опять же, мы хотим получить всю информацию о фотографии с заданным идентификатором.
public function show($id)
{
try{
$photo = Photo::find($id);
$statusCode = 200;
$response = [ "photo" => [
'id' => (int) $id,
'user_id' => (int) $photo->user_id,
'title' => $photo->title,
'url' => $photo->url,
'category' => (int) $photo->category,
'description' => $photo->description
]];
}catch(Exception $e){
$response = [
"error" => "File doesn`t exists"
];
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
При создании собственного приложения не забудьте добавить проверку ввода пользователя.
Логика для UserController почти такая же. На этот раз мы запросим модель пользователя.
public function index()
{
try{
$response = [
'users' => []
];
$statusCode = 200;
$users = User::all()->take(9);
foreach($users as $user){
$response['users'][] = [
'id' => $user->id,
'username' => $user->username,
'lastname' => $user->lastname,
'name' => $user->username
];
}
}catch (Exception $e){
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
Все практически идентично, меняются только модель и поля. Выходной JSON. Метод show
public function show($id)
{
try{
$response = [
'user' => []
];
$statusCode = 200;
$user = User::find($id);
$response = [
'id' => $user->id,
'name' => $user->name,
'lastname' => $user->lastname,
'username' => $user->username
];
}catch (Exception $e){
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
Эта функция извлекает пользователя с заданным идентификатором.
Последний контроллер, с которым мы имеем дело, это ImagesController. Логика так же проста, как захват изображений из файловой системы и их обслуживание. Это просто, когда вы сохраняете файлы и извлекаете их из локальной файловой системы или из файловой системы сервера. К сожалению, вы не можете сохранить файлы в Heroku, поэтому вы будете использовать Dropbox и обслуживать эти файлы с этой конечной точки.
Импортируйте клиент Dropbox и адаптер Flysystem. Если наша среда локальна, то мы будем использовать flysystem с локальным адаптером; если среда является производственной, то используйте адаптер Dropbox. Назначьте класс Flysystem в приватную переменную внутри этого контроллера.
if(App::environment() === "local"){
$this->filesystem = new Filesystem(new Adapter( public_path() . '/images/'));
}else{
$client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
$this->filesystem = new Filesystem(new Dropbox($client, '/images/'));
}
Метод show
destroy
Используя эту библиотеку, мы добавляем уровень абстракции в наше приложение.
public function show($name)
{
try{
$file = $this->filesystem->read($name); // Read the file
}catch (Exception $e){
return Response::json("{}", 404); // Return a 404 status code in a error case
}
$response = Response::make($file, 200); // If everything goes ok then return that file and 200 status code
return $response;
}
Функция destroy()
Просто выберите этот файл, используя метод удаления и передав имя удаляемого файла. Если файл не найден, верните 404.
public function destroy($name)
{
try{
$this->filesystem->delete($name);
return Response::json("{}", 200);
}catch (\Dropbox\Exception $e){
return Response::json("{}", 404);
}
}
В итоге ImageController должен выглядеть примерно так:
/* /app/controllers/ImagesController.php */
use Dropbox\Client;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local as Adapter;
use League\Flysystem\Adapter\Dropbox;
class ImagesController extends \BaseController {
private $filesystem;
public function __construct(){
if(App::environment() === "local"){
$this->filesystem = new Filesystem(new Adapter( public_path() . '/images/'));
}else{
$client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
$this->filesystem = new Filesystem(new Dropbox($client, '/images/'));
}
}
public function show($name)
{
try{
$file = $this->filesystem->read($name);
}catch (Exception $e){
return Response::json("{}", 404);
}
$response = Response::make($file, 200);
return $response;
}
public function destroy($name)
{
try{
$this->filesystem->delete($name);
return Response::json("{}", 200);
}catch (\Dropbox\Exception $e){
return Response::json("{}", 404);
}
}
}
Формат, который мы обслуживаем, — HTML. Хорошо, это немного странно. Мы хотели обслуживать изображения, а не HTML. Однако это не проблема, потому что браузер ищет формат файла и распознает, как использовать этот файл.
Попробуй создать CategoryController. Я оставил это как упражнение для вас.
Тестирование API
Я должен признать, что я влюблен в PhpStorm, и для тестирования API-интерфейсов Rest я использую инструмент под названием Rest Client. Это графический интерфейс, который упрощает тестирование. Вы также можете использовать CURL из терминала, если хотите. Давайте сделаем несколько тестов:
curl http://localhost:8000/api/v1/users
И вот что возвращается:
С клиентом PhpStorm Rest я получаю тот же результат в JSON.
И если я хочу увидеть результаты в лучшем формате, я могу просто нажать значок js в левой части инструмента, и клиент Rest дает мне более хорошее представление.
Вы также можете проверить другие глаголы, такие как удаление и запись. Идите и протестируйте столько, сколько сможете. Есть и другие клиенты, которые вы можете использовать для тестирования: Rest Console и Postman — два из них. Первый доступен только в Chrome, а второй, Postman, доступен как в Chrome, так и в Firefox. Почтальон кажется проще и удобнее для пользователя. Давай, попробуй.
Вывод
Laravel упрощает нашу работу по созданию REST Apis с контроллерами ресурсов. Мы увидели, как интерфейс должен быть построен с использованием соглашений Ember. Ember выбрал хороший интерфейс, и, следуя этой логике, вы можете легко повторно использовать свой код на других платформах.
В этой части я больше сосредоточился на концепциях и не слишком много писал. Заполнение всех методов и добавление проверки излишне расширило бы этот пост, когда он уже достаточно длинный и в длинной серии. При разработке вы всегда должны проверять ввод. Не забывайте об этом, и тестируйте, тестируйте, тестируйте. Тестирование должно быть твоим лучшим другом.
В последней части этой серии мы соберем все вместе в полнофункциональное живое приложение.