Статьи

Мы создаем Marvel Catalog Reader! Мстители, соберись!

В этом руководстве мы рассмотрим API Marvel , инструмент, предоставленный Marvel разработчикам для получения доступа к более чем 70-летним данным комиксов Marvel.

Марвел логотип

Сначала мы пройдемся по шагам, с помощью которых можно получить ключи, необходимые для отправки запросов к API. Затем мы рассмотрим инструменты, которые мы можем использовать для тестирования API. Наконец, мы собираемся создать сайт, который использует API.

Подписываясь

Первое, что нам нужно сделать, это зайти на сайт разработчика Marvel и щелкнуть ссылку « Получить ключ» . Затем мы получим открытый и закрытый ключи, которые мы можем использовать для выполнения запросов к API. Если вы планируете использовать проект в производстве, вы также можете добавить доменное имя вашего сайта в список авторизованных реферреров. Это обеспечивает уровень безопасности на случай, если вы случайно отправите свои открытые и закрытые ключи на Github.

Важные замечания относительно использования API

Ограничения и правила см. На странице атрибуции, ссылок и ограничений скорости их документации. Также обязательно ознакомьтесь с Условиями использования Marvel API, если вы планируете использовать API в работе. Я резюмировал это ниже:

  • Остерегайтесь ограничения скорости API . На момент написания данного руководства ограничение по тарифу составляло 3000 звонков в день. Это применимо ко всем конечным точкам.
  • Всегда указывайте Marvel в качестве источника при отображении данных и изображений API . Marvel рекомендует использовать этот текст: Данные предоставлены Marvel. © 2016 Marvel.

Игра с API

API поставляется с интерактивной документацией, которая позволяет легко тестировать все доступные конечные точки API.

Есть информация о данных, которые можно ожидать:

ожидаемые данные

… Текстовые поля для указания различных параметров, которые будут отправлены для запроса:

параметры

… Коды ошибок

коды ошибок

… URL запроса, тело ответа, код ответа и заголовки ответа:

запрос и ответ

Обратите внимание, что URL-адрес запроса на самом деле ничего не возвращает, если вы обращаетесь к нему вне инструмента тестирования API. Это потому, что в нем отсутствуют обязательные параметры для запроса. Как минимум, вам нужно будет предоставить открытый ключ, текущую временную метку Unix и хэш md5 временной метки, а также закрытый и открытый ключи вместе.

<?php $ts = time(); $public_key = 'your public key'; $private_key = 'your private key'; $hash = md5($ts . $private_key . $public_key); 

Если у вас есть такие, только тогда вы можете выполнить запрос к API:

 <?php $query_params = [ 'apikey' => $public_key, 'ts' => $ts, 'hash' => $hash ]; //convert array into query parameters $query = http_build_query($query_params); //make the request $response = file_get_contents('http://gateway.marvel.com/v1/public/comics?' . $query); //convert the json string to an array $response_data = json_decode($response, true); 

Мы рассмотрим это более подробно, когда перейдем к разделу, в котором мы начнем делать запросы на приложение, которое будем создавать.

Создание сайта

Надеюсь, вы потратили время, чтобы поиграться с инструментом API, потому что теперь мы собираемся приступить к созданию сайта. Для этого урока мы будем использовать Laravel.

 composer create-project --prefer-dist laravel/laravel marvel-viewer 

Команда выше установит Laravel в каталог marvel-viewer .

Как только это будет сделано, установите Guzzle .

 php composer require guzzlehttp/guzzle 

Создайте файл .env в корне каталога проекта и вставьте следующее:

 APP_ENV=local APP_DEBUG=true APP_KEY=fxxq7HcbypI36Cil5cmOxO7vWFZu0QOD CACHE_DRIVER=file 

Затем создайте файл marvel.php в каталоге config . Затем верните массив, содержащий закрытый и открытый ключ, который вы получили ранее с сайта разработчика Marvel:

 <?php return [ 'private_key' => 'xxx', 'public_key' => 'yyy' ]; 

Это позволяет нам вызывать функцию config для получения этих значений:

 echo config('marvel.private_key'); echo config('marvel.public_key'); 

Результаты API кэширования

На веб-сайте разработчика Marvel один из советов, как оставаться в рамках ограничений, заключается в кэшировании результатов. Это делает приложение более производительным, особенно для вызовов API, которые возвращают много данных.

Для начала давайте создадим новую консольную команду, используя artisan:

 php artisan make:console CacheApi 

Это создаст файл app/Console/Commands/CacheApi.php . Откройте это и измените значения для подписи и описания следующим образом:

 protected $signature = 'cache:api'; protected $description = 'Saves data from marvel API into the cache'; 

Чтобы зарегистрировать команду для использования, откройте app/Console/Kernel.php и добавьте ее в качестве элемента в массиве $commands .

 protected $commands = [ Commands\Inspire::class, Commands\CacheApi::class // add this ]; 

Это позволит нам выполнить его в командной строке следующим образом:

 php artisan cache:api 

Чтобы определить, что будет делать команда, вернитесь в файл CacheApi.php и добавьте следующее в функцию handle :

 $ts = time(); $hash = md5($ts . config('marvel.private_key') . config('marvel.public_key')); $client = new Client([ 'base_uri' => 'http://gateway.marvel.com/v1/public/', 'query' => [ 'apikey' => config('marvel.public_key'), 'ts' => $ts, 'hash' => $hash ] ]); $endpoints = [ 'characters', 'comics' ]; $results_per_page = 20; $total_page_count = 10; $minutes_to_cache = 1440; // 1 day foreach($endpoints as $ep){ $data = []; for($x = 0; $x <= $total_page_count; $x++){ $query = $client->getConfig('query'); $query['offset'] = $results_per_page * $x; $response = $client->get('http://gateway.marvel.com/v1/public/' . $ep, ['query' => $query]); $response = json_decode($response->getBody(), true); $current_data = $response['data']['results']; $data = array_merge($data, $current_data); } Cache::put($ep, $data, $minutes_to_cache); } 

Давайте разберемся с этим. Сначала мы назначаем переменную всем данным, требуемым API Marvel. Первая — текущая временная метка Unix ( $ts ), а вторая — хэш md5 объединенных значений $ts , секретный ключ Marvel и открытый ключ.

 $ts = time(); $hash = md5($ts . config('marvel.private_key') . config('marvel.public_key')); 

Затем создается новый клиент Guzzle, в котором мы предоставляем базовый URL, используемый API, и данные запрашивают все запросы.

 $client = new Client([ 'base_uri' => 'http://gateway.marvel.com/v1/public/', 'query' => [ 'apikey' => config('marvel.public_key'), 'ts' => $ts, 'hash' => $hash ] ]); 

Определите конечные точки, чьи данные будут кэшироваться. В этом случае нам нужно только кэшировать данные персонажей и комиксы.

 $endpoints = [ 'characters', 'comics' ]; 

Определите итоговые результаты, которые должны быть возвращены API для каждого запроса, общее количество страниц, запрашиваемых для каждой конечной точки, и общее количество минут, за которое необходимо кэшировать результаты. В этом случае мы хотим, чтобы он хранился только один день.

 $results_per_page = 20; $total_page_count = 10; // there's a bazillion characters and comics, we don't want to take all day $minutes_to_cache = 1440; // 1 day 

Перебрать все конечные точки. Для каждой конечной точки у нас есть цикл for который будет выполняться на основе значения для $total_page_count . Для каждой итерации мы получаем текущую конфигурацию запроса и назначаем offset . На первой итерации это будет 0 ( 20 * 0 ), на второй — 20 ( 20 * 1 ) и так далее. Затем мы делаем запрос к конечной точке, преобразуем объект ответа в массив, а затем объединяем результат с массивом $data . Наконец, мы помещаем данные в кеш.

 foreach($endpoints as $ep){ $data = []; for($x = 0; $x <= $total_page_count; $x++){ $query = $client->getConfig('query'); $query['offset'] = $results_per_page * $x; $response = $client->get('http://gateway.marvel.com/v1/public/' . $ep, ['query' => $query]); $response = json_decode($response->getBody(), true); $current_data = $response['data']['results']; $data = array_merge($data, $current_data); } Cache::put($ep, $data, $minutes_to_cache); } 

Как только это будет сделано, мы можем выполнить команду, чтобы начать кэширование:

 php artisan cache:api 

Маршруты

Приложение будет иметь только три страницы: страницу для просмотра случайных комиксов, страницу для просмотра сведений о комиксе и страницу для просмотра различных персонажей.

 <?php Route::get('/comics', 'HomeController@comics'); Route::get('/comics/{id}', 'HomeController@comic'); Route::get('/characters', 'HomeController@characters'); 

контроллер

Контроллер обрабатывает запросы, поступающие с маршрутов. Выполните следующую команду, чтобы сгенерировать файл контроллера:

 php artisan make:controller HomeController 

Это создает файл app/Http/Controllers/HomeController.php . Добавьте к этому следующее:

 <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use GuzzleHttp\Client; use Cache; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; class HomeController extends Controller { private $client; public function __construct(){ $ts = time(); $hash = md5($ts . config('marvel.private_key') . config('marvel.public_key')); $this->client = new Client([ 'base_uri' => 'http://gateway.marvel.com/v1/public/', 'query' => [ 'apikey' => config('marvel.public_key'), 'ts' => $ts, 'hash' => $hash ] ]); } public function comics(Request $request){ $search_term = ''; if($request->has('query')){ $search_term = $request->input('query'); $query = $this->client->getConfig('query'); $query['titleStartsWith'] = $search_term; $response = $this->client->get('comics', ['query' => $query]); $response = json_decode($response->getBody(), true); $comics = $response['data']['results']; }else{ $comics = Cache::get('comics'); shuffle($comics); $comics = array_slice($comics, 0, 20); } return view('comics', ['comics' => $comics, 'query' => $search_term]); } public function comic($id){ $page_data = []; $response = $this->client->get('comics/' . $id); $response = json_decode($response->getBody(), true); $comic = $response['data']['results'][0]; $page_data['comic'] = $comic; if(!empty($comic['series'])){ $series_response = $this->client->get($comic['series']['resourceURI']); $series_response = json_decode($series_response->getBody(), true); $page_data['series'] = $series_response['data']['results'][0]; } return view('comic', $page_data); } public function characters(Request $request){ $characters = Cache::get('characters'); $current_page = LengthAwarePaginator::resolveCurrentPage(); if(is_null($current_page)){ $current_page = 1; } $characters_collection = new Collection($characters); $items_per_page = 8; $current_page_results = $characters_collection->slice(($current_page - 1) * $items_per_page, $items_per_page)->all(); $paginated_results = new LengthAwarePaginator($current_page_results, count($characters_collection), $items_per_page); return view('characters', ['paginated_results' => $paginated_results, 'characters' => $characters]); } } 

Разбивая его, сначала мы определяем и инициализируем клиент Guzzle. Это почти то же самое, что мы делали ранее в CacheApi.php .

 private $client; public function __construct(){ $ts = time(); $hash = md5($ts . config('marvel.private_key') . config('marvel.public_key')); $this->client = new Client([ 'base_uri' => 'http://gateway.marvel.com/v1/public/', 'query' => [ 'apikey' => config('marvel.public_key'), 'ts' => $ts, 'hash' => $hash ] ]); } 

Функция comics отвечает за обслуживание страницы комиксов. Если запрос не предоставлен, в нем перечислены случайные элементы, извлеченные из кэша. Если запрос предоставлен, он отправит запрос API, используя этот запрос. Здесь мы предоставляем параметр titleStartsWith а затем titleStartsWith пользовательский ввод. Если вам повезет, вы также можете использовать параметр title который возвращает только прямое соответствие пользовательского ввода.

 public function comics(Request $request){ $search_term = ''; if($request->has('query')){ $search_term = $request->input('query'); $query = $this->client->getConfig('query'); $query['titleStartsWith'] = $search_term; //supply the query //make a request to the api $response = $this->client->get('comics', ['query' => $query]); $response = json_decode($response->getBody(), true); //convert response object to array $comics = $response['data']['results']; //extract the item/s }else{ $comics = Cache::get('comics'); //get items from the cache shuffle($comics); //jumble the array $comics = array_slice($comics, 0, 20); //extract the first 20 items from the jumbled array } //return the page with the data return view('comics', ['comics' => $comics, 'query' => $search_term]); } 

Функция comic отвечает за обслуживание подробных комических страниц. При этом по-прежнему используется конечная точка comics , но на этот раз к концу URL добавляется комический идентификатор, поэтому он запрашивает только один элемент из API. После того, как отдельные комические данные были выбраны, он использует элемент series чтобы получить больше деталей для этой конкретной серии. Думайте о серии как о коллекции историй. Мы используем это для получения данных, таких как создатели, персонажи и истории. Вы увидите, как эти данные используются позже в разделе представлений.

 public function comic($id){ $page_data = []; //get a specific comic $response = $this->client->get('comics/' . $id); $response = json_decode($response->getBody(), true); $comic = $response['data']['results'][0]; $page_data['comic'] = $comic; if(!empty($comic['series'])){ //get series data $series_response = $this->client->get($comic['series']['resourceURI']); $series_response = json_decode($series_response->getBody(), true); $page_data['series'] = $series_response['data']['results'][0]; } return view('comic', $page_data); } 

Функция characters отвечает за обслуживание страницы символов. В отличие от comics и comic функций, это напрямую не запрашивает данные у API. Используются только те символьные данные, которые были сохранены в кеше. Кроме того, он не выбирает случайные предметы, а разбивает на страницы все доступные предметы.

 public function characters(Request $request){ $characters = Cache::get('characters'); $current_page = LengthAwarePaginator::resolveCurrentPage(); if(is_null($current_page)){ $current_page = 1; } $characters_collection = new Collection($characters); $items_per_page = 8; $current_page_results = $characters_collection->slice(($current_page - 1) * $items_per_page, $items_per_page)->all(); $paginated_results = new LengthAwarePaginator($current_page_results, count($characters_collection), $items_per_page); return view('characters', ['paginated_results' => $paginated_results, 'characters' => $characters]); } 

Давайте разберемся с этим — сначала мы получаем символы из кэша:

 $characters = Cache::get('characters'); 

Получить текущую страницу пагинатора:

 $current_page = LengthAwarePaginator::resolveCurrentPage(); 

Если их нет, мы предполагаем, что это первая страница:

 if(is_null($current_page)){ $current_page = 1; } 

Создайте коллекцию из массива символов. Это позволяет нам манипулировать данными более удобным способом. Он в основном превращает ваш массив в нечто похожее на результаты, которые вы получаете при использовании Eloquent или построителя запросов.

 $characters_collection = new Collection($characters); 

Укажите количество элементов для отображения на странице.

 $items_per_page = 8; 

Извлеките элементы для текущей страницы. Здесь мы вызываем метод slice для извлечения фрагмента массива.

 $current_page_results = $characters_collection ->slice(($current_page - 1) * $items_per_page, $items_per_page) ->all(); 

Создайте постраничные результаты.

 $paginated_results = new LengthAwarePaginator($current_page_results, count($characters_collection), $items_per_page); 

Взгляды

У нас есть только четыре представления: представление комиксов для отображения случайного списка комиксов, представление комиксов, отображающее один комикс, представление символов, отображающее список символов, и родительское представление, от которого наследуются все другие представления.

Comics View

В комиксном представлении есть форма для ввода пользовательского запроса, а под формой — результаты. Для каждого элемента отображаются фотография, заголовок и описание комикса.

 @extends('layouts.default') @section('content') <div id="content"> <h2>Comics</h2> <form action="/comics"> <p> <label for="query">Query</label> <input type="text" name="query" id="query" value="{{ $query }}"> <button>Search</button> </p> </form> <div id="comics" class="results"> @foreach($comics as $com) <article class="card"> <img src="{{ $com['thumbnail']['path'] }}/portrait_incredible.jpg" alt="{{ $com['title'] }} thumbnail"> <footer> <h5> <a href="/comics/{{ $com['id'] }}" class="card-title">{{ $com['title'] }}</a> </h5> <p> {{ str_limit($com['description'], 160) }} </p> </footer> </article> @endforeach </div> </div> @stop 

Ссылка на фотографию создается с использованием пути миниатюр, затем мы объединяем тип изображения, которое мы хотим. Здесь мы используем вариант Portrait_incredible. Существует много других вариантов изображений — ознакомьтесь с документацией по изображениям, если хотите узнать больше.

 <img src="{{ $com['thumbnail']['path'] }}/portrait_incredible.jpg" alt="{{ $com['title'] }} thumbnail"> 

Заголовок ссылается на отдельную страницу комиксов:

 <h5> <a href="/comics/{{ $com['id'] }}" class="card-title">{{ $com['title'] }}</a> </h5> 

Описание урезано, поэтому оно не мешает стилю для более длинных описаний:

 <p> {{ str_limit($com['description'], 160) }} </p> 

Вот как будет выглядеть страница комиксов:

страница комиксов

Комический вид

В комическом представлении отображаются заголовок, полное описание, заголовок серии, год начала и окончания серии, рейтинг, создатели, персонажи и истории в серии.

 @extends('layouts.default') @section('content') <div id="content"> <div class="results"> <article> <img src="{{ $comic['thumbnail']['path'] }}.jpg" alt="{{ $comic['title'] }} thumbnail"> <h2>{{ $comic['title'] }}</h2> <p> {{ $comic['description'] }} </p> <div id="series"> <h3>From Series: {{ $series['title'] }}</h3> <div class="years"> <span>Start year: {{ $series['startYear'] }}</span> <span>End year: {{ $series['endYear'] }}</span> </div> <div class="rating"> Rating: {{ $series['rating'] }} </div> @if($series['creators']['available'] > 0) <div class="creators"> <h4>Creators</h4> <ul> @foreach($series['creators']['items'] as $creator) <li>{{ $creator['name'] }} ({{ $creator['role'] }})</li> @endforeach </ul> </div> @endif @if($series['characters']['available'] > 0) <div class="characters"> <h4>Characters</h4> <ul> @foreach($series['characters']['items'] as $character) <li>{{ $character['name'] }}</li> @endforeach </ul> </div> @endif @if($series['stories']['available'] > 0) <div class="stories"> <h4>Stories</h4> <ul> @foreach($series['stories']['items'] as $story) <li> {{ $story['name'] }} <br> type: {{ $story['type'] }} </li> @endforeach </ul> </div> @endif </div> </footer> </article> </div> </div> @stop 

Вот как будет выглядеть комическая страница:

комическая страница

Просмотр персонажей

В представлении символов отображаются символы Marvel в виде страниц. Для каждого персонажа мы показываем только фотографию и заголовок. Заголовок ссылается на вики-страницу персонажа, если она доступна. Ниже приведены результаты разбивки на страницы.

 @extends('layouts.default') @section('content') <div id="content"> <h2>Characters</h2> <div id="characters" class="results"> @foreach($paginated_results as $char) <article class="card"> <img src="{{ $char['thumbnail']['path'] }}/portrait_incredible.jpg" alt="{{ $char['name'] }} thumbnail"> <footer> <h5> <a href="{{ getCharacterURL($char) }}" class="card-title">{{ $char['name'] }}</a> </h5> </footer> </article> @endforeach </div> <div id="pagination"> {{ $paginated_results->setPath('characters')->render() }} </div> </div> @stop 

Вот как будет выглядеть страница с персонажами:

страница персонажей

Родительский вид

Создайте файл layouts/default.blade.php каталоге resources/views и добавьте следующий код:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Marvel Viewer</title> <link rel="stylesheet" href="{{ url('lib/picnic/releases/picnic.min.css') }}"> <link rel="stylesheet" href="{{ url('css/style.css') }}"> </head> <body> <div id="wrapper"> <div id="header"> <h1>Marvel Viewer</h1> </div> @yield('content') </div> </body> </html> 

Родительское представление возвращает основное содержание. Все, что мы определили в разделе content дочерних представлений, которые мы использовали ранее, будет обработано. Здесь мы также связываем таблицу стилей, используемую веб-сайтом.

помощник

Ранее мы использовали функцию getCharacterURL в представлении символов, но мы еще не определили ее. Чтобы определить его, создайте файл helpers.php в каталоге app и добавьте следующий код:

 <?php function getCharacterURL($character){ $urls = []; if(!empty($character['urls'])){ foreach($character['urls'] as $curl){ $urls[$curl['type']] = $curl['url']; } } return (!empty($urls['wiki'])) ? $urls['wiki'] : '#'; } 

Все, что делает эта функция, это перебирает все доступные символьные URL и затем возвращает вики-URL, если он доступен, или символ фунта, если нет.

Затем откройте файл composer.json и добавьте его в массив files в объекте autoload :

 "autoload": { "classmap": [ "database" ], "psr-4": { "App\\": "app/" }, "files": [ "app/helpers.php" ] }, 

Выполните composer dump-autoload чтобы изменения вступили в силу.

Стили

Теперь пришло время добавить немного CSS, чтобы приложение выглядело прилично.

Родительское представление использует picnic.css, чтобы все выглядело лучше. Вы можете скачать файл css здесь и поместить его в указанный каталог, либо использовать bower или (предпочтительно) BowerPHP для его установки.

Вот файл .bowerrc который нужно поместить в корневой каталог проекта:

 { "directory": "public/lib" } 

Затем создайте файл style.css каталоге public/css и добавьте следующий код:

 #wrapper { width: 900px; margin: 0 auto; } #query { width: 700px; } .card { width: 200px; float: left; margin-left: 10px; margin-right: 10px; margin-bottom: 25px; } #comics .card { height: 650px; } #characters .card { height: 450px; } .card p { font-size: 15px; color: #676767; } .card h5 { line-height: 18px; } .results { overflow: auto; } .pagination li { display: inline-block; padding: 30px; list-style: none; } 

Вывод

Из этого руководства вы узнали, как работать с API Marvel, чтобы получать данные о различных комиксах и персонажах, созданных Marvel за 70 с лишним лет своего существования. Вы можете получить доступ к исходному коду, используемому в этом руководстве, в этом репозитории Github .

Вы создали что-нибудь интересное с их API? У вас есть идеи для некоторых крутых приложений на базе Marvel? Дайте нам знать об этом в комментариях!