diff --git a/composer.json b/composer.json
index fa9a9e4c..39f80afd 100644
--- a/composer.json
+++ b/composer.json
@@ -50,7 +50,8 @@
"spatie/phpunit-snapshot-assertions": "^2.2.1",
"squizlabs/php_codesniffer": "^3.2.2",
"symfony/var-dumper": "^4.4.1",
- "theseer/phpdox": "*"
+ "theseer/phpdox": "*",
+ "vimeo/psalm": "^3.7"
},
"scripts": {
"build": "vendor/bin/robo build",
@@ -58,10 +59,11 @@
"build:js": "cd public && npm run build:js && cd ..",
"clean": "vendor/bin/robo clean",
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build",
- "phpstan": "phpstan analyse -l 4 -c phpstan.neon src tests ./console index.php",
+ "phpstan": "phpstan analyse -c phpstan.neon",
"watch:css": "cd public && npm run watch:css",
"watch:js": "cd public && npm run watch:js",
- "test": "vendor/bin/phpunit"
+ "test": "vendor/bin/phpunit -c build --no-coverage",
+ "test-update": "vendor/bin/phpunit -c build --no-coverage -d --update-snapshots"
},
"scripts-descriptions": {
"build": "Generate the api docs",
diff --git a/phpstan.neon b/phpstan.neon
index 964eb301..46125efa 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,9 +1,18 @@
parameters:
+ checkGenericClassInNonGenericObjectType: false
+ inferPrivatePropertyTypeFromConstructor: true
+ level: 7
autoload_files:
- %rootDir%/../../../tests/mocks.php
+ paths:
+ - src
+ - tests
+ - ./console
+ - index.php
ignoreErrors:
- '#Access to an undefined property Aviat\\\Ion\\\Friend::\$[a-zA-Z0-9_]+#'
- '#Call to an undefined method Aviat\\\Ion\\\Friend::[a-zA-Z0-9_]+\(\)#'
- '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#'
- - '#Undefined variable: \$var#'
- '#Property Amp\\Artax\\Internal\\RequestCycle::\$[a-zA-Z0-9_]+#'
+ excludes_analyse:
+ - tests/mocks.php
\ No newline at end of file
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 00000000..42f355b2
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/API/APIRequestBuilder.php b/src/API/APIRequestBuilder.php
index 3edf9476..d42fdf81 100644
--- a/src/API/APIRequestBuilder.php
+++ b/src/API/APIRequestBuilder.php
@@ -65,7 +65,7 @@ class APIRequestBuilder {
/**
* The current request
- * @var \Amp\Artax\Request
+ * @var Request
*/
protected $request;
@@ -219,14 +219,14 @@ class APIRequestBuilder {
/**
* Return the promise for the current request
*
+ * @return Request
* @throws \Throwable
- * @return \Amp\Artax\Request
*/
public function getFullRequest(): Request
{
$this->buildUri();
- if ($this->logger)
+ if ($this->logger !== NULL)
{
$this->logger->debug('API Request', [
'request_url' => $this->request->getUri(),
diff --git a/src/API/Anilist/Model.php b/src/API/Anilist/Model.php
index fe6516f5..d3b51f82 100644
--- a/src/API/Anilist/Model.php
+++ b/src/API/Anilist/Model.php
@@ -25,6 +25,8 @@ use Aviat\AnimeClient\API\Anilist;
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Json;
+use Aviat\Ion\Di\Exception\ContainerException;
+use Aviat\Ion\Di\Exception\NotFoundException;
/**
* Anilist API Model
@@ -92,8 +94,8 @@ final class Model
*
* @param string $type
* @return array
- * @throws \Aviat\Ion\Di\Exception\ContainerException
- * @throws \Aviat\Ion\Di\Exception\NotFoundException
+ * @throws ContainerException
+ * @throws NotFoundException
*/
public function getSyncList(string $type = 'anime'): array
{
@@ -144,7 +146,7 @@ final class Model
];
}
- return $this->listItem->create($createData, $type);
+ return $this->listItem->create($createData);
}
/**
diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php
index 3acad63c..6718561c 100644
--- a/src/API/Kitsu/Auth.php
+++ b/src/API/Kitsu/Auth.php
@@ -16,6 +16,8 @@
namespace Aviat\AnimeClient\API\Kitsu;
+use Aura\Session\Segment;
+
use const Aviat\AnimeClient\SESSION_SEGMENT;
use Aviat\AnimeClient\API\{
@@ -23,7 +25,6 @@ use Aviat\AnimeClient\API\{
Kitsu as K
};
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
-use Exception;
/**
* Kitsu API Authentication
@@ -42,7 +43,7 @@ final class Auth {
/**
* Session object
*
- * @var \Aura\Session\Segment
+ * @var Segment
*/
private $segment;
diff --git a/src/API/Kitsu/ListItem.php b/src/API/Kitsu/ListItem.php
index 8871c615..8d685feb 100644
--- a/src/API/Kitsu/ListItem.php
+++ b/src/API/Kitsu/ListItem.php
@@ -137,6 +137,9 @@ final class ListItem implements ListItemInterface {
return $request->getFullRequest();
}
+ /**
+ * @return false|string
+ */
private function getAuthHeader()
{
$cache = $this->getContainer()->get('cache');
diff --git a/src/API/Kitsu/Transformer/CharacterTransformer.php b/src/API/Kitsu/Transformer/CharacterTransformer.php
index 8bfc7206..adf7d9b7 100644
--- a/src/API/Kitsu/Transformer/CharacterTransformer.php
+++ b/src/API/Kitsu/Transformer/CharacterTransformer.php
@@ -142,23 +142,24 @@ final class CharacterTransformer extends AbstractTransformer {
foreach ($role['relationships']['person']['people'] as $pid => $peoples)
{
$p = $peoples;
+
+ $person = $p['attributes'];
+ $person['id'] = $pid;
+ $person['image'] = $person['image']['original'];
+
+ uasort($role['relationships']['media']['anime'], static function ($a, $b) {
+ return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
+ });
+
+ $item = [
+ 'person' => $person,
+ 'series' => $role['relationships']['media']['anime']
+ ];
+
+ $output[$roleName][$language][] = $item;
}
-
- $person = $p['attributes'];
- $person['id'] = $pid;
- $person['image'] = $person['image']['original'];
-
- uasort($role['relationships']['media']['anime'], function ($a, $b) {
- return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
- });
-
- $item = [
- 'person' => $person,
- 'series' => $role['relationships']['media']['anime']
- ];
-
- $output[$roleName][$language][] = $item;
- } else
+ }
+ else
{
foreach ($role['relationships']['person']['people'] as $pid => $person)
{
diff --git a/src/API/Kitsu/Transformer/MangaTransformer.php b/src/API/Kitsu/Transformer/MangaTransformer.php
index a6e0fb8b..f268248f 100644
--- a/src/API/Kitsu/Transformer/MangaTransformer.php
+++ b/src/API/Kitsu/Transformer/MangaTransformer.php
@@ -130,6 +130,9 @@ final class MangaTransformer extends AbstractTransformer {
]);
}
+ /**
+ * @return int|null|string
+ */
private function count(int $value = NULL)
{
return ((int)$value === 0)
diff --git a/src/Command/UpdateThumbnails.php b/src/Command/UpdateThumbnails.php
index afdfc01b..ff7fe97e 100644
--- a/src/Command/UpdateThumbnails.php
+++ b/src/Command/UpdateThumbnails.php
@@ -62,7 +62,12 @@ final class UpdateThumbnails extends ClearThumbnails {
$this->echoBox('Finished regenerating all thumbnails');
}
- public function getImageList()
+ /**
+ * @return array-key[][]
+ *
+ * @psalm-return array{anime: list, manga: list}
+ */
+ public function getImageList(): array
{
$mangaList = $this->kitsuModel->getFullRawMangaList();
$includes = JsonAPI::organizeIncludes($mangaList['included']);
diff --git a/src/Controller/Images.php b/src/Controller/Images.php
index 55a17b69..4e047e63 100644
--- a/src/Controller/Images.php
+++ b/src/Controller/Images.php
@@ -16,6 +16,8 @@
namespace Aviat\AnimeClient\Controller;
+use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
+
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use function Aviat\AnimeClient\createPlaceholderImage;
@@ -32,17 +34,14 @@ final class Images extends BaseController {
* @param string $type The category of image
* @param string $file The filename to look for
* @param bool $display Whether to output the image to the server
- * @throws \Aviat\Ion\Di\ContainerException
- * @throws \Aviat\Ion\Di\NotFoundException
- * @throws \InvalidArgumentException
- * @throws \TypeError
- * @throws \Error
- * @throws \Throwable
* @return void
+ * @throws NotFoundException
+ * @throws \Throwable
+ * @throws ContainerException
*/
public function cache(string $type, string $file, $display = TRUE): void
{
- $currentUrl = $this->request->getUri()->__toString();
+ $currentUrl = (string)$this->request->getUri();
$kitsuUrl = 'https://media.kitsu.io/';
$fileName = str_replace('-original', '', $file);
diff --git a/src/Types/AbstractType.php b/src/Types/AbstractType.php
index b6353894..f15371a1 100644
--- a/src/Types/AbstractType.php
+++ b/src/Types/AbstractType.php
@@ -37,7 +37,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
*
* @param mixed $data
*/
- public function __construct($data = [])
+ final public function __construct($data = [])
{
$typeKeys = array_keys((array)$this);
$dataKeys = array_keys((array)$data);
@@ -187,7 +187,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
{
$object = $parent ?? $this;
- if (is_scalar($object) || empty($object))
+ if (is_scalar($object) || $object === NULL)
{
return $object;
}
diff --git a/src/Util.php b/src/Util.php
index b04f5ff6..565f589a 100644
--- a/src/Util.php
+++ b/src/Util.php
@@ -16,7 +16,8 @@
namespace Aviat\AnimeClient;
-use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
+use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
+use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
/**
* Utility method class
@@ -45,8 +46,8 @@ class Util {
* Set up the Util class
*
* @param ContainerInterface $container
- * @throws \Aviat\Ion\Di\ContainerException
- * @throws \Aviat\Ion\Di\NotFoundException
+ * @throws ContainerException
+ * @throws NotFoundException
*/
public function __construct(ContainerInterface $container)
{
@@ -80,8 +81,8 @@ class Util {
/**
* Determine whether to show the sub-menu
*
- * @throws \Aviat\Ion\Di\ContainerException
- * @throws \Aviat\Ion\Di\NotFoundException
+ * @throws ContainerException
+ * @throws NotFoundException
* @return bool
*/
public function isViewPage(): bool
@@ -98,8 +99,8 @@ class Util {
* Determine whether the page is a page with a form, and
* not suitable for redirection
*
- * @throws \Aviat\Ion\Di\ContainerException
- * @throws \Aviat\Ion\Di\NotFoundException
+ * @throws ContainerException
+ * @throws NotFoundException
* @return bool
*/
public function isFormPage(): bool
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testTransform__1.php b/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testTransform__1.php
index 8fb7c0d6..7559803d 100644
--- a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testTransform__1.php
+++ b/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testTransform__1.php
@@ -36,10 +36,10 @@
1 => '小林さんちのメイドラゴン',
),
)),
- 'watching_status' => 'current',
'notes' => NULL,
+ 'private' => false,
'rewatching' => false,
'rewatched' => 0,
'user_rating' => '-',
- 'private' => false,
+ 'watching_status' => 'current',
));
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #0__1.php b/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #0__1.php
deleted file mode 100644
index 4b97af5e..00000000
--- a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #0__1.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 14047981,
- 'anilist_item_id' => NULL,
- 'mal_id' => NULL,
- 'data' =>
- Aviat\AnimeClient\Types\FormItemData::__set_state(array(
- 'notes' => 'Very formulaic.',
- 'private' => false,
- 'progress' => 38,
- 'ratingTwenty' => 16,
- 'reconsumeCount' => 0,
- 'reconsuming' => false,
- 'status' => 'current',
- )),
-));
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #1__1.php b/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #1__1.php
deleted file mode 100644
index ca751d41..00000000
--- a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #1__1.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 14047981,
- 'anilist_item_id' => NULL,
- 'mal_id' => '12345',
- 'data' =>
- Aviat\AnimeClient\Types\FormItemData::__set_state(array(
- 'notes' => 'Very formulaic.',
- 'private' => true,
- 'progress' => 38,
- 'ratingTwenty' => 16,
- 'reconsumeCount' => 0,
- 'reconsuming' => true,
- 'status' => 'current',
- )),
-));
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #2__1.php b/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #2__1.php
deleted file mode 100644
index 9375de10..00000000
--- a/tests/API/Kitsu/Transformer/__snapshots__/AnimeListTransformerTest__testUntransform with data set #2__1.php
+++ /dev/null
@@ -1,14 +0,0 @@
- 14047983,
- 'anilist_item_id' => NULL,
- 'mal_id' => '12347',
- 'data' =>
- Aviat\AnimeClient\Types\FormItemData::__set_state(array(
- 'notes' => '',
- 'private' => true,
- 'progress' => 12,
- 'reconsumeCount' => 0,
- 'reconsuming' => true,
- 'status' => 'current',
- )),
-));
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.php b/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.php
deleted file mode 100644
index 8d1c950f..00000000
--- a/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.php
+++ /dev/null
@@ -1,201 +0,0 @@
-
- Aviat\AnimeClient\Types\MangaListItem::__set_state(array(
- 'id' => '15084773',
- 'mal_id' => '26769',
- 'chapters' =>
- array (
- 'read' => 67,
- 'total' => '-',
- ),
- 'volumes' =>
- array (
- 'read' => '-',
- 'total' => '-',
- ),
- 'manga' =>
- Aviat\AnimeClient\Types\MangaListItemDetail::__set_state(array(
- 'genres' =>
- array (
- 0 => 'Comedy',
- 1 => 'Romance',
- 2 => 'School',
- 3 => 'Slice of Life',
- 4 => 'Thriller',
- ),
- 'id' => '20286',
- 'image' => 'https://media.kitsu.io/manga/poster_images/20286/small.jpg?1434293999',
- 'slug' => 'bokura-wa-minna-kawaisou',
- 'title' => 'Bokura wa Minna Kawaisou',
- 'titles' =>
- array (
- ),
- 'type' => 'Manga',
- 'url' => 'https://kitsu.io/manga/bokura-wa-minna-kawaisou',
- )),
- 'reading_status' => 'current',
- 'notes' => '',
- 'rereading' => false,
- 'reread' => 0,
- 'user_rating' => 9,
- )),
- 1 =>
- Aviat\AnimeClient\Types\MangaListItem::__set_state(array(
- 'id' => '15085607',
- 'mal_id' => '16',
- 'chapters' =>
- array (
- 'read' => 17,
- 'total' => 120,
- ),
- 'volumes' =>
- array (
- 'read' => '-',
- 'total' => 14,
- ),
- 'manga' =>
- Aviat\AnimeClient\Types\MangaListItemDetail::__set_state(array(
- 'genres' =>
- array (
- 0 => 'Comedy',
- 1 => 'Ecchi',
- 2 => 'Harem',
- 3 => 'Romance',
- 4 => 'Sports',
- ),
- 'id' => '47',
- 'image' => 'https://media.kitsu.io/manga/poster_images/47/small.jpg?1434249493',
- 'slug' => 'love-hina',
- 'title' => 'Love Hina',
- 'titles' =>
- array (
- ),
- 'type' => 'Manga',
- 'url' => 'https://kitsu.io/manga/love-hina',
- )),
- 'reading_status' => 'current',
- 'notes' => '',
- 'rereading' => false,
- 'reread' => 0,
- 'user_rating' => 7,
- )),
- 2 =>
- Aviat\AnimeClient\Types\MangaListItem::__set_state(array(
- 'id' => '15084529',
- 'mal_id' => '35003',
- 'chapters' =>
- array (
- 'read' => 16,
- 'total' => '-',
- ),
- 'volumes' =>
- array (
- 'read' => '-',
- 'total' => '-',
- ),
- 'manga' =>
- Aviat\AnimeClient\Types\MangaListItemDetail::__set_state(array(
- 'genres' =>
- array (
- 0 => 'Comedy',
- 1 => 'Ecchi',
- 2 => 'Gender Bender',
- 3 => 'Romance',
- 4 => 'School',
- 5 => 'Sports',
- 6 => 'Supernatural',
- ),
- 'id' => '11777',
- 'image' => 'https://media.kitsu.io/manga/poster_images/11777/small.jpg?1438784325',
- 'slug' => 'yamada-kun-to-7-nin-no-majo',
- 'title' => 'Yamada-kun to 7-nin no Majo',
- 'titles' =>
- array (
- 0 => 'Yamada-kun and the Seven Witches',
- ),
- 'type' => 'Manga',
- 'url' => 'https://kitsu.io/manga/yamada-kun-to-7-nin-no-majo',
- )),
- 'reading_status' => 'current',
- 'notes' => '',
- 'rereading' => false,
- 'reread' => 0,
- 'user_rating' => 9,
- )),
- 3 =>
- Aviat\AnimeClient\Types\MangaListItem::__set_state(array(
- 'id' => '15312827',
- 'mal_id' => '78523',
- 'chapters' =>
- array (
- 'read' => 68,
- 'total' => '-',
- ),
- 'volumes' =>
- array (
- 'read' => '-',
- 'total' => '-',
- ),
- 'manga' =>
- Aviat\AnimeClient\Types\MangaListItemDetail::__set_state(array(
- 'genres' =>
- array (
- 0 => 'Romance',
- 1 => 'School',
- 2 => 'Slice of Life',
- ),
- 'id' => '27175',
- 'image' => 'https://media.kitsu.io/manga/poster_images/27175/small.jpg?1464379411',
- 'slug' => 'relife',
- 'title' => 'ReLIFE',
- 'titles' =>
- array (
- ),
- 'type' => 'Manga',
- 'url' => 'https://kitsu.io/manga/relife',
- )),
- 'reading_status' => 'current',
- 'notes' => '',
- 'rereading' => false,
- 'reread' => 0,
- 'user_rating' => '-',
- )),
- 4 =>
- Aviat\AnimeClient\Types\MangaListItem::__set_state(array(
- 'id' => '15084769',
- 'mal_id' => '60815',
- 'chapters' =>
- array (
- 'read' => 43,
- 'total' => '-',
- ),
- 'volumes' =>
- array (
- 'read' => '-',
- 'total' => '-',
- ),
- 'manga' =>
- Aviat\AnimeClient\Types\MangaListItemDetail::__set_state(array(
- 'genres' =>
- array (
- 0 => 'Comedy',
- 1 => 'School',
- 2 => 'Slice of Life',
- ),
- 'id' => '25491',
- 'image' => 'https://media.kitsu.io/manga/poster_images/25491/small.jpg?1434305043',
- 'slug' => 'joshikausei',
- 'title' => 'Joshikausei',
- 'titles' =>
- array (
- ),
- 'type' => 'Manga',
- 'url' => 'https://kitsu.io/manga/joshikausei',
- )),
- 'reading_status' => 'current',
- 'notes' => '',
- 'rereading' => false,
- 'reread' => 0,
- 'user_rating' => 8,
- )),
-);
diff --git a/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.yml b/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.yml
deleted file mode 100644
index 87effff0..00000000
--- a/tests/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-- null
-- null
-- null
-- null
-- null