Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
17 changed files with 192 additions and 270 deletions
Showing only changes of commit e4b8e6ce51 - Show all commits

View File

@ -60,6 +60,7 @@
"clean": "vendor/bin/robo clean", "clean": "vendor/bin/robo clean",
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build", "coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build",
"phpstan": "phpstan analyse -c phpstan.neon", "phpstan": "phpstan analyse -c phpstan.neon",
"psalm": "vendor/bin/psalm",
"watch:css": "cd public && npm run watch:css", "watch:css": "cd public && npm run watch:css",
"watch:js": "cd public && npm run watch:js", "watch:js": "cd public && npm run watch:js",
"test": "vendor/bin/phpunit -c build --no-coverage", "test": "vendor/bin/phpunit -c build --no-coverage",

View File

@ -1,12 +1,12 @@
parameters: parameters:
checkGenericClassInNonGenericObjectType: false checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
inferPrivatePropertyTypeFromConstructor: true inferPrivatePropertyTypeFromConstructor: true
level: 7 level: 7
autoload_files: autoload_files:
- %rootDir%/../../../tests/mocks.php - %rootDir%/../../../tests/mocks.php
paths: paths:
- src - src
- tests
- ./console - ./console
- index.php - index.php
ignoreErrors: ignoreErrors:

View File

@ -15,5 +15,41 @@
<issueHandlers> <issueHandlers>
<LessSpecificReturnType errorLevel="info" /> <LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
<DeprecatedFunction errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<DeprecatedTrait errorLevel="info" />
<InternalMethod errorLevel="info" />
<InternalProperty errorLevel="info" />
<InternalClass errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<MisplacedRequiredParam errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<InvalidStringClass errorLevel="info" />
</issueHandlers> </issueHandlers>
</psalm> </psalm>

View File

@ -28,8 +28,9 @@ final class CharacterTransformer extends AbstractTransformer {
/** /**
* @param array $characterData * @param array $characterData
* @return Character
*/ */
public function transform(array $characterData): Character public function transform($characterData): Character
{ {
$data = JsonAPI::organizeData($characterData); $data = JsonAPI::organizeData($characterData);
$attributes = $data[0]['attributes']; $attributes = $data[0]['attributes'];

View File

@ -25,7 +25,11 @@ use Aviat\Ion\Transformer\AbstractTransformer;
*/ */
final class PersonTransformer extends AbstractTransformer { final class PersonTransformer extends AbstractTransformer {
public function transform(array $personData): Person /**
* @param array|object $personData
* @return Person
*/
public function transform($personData): Person
{ {
$data = JsonAPI::organizeData($personData); $data = JsonAPI::organizeData($personData);
$included = JsonAPI::organizeIncludes($personData['included']); $included = JsonAPI::organizeIncludes($personData['included']);

View File

@ -24,9 +24,12 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transform user profile data for display * Transform user profile data for display
*
* @param array|object $profileData
* @return User
*/ */
final class UserTransformer extends AbstractTransformer { final class UserTransformer extends AbstractTransformer {
public function transform(array $profileData): User public function transform($profileData): User
{ {
$orgData = JsonAPI::organizeData($profileData)[0]; $orgData = JsonAPI::organizeData($profileData)[0];
$attributes = $orgData['attributes']; $attributes = $orgData['attributes'];

View File

@ -75,6 +75,11 @@ final class AnimeCollection extends Collection {
*/ */
public function getMediaTypeList(): array public function getMediaTypeList(): array
{ {
if ($this->validDatabase === FALSE)
{
return [];
}
$output = []; $output = [];
$query = $this->db->select('id, type') $query = $this->db->select('id, type')
@ -136,6 +141,11 @@ final class AnimeCollection extends Collection {
*/ */
public function add($data): void public function add($data): void
{ {
if ($this->validDatabase === FALSE)
{
return;
}
$id = $data['id']; $id = $data['id'];
// Check that the anime doesn't already exist // Check that the anime doesn't already exist
@ -166,13 +176,16 @@ final class AnimeCollection extends Collection {
/** /**
* Verify that an item was added * Verify that an item was added
* *
* @param $data
* @param array|null|object $data * @param array|null|object $data
*
* @return bool * @return bool
*/ */
public function wasAdded($data): bool public function wasAdded($data): bool
{ {
if ($this->validDatabase === FALSE)
{
return FALSE;
}
$row = $this->get($data['id']); $row = $this->get($data['id']);
return ! empty($row); return ! empty($row);
@ -186,6 +199,11 @@ final class AnimeCollection extends Collection {
*/ */
public function update($data): void public function update($data): void
{ {
if ($this->validDatabase === FALSE)
{
return;
}
// If there's no id to update, don't update // If there's no id to update, don't update
if ( ! array_key_exists('hummingbird_id', $data)) if ( ! array_key_exists('hummingbird_id', $data))
{ {
@ -206,13 +224,17 @@ final class AnimeCollection extends Collection {
/** /**
* Verify that the collection item was updated * Verify that the collection item was updated
* *
* @param $data
* @param array|null|object $data * @param array|null|object $data
* *
* @return bool * @return bool
*/ */
public function wasUpdated($data): bool public function wasUpdated($data): bool
{ {
if ($this->validDatabase === FALSE)
{
return FALSE;
}
$row = $this->get($data['hummingbird_id']); $row = $this->get($data['hummingbird_id']);
foreach ($data as $key => $value) foreach ($data as $key => $value)
@ -234,6 +256,11 @@ final class AnimeCollection extends Collection {
*/ */
public function delete($data): void public function delete($data): void
{ {
if ($this->validDatabase === FALSE)
{
return;
}
// If there's no id to update, don't delete // If there's no id to update, don't delete
if ( ! array_key_exists('hummingbird_id', $data)) if ( ! array_key_exists('hummingbird_id', $data))
{ {
@ -249,9 +276,15 @@ final class AnimeCollection extends Collection {
/** /**
* @param array|null|object $data * @param array|null|object $data
* @return bool
*/ */
public function wasDeleted($data): bool public function wasDeleted($data): bool
{ {
if ($this->validDatabase === FALSE)
{
return FALSE;
}
$animeRow = $this->get($data['hummingbird_id']); $animeRow = $this->get($data['hummingbird_id']);
return empty($animeRow); return empty($animeRow);
@ -265,6 +298,11 @@ final class AnimeCollection extends Collection {
*/ */
public function get($kitsuId) public function get($kitsuId)
{ {
if ($this->validDatabase === FALSE)
{
return FALSE;
}
$query = $this->db->from('anime_set') $query = $this->db->from('anime_set')
->where('hummingbird_id', $kitsuId) ->where('hummingbird_id', $kitsuId)
->get(); ->get();
@ -280,6 +318,11 @@ final class AnimeCollection extends Collection {
*/ */
public function getGenreList(array $filter = []): array public function getGenreList(array $filter = []): array
{ {
if ($this->validDatabase === FALSE)
{
return [];
}
$output = []; $output = [];
// Catch the missing table PDOException // Catch the missing table PDOException
@ -337,6 +380,11 @@ final class AnimeCollection extends Collection {
*/ */
private function updateGenre($animeId): void private function updateGenre($animeId): void
{ {
if ($this->validDatabase === FALSE)
{
return;
}
// Get api information // Get api information
$anime = $this->animeModel->getAnimeById($animeId); $anime = $this->animeModel->getAnimeById($animeId);
@ -379,6 +427,11 @@ final class AnimeCollection extends Collection {
*/ */
private function addNewGenres(array $genres): void private function addNewGenres(array $genres): void
{ {
if ($this->validDatabase === FALSE)
{
return;
}
$existingGenres = $this->getExistingGenres(); $existingGenres = $this->getExistingGenres();
$newGenres = array_diff($genres, $existingGenres); $newGenres = array_diff($genres, $existingGenres);
@ -416,6 +469,11 @@ final class AnimeCollection extends Collection {
private function getExistingGenres(): array private function getExistingGenres(): array
{ {
if ($this->validDatabase === FALSE)
{
return [];
}
$genres = []; $genres = [];
// Get existing genres // Get existing genres
@ -435,6 +493,11 @@ final class AnimeCollection extends Collection {
private function getExistingGenreLinkEntries(): array private function getExistingGenreLinkEntries(): array
{ {
if ($this->validDatabase === FALSE)
{
return [];
}
$links = []; $links = [];
$query = $this->db->select('hummingbird_id, genre_id') $query = $this->db->select('hummingbird_id, genre_id')

View File

@ -19,6 +19,7 @@ namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use PDOException; use PDOException;
use Query\Query_Builder_Interface;
use function Query; use function Query;
/** /**
@ -28,7 +29,7 @@ class Collection extends DB {
/** /**
* The query builder object * The query builder object
* @var \Query\Query_Builder_Interface * @var Query_Builder_Interface
*/ */
protected $db; protected $db;
@ -52,7 +53,13 @@ class Collection extends DB {
$this->db = Query($this->dbConfig); $this->db = Query($this->dbConfig);
$this->validDatabase = TRUE; $this->validDatabase = TRUE;
} }
catch (PDOException $e) {} catch (PDOException $e)
{
$this->db = Query([
'type' => 'sqlite',
'file' => ':memory:',
]);
}
// Is database valid? If not, set a flag so the // Is database valid? If not, set a flag so the
// app can be run without a valid database // app can be run without a valid database

View File

@ -17,7 +17,6 @@
namespace Aviat\AnimeClient\Model; namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use PDO;
/** /**
* Model for getting anime collection data * Model for getting anime collection data
@ -40,249 +39,5 @@ final class MangaCollection extends Collection {
parent::__construct($container); parent::__construct($container);
$this->mangaModel = $container->get('manga-model'); $this->mangaModel = $container->get('manga-model');
} }
/**
* Get collection from the database, and organize by media type
*
* @return array
*/
public function getCollection(): array
{
$rawCollection = $this->getCollectionFromDatabase();
$collection = [];
foreach ($rawCollection as $row)
{
if (array_key_exists($row['media'], $collection))
{
$collection[$row['media']][] = $row;
}
else
{
$collection[$row['media']] = [$row];
}
}
return $collection;
}
/**
* Get list of media types
*
* @return array
*/
public function getMediaTypeList(): array
{
$output = [];
$query = $this->db->select('id, type')
->from('media')
->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row)
{
$output[$row['id']] = $row['type'];
}
return $output;
}
/**
* Get full collection from the database
*
* @return array
*/
private function getCollectionFromDatabase(): array
{
if ( ! $this->validDatabase)
{
return [];
}
$query = $this->db->select('hummingbird_id, slug, title, alternate_title, show_type,
age_rating, episode_count, episode_length, cover_image, notes, media.type as media')
->from('manga_set a')
->join('media', 'media.id=a.media_id', 'inner')
->order_by('media')
->order_by('title')
->get();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Add an item to the anime collection
*
* @param array $data
* @return void
*/
public function add($data): void
{
$anime = (object)$this->mangaModel->getMangaById($data['id']);
$this->db->set([
'hummingbird_id' => $data['id'],
'slug' => $anime->slug,
'title' => array_shift($anime->titles),
'alternate_title' => implode('<br />', $anime->titles),
'show_type' => $anime->show_type,
'age_rating' => $anime->age_rating,
'cover_image' => $anime->cover_image,
'episode_count' => $anime->episode_count,
'episode_length' => $anime->episode_length,
'media_id' => $data['media_id'],
'notes' => $data['notes']
])->insert('manga_set');
$this->updateGenre($data['id']);
}
/**
* Update a collection item
*
* @param array $data
* @return void
*/
public function update($data): void
{
// If there's no id to update, don't update
if ( ! array_key_exists('hummingbird_id', $data))
{
return;
}
$id = $data['hummingbird_id'];
unset($data['hummingbird_id']);
$this->db->set($data)
->where('hummingbird_id', $id)
->update('manga_set');
}
/**
* Remove a collection item
*
* @param array $data
* @return void
*/
public function delete($data): void
{
// If there's no id to update, don't delete
if ( ! array_key_exists('hummingbird_id', $data))
{
return;
}
$this->db->where('hummingbird_id', $data['hummingbird_id'])
->delete('genre_manga_set_link');
$this->db->where('hummingbird_id', $data['hummingbird_id'])
->delete('manga_set');
}
/**
* Get the details of a collection item
*
* @param string $kitsuId
* @return array
*/
public function get($kitsuId): array
{
$query = $this->db->from('manga_set')
->where('hummingbird_id', $kitsuId)
->get();
return $query->fetch(PDO::FETCH_ASSOC);
}
/**
* Update genre information for selected manga
*
* @param string $mangaId The current manga
* @return void
*/
private function updateGenre($mangaId): void
{
$genreInfo = $this->getGenreData();
$genres = $genreInfo['genres'];
$links = $genreInfo['links'];
// Get api information
$manga = $this->mangaModel->getMangaById($mangaId);
foreach ($manga['genres'] as $genre)
{
// Add genres that don't currently exist
if ( ! \in_array($genre, $genres, TRUE))
{
$this->db->set('genre', $genre)
->insert('genres');
$genres[] = $genre;
}
// Update link table
// Get id of genre to put in link table
$flippedGenres = array_flip($genres);
$insertArray = [
'hummingbird_id' => $mangaId,
'genre_id' => $flippedGenres[$genre]
];
if (array_key_exists($mangaId, $links))
{
if ( ! \in_array($flippedGenres[$genre], $links[$mangaId], TRUE))
{
$this->db->set($insertArray)->insert('genre_manga_set_link');
}
}
else
{
$this->db->set($insertArray)->insert('genre_manga_set_link');
}
}
}
/**
* Get list of existing genres
*
* @return array
*/
private function getGenreData(): array
{
$genres = [];
$links = [];
// Get existing genres
$query = $this->db->select('id, genre')
->from('genres')
->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
{
$genres[$genre['id']] = $genre['genre'];
}
// Get existing link table entries
$query = $this->db->select('hummingbird_id, genre_id')
->from('genre_manga_set_link')
->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link)
{
if (array_key_exists($link['hummingbird_id'], $links))
{
$links[$link['hummingbird_id']][] = $link['genre_id'];
}
else
{
$links[$link['hummingbird_id']] = [$link['genre_id']];
}
}
return [
'genres' => $genres,
'links' => $links
];
}
} }
// End of MangaCollectionModel.php // End of MangaCollectionModel.php

View File

@ -179,7 +179,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
/** /**
* Recursively cast properties to an array * Recursively cast properties to an array
* *
* @param null $parent * @param mixed $parent
* @return mixed * @return mixed
*/ */
public function toArray($parent = null) public function toArray($parent = null)

View File

@ -38,12 +38,12 @@ class Anime extends AbstractType {
public $cover_image; public $cover_image;
/** /**
* @var string|number * @var string|int
*/ */
public $episode_count; public $episode_count;
/** /**
* @var string|number * @var string|int
*/ */
public $episode_length; public $episode_length;

View File

@ -74,12 +74,12 @@ final class AnimeListItem extends AbstractType {
public $rewatching; public $rewatching;
/** /**
* @var number * @var int
*/ */
public $rewatched; public $rewatched;
/** /**
* @var number * @var int
*/ */
public $user_rating; public $user_rating;

View File

@ -41,7 +41,7 @@ final class Character extends AbstractType {
public $included; public $included;
/** /**
* @var array * @var Media
*/ */
public $media; public $media;
@ -62,9 +62,6 @@ final class Character extends AbstractType {
public function setMedia ($media): void public function setMedia ($media): void
{ {
$this->media = new class($media) extends AbstractType { $this->media = new Media($media);
public $anime;
public $manga;
};
} }
} }

29
src/Types/Characters.php Normal file
View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.2
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2019 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
final class Characters extends AbstractType {
/**
* @var array
*/
public $main;
/**
* @var array
*/
public $supporting;
}

View File

@ -40,7 +40,7 @@ class Database extends AbstractType {
public $pass; public $pass;
/** /**
* @var string|number * @var string|int
*/ */
public $port; public $port;

29
src/Types/Media.php Normal file
View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.2
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2019 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
final class Media extends AbstractType {
/**
* @var array
*/
public $anime = [];
/**
* @var array
*/
public $manga = [];
}

View File

@ -31,7 +31,7 @@ final class Person extends AbstractType {
public $name; public $name;
/** /**
* @var object * @var Characters
*/ */
public $characters; public $characters;
@ -42,9 +42,6 @@ final class Person extends AbstractType {
public function setCharacters($characters): void public function setCharacters($characters): void
{ {
$this->characters = new class($characters) extends AbstractType { $this->characters = new Characters($characters);
public $main;
public $supporting;
};
} }
} }