Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
57 changed files with 429 additions and 149 deletions
Showing only changes of commit f04cc7d1d5 - Show all commits

View File

@ -24,7 +24,7 @@
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.jpg") ?>" alt="" /> <img src="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.jpg") ?>" alt="" />
<div class="name"> <div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
<?= array_shift($item['anime']['titles']) ?> <?= $item['anime']['title'] ?>
<?php foreach ($item['anime']['titles'] as $title): ?> <?php foreach ($item['anime']['titles'] as $title): ?>
<br /><small><?= $title ?></small> <br /><small><?= $title ?></small>
<?php endforeach ?> <?php endforeach ?>
@ -85,7 +85,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="media_type"><?= $escape->html($item['anime']['type']) ?></div> <div class="media_type"><?= $escape->html($item['anime']['show_type']) ?></div>
<div class="airing_status"><?= $escape->html($item['airing']['status']) ?></div> <div class="airing_status"><?= $escape->html($item['airing']['status']) ?></div>
<div class="age_rating"><?= $escape->html($item['anime']['age_rating']) ?></div> <div class="age_rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
</div> </div>

View File

@ -38,7 +38,7 @@
</table> </table>
</div> </div>
<div> <div>
<h2><a rel="external" href="<?= $show_data['url'] ?>"><?= array_shift($show_data['titles']) ?></a></h2> <h2><a rel="external" href="<?= $show_data['url'] ?>"><?= $show_data['title'] ?></a></h2>
<?php foreach ($show_data['titles'] as $title): ?> <?php foreach ($show_data['titles'] as $title): ?>
<h3><?= $title ?></h3> <h3><?= $title ?></h3>
<?php endforeach ?> <?php endforeach ?>

View File

@ -6,7 +6,7 @@
<thead> <thead>
<tr> <tr>
<th> <th>
<h3><?= $escape->html(array_shift($item['anime']['titles'])) ?></h3> <h3><?= $escape->html($item['anime']['title']) ?></h3>
<?php foreach($item['anime']['titles'] as $title): ?> <?php foreach($item['anime']['titles'] as $title): ?>
<h4><?= $escape->html($title) ?></h4> <h4><?= $escape->html($title) ?></h4>
<?php endforeach ?> <?php endforeach ?>

View File

@ -42,15 +42,15 @@
<?php endif ?> <?php endif ?>
<td class="justify"> <td class="justify">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
<?= array_shift($item['anime']['titles']) ?> <?= $item['anime']['title'] ?>
</a> </a>
<?php foreach($item['anime']['titles'] as $title): ?> <?php foreach ($item['anime']['titles'] as $title): ?>
<br /><?= $title ?> <br/><?= $title ?>
<?php endforeach ?> <?php endforeach ?>
</td> </td>
<td><?= $item['airing']['status'] ?></td> <td><?= $item['airing']['status'] ?></td>
<td><?= $item['user_rating'] ?> / 10 </td> <td><?= $item['user_rating'] ?> / 10 </td>
<td><?= $item['anime']['type'] ?></td> <td><?= $item['anime']['show_type'] ?></td>
<td id="<?= $item['anime']['slug'] ?>"> <td id="<?= $item['anime']['slug'] ?>">
Episodes: <br /> Episodes: <br />
<span class="completed_number"><?= $item['episodes']['watched'] ?></span>&nbsp;/&nbsp;<span class="total_number"><?= $item['episodes']['total'] ?></span> <span class="completed_number"><?= $item['episodes']['watched'] ?></span>&nbsp;/&nbsp;<span class="total_number"><?= $item['episodes']['total'] ?></span>
@ -83,8 +83,8 @@
<p><?= $escape->html($item['notes']) ?></p> <p><?= $escape->html($item['notes']) ?></p>
</td> </td>
<td class="align_left"> <td class="align_left">
<?php sort($item['anime']['genres']) ?> <?php sort($item['anime']->genres) ?>
<?= implode(', ', $item['anime']['genres']) ?> <?= implode(', ', $item['anime']->genres) ?>
</td> </td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>

View File

@ -39,6 +39,7 @@
"phpmd/phpmd": "^2.4", "phpmd/phpmd": "^2.4",
"phpstan/phpstan": "^0.9.1", "phpstan/phpstan": "^0.9.1",
"phpunit/phpunit": "^6.0", "phpunit/phpunit": "^6.0",
"roave/security-advisories": "dev-master",
"robmorgan/phinx": "^0.9.1", "robmorgan/phinx": "^0.9.1",
"sebastian/phpcpd": "^3.0", "sebastian/phpcpd": "^3.0",
"spatie/phpunit-snapshot-assertions": "^1.2.0", "spatie/phpunit-snapshot-assertions": "^1.2.0",

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
class Kitsu extends Enum { final class Kitsu extends Enum {
const WATCHING = 'current'; const WATCHING = 'current';
const PLAN_TO_WATCH = 'planned'; const PLAN_TO_WATCH = 'planned';
const ON_HOLD = 'on_hold'; const ON_HOLD = 'on_hold';

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
class MAL extends Enum { final class MAL extends Enum {
const WATCHING = 1; const WATCHING = 1;
const COMPLETED = 2; const COMPLETED = 2;
const ON_HOLD = 3; const ON_HOLD = 3;

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum as Enum;
/** /**
* Possible values for current watching status of anime * Possible values for current watching status of anime
*/ */
class Route extends Enum { final class Route extends Enum {
const ALL = 'all'; const ALL = 'all';
const WATCHING = 'watching'; const WATCHING = 'watching';
const PLAN_TO_WATCH = 'plan_to_watch'; const PLAN_TO_WATCH = 'plan_to_watch';

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum as Enum;
/** /**
* Possible values for current watching status of anime * Possible values for current watching status of anime
*/ */
class Title extends Enum { final class Title extends Enum {
const ALL = 'All'; const ALL = 'All';
const WATCHING = 'Currently Watching'; const WATCHING = 'Currently Watching';
const PLAN_TO_WATCH = 'Plan to Watch'; const PLAN_TO_WATCH = 'Plan to Watch';

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
class Kitsu extends Enum { final class Kitsu extends Enum {
const READING = 'current'; const READING = 'current';
const PLAN_TO_READ = 'planned'; const PLAN_TO_READ = 'planned';
const DROPPED = 'dropped'; const DROPPED = 'dropped';

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
class MAL extends Enum { final class MAL extends Enum {
const READING = 'reading'; const READING = 'reading';
const COMPLETED = 'completed'; const COMPLETED = 'completed';
const ON_HOLD = 'onhold'; const ON_HOLD = 'onhold';

View File

@ -16,12 +16,12 @@
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
use Aviat\Ion\Enum as Enum; use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
class Route extends Enum { final class Route extends Enum {
const ALL = 'all'; const ALL = 'all';
const READING = 'reading'; const READING = 'reading';
const PLAN_TO_READ = 'plan_to_read'; const PLAN_TO_READ = 'plan_to_read';

View File

@ -16,12 +16,12 @@
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
use Aviat\Ion\Enum as Enum; use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
class Title extends Enum { final class Title extends Enum {
const ALL = 'All'; const ALL = 'All';
const READING = 'Currently Reading'; const READING = 'Currently Reading';
const PLAN_TO_READ = 'Plan to Read'; const PLAN_TO_READ = 'Plan to Read';

View File

@ -19,7 +19,7 @@ namespace Aviat\AnimeClient\API;
/** /**
* Class encapsulating Json API data structure for a request or response * Class encapsulating Json API data structure for a request or response
*/ */
class JsonAPI { final class JsonAPI {
/** /**
* The full data array * The full data array

View File

@ -22,7 +22,7 @@ use DateTimeImmutable;
/** /**
* Data massaging helpers for the Kitsu API * Data massaging helpers for the Kitsu API
*/ */
class Kitsu { final class Kitsu {
const AUTH_URL = 'https://kitsu.io/api/oauth/token'; const AUTH_URL = 'https://kitsu.io/api/oauth/token';
const AUTH_USER_ID_KEY = 'kitsu-auth-userid'; const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token'; const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';

View File

@ -28,7 +28,7 @@ use Exception;
/** /**
* Kitsu API Authentication * Kitsu API Authentication
*/ */
class Auth { final class Auth {
use CacheTrait; use CacheTrait;
use ContainerAware; use ContainerAware;
@ -37,14 +37,14 @@ class Auth {
* *
* @var Model * @var Model
*/ */
protected $model; private $model;
/** /**
* Session object * Session object
* *
* @var \Aura\Session\Segment * @var \Aura\Session\Segment
*/ */
protected $segment; private $segment;
/** /**
* Constructor * Constructor

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Enum as BaseEnum;
/** /**
* Status of when anime is being/was/will be aired * Status of when anime is being/was/will be aired
*/ */
class AnimeAiringStatus extends BaseEnum { final class AnimeAiringStatus extends BaseEnum {
const NOT_YET_AIRED = 'Not Yet Aired'; const NOT_YET_AIRED = 'Not Yet Aired';
const AIRING = 'Currently Airing'; const AIRING = 'Currently Airing';
const FINISHED_AIRING = 'Finished Airing'; const FINISHED_AIRING = 'Finished Airing';

View File

@ -18,7 +18,7 @@ namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\APIRequestBuilder;
class KitsuRequestBuilder extends APIRequestBuilder { final class KitsuRequestBuilder extends APIRequestBuilder {
/** /**
* The base url for api requests * The base url for api requests

View File

@ -25,39 +25,17 @@ use Aviat\AnimeClient\API\{
HummingbirdClient, HummingbirdClient,
ListItemInterface ListItemInterface
}; };
use Aviat\AnimeClient\Types\AbstractType;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json; use Aviat\Ion\Json;
/** /**
* CRUD operations for Kitsu list items * CRUD operations for Kitsu list items
*/ */
class ListItem implements ListItemInterface { final class ListItem implements ListItemInterface {
use ContainerAware; use ContainerAware;
use KitsuTrait; use KitsuTrait;
private function getAuthHeader()
{
$cache = $this->getContainer()->get('cache');
$cacheItem = $cache->getItem('kitsu-auth-token');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== NULL)
{
$token = $sessionSegment->get('auth_token');
return "bearer {$token}";
}
if ($cacheItem->isHit())
{
$token = $cacheItem->get();
return "bearer {$token}";
}
return FALSE;
}
public function create(array $data): Request public function create(array $data): Request
{ {
$body = [ $body = [
@ -134,7 +112,7 @@ class ListItem implements ListItemInterface {
return Json::decode(wait($response->getBody())); return Json::decode(wait($response->getBody()));
} }
public function update(string $id, array $data): Request public function update(string $id, AbstractType $data): Request
{ {
$authHeader = $this->getAuthHeader(); $authHeader = $this->getAuthHeader();
$requestData = [ $requestData = [
@ -155,4 +133,25 @@ class ListItem implements ListItemInterface {
return $request->getFullRequest(); return $request->getFullRequest();
} }
private function getAuthHeader()
{
$cache = $this->getContainer()->get('cache');
$cacheItem = $cache->getItem('kitsu-auth-token');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== NULL) {
$token = $sessionSegment->get('auth_token');
return "bearer {$token}";
}
if ($cacheItem->isHit()) {
$token = $cacheItem->get();
return "bearer {$token}";
}
return FALSE;
}
} }

View File

@ -36,17 +36,22 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{
MangaTransformer, MangaTransformer,
MangaListTransformer MangaListTransformer
}; };
use Aviat\AnimeClient\Types\{
Anime,
AnimeFormItem,
AnimeListItem
};
use Aviat\Ion\{Di\ContainerAware, Json}; use Aviat\Ion\{Di\ContainerAware, Json};
/** /**
* Kitsu API Model * Kitsu API Model
*/ */
class Model { final class Model {
use CacheTrait; use CacheTrait;
use ContainerAware; use ContainerAware;
use KitsuTrait; use KitsuTrait;
protected const LIST_PAGE_SIZE = 100; private const LIST_PAGE_SIZE = 100;
/** /**
* Class to map anime list items * Class to map anime list items
@ -55,27 +60,27 @@ class Model {
* *
* @var AnimeListTransformer * @var AnimeListTransformer
*/ */
protected $animeListTransformer; private $animeListTransformer;
/** /**
* @var AnimeTransformer * @var AnimeTransformer
*/ */
protected $animeTransformer; private $animeTransformer;
/** /**
* @var ListItem * @var ListItem
*/ */
protected $listItem; private $listItem;
/** /**
* @var MangaTransformer * @var MangaTransformer
*/ */
protected $mangaTransformer; private $mangaTransformer;
/** /**
* @var MangaListTransformer * @var MangaListTransformer
*/ */
protected $mangaListTransformer; private $mangaListTransformer;
/** /**
* Constructor * Constructor
@ -313,15 +318,15 @@ class Model {
* Get information about a particular anime * Get information about a particular anime
* *
* @param string $slug * @param string $slug
* @return array * @return Anime
*/ */
public function getAnime(string $slug): array public function getAnime(string $slug): Anime
{ {
$baseData = $this->getRawMediaData('anime', $slug); $baseData = $this->getRawMediaData('anime', $slug);
if (empty($baseData)) if (empty($baseData))
{ {
return []; return new Anime();
} }
$transformed = $this->animeTransformer->transform($baseData); $transformed = $this->animeTransformer->transform($baseData);
@ -803,9 +808,9 @@ class Model {
* Get the data for a specific list item, generally for editing * Get the data for a specific list item, generally for editing
* *
* @param string $listId - The unique identifier of that list item * @param string $listId - The unique identifier of that list item
* @return array * @return mixed
*/ */
public function getListItem(string $listId): array public function getListItem(string $listId)
{ {
$baseData = $this->listItem->get($listId); $baseData = $this->listItem->get($listId);
$included = JsonAPI::organizeIncludes($baseData['included']); $included = JsonAPI::organizeIncludes($baseData['included']);
@ -813,12 +818,12 @@ class Model {
switch (TRUE) switch (TRUE)
{ {
case in_array('anime', array_keys($included)): case array_key_exists('anime', $included): // in_array('anime', array_keys($included)):
$included = JsonAPI::inlineIncludedRelationships($included, 'anime'); $included = JsonAPI::inlineIncludedRelationships($included, 'anime');
$baseData['data']['included'] = $included; $baseData['data']['included'] = $included;
return $this->animeListTransformer->transform($baseData['data']); return $this->animeListTransformer->transform($baseData['data']);
case in_array('manga', array_keys($included)): case array_key_exists('manga', $included): // in_array('manga', array_keys($included)):
$included = JsonAPI::inlineIncludedRelationships($included, 'manga'); $included = JsonAPI::inlineIncludedRelationships($included, 'manga');
$baseData['data']['included'] = $included; $baseData['data']['included'] = $included;
$baseData['data']['manga'] = $baseData['included'][0]; $baseData['data']['manga'] = $baseData['included'][0];
@ -832,10 +837,10 @@ class Model {
/** /**
* Modify a list item * Modify a list item
* *
* @param array $data * @param AnimeFormItem $data
* @return Request * @return Request
*/ */
public function updateListItem(array $data): Request public function updateListItem(AnimeFormItem $data): Request
{ {
return $this->listItem->update($data['id'], $data['data']); return $this->listItem->update($data['id'], $data['data']);
} }

View File

@ -17,21 +17,27 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Kitsu; use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{
Anime,
AnimeFormItem,
AnimeFormItemData,
AnimeListItem
};
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for anime list * Transformer for anime list
*/ */
class AnimeListTransformer extends AbstractTransformer { final class AnimeListTransformer extends AbstractTransformer {
/** /**
* Convert raw api response to a more * Convert raw api response to a more
* logical and workable structure * logical and workable structure
* *
* @param array $item API library item * @param array $item API library item
* @return array * @return AnimeListItem
*/ */
public function transform($item): array public function transform($item): AnimeListItem
{ {
$included = $item['included']; $included = $item['included'];
$animeId = $item['relationships']['media']['data']['id']; $animeId = $item['relationships']['media']['data']['id'];
@ -66,7 +72,10 @@ class AnimeListTransformer extends AbstractTransformer {
? Kitsu::parseListItemStreamingLinks($included, $animeId) ? Kitsu::parseListItemStreamingLinks($included, $animeId)
: []; : [];
return [ $titles = Kitsu::filterTitles($anime);
$title = array_shift($titles);
return new AnimeListItem([
'id' => $item['id'], 'id' => $item['id'],
'mal_id' => $MALid, 'mal_id' => $MALid,
'episodes' => [ 'episodes' => [
@ -81,24 +90,24 @@ class AnimeListTransformer extends AbstractTransformer {
'started' => $anime['startDate'], 'started' => $anime['startDate'],
'ended' => $anime['endDate'] 'ended' => $anime['endDate']
], ],
'anime' => [ 'anime' => new Anime([
'id' => $animeId, 'id' => $animeId,
'age_rating' => $anime['ageRating'], 'age_rating' => $anime['ageRating'],
'title' => $anime['canonicalTitle'], 'title' => $title,
'titles' => Kitsu::filterTitles($anime), 'titles' => $titles,
'slug' => $anime['slug'], 'slug' => $anime['slug'],
'type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(), 'show_type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
'image' => $anime['posterImage']['small'], 'cover_image' => $anime['posterImage']['small'],
'genres' => $genres, 'genres' => $genres,
'streaming_links' => $streamingLinks, 'streaming_links' => $streamingLinks,
], ]),
'watching_status' => $item['attributes']['status'], 'watching_status' => $item['attributes']['status'],
'notes' => $item['attributes']['notes'], 'notes' => $item['attributes']['notes'],
'rewatching' => (bool) $item['attributes']['reconsuming'], 'rewatching' => (bool) $item['attributes']['reconsuming'],
'rewatched' => (int) $item['attributes']['reconsumeCount'], 'rewatched' => (int) $item['attributes']['reconsumeCount'],
'user_rating' => $rating, 'user_rating' => $rating,
'private' => $item['attributes']['private'] ?? FALSE, 'private' => $item['attributes']['private'] ?? FALSE,
]; ]);
} }
/** /**
@ -106,24 +115,24 @@ class AnimeListTransformer extends AbstractTransformer {
* api response format * api response format
* *
* @param array $item Transformed library item * @param array $item Transformed library item
* @return array API library item * @return AnimeFormItem API library item
*/ */
public function untransform($item): array public function untransform($item): AnimeFormItem
{ {
$privacy = (array_key_exists('private', $item) && $item['private']); $privacy = (array_key_exists('private', $item) && $item['private']);
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']); $rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
$untransformed = [ $untransformed = new AnimeFormItem([
'id' => $item['id'], 'id' => $item['id'],
'mal_id' => $item['mal_id'] ?? NULL, 'mal_id' => $item['mal_id'] ?? NULL,
'data' => [ 'data' => new AnimeFormItemData([
'status' => $item['watching_status'], 'status' => $item['watching_status'],
'reconsuming' => $rewatching, 'reconsuming' => $rewatching,
'reconsumeCount' => $item['rewatched'], 'reconsumeCount' => $item['rewatched'],
'notes' => $item['notes'], 'notes' => $item['notes'],
'private' => $privacy 'private' => $privacy
] ])
]; ]);
if (is_numeric($item['episodes_watched']) && $item['episodes_watched'] > 0) if (is_numeric($item['episodes_watched']) && $item['episodes_watched'] > 0)
{ {

View File

@ -17,31 +17,32 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\{JsonAPI, Kitsu}; use Aviat\AnimeClient\API\{JsonAPI, Kitsu};
use Aviat\AnimeClient\Types\Anime;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for anime description page * Transformer for anime description page
*/ */
class AnimeTransformer extends AbstractTransformer { final class AnimeTransformer extends AbstractTransformer {
/** /**
* Convert raw api response to a more * Convert raw api response to a more
* logical and workable structure * logical and workable structure
* *
* @param array $item API library item * @param array $item API library item
* @return array * @return Anime
*/ */
public function transform($item): array public function transform($item): Anime
{ {
$item['included'] = JsonAPI::organizeIncludes($item['included']); $item['included'] = JsonAPI::organizeIncludes($item['included']);
$genres = $item['included']['categories'] ?? []; $genres = $item['included']['categories'] ?? [];
$item['genres'] = array_column($genres, 'title') ?? []; $item['genres'] = array_column($genres, 'title') ?? [];
sort($item['genres']); sort($item['genres']);
$titles = Kitsu::filterTitles($item); $titles = Kitsu::filterTitles($item);
$title = array_shift($titles);
return [ return new Anime([
'age_rating' => $item['ageRating'], 'age_rating' => $item['ageRating'],
'age_rating_guide' => $item['ageRatingGuide'], 'age_rating_guide' => $item['ageRatingGuide'],
'cover_image' => $item['posterImage']['small'], 'cover_image' => $item['posterImage']['small'],
@ -54,10 +55,10 @@ class AnimeTransformer extends AbstractTransformer {
'status' => Kitsu::getAiringStatus($item['startDate'], $item['endDate']), 'status' => Kitsu::getAiringStatus($item['startDate'], $item['endDate']),
'streaming_links' => Kitsu::parseStreamingLinks($item['included']), 'streaming_links' => Kitsu::parseStreamingLinks($item['included']),
'synopsis' => $item['synopsis'], 'synopsis' => $item['synopsis'],
'title' => $titles[0], 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'trailer_id' => $item['youtubeVideoId'], 'trailer_id' => $item['youtubeVideoId'],
'url' => "https://kitsu.io/anime/{$item['slug']}", 'url' => "https://kitsu.io/anime/{$item['slug']}",
]; ]);
} }
} }

View File

@ -23,7 +23,7 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Data transformation class for zippered Hummingbird manga * Data transformation class for zippered Hummingbird manga
*/ */
class MangaListTransformer extends AbstractTransformer { final class MangaListTransformer extends AbstractTransformer {
use StringWrapper; use StringWrapper;

View File

@ -21,7 +21,7 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for anime description page * Transformer for anime description page
*/ */
class MangaTransformer extends AbstractTransformer { final class MangaTransformer extends AbstractTransformer {
/** /**
* Convert raw api response to a more * Convert raw api response to a more

View File

@ -17,6 +17,7 @@
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
use Amp\Artax\Request; use Amp\Artax\Request;
use Aviat\AnimeClient\Types\AbstractType;
/** /**
* Common interface for anime and manga list item CRUD * Common interface for anime and manga list item CRUD
@ -43,10 +44,10 @@ interface ListItemInterface {
* Update a list item * Update a list item
* *
* @param string $id - The id of the list item to update * @param string $id - The id of the list item to update
* @param array $data - The data with which to update the list item * @param AbstractType $data - The data with which to update the list item
* @return Request * @return Request
*/ */
public function update(string $id, array $data): Request; public function update(string $id, AbstractType $data): Request;
/** /**
* Delete a list item * Delete a list item

View File

@ -28,7 +28,7 @@ use Aviat\AnimeClient\API\Enum\{
/** /**
* Constants and mappings for the My Anime List API * Constants and mappings for the My Anime List API
*/ */
class MAL { final class MAL {
const AUTH_URL = 'https://myanimelist.net/api/account/verify_credentials.xml'; const AUTH_URL = 'https://myanimelist.net/api/account/verify_credentials.xml';
const BASE_URL = 'https://myanimelist.net/api/'; const BASE_URL = 'https://myanimelist.net/api/';

View File

@ -20,12 +20,13 @@ use Amp\Artax\{FormBody, Request};
use Aviat\AnimeClient\API\{ use Aviat\AnimeClient\API\{
XML XML
}; };
use Aviat\AnimeClient\Types\AbstractType;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
/** /**
* CRUD operations for MAL list items * CRUD operations for MAL list items
*/ */
class ListItem { final class ListItem {
use ContainerAware; use ContainerAware;
use MALTrait; use MALTrait;
@ -84,11 +85,11 @@ class ListItem {
* Update a list item * Update a list item
* *
* @param string $id * @param string $id
* @param array $data * @param AbstractType $data
* @param string $type * @param string $type
* @return Request * @return Request
*/ */
public function update(string $id, array $data, string $type = 'anime'): Request public function update(string $id, AbstractType $data, string $type = 'anime'): Request
{ {
$config = $this->container->get('config'); $config = $this->container->get('config');

View File

@ -21,7 +21,7 @@ use Aviat\AnimeClient\API\{
MAL as M MAL as M
}; };
class MALRequestBuilder extends APIRequestBuilder { final class MALRequestBuilder extends APIRequestBuilder {
/** /**
* The base url for api requests * The base url for api requests

View File

@ -24,12 +24,13 @@ use Aviat\AnimeClient\API\MAL\{
}; };
use Aviat\AnimeClient\API\XML; use Aviat\AnimeClient\API\XML;
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus}; use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\Types\{Anime, AnimeFormItem};
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
/** /**
* MyAnimeList API Model * MyAnimeList API Model
*/ */
class Model { final class Model {
use ContainerAware; use ContainerAware;
use MALTrait; use MALTrait;
@ -147,11 +148,11 @@ class Model {
/** /**
* Update a list item * Update a list item
* *
* @param array $data * @param AnimeFormItem $data
* @param string $type "anime" or "manga" * @param string $type "anime" or "manga"
* @return Request * @return Request
*/ */
public function updateListItem(array $data, string $type = 'anime'): Request public function updateListItem(AnimeFormItem $data, string $type = 'anime'): Request
{ {
$updateData = []; $updateData = [];

View File

@ -17,12 +17,13 @@
namespace Aviat\AnimeClient\API\MAL\Transformer; namespace Aviat\AnimeClient\API\MAL\Transformer;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\{AnimeFormItem, AnimeFormItemData};
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for updating MAL List * Transformer for updating MAL List
*/ */
class AnimeListTransformer extends AbstractTransformer { final class AnimeListTransformer extends AbstractTransformer {
/** /**
* Identity transformation * Identity transformation
* *
@ -38,16 +39,14 @@ class AnimeListTransformer extends AbstractTransformer {
* Transform Kitsu episode data to MAL episode data * Transform Kitsu episode data to MAL episode data
* *
* @param array $item * @param array $item
* @return array * @return AnimeFormItem
*/ */
public function untransform(array $item): array public function untransform(array $item): AnimeFormItem
{ {
$map = [ $map = new AnimeFormItem([
'id' => $item['mal_id'], 'id' => $item['mal_id'],
'data' => [] 'data' => new AnimeFormItemData([]),
]; ]);
$data =& $item['data'];
foreach($item['data'] as $key => $value) foreach($item['data'] as $key => $value)
{ {
@ -56,7 +55,7 @@ class AnimeListTransformer extends AbstractTransformer {
case 'progress': case 'progress':
$map['data']['episode'] = $value; $map['data']['episode'] = $value;
break; break;
case 'notes': case 'notes':
$map['data']['comments'] = $value; $map['data']['comments'] = $value;
break; break;

View File

@ -22,7 +22,7 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for updating MAL List * Transformer for updating MAL List
*/ */
class MangaListTransformer extends AbstractTransformer { final class MangaListTransformer extends AbstractTransformer {
/** /**
* Identity transformation * Identity transformation
* *

View File

@ -23,7 +23,7 @@ use Aviat\Ion\Enum;
* Anime watching status mappings, among Kitsu, MAL, Page titles * Anime watching status mappings, among Kitsu, MAL, Page titles
* and url route segments * and url route segments
*/ */
class AnimeWatchingStatus extends Enum { final class AnimeWatchingStatus extends Enum {
const KITSU_TO_MAL = [ const KITSU_TO_MAL = [
Kitsu::WATCHING => MAL::WATCHING, Kitsu::WATCHING => MAL::WATCHING,
Kitsu::PLAN_TO_WATCH => MAL::PLAN_TO_WATCH, Kitsu::PLAN_TO_WATCH => MAL::PLAN_TO_WATCH,

View File

@ -23,7 +23,7 @@ use Aviat\Ion\Enum;
* Manga reading status mappings, among Kitsu, MAL, Page titles * Manga reading status mappings, among Kitsu, MAL, Page titles
* and url route segments * and url route segments
*/ */
class MangaReadingStatus extends Enum { final class MangaReadingStatus extends Enum {
const KITSU_TO_MAL = [ const KITSU_TO_MAL = [
Kitsu::READING => MAL::READING, Kitsu::READING => MAL::READING,
Kitsu::PLAN_TO_READ => MAL::PLAN_TO_READ, Kitsu::PLAN_TO_READ => MAL::PLAN_TO_READ,

View File

@ -22,14 +22,14 @@ use function Amp\Promise\{all, wait};
/** /**
* Class to simplify making and validating simultaneous requests * Class to simplify making and validating simultaneous requests
*/ */
class ParallelAPIRequest { final class ParallelAPIRequest {
/** /**
* Set of requests to make in parallel * Set of requests to make in parallel
* *
* @var array * @var array
*/ */
protected $requests = []; private $requests = [];
/** /**
* Add a request * Add a request
@ -76,9 +76,7 @@ class ParallelAPIRequest {
{ {
$promises[$key] = call(function () use ($client, $url) { $promises[$key] = call(function () use ($client, $url) {
$response = yield $client->request($url); $response = yield $client->request($url);
$body = yield $response->getBody(); return yield $response->getBody();
return $body;
}); });
} }

View File

@ -21,7 +21,7 @@ use DOMDocument, DOMNode, DOMNodeList, InvalidArgumentException;
/** /**
* XML <=> PHP Array codec * XML <=> PHP Array codec
*/ */
class XML { final class XML {
/** /**
* XML representation of the data * XML representation of the data

View File

@ -19,7 +19,7 @@ namespace Aviat\AnimeClient\Command;
/** /**
* Clears the API Cache * Clears the API Cache
*/ */
class CacheClear extends BaseCommand { final class CacheClear extends BaseCommand {
/** /**
* Clear the API cache * Clear the API cache
* *

View File

@ -19,7 +19,7 @@ namespace Aviat\AnimeClient\Command;
/** /**
* Clears the API Cache * Clears the API Cache
*/ */
class CachePrime extends BaseCommand { final class CachePrime extends BaseCommand {
/** /**
* Clear, then prime the API cache * Clear, then prime the API cache
* *

View File

@ -32,7 +32,7 @@ use DateTime;
/** /**
* Clears the API Cache * Clears the API Cache
*/ */
class SyncLists extends BaseCommand { final class SyncLists extends BaseCommand {
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API

View File

@ -27,7 +27,7 @@ use Aviat\Ion\StringWrapper;
/** /**
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
class Anime extends BaseController { final class Anime extends BaseController {
use StringWrapper; use StringWrapper;
@ -277,7 +277,7 @@ class Anime extends BaseController {
$show_data = $this->model->getAnime($animeId); $show_data = $this->model->getAnime($animeId);
$characters = []; $characters = [];
if (empty($show_data)) if ($show_data->title === '')
{ {
$this->notFound( $this->notFound(
$this->config->get('whose_list') . $this->config->get('whose_list') .
@ -301,7 +301,7 @@ class Anime extends BaseController {
'title' => $this->formatTitle( 'title' => $this->formatTitle(
$this->config->get('whose_list') . "'s Anime List", $this->config->get('whose_list') . "'s Anime List",
'Anime', 'Anime',
$show_data['titles'][0] $show_data->title
), ),
'characters' => $characters, 'characters' => $characters,
'show_data' => $show_data, 'show_data' => $show_data,

View File

@ -26,7 +26,7 @@ use Aviat\Ion\Di\ContainerInterface;
/** /**
* Controller for Anime collection pages * Controller for Anime collection pages
*/ */
class AnimeCollection extends BaseController { final class AnimeCollection extends BaseController {
/** /**
* The anime collection model * The anime collection model

View File

@ -23,7 +23,7 @@ use Aviat\Ion\ArrayWrapper;
/** /**
* Controller for character description pages * Controller for character description pages
*/ */
class Character extends BaseController { final class Character extends BaseController {
use ArrayWrapper; use ArrayWrapper;

View File

@ -25,7 +25,7 @@ use Aviat\Ion\View\HtmlView;
/** /**
* Controller for handling routes that don't fit elsewhere * Controller for handling routes that don't fit elsewhere
*/ */
class Index extends BaseController { final class Index extends BaseController {
/** /**
* Purges the API cache * Purges the API cache

View File

@ -26,7 +26,7 @@ use Aviat\Ion\{Json, StringWrapper};
/** /**
* Controller for manga list * Controller for manga list
*/ */
class Manga extends Controller { final class Manga extends Controller {
use StringWrapper; use StringWrapper;

View File

@ -26,7 +26,7 @@ use Aviat\Ion\Di\ContainerInterface;
/** /**
* Controller for manga collection pages * Controller for manga collection pages
*/ */
class MangaCollection extends BaseController { final class MangaCollection extends BaseController {
/** /**
* The manga collection model * The manga collection model

View File

@ -28,7 +28,7 @@ use Aviat\Ion\StringWrapper;
/** /**
* Basic routing/ dispatch * Basic routing/ dispatch
*/ */
class Dispatcher extends RoutingBase { final class Dispatcher extends RoutingBase {
use StringWrapper; use StringWrapper;

View File

@ -17,13 +17,14 @@
namespace Aviat\AnimeClient\Helper; namespace Aviat\AnimeClient\Helper;
use Aviat\AnimeClient\MenuGenerator; use Aviat\AnimeClient\MenuGenerator;
use Aviat\Ion\Di\ContainerAware;
/** /**
* MenuGenerator helper wrapper * MenuGenerator helper wrapper
*/ */
class Menu { final class Menu {
use \Aviat\Ion\Di\ContainerAware; use ContainerAware;
/** /**
* Create the html for the selected menu * Create the html for the selected menu

View File

@ -25,7 +25,7 @@ use Aviat\Ion\Exception\ConfigException;
/** /**
* Helper object to manage menu creation and selection * Helper object to manage menu creation and selection
*/ */
class MenuGenerator extends UrlGenerator { final class MenuGenerator extends UrlGenerator {
use ArrayWrapper; use ArrayWrapper;
use StringWrapper; use StringWrapper;

View File

@ -44,7 +44,7 @@ class API {
foreach ($array as $key => $item) foreach ($array as $key => $item)
{ {
$sort[$key] = $item[$sortKey]['titles'][0]; $sort[$key] = $item[$sortKey]['title'];
} }
array_multisort($sort, SORT_ASC, $array); array_multisort($sort, SORT_ASC, $array);

View File

@ -18,13 +18,18 @@ namespace Aviat\AnimeClient\Model;
use Aviat\AnimeClient\API\ParallelAPIRequest; use Aviat\AnimeClient\API\ParallelAPIRequest;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\{
Anime as AnimeType,
AnimeFormItem,
AnimeListItem,
};
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json; use Aviat\Ion\Json;
/** /**
* Model for handling requests dealing with the anime list * Model for handling requests dealing with the anime list
*/ */
class Anime extends API { final class Anime extends API {
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API
* *
@ -93,9 +98,9 @@ class Anime extends API {
* Get information about an anime from its slug * Get information about an anime from its slug
* *
* @param string $slug * @param string $slug
* @return array * @return AnimeType
*/ */
public function getAnime(string $slug): array public function getAnime(string $slug): AnimeType
{ {
return $this->kitsuModel->getAnime($slug); return $this->kitsuModel->getAnime($slug);
} }
@ -127,9 +132,9 @@ class Anime extends API {
* for editing/updating that item * for editing/updating that item
* *
* @param string $itemId * @param string $itemId
* @return array * @return AnimeListItem
*/ */
public function getLibraryItem(string $itemId): array public function getLibraryItem(string $itemId): AnimeListItem
{ {
return $this->kitsuModel->getListItem($itemId); return $this->kitsuModel->getListItem($itemId);
} }
@ -166,10 +171,10 @@ class Anime extends API {
/** /**
* Update a list entry * Update a list entry
* *
* @param array $data * @param AnimeFormItem $data
* @return array * @return array
*/ */
public function updateLibraryItem(array $data): array public function updateLibraryItem(AnimeFormItem $data): array
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();

View File

@ -22,7 +22,7 @@ use PDO;
/** /**
* Model for getting anime collection data * Model for getting anime collection data
*/ */
class AnimeCollection extends Collection { final class AnimeCollection extends Collection {
/** /**
* Anime API Model * Anime API Model

View File

@ -27,7 +27,7 @@ use Aviat\Ion\Json;
/** /**
* Model for handling requests dealing with the manga list * Model for handling requests dealing with the manga list
*/ */
class Manga extends API final class Manga extends API
{ {
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API

View File

@ -22,7 +22,7 @@ use PDO;
/** /**
* Model for getting anime collection data * Model for getting anime collection data
*/ */
class MangaCollection extends Collection { final class MangaCollection extends Collection {
/** /**
* Manga API Model * Manga API Model

121
src/Types/AbstractType.php Normal file
View File

@ -0,0 +1,121 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
use ArrayAccess;
use LogicException;
abstract class AbstractType implements ArrayAccess {
/**
* Sets the properties by using the constructor
*
* @param array $data
*/
public function __construct(array $data = [])
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
/**
* See if a property is set
*
* @param $name
* @return bool
*/
public function __isset($name): bool
{
return property_exists($this, $name);
}
/**
* Set a property on the type object
*
* @param string $name
* @param mixed $value
* @return void
*/
public function __set($name, $value): void
{
if (!property_exists($this, $name)) {
$existing = json_encode($this);
throw new LogicException("Trying to set non-existent property: '$name'. Existing properties: $existing");
}
$this->$name = $value;
}
/**
* Get a property from the type object
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->$name;
}
throw new LogicException("Trying to get non-existent property: '$name'");
}
/**
* Implementing ArrayAccess
*
* @param $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return $this->__isset($offset);
}
/**
* Implementing ArrayAccess
*
* @param $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->__get($offset);
}
/**
* Implementing ArrayAccess
*
* @param $offset
* @param $value
*/
public function offsetSet($offset, $value): void
{
$this->__set($offset, $value);
}
/**
* Implementing ArrayAccess
*
* @param $offset
*/
public function offsetUnset($offset): void
{
// Do nothing!
}
}

40
src/Types/Anime.php Normal file
View File

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
*/
final class Anime extends AbstractType {
public $age_rating;
public $age_rating_guide;
public $cover_image;
public $episode_count;
public $episode_length;
public $genres;
public $id;
public $included;
public $show_type;
public $slug;
public $status;
public $streaming_links;
public $synopsis;
public $title;
public $titles;
public $trailer_id;
public $url;
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
*/
final class AnimeFormItem extends AbstractType {
public $data;
public $id;
public $mal_id;
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
*/
final class AnimeFormItemData extends AbstractType {
public $notes;
public $private;
public $progress;
public $rating;
public $reconsumeCount;
public $reconsuming;
public $status;
}

View File

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
*/
final class AnimeListItem extends AbstractType {
public $id;
public $mal_id;
public $episodes = [
'length' => 0,
'total' => 0,
'watched' => '',
];
public $airing = [
'status' => '',
'started' => '',
'ended' => '',
];
public $anime;
public $watching_status;
public $notes;
public $rewatching;
public $rewatched;
public $user_rating;
public $private;
}