Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details

This commit is contained in:
Timothy Warren 2021-02-16 12:21:52 -05:00
commit db6e089d93
68 changed files with 627 additions and 457 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## Version 5.2
* Updated PHP requirement to 8
## Version 5.1 ## Version 5.1
* Added session check, so when coming back to a page, if the session is expired, the page will refresh. * Added session check, so when coming back to a page, if the session is expired, the page will refresh.
* Updated logging config so that much fewer, much smaller files are generated. * Updated logging config so that much fewer, much smaller files are generated.

8
Jenkinsfile vendored
View File

@ -34,6 +34,13 @@ pipeline {
sh 'php ./vendor/bin/phpunit --colors=never' sh 'php ./vendor/bin/phpunit --colors=never'
} }
} }
stage('Code Cleanliness') {
agent any
steps {
sh "php8 ./vendor/bin/phpstan analyse -c phpstan.neon -n --no-ansi --no-progress --error-format=checkstyle | awk '{\$1=\$1;print}' > build/logs/checkstyle-result.xml"
recordIssues(tools: [checkStyle(reportEncoding: 'UTF-8')])
}
}
stage('Coverage') { stage('Coverage') {
agent any agent any
steps { steps {
@ -43,6 +50,7 @@ pipeline {
cloverReportDir: '', cloverReportDir: '',
cloverReportFileName: 'build/logs/clover.xml', cloverReportFileName: 'build/logs/clover.xml',
]) ])
junit 'build/logs/junit.xml'
} }
} }
} }

View File

@ -3,7 +3,7 @@
Update your anime/manga list on Kitsu.io and Anilist Update your anime/manga list on Kitsu.io and Anilist
[![Build Status](https://travis-ci.com/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.com/github/timw4mail/HummingBirdAnimeClient) [![Build Status](https://travis-ci.com/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.com/github/timw4mail/HummingBirdAnimeClient)
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=timw4mail/HummingBirdAnimeClient/develop)](https://jenkins.timshomepage.net/job/timw4mail/HummingBirdAnimeClient/develop) [![Build Status](https://jenkins.timshome.page/buildStatus/icon?job=timw4mail/HummingBirdAnimeClient/develop)](https://jenkins.timshome.page/job/timw4mail/job/HummingBirdAnimeClient/job/develop/)
[[Hosted Example](https://list.timshomepage.net)] [[Hosted Example](https://list.timshomepage.net)]
@ -31,7 +31,7 @@ Update your anime/manga list on Kitsu.io and Anilist
### Requirements ### Requirements
* PHP 7.4+ * PHP 8
* PDO SQLite or PDO PostgreSQL (For collection tab) * PDO SQLite or PDO PostgreSQL (For collection tab)
* GD extension for caching images * GD extension for caching images

View File

@ -17,7 +17,9 @@
<directory>../tests/Ion</directory> <directory>../tests/Ion</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<logging/> <logging>
<junit outputFile="logs/junit.xml"/>
</logging>
<php> <php>
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"/> <server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"/>
<server name="HTTP_HOST" value="localhost"/> <server name="HTTP_HOST" value="localhost"/>

View File

@ -64,7 +64,7 @@ if (array_key_exists('timezone', $checkedConfig) && ! empty($checkedConfig['time
{ {
date_default_timezone_set($checkedConfig['timezone']); date_default_timezone_set($checkedConfig['timezone']);
} }
else if ($timezone !== '') else if (is_string($timezone) && $timezone !== '')
{ {
date_default_timezone_set($timezone); date_default_timezone_set($timezone);
} }

View File

@ -2,17 +2,19 @@ parameters:
checkGenericClassInNonGenericObjectType: false checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false checkMissingIterableValueType: false
inferPrivatePropertyTypeFromConstructor: true inferPrivatePropertyTypeFromConstructor: true
level: 7 level: 8
autoload_files:
- %rootDir%/../../../tests/mocks.php
paths: paths:
- src - src
- ./console - ./console
- index.php - index.php
ignoreErrors: ignoreErrors:
- '#Access to an undefined property Aviat\\\Ion\\\Friend::\$[a-zA-Z0-9_]+#' - "#Offset 'fields' does not exist on array#"
- '#Call to an undefined method Aviat\\\Ion\\\Friend::[a-zA-Z0-9_]+\(\)#' - '#Function imagepalletetotruecolor not found#'
- '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#' - '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#'
- '#Property Amp\\Artax\\Internal\\RequestCycle::\$[a-zA-Z0-9_]+#' - '#Call to an undefined method Query\\QueryBuilderInterface::[a-zA-Z0-9_]+\(\)#'
excludes_analyse: excludes_analyse:
- tests/mocks.php - tests/mocks.php
- vendor
# These are objects that basically can return anything
universalObjectCratesClasses:
- Aviat\Ion\Friend

View File

@ -116,10 +116,9 @@ abstract class APIRequestBuilder {
* Set the request body * Set the request body
* *
* @param FormBody|string $body * @param FormBody|string $body
* @throws \TypeError
* @return self * @return self
*/ */
public function setBody($body): self public function setBody(FormBody|string $body): self
{ {
$this->request->setBody($body); $this->request->setBody($body);
return $this; return $this;
@ -129,7 +128,6 @@ abstract class APIRequestBuilder {
* Set body as form fields * Set body as form fields
* *
* @param array $fields Mapping of field names to values * @param array $fields Mapping of field names to values
* @throws \TypeError
* @return self * @return self
*/ */
public function setFormFields(array $fields): self public function setFormFields(array $fields): self
@ -293,74 +291,6 @@ abstract class APIRequestBuilder {
return $this; return $this;
} }
/**
* Create a GraphQL query and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
*/
public function queryRequest(string $name, array $variables = []): Request
{
$file = realpath("{$this->filePath}/Queries/{$name}.graphql");
if ( ! file_exists($file))
{
throw new LogicException('GraphQL query file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if ( ! empty($variables))
{
$body['variables'] = [];
foreach($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest('POST', $this->baseUrl, [
'body' => $body,
]);
}
/**
* Create a GraphQL mutation request, and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
* @throws Throwable
*/
public function mutateRequest (string $name, array $variables = []): Request
{
$file = "{$this->filePath}/Mutations/{$name}.graphql";
if ( ! file_exists($file))
{
throw new LogicException('GraphQL mutation file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if (!empty($variables)) {
$body['variables'] = [];
foreach ($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest('POST', $this->baseUrl, [
'body' => $body,
]);
}
/** /**
* Create the full request url * Create the full request url
* *

View File

@ -31,6 +31,7 @@ use const Aviat\AnimeClient\USER_AGENT;
use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\APIRequestBuilder;
use LogicException; use LogicException;
use Throwable;
final class RequestBuilder extends APIRequestBuilder { final class RequestBuilder extends APIRequestBuilder {
use ContainerAware; use ContainerAware;
@ -117,7 +118,7 @@ final class RequestBuilder extends APIRequestBuilder {
*/ */
public function runQuery(string $name, array $variables = []): array public function runQuery(string $name, array $variables = []): array
{ {
$file = realpath(__DIR__ . "/Queries/{$name}.graphql"); $file = __DIR__ . "/Queries/{$name}.graphql";
if ( ! file_exists($file)) if ( ! file_exists($file))
{ {
throw new LogicException('GraphQL query file does not exist.'); throw new LogicException('GraphQL query file does not exist.');
@ -150,8 +151,8 @@ final class RequestBuilder extends APIRequestBuilder {
*/ */
public function mutateRequest (string $name, array $variables = []): Request public function mutateRequest (string $name, array $variables = []): Request
{ {
$file = realpath(__DIR__ . "/Mutations/{$name}.graphql"); $file = __DIR__ . "/Mutations/{$name}.graphql";
if (!file_exists($file)) if ( ! file_exists($file))
{ {
throw new LogicException('GraphQL mutation file does not exist.'); throw new LogicException('GraphQL mutation file does not exist.');
} }

View File

@ -27,7 +27,7 @@ use DateTime;
class AnimeListTransformer extends AbstractTransformer { class AnimeListTransformer extends AbstractTransformer {
public function transform($item): AnimeListItem public function transform(array|object $item): AnimeListItem
{ {
return AnimeListItem::from([]); return AnimeListItem::from([]);
} }

View File

@ -28,7 +28,7 @@ use DateTime;
class MangaListTransformer extends AbstractTransformer { class MangaListTransformer extends AbstractTransformer {
public function transform($item) public function transform(array|object $item): MangaListItem
{ {
return MangaListItem::from([]); return MangaListItem::from([]);
} }

View File

@ -58,10 +58,9 @@ trait CacheTrait {
* @param string $key * @param string $key
* @param callable $primer * @param callable $primer
* @param array|null $primeArgs * @param array|null $primeArgs
* @return mixed|null * @return mixed
* @throws InvalidArgumentException
*/ */
public function getCached(string $key, callable $primer, ?array $primeArgs = []) public function getCached(string $key, callable $primer, ?array $primeArgs = []): mixed
{ {
$value = $this->cache->get($key, NULL); $value = $this->cache->get($key, NULL);

View File

@ -26,10 +26,6 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException}; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Event; use Aviat\Ion\Event;
use Psr\SimpleCache\InvalidArgumentException;
use Throwable;
/** /**
* Kitsu API Authentication * Kitsu API Authentication
*/ */
@ -55,8 +51,6 @@ final class Auth {
* Constructor * Constructor
* *
* @param ContainerInterface $container * @param ContainerInterface $container
* @throws ContainerException
* @throws NotFoundException
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
@ -66,7 +60,7 @@ final class Auth {
->getSegment(SESSION_SEGMENT); ->getSegment(SESSION_SEGMENT);
$this->model = $container->get('kitsu-model'); $this->model = $container->get('kitsu-model');
Event::on('::unauthorized::', [$this, 'reAuthenticate'], []); Event::on('::unauthorized::', [$this, 'reAuthenticate']);
} }
/** /**
@ -75,7 +69,6 @@ final class Auth {
* *
* @param string $password * @param string $password
* @return boolean * @return boolean
* @throws Throwable
*/ */
public function authenticate(string $password): bool public function authenticate(string $password): bool
{ {
@ -90,9 +83,8 @@ final class Auth {
/** /**
* Make the call to re-authenticate with the existing refresh token * Make the call to re-authenticate with the existing refresh token
* *
* @param string $refreshToken * @param string|null $refreshToken
* @return boolean * @return boolean
* @throws Throwable|InvalidArgumentException
*/ */
public function reAuthenticate(?string $refreshToken = NULL): bool public function reAuthenticate(?string $refreshToken = NULL): bool
{ {
@ -112,7 +104,6 @@ final class Auth {
* Check whether the current user is authenticated * Check whether the current user is authenticated
* *
* @return boolean * @return boolean
* @throws InvalidArgumentException
*/ */
public function isAuthenticated(): bool public function isAuthenticated(): bool
{ {
@ -133,7 +124,6 @@ final class Auth {
* Retrieve the authentication token from the session * Retrieve the authentication token from the session
* *
* @return string * @return string
* @throws InvalidArgumentException
*/ */
public function getAuthToken(): ?string public function getAuthToken(): ?string
{ {
@ -150,7 +140,6 @@ final class Auth {
* Retrieve the refresh token * Retrieve the refresh token
* *
* @return string|null * @return string|null
* @throws InvalidArgumentException
*/ */
private function getRefreshToken(): ?string private function getRefreshToken(): ?string
{ {
@ -166,11 +155,10 @@ final class Auth {
/** /**
* Save the new authentication information * Save the new authentication information
* *
* @param $auth * @param array|false $auth
* @return bool * @return bool
* @throws InvalidArgumentException
*/ */
private function storeAuth($auth): bool private function storeAuth(array|false $auth): bool
{ {
if (FALSE !== $auth) if (FALSE !== $auth)
{ {

View File

@ -37,12 +37,11 @@ use Aviat\AnimeClient\Enum\MediaType;
use Aviat\AnimeClient\Kitsu as K; use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\Types\Anime; use Aviat\AnimeClient\Types\Anime;
use Aviat\AnimeClient\Types\MangaPage; use Aviat\AnimeClient\Types\MangaPage;
use Aviat\Banker\Exception\InvalidArgumentException;
use Aviat\Ion\{ use Aviat\Ion\{
Di\ContainerAware, Di\ContainerAware,
Json Json
}; };
use Throwable; use Generator;
use function Amp\Promise\wait; use function Amp\Promise\wait;
use function Aviat\AnimeClient\getApiClient; use function Aviat\AnimeClient\getApiClient;
@ -91,7 +90,6 @@ final class Model {
* @param string $username * @param string $username
* @param string $password * @param string $password
* @return bool|array * @return bool|array
* @throws Throwable
*/ */
public function authenticate(string $username, string $password) public function authenticate(string $username, string $password)
{ {
@ -134,7 +132,6 @@ final class Model {
* *
* @param string $token * @param string $token
* @return bool|array * @return bool|array
* @throws Throwable
*/ */
public function reAuthenticate(string $token) public function reAuthenticate(string $token)
{ {
@ -174,7 +171,6 @@ final class Model {
* *
* @param string|null $username * @param string|null $username
* @return string * @return string
* @throws \Psr\SimpleCache\InvalidArgumentException
*/ */
public function getUserIdByUsername(string $username = NULL): string public function getUserIdByUsername(string $username = NULL): string
{ {
@ -210,7 +206,6 @@ final class Model {
* *
* @param string $slug * @param string $slug
* @return array * @return array
* @throws InvalidArgumentException
*/ */
public function getPerson(string $slug): array public function getPerson(string $slug): array
{ {
@ -289,8 +284,6 @@ final class Model {
* Retrieve the data for the anime watch history page * Retrieve the data for the anime watch history page
* *
* @return array * @return array
* @throws InvalidArgumentException
* @throws Throwable
*/ */
public function getAnimeHistory(): array public function getAnimeHistory(): array
{ {
@ -315,7 +308,6 @@ final class Model {
* *
* @param string $status - The watching status to filter the list with * @param string $status - The watching status to filter the list with
* @return array * @return array
* @throws InvalidArgumentException
*/ */
public function getAnimeList(string $status): array public function getAnimeList(string $status): array
{ {
@ -354,7 +346,6 @@ final class Model {
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
* @return int * @return int
* @throws InvalidArgumentException
*/ */
public function getAnimeListCount(string $status = '') : int public function getAnimeListCount(string $status = '') : int
{ {
@ -365,8 +356,6 @@ final class Model {
* Get all the anime entries, that are organized for output to html * Get all the anime entries, that are organized for output to html
* *
* @return array * @return array
* @throws ReflectionException
* @throws InvalidArgumentException
*/ */
public function getFullOrganizedAnimeList(): array public function getFullOrganizedAnimeList(): array
{ {
@ -434,8 +423,6 @@ final class Model {
* Retrieve the data for the manga read history page * Retrieve the data for the manga read history page
* *
* @return array * @return array
* @throws InvalidArgumentException
* @throws Throwable
*/ */
public function getMangaHistory(): array public function getMangaHistory(): array
{ {
@ -458,7 +445,6 @@ final class Model {
* *
* @param string $status - The reading status by which to filter the list * @param string $status - The reading status by which to filter the list
* @return array * @return array
* @throws InvalidArgumentException
*/ */
public function getMangaList(string $status): array public function getMangaList(string $status): array
{ {
@ -497,7 +483,6 @@ final class Model {
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
* @return int * @return int
* @throws InvalidArgumentException
*/ */
public function getMangaListCount(string $status = '') : int public function getMangaListCount(string $status = '') : int
{ {
@ -508,8 +493,6 @@ final class Model {
* Get all Manga lists * Get all Manga lists
* *
* @return array * @return array
* @throws ReflectionException
* @throws InvalidArgumentException
*/ */
public function getFullOrganizedMangaList(): array public function getFullOrganizedMangaList(): array
{ {
@ -638,8 +621,6 @@ final class Model {
* *
* @param string $type * @param string $type
* @return array * @return array
* @throws InvalidArgumentException
* @throws Throwable
*/ */
public function getSyncList(string $type): array public function getSyncList(string $type): array
{ {
@ -831,7 +812,7 @@ final class Model {
}); });
} }
private function getPages(callable $method, ...$args): ?\Generator private function getPages(callable $method, mixed ...$args): ?Generator
{ {
$items = $method(...$args); $items = $method(...$args);

View File

@ -32,8 +32,7 @@ trait MutationTrait {
* Create a list item * Create a list item
* *
* @param array $data * @param array $data
* @return Request * @return Request|null
* @throws InvalidArgumentException
*/ */
public function createListItem(array $data): ?Request public function createListItem(array $data): ?Request
{ {

View File

@ -26,7 +26,6 @@ use Amp\Http\Client\Request;
use Amp\Http\Client\Response; use Amp\Http\Client\Response;
use Aviat\AnimeClient\Kitsu as K; use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\AnimeClient\API\FailedResponseException;
use Aviat\AnimeClient\Enum\EventType; use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
@ -157,7 +156,6 @@ final class RequestBuilder extends APIRequestBuilder {
* @param string $name * @param string $name
* @param array $variables * @param array $variables
* @return array * @return array
* @throws Throwable
*/ */
public function mutate(string $name, array $variables = []): array public function mutate(string $name, array $variables = []): array
{ {
@ -181,7 +179,6 @@ final class RequestBuilder extends APIRequestBuilder {
* @param string $url * @param string $url
* @param array $options * @param array $options
* @return Response * @return Response
* @throws \Throwable
*/ */
public function getResponse(string $type, string $url, array $options = []): Response public function getResponse(string $type, string $url, array $options = []): Response
{ {
@ -200,15 +197,79 @@ final class RequestBuilder extends APIRequestBuilder {
return $response; return $response;
} }
/**
* Create a GraphQL query and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
*/
public function queryRequest(string $name, array $variables = []): Request
{
$file = realpath("{$this->filePath}/Queries/{$name}.graphql");
if ($file === FALSE || ! file_exists($file))
{
throw new LogicException('GraphQL query file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if ( ! empty($variables))
{
$body['variables'] = [];
foreach($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest('POST', $this->baseUrl, [
'body' => $body,
]);
}
/**
* Create a GraphQL mutation request, and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
*/
public function mutateRequest (string $name, array $variables = []): Request
{
$file = realpath("{$this->filePath}/Mutations/{$name}.graphql");
if ($file === FALSE || ! file_exists($file))
{
throw new LogicException('GraphQL mutation file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if (!empty($variables)) {
$body['variables'] = [];
foreach ($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest('POST', $this->baseUrl, [
'body' => $body,
]);
}
/** /**
* Make a request * Make a request
* *
* @param string $type * @param string $type
* @param string $url * @param string $url
* @param array $options * @param array $options
* @throws JsonException
* @throws FailedResponseException
* @throws Throwable
* @return array * @return array
*/ */
private function request(string $type, string $url, array $options = []): array private function request(string $type, string $url, array $options = []): array

View File

@ -33,11 +33,12 @@ 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|object $item API library item
* @return AnimeListItem * @return AnimeListItem
*/ */
public function transform($item): AnimeListItem public function transform(array|object $item): AnimeListItem
{ {
$item = (array)$item;
$animeId = $item['media']['id']; $animeId = $item['media']['id'];
$anime = $item['media']; $anime = $item['media'];
@ -115,14 +116,13 @@ final class AnimeListTransformer extends AbstractTransformer {
* @param array $item Transformed library item * @param array $item Transformed library item
* @return FormItem API library item * @return FormItem API library item
*/ */
public function untransform($item): FormItem public function untransform(array $item): FormItem
{ {
$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 = FormItem::from([ $untransformed = FormItem::from([
'id' => $item['id'], 'id' => $item['id'],
'anilist_item_id' => $item['anilist_item_id'] ?? NULL,
'mal_id' => $item['mal_id'] ?? NULL, 'mal_id' => $item['mal_id'] ?? NULL,
'data' => [ 'data' => [
'status' => $item['watching_status'], 'status' => $item['watching_status'],

View File

@ -29,11 +29,12 @@ 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|object $item API library item
* @return AnimePage * @return AnimePage
*/ */
public function transform($item): AnimePage public function transform(array|object $item): AnimePage
{ {
$item = (array)$item;
$base = $item['data']['findAnimeBySlug'] ?? $item['data']['findAnimeById'] ?? $item['data']['randomMedia']; $base = $item['data']['findAnimeBySlug'] ?? $item['data']['findAnimeById'] ?? $item['data']['randomMedia'];
$characters = []; $characters = [];
$links = []; $links = [];

View File

@ -28,12 +28,13 @@ use Locale;
final class CharacterTransformer extends AbstractTransformer { final class CharacterTransformer extends AbstractTransformer {
/** /**
* @param array $characterData * @param array|object $item
* @return Character * @return Character
*/ */
public function transform($characterData): Character public function transform(array|object $item): Character
{ {
$data = $characterData['data']['findCharacterBySlug'] ?? []; $item = (array)$item;
$data = $item['data']['findCharacterBySlug'] ?? [];
$castings = []; $castings = [];
$media = [ $media = [
'anime' => [], 'anime' => [],

View File

@ -65,7 +65,7 @@ abstract class HistoryTransformer {
foreach ($base as $entry) foreach ($base as $entry)
{ {
if ( ! (strtolower($entry['media']['__typename']) === $this->type)) if (strtolower($entry['media']['__typename']) !== $this->type)
{ {
continue; continue;
} }
@ -210,7 +210,7 @@ abstract class HistoryTransformer {
]); ]);
} }
protected function transformUpdated($entry): HistoryItem protected function transformUpdated(array $entry): HistoryItem
{ {
$id = $entry['media']['id']; $id = $entry['media']['id'];
$data = $entry['media']; $data = $entry['media'];
@ -242,7 +242,7 @@ abstract class HistoryTransformer {
]); ]);
} }
return $entry; return HistoryItem::from($entry);
} }
protected function linkTitle (array $data): string protected function linkTitle (array $data): string
@ -257,6 +257,11 @@ abstract class HistoryTransformer {
$date $date
); );
if ($dateTime === FALSE)
{
return new DateTimeImmutable();
}
return $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); return $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
} }
@ -265,7 +270,7 @@ abstract class HistoryTransformer {
return "/{$this->type}/details/{$data['slug']}"; return "/{$this->type}/details/{$data['slug']}";
} }
protected function isReconsuming ($entry): bool protected function isReconsuming (array $entry): bool
{ {
return $entry['libraryEntry']['reconsuming']; return $entry['libraryEntry']['reconsuming'];
} }

View File

@ -26,8 +26,9 @@ use Aviat\Ion\Type\StringType;
*/ */
final class LibraryEntryTransformer extends AbstractTransformer final class LibraryEntryTransformer extends AbstractTransformer
{ {
public function transform($item) public function transform(array|object $item): AnimeListItem|MangaListItem
{ {
$item = (array)$item;
$type = $item['media']['type'] ?? ''; $type = $item['media']['type'] ?? '';
$genres = []; $genres = [];
@ -37,20 +38,15 @@ final class LibraryEntryTransformer extends AbstractTransformer
sort($genres); sort($genres);
} }
switch (strtolower($type)) return match (strtolower($type))
{ {
case 'anime': 'anime' => $this->animeTransform($item, $genres),
return $this->animeTransform($item, $genres); 'manga' => $this->mangaTransform($item, $genres),
default => AnimeListItem::from([]),
case 'manga': };
return $this->mangaTransform($item, $genres);
default:
return [];
}
} }
private function animeTransform($item, array $genres): AnimeListItem private function animeTransform(array $item, array $genres): AnimeListItem
{ {
$animeId = $item['media']['id']; $animeId = $item['media']['id'];
$anime = $item['media']; $anime = $item['media'];
@ -119,7 +115,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
]); ]);
} }
private function mangaTransform($item, array $genres): MangaListItem private function mangaTransform(array $item, array $genres): MangaListItem
{ {
$mangaId = $item['media']['id']; $mangaId = $item['media']['id'];
$manga = $item['media']; $manga = $item['media'];

View File

@ -31,11 +31,12 @@ final class MangaListTransformer extends AbstractTransformer {
/** /**
* Remap zipped anime data to a more logical form * Remap zipped anime data to a more logical form
* *
* @param array $item manga entry item * @param array|object $item manga entry item
* @return MangaListItem * @return MangaListItem
*/ */
public function transform($item): MangaListItem public function transform(array|object $item): MangaListItem
{ {
$item = (array)$item;
$mangaId = $item['media']['id']; $mangaId = $item['media']['id'];
$manga = $item['media']; $manga = $item['media'];

View File

@ -29,11 +29,12 @@ final class MangaTransformer 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|object $item API library item
* @return MangaPage * @return MangaPage
*/ */
public function transform($item): MangaPage public function transform(array|object $item): MangaPage
{ {
$item = (array)$item;
$base = $item['data']['findMangaBySlug'] ?? $item['data']['findMangaById'] ?? $item['data']['randomMedia']; $base = $item['data']['findMangaBySlug'] ?? $item['data']['findMangaById'] ?? $item['data']['randomMedia'];
$characters = []; $characters = [];
$links = []; $links = [];

View File

@ -26,12 +26,13 @@ use Aviat\Ion\Transformer\AbstractTransformer;
final class PersonTransformer extends AbstractTransformer { final class PersonTransformer extends AbstractTransformer {
/** /**
* @param array|object $personData * @param array|object $item
* @return Person * @return Person
*/ */
public function transform($personData): Person public function transform(array|object $item): Person
{ {
$data = $personData['data']['findPersonBySlug'] ?? []; $item = (array)$item;
$data = $item['data']['findPersonBySlug'] ?? [];
$canonicalName = $data['names']['localized'][$data['names']['canonical']] $canonicalName = $data['names']['localized'][$data['names']['canonical']]
?? array_shift($data['names']['localized']); ?? array_shift($data['names']['localized']);

View File

@ -29,9 +29,10 @@ use Aviat\Ion\Transformer\AbstractTransformer;
* @return User * @return User
*/ */
final class UserTransformer extends AbstractTransformer { final class UserTransformer extends AbstractTransformer {
public function transform($profileData): User public function transform(array|object $item): User
{ {
$base = $profileData['data']['findProfileBySlug'] ?? []; $item = (array)$item;
$base = $item['data']['findProfileBySlug'] ?? [];
$favorites = $base['favorites']['nodes'] ?? []; $favorites = $base['favorites']['nodes'] ?? [];
$stats = $base['stats'] ?? []; $stats = $base['stats'] ?? [];
$waifu = (array_key_exists('waifu', $base)) ? [ $waifu = (array_key_exists('waifu', $base)) ? [
@ -71,7 +72,7 @@ final class UserTransformer extends AbstractTransformer {
return $output; return $output;
} }
private function organizeStats(array $stats, $data = []): array private function organizeStats(array $stats, array $data = []): array
{ {
$animeStats = []; $animeStats = [];
$mangaStats = []; $mangaStats = [];

View File

@ -48,6 +48,11 @@ function loadConfig(string $path): array
$output = []; $output = [];
$files = glob("{$path}/*.toml"); $files = glob("{$path}/*.toml");
if ( ! is_array($files))
{
return [];
}
foreach ($files as $file) foreach ($files as $file)
{ {
$key = str_replace('.toml', '', basename($file)); $key = str_replace('.toml', '', basename($file));
@ -86,7 +91,7 @@ function loadTomlFile(string $filename): array
return Toml::parseFile($filename); return Toml::parseFile($filename);
} }
function _iterateToml(TomlBuilder $builder, iterable $data, $parentKey = NULL): void function _iterateToml(TomlBuilder $builder, iterable $data, mixed $parentKey = NULL): void
{ {
foreach ($data as $key => $value) foreach ($data as $key => $value)
{ {
@ -119,7 +124,7 @@ function _iterateToml(TomlBuilder $builder, iterable $data, $parentKey = NULL):
/** /**
* Serialize config data into a Toml file * Serialize config data into a Toml file
* *
* @param mixed $data * @param iterable $data
* @return string * @return string
*/ */
function arrayToToml(iterable $data): string function arrayToToml(iterable $data): string
@ -152,7 +157,7 @@ function tomlToArray(string $toml): array
* @param mixed $array * @param mixed $array
* @return bool * @return bool
*/ */
function isSequentialArray($array): bool function isSequentialArray(mixed $array): bool
{ {
if ( ! is_array($array)) if ( ! is_array($array))
{ {
@ -265,7 +270,7 @@ function getLocalImg (string $kitsuUrl, $webp = TRUE): string
$parts = parse_url($kitsuUrl); $parts = parse_url($kitsuUrl);
if ($parts === FALSE) if ($parts === FALSE || ! array_key_exists('path', $parts))
{ {
return 'images/placeholder.webp'; return 'images/placeholder.webp';
} }
@ -291,22 +296,35 @@ function getLocalImg (string $kitsuUrl, $webp = TRUE): string
* @param int|null $width * @param int|null $width
* @param int|null $height * @param int|null $height
* @param string $text * @param string $text
* @return bool
*/ */
function createPlaceholderImage (string $path, ?int $width, ?int $height, $text = 'Image Unavailable'): void function createPlaceholderImage (string $path, ?int $width, ?int $height, $text = 'Image Unavailable'): bool
{ {
$width = $width ?? 200; $width = $width ?? 200;
$height = $height ?? 200; $height = $height ?? 200;
$img = imagecreatetruecolor($width, $height); $img = imagecreatetruecolor($width, $height);
if ($img === FALSE)
{
return FALSE;
}
imagealphablending($img, TRUE); imagealphablending($img, TRUE);
$path = rtrim($path, '/'); $path = rtrim($path, '/');
// Background is the first color by default // Background is the first color by default
$fillColor = imagecolorallocatealpha($img, 255, 255, 255, 127); $fillColor = imagecolorallocatealpha($img, 255, 255, 255, 127);
if ($fillColor === FALSE)
{
return FALSE;
}
imagefill($img, 0, 0, $fillColor); imagefill($img, 0, 0, $fillColor);
$textColor = imagecolorallocate($img, 64, 64, 64); $textColor = imagecolorallocate($img, 64, 64, 64);
if ($textColor === FALSE)
{
return FALSE;
}
imagealphablending($img, TRUE); imagealphablending($img, TRUE);
@ -328,12 +346,18 @@ function createPlaceholderImage (string $path, ?int $width, ?int $height, $text
imagedestroy($img); imagedestroy($img);
$pngImage = imagecreatefrompng($path . '/placeholder.png'); $pngImage = imagecreatefrompng($path . '/placeholder.png');
if ($pngImage === FALSE)
{
return FALSE;
}
imagealphablending($pngImage, TRUE); imagealphablending($pngImage, TRUE);
imagesavealpha($pngImage, TRUE); imagesavealpha($pngImage, TRUE);
imagewebp($pngImage, $path . '/placeholder.webp'); imagewebp($pngImage, $path . '/placeholder.webp');
imagedestroy($pngImage); imagedestroy($pngImage);
return TRUE;
} }
/** /**
@ -354,7 +378,7 @@ function colNotEmpty(array $search, string $key): bool
* *
* @param CacheInterface $cache * @param CacheInterface $cache
* @return bool * @return bool
* @throws InvalidArgumentException * @throws Throwable
*/ */
function clearCache(CacheInterface $cache): bool function clearCache(CacheInterface $cache): bool
{ {
@ -389,5 +413,6 @@ function renderTemplate(string $path, array $data): string
ob_start(); ob_start();
extract($data, EXTR_OVERWRITE); extract($data, EXTR_OVERWRITE);
include $path; include $path;
return ob_get_clean(); $rawOutput = ob_get_clean();
return (is_string($rawOutput)) ? $rawOutput : '';
} }

View File

@ -52,13 +52,22 @@ abstract class BaseCommand extends Command {
* @param string|int|null $bgColor * @param string|int|null $bgColor
* @return void * @return void
*/ */
public function echoBox($message, $fgColor = NULL, $bgColor = NULL): void public function echoBox(string|array $message, string|int|null $fgColor = NULL, string|int|null $bgColor = NULL): void
{ {
if (is_array($message)) if (is_array($message))
{ {
$message = implode("\n", $message); $message = implode("\n", $message);
} }
if ($fgColor !== NULL)
{
$fgColor = (string)$fgColor;
}
if ($bgColor !== NULL)
{
$bgColor = (string)$bgColor;
}
// color message // color message
$message = Colors::colorize($message, $fgColor, $bgColor); $message = Colors::colorize($message, $fgColor, $bgColor);
@ -129,8 +138,17 @@ abstract class BaseCommand extends Command {
return $this->_di($configArray, $APP_DIR); return $this->_di($configArray, $APP_DIR);
} }
private function _line(string $message, $fgColor = NULL, $bgColor = NULL): void private function _line(string $message, int|string|null $fgColor = NULL, int|string|null $bgColor = NULL): void
{ {
if ($fgColor !== NULL)
{
$fgColor = (string)$fgColor;
}
if ($bgColor !== NULL)
{
$bgColor = (string)$bgColor;
}
$message = Colors::colorize($message, $fgColor, $bgColor); $message = Colors::colorize($message, $fgColor, $bgColor);
$this->getConsole()->writeln($message); $this->getConsole()->writeln($message);
} }

View File

@ -218,7 +218,7 @@ final class SyncLists extends BaseCommand {
* @param array $data * @param array $data
* @throws Throwable * @throws Throwable
*/ */
protected function update(string $type, array $data) protected function update(string $type, array $data): void
{ {
if ( ! empty($data['addToAnilist'])) if ( ! empty($data['addToAnilist']))
{ {
@ -259,7 +259,7 @@ final class SyncLists extends BaseCommand {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Fetch helpers // Fetch helpers
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
private function fetchAnilistCount(string $type) private function fetchAnilistCount(string $type): int
{ {
$list = $this->fetchAnilist($type); $list = $this->fetchAnilist($type);

View File

@ -203,7 +203,7 @@ class Controller {
* @throws NotFoundException * @throws NotFoundException
* @return string * @return string
*/ */
protected function loadPartial($view, string $template, array $data = []): string protected function loadPartial(HtmlView $view, string $template, array $data = []): string
{ {
$router = $this->container->get('dispatcher'); $router = $this->container->get('dispatcher');
@ -236,7 +236,7 @@ class Controller {
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
protected function renderFullPage($view, string $template, array $data): HtmlView protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
{ {
$csp = [ $csp = [
"default-src 'self'", "default-src 'self'",
@ -360,7 +360,7 @@ class Controller {
* @throws NotFoundException * @throws NotFoundException
* @return string * @return string
*/ */
protected function showMessage($view, string $type, string $message): string protected function showMessage(HtmlView $view, string $type, string $message): string
{ {
return $this->loadPartial($view, 'message', [ return $this->loadPartial($view, 'message', [
'message_type' => $type, 'message_type' => $type,
@ -399,7 +399,7 @@ class Controller {
* @throws DoubleRenderException * @throws DoubleRenderException
* @return void * @return void
*/ */
protected function outputJSON($data, int $code): void protected function outputJSON(mixed $data, int $code): void
{ {
(new JsonView()) (new JsonView())
->setOutput($data) ->setOutput($data)
@ -420,10 +420,7 @@ class Controller {
{ {
(new HttpView())->redirect($url, $code)->send(); (new HttpView())->redirect($url, $code)->send();
} }
catch (\Throwable $e) catch (\Throwable) {}
{
}
} }
} }
// End of BaseController.php // End of BaseController.php

View File

@ -145,7 +145,7 @@ final class Anime extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
if (empty($data['mal_id'])) if (empty($data['mal_id']))
{ {
@ -220,7 +220,7 @@ final class Anime extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
// large form-based updates // large form-based updates
@ -257,7 +257,7 @@ final class Anime extends BaseController {
} }
else else
{ {
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
} }
if (empty($data)) if (empty($data))
@ -282,7 +282,7 @@ final class Anime extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$body = $this->request->getParsedBody(); $body = (array)$this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
if ($response === TRUE) if ($response === TRUE)

View File

@ -154,7 +154,7 @@ final class AnimeCollection extends BaseController {
public function edit(): void public function edit(): void
{ {
$this->checkAuth(); $this->checkAuth();
$this->update($this->request->getParsedBody()); $this->update((array)$this->request->getParsedBody());
} }
/** /**
@ -169,7 +169,7 @@ final class AnimeCollection extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
if (array_key_exists('id', $data)) if (array_key_exists('id', $data))
{ {
// Check for existing entry // Check for existing entry
@ -218,7 +218,7 @@ final class AnimeCollection extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
if ( ! array_key_exists('hummingbird_id', $data)) if ( ! array_key_exists('hummingbird_id', $data))
{ {
$this->setFlashMessage("Can't delete item that doesn't exist", 'error'); $this->setFlashMessage("Can't delete item that doesn't exist", 'error');
@ -238,11 +238,11 @@ final class AnimeCollection extends BaseController {
/** /**
* Update a collection item * Update a collection item
* *
* @param $data * @param array $data
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
protected function update($data): void protected function update(array $data): void
{ {
if (array_key_exists('hummingbird_id', $data)) if (array_key_exists('hummingbird_id', $data))
{ {

View File

@ -37,9 +37,7 @@ final class Images extends BaseController {
* @param string $file The filename to look for * @param string $file The filename to look for
* @param bool $display Whether to output the image to the server * @param bool $display Whether to output the image to the server
* @return void * @return void
* @throws NotFoundException
* @throws Throwable * @throws Throwable
* @throws ContainerException
*/ */
public function cache(string $type, string $file, $display = TRUE): void public function cache(string $type, string $file, $display = TRUE): void
{ {
@ -134,7 +132,16 @@ final class Images extends BaseController {
[$origWidth] = getimagesizefromstring($data); [$origWidth] = getimagesizefromstring($data);
$gdImg = imagecreatefromstring($data); $gdImg = imagecreatefromstring($data);
if ($gdImg === FALSE)
{
return;
}
$resizedImg = imagescale($gdImg, $width ?? $origWidth); $resizedImg = imagescale($gdImg, $width ?? $origWidth);
if ($resizedImg === FALSE)
{
return;
}
if ($ext === 'gif') if ($ext === 'gif')
{ {
@ -161,7 +168,7 @@ final class Images extends BaseController {
? 'image/webp' ? 'image/webp'
: $response->getHeader('content-type')[0]; : $response->getHeader('content-type')[0];
$outputFile = (strpos($file, '-original') !== FALSE) $outputFile = (str_contains($file, '-original'))
? "{$filePrefix}-original.{$ext}" ? "{$filePrefix}-original.{$ext}"
: "{$filePrefix}.{$ext}"; : "{$filePrefix}.{$ext}";

View File

@ -135,15 +135,13 @@ final class Manga extends Controller {
* Add an manga to the list * Add an manga to the list
* *
* @return void * @return void
* @throws NotFoundException
* @throws Throwable * @throws Throwable
* @throws ContainerException
*/ */
public function add(): void public function add(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
if ( ! array_key_exists('id', $data)) if ( ! array_key_exists('id', $data))
{ {
$this->redirect('manga/add', 303); $this->redirect('manga/add', 303);
@ -163,7 +161,7 @@ final class Manga extends Controller {
} }
else else
{ {
$this->setFlashMessage('Failed to add new manga to list' . $result['body'], 'error'); $this->setFlashMessage('Failed to add new manga to list:' . print_r($data, TRUE), 'error');
} }
$this->sessionRedirect(); $this->sessionRedirect();
@ -180,7 +178,7 @@ final class Manga extends Controller {
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void * @return void
*/ */
public function edit($id, $status = 'All'): void public function edit(string $id, string $status = 'All'): void
{ {
$this->checkAuth(); $this->checkAuth();
@ -218,14 +216,12 @@ final class Manga extends Controller {
* *
* @return void * @return void
* @throws Throwable * @throws Throwable
* @throws NotFoundException
* @throws ContainerException
*/ */
public function formUpdate(): void public function formUpdate(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = $this->request->getParsedBody(); $data = (array)$this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
// large form-based updates // large form-based updates
@ -275,8 +271,6 @@ final class Manga extends Controller {
/** /**
* Remove an manga from the list * Remove an manga from the list
* *
* @throws ContainerException
* @throws NotFoundException
* @throws Throwable * @throws Throwable
* @return void * @return void
*/ */
@ -284,7 +278,7 @@ final class Manga extends Controller {
{ {
$this->checkAuth(); $this->checkAuth();
$body = $this->request->getParsedBody(); $body = (array)$this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
if ($response) if ($response)

View File

@ -74,7 +74,7 @@ final class Misc extends BaseController {
*/ */
public function loginAction(): void public function loginAction(): void
{ {
$post = $this->request->getParsedBody(); $post = (array)$this->request->getParsedBody();
if ($this->auth->authenticate($post['password'])) if ($this->auth->authenticate($post['password']))
{ {
@ -83,7 +83,11 @@ final class Misc extends BaseController {
} }
$this->setFlashMessage('Invalid username or password.'); $this->setFlashMessage('Invalid username or password.');
$this->redirect($this->url->generate('login'), 303);
$redirectUrl = $this->url->generate('login');
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
$this->redirect($redirectUrl, 303);
} }
/** /**

View File

@ -84,7 +84,7 @@ final class Settings extends BaseController {
*/ */
public function update(): void public function update(): void
{ {
$post = $this->request->getParsedBody(); $post = (array)$this->request->getParsedBody();
unset($post['settings-tabs']); unset($post['settings-tabs']);
$saved = $this->settingsModel->saveSettingsFile($post); $saved = $this->settingsModel->saveSettingsFile($post);
@ -93,7 +93,10 @@ final class Settings extends BaseController {
? $this->setFlashMessage('Saved config settings.', 'success') ? $this->setFlashMessage('Saved config settings.', 'success')
: $this->setFlashMessage('Failed to save config file.', 'error'); : $this->setFlashMessage('Failed to save config file.', 'error');
$this->redirect($this->url->generate('settings'), 303); $redirectUrl = $this->url->generate('settings');
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
$this->redirect($redirectUrl, 303);
} }
/** /**
@ -152,6 +155,9 @@ final class Settings extends BaseController {
? $this->setFlashMessage('Linked Anilist Account', 'success') ? $this->setFlashMessage('Linked Anilist Account', 'success')
: $this->setFlashMessage('Error Linking Anilist Account', 'error'); : $this->setFlashMessage('Error Linking Anilist Account', 'error');
$this->redirect($this->url->generate('settings'), 303); $redirectUrl = $this->url->generate('settings');
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
$this->redirect($redirectUrl, 303);
} }
} }

View File

@ -16,19 +16,23 @@
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\AnimeClient\Enum\EventType; use Aviat\Ion\Json;
use function Aviat\Ion\_dir; use Aura\Router\{
Map,
use Aura\Router\{Map, Matcher, Route, Rule}; Matcher,
Route,
Rule,
};
use Aviat\AnimeClient\API\FailedResponseException; use Aviat\AnimeClient\API\FailedResponseException;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Event;
use Aviat\Ion\Friend; use Aviat\Ion\Friend;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
use JetBrains\PhpStorm\ArrayShape;
use LogicException; use LogicException;
use ReflectionException; use ReflectionException;
use function Aviat\Ion\_dir;
/** /**
* Basic routing/ dispatch * Basic routing/ dispatch
*/ */
@ -78,7 +82,7 @@ final class Dispatcher extends RoutingBase {
* *
* @return Route|false * @return Route|false
*/ */
public function getRoute() public function getRoute(): Route | false
{ {
$logger = $this->container->getLogger(); $logger = $this->container->getLogger();
@ -132,7 +136,7 @@ final class Dispatcher extends RoutingBase {
{ {
// If not route was matched, return an appropriate http // If not route was matched, return an appropriate http
// error message // error message
$errorRoute = $this->getErrorParams(); $errorRoute = (array)$this->getErrorParams();
$controllerName = DEFAULT_CONTROLLER; $controllerName = DEFAULT_CONTROLLER;
$actionMethod = $errorRoute['action_method']; $actionMethod = $errorRoute['action_method'];
$params = $errorRoute['params']; $params = $errorRoute['params'];
@ -152,11 +156,11 @@ final class Dispatcher extends RoutingBase {
* Parse out the arguments for the appropriate controller for * Parse out the arguments for the appropriate controller for
* the current route * the current route
* *
* @param Route $route * @param Friend $route
* @throws LogicException * @throws LogicException
* @return array * @return array
*/ */
protected function processRoute($route): array protected function processRoute(Friend $route): array
{ {
if ( ! array_key_exists('controller', $route->attributes)) if ( ! array_key_exists('controller', $route->attributes))
{ {
@ -166,7 +170,7 @@ final class Dispatcher extends RoutingBase {
$controllerName = $route->attributes['controller']; $controllerName = $route->attributes['controller'];
// Get the full namespace for a controller if a short name is given // Get the full namespace for a controller if a short name is given
if (strpos($controllerName, '\\') === FALSE) if ( ! str_contains($controllerName, '\\'))
{ {
$map = $this->getControllerList(); $map = $this->getControllerList();
$controllerName = $map[$controllerName]; $controllerName = $map[$controllerName];
@ -191,7 +195,7 @@ final class Dispatcher extends RoutingBase {
$logger = $this->container->getLogger(); $logger = $this->container->getLogger();
if ($logger !== NULL) if ($logger !== NULL)
{ {
$logger->info(json_encode($params)); $logger->info(Json::encode($params));
} }
return [ return [
@ -244,6 +248,10 @@ final class Dispatcher extends RoutingBase {
$path = trim($path, '/'); $path = trim($path, '/');
$actualPath = realpath(_dir(SRC_DIR, $path)); $actualPath = realpath(_dir(SRC_DIR, $path));
$classFiles = glob("{$actualPath}/*.php"); $classFiles = glob("{$actualPath}/*.php");
if ($classFiles === FALSE)
{
return [];
}
$controllers = []; $controllers = [];
@ -282,9 +290,10 @@ final class Dispatcher extends RoutingBase {
$logger->debug('Dispatcher - controller arguments', $params); $logger->debug('Dispatcher - controller arguments', $params);
} }
call_user_func_array([$controller, $method], array_values($params)); $params = array_values($params);
$controller->$method(...$params);
} }
catch (FailedResponseException $e) catch (FailedResponseException)
{ {
$controllerName = DEFAULT_CONTROLLER; $controllerName = DEFAULT_CONTROLLER;
$controller = new $controllerName($this->container); $controller = new $controllerName($this->container);

View File

@ -48,7 +48,7 @@ final class FormGenerator {
* Create a new FormGenerator * Create a new FormGenerator
* *
* @param ContainerInterface $container * @param ContainerInterface $container
* @return $this * @return self
*/ */
public static function new(ContainerInterface $container): self public static function new(ContainerInterface $container): self
{ {

View File

@ -166,7 +166,7 @@ final class Kitsu {
*/ */
public static function parseStreamingLinks(array $nodes): array public static function parseStreamingLinks(array $nodes): array
{ {
if (count($nodes) === 0) if (empty($nodes))
{ {
return []; return [];
} }
@ -179,12 +179,16 @@ final class Kitsu {
// 'Fix' links that start with the hostname, // 'Fix' links that start with the hostname,
// rather than a protocol // rather than a protocol
if (strpos($url, '//') === FALSE) if ( ! str_contains($url, '//'))
{ {
$url = '//' . $url; $url = '//' . $url;
} }
$host = parse_url($url, \PHP_URL_HOST); $host = parse_url($url, \PHP_URL_HOST);
if ($host === FALSE)
{
return [];
}
$links[] = [ $links[] = [
'meta' => self::getServiceMetaData($host), 'meta' => self::getServiceMetaData($host),
@ -401,7 +405,7 @@ final class Kitsu {
if (empty($parts)) if (empty($parts))
{ {
return $last; return ($last !== NULL) ? $last : '';
} }
return (count($parts) > 1) return (count($parts) > 1)
@ -412,11 +416,11 @@ final class Kitsu {
/** /**
* Determine if an alternate title is unique enough to list * Determine if an alternate title is unique enough to list
* *
* @param string $title * @param string|null $title
* @param array $existingTitles * @param array $existingTitles
* @return bool * @return bool
*/ */
private static function titleIsUnique(string $title = '', array $existingTitles = []): bool protected static function titleIsUnique(?string $title = '', array $existingTitles = []): bool
{ {
if (empty($title)) if (empty($title))
{ {

View File

@ -22,7 +22,7 @@ use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Exception\ConfigException; use Aviat\Ion\Exception\ConfigException;
use Aviat\Ion\Type\ArrayType; use Aviat\Ion\Type\ArrayType;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface;
/** /**
* Helper object to manage menu creation and selection * Helper object to manage menu creation and selection
@ -39,13 +39,13 @@ final class MenuGenerator extends UrlGenerator {
/** /**
* Request object * Request object
* *
* @var RequestInterface * @var ServerRequestInterface
*/ */
protected RequestInterface $request; protected ServerRequestInterface $request;
/** /**
* @param ContainerInterface $container * @param ContainerInterface $container
* @return static * @return self
*/ */
public static function new(ContainerInterface $container): self public static function new(ContainerInterface $container): self
{ {

View File

@ -50,6 +50,11 @@ final class AnimeCollection extends Collection {
*/ */
public function getCollection(): array public function getCollection(): array
{ {
if ($this->db === NULL)
{
return [];
}
$rawCollection = $this->getCollectionFromDatabase(); $rawCollection = $this->getCollectionFromDatabase();
$collection = []; $collection = [];
@ -76,7 +81,7 @@ final class AnimeCollection extends Collection {
*/ */
public function getFlatCollection(): array public function getFlatCollection(): array
{ {
if ( ! $this->validDatabase) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -92,6 +97,11 @@ final class AnimeCollection extends Collection {
$genres = $this->getGenreList(); $genres = $this->getGenreList();
$media = $this->getMediaList(); $media = $this->getMediaList();
if ($rows === FALSE)
{
return [];
}
foreach($rows as &$row) foreach($rows as &$row)
{ {
$id = $row['hummingbird_id']; $id = $row['hummingbird_id'];
@ -117,7 +127,7 @@ final class AnimeCollection extends Collection {
*/ */
public function getMediaTypeList(): array public function getMediaTypeList(): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -128,7 +138,13 @@ final class AnimeCollection extends Collection {
->from('media') ->from('media')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
foreach ($rows as $row)
{ {
$flatList[$row['id']] = $row['type']; $flatList[$row['id']] = $row['type'];
} }
@ -158,12 +174,12 @@ final class AnimeCollection extends Collection {
/** /**
* Add an item to the anime collection * Add an item to the anime collection
* *
* @param array $data * @param mixed $data
* @return void * @return void
*/ */
public function add($data): void public function add(mixed $data): void
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return; return;
} }
@ -197,12 +213,12 @@ final class AnimeCollection extends Collection {
/** /**
* Verify that an item was added * Verify that an item was added
* *
* @param array|null|object $data * @param array $data
* @return bool * @return bool
*/ */
public function wasAdded($data): bool public function wasAdded(array $data): bool
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return FALSE; return FALSE;
} }
@ -218,9 +234,9 @@ final class AnimeCollection extends Collection {
* @param array $data * @param array $data
* @return void * @return void
*/ */
public function update($data): void public function update(array $data): void
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return; return;
} }
@ -253,13 +269,13 @@ final class AnimeCollection extends Collection {
/** /**
* Verify that the collection item was updated * Verify that the collection item was updated
* *
* @param array|null|object $data * @param array $data
* *
* @return bool * @return bool
*/ */
public function wasUpdated($data): bool public function wasUpdated(array $data): bool
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return FALSE; return FALSE;
} }
@ -288,9 +304,9 @@ final class AnimeCollection extends Collection {
* @param array $data * @param array $data
* @return void * @return void
*/ */
public function delete($data): void public function delete(array $data): void
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return; return;
} }
@ -316,12 +332,12 @@ final class AnimeCollection extends Collection {
} }
/** /**
* @param array|null|object $data * @param array $data
* @return bool * @return bool
*/ */
public function wasDeleted($data): bool public function wasDeleted(array $data): bool
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return FALSE; return FALSE;
} }
@ -335,9 +351,9 @@ final class AnimeCollection extends Collection {
* @param int|string $kitsuId * @param int|string $kitsuId
* @return array * @return array
*/ */
public function get($kitsuId): array public function get(int|string $kitsuId): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -348,6 +364,11 @@ final class AnimeCollection extends Collection {
->get() ->get()
->fetch(PDO::FETCH_ASSOC); ->fetch(PDO::FETCH_ASSOC);
if ($row === FALSE)
{
return [];
}
// Get the media ids // Get the media ids
$mediaRows = $this->db->select('media_id') $mediaRows = $this->db->select('media_id')
->from('anime_set_media_link') ->from('anime_set_media_link')
@ -355,6 +376,11 @@ final class AnimeCollection extends Collection {
->get() ->get()
->fetchAll(PDO::FETCH_ASSOC); ->fetchAll(PDO::FETCH_ASSOC);
if ($mediaRows === FALSE)
{
return [];
}
$row['media_id'] = array_column($mediaRows, 'media_id'); $row['media_id'] = array_column($mediaRows, 'media_id');
return $row; return $row;
@ -366,9 +392,9 @@ final class AnimeCollection extends Collection {
* @param int|string $kitsuId * @param int|string $kitsuId
* @return bool * @return bool
*/ */
public function has($kitsuId): bool public function has(int|string $kitsuId): bool
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return FALSE; return FALSE;
} }
@ -390,7 +416,7 @@ final class AnimeCollection extends Collection {
*/ */
public function getGenreList(array $filter = []): array public function getGenreList(array $filter = []): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -416,7 +442,13 @@ final class AnimeCollection extends Collection {
->orderBy('genre') ->orderBy('genre')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
foreach ($rows as $row)
{ {
$id = $row['hummingbird_id']; $id = $row['hummingbird_id'];
$genre = $row['genre']; $genre = $row['genre'];
@ -437,7 +469,7 @@ final class AnimeCollection extends Collection {
} }
} }
} }
catch (PDOException $e) {} catch (PDOException) {}
$this->db->resetQuery(); $this->db->resetQuery();
@ -452,7 +484,7 @@ final class AnimeCollection extends Collection {
*/ */
public function getMediaList(array $filter = []): array public function getMediaList(array $filter = []): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -478,7 +510,13 @@ final class AnimeCollection extends Collection {
->orderBy('media') ->orderBy('media')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
foreach ($rows as $row)
{ {
$id = $row['hummingbird_id']; $id = $row['hummingbird_id'];
$media = $row['media']; $media = $row['media'];
@ -508,6 +546,11 @@ final class AnimeCollection extends Collection {
private function updateMediaLink(string $animeId, array $media): void private function updateMediaLink(string $animeId, array $media): void
{ {
if ($this->db === NULL)
{
return;
}
$this->db->beginTransaction(); $this->db->beginTransaction();
// Delete the old entries // Delete the old entries
@ -537,7 +580,7 @@ final class AnimeCollection extends Collection {
*/ */
private function updateGenres($animeId): void private function updateGenres($animeId): void
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return; return;
} }
@ -571,13 +614,13 @@ final class AnimeCollection extends Collection {
} }
} }
if ( ! empty($linksToInsert)) if ($this->db !== NULL && ! empty($linksToInsert))
{ {
try try
{ {
$this->db->insertBatch('anime_set_genre_link', $linksToInsert); $this->db->insertBatch('anime_set_genre_link', $linksToInsert);
} }
catch (PDOException $e) {} catch (PDOException) {}
} }
} }
@ -588,7 +631,7 @@ final class AnimeCollection extends Collection {
*/ */
private function addNewGenres(array $genres): void private function addNewGenres(array $genres): void
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return; return;
} }
@ -609,7 +652,7 @@ final class AnimeCollection extends Collection {
{ {
$this->db->insertBatch('genres', $insert); $this->db->insertBatch('genres', $insert);
} }
catch (PDOException $e) catch (PDOException)
{ {
// dump($e); // dump($e);
} }
@ -630,7 +673,7 @@ final class AnimeCollection extends Collection {
private function getExistingGenres(): array private function getExistingGenres(): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -642,7 +685,13 @@ final class AnimeCollection extends Collection {
->from('genres') ->from('genres')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $genre) $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
foreach ($rows as $genre)
{ {
$genres[$genre['id']] = $genre['genre']; $genres[$genre['id']] = $genre['genre'];
} }
@ -654,7 +703,7 @@ final class AnimeCollection extends Collection {
private function getExistingGenreLinkEntries(): array private function getExistingGenreLinkEntries(): array
{ {
if ($this->validDatabase === FALSE) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -665,7 +714,13 @@ final class AnimeCollection extends Collection {
->from('anime_set_genre_link') ->from('anime_set_genre_link')
->get(); ->get();
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link) $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
foreach ($rows as $link)
{ {
if (array_key_exists($link['hummingbird_id'], $links)) if (array_key_exists($link['hummingbird_id'], $links))
{ {
@ -689,7 +744,7 @@ final class AnimeCollection extends Collection {
*/ */
private function getCollectionFromDatabase(): array private function getCollectionFromDatabase(): array
{ {
if ( ! $this->validDatabase) if ($this->db === NULL)
{ {
return []; return [];
} }
@ -706,6 +761,11 @@ final class AnimeCollection extends Collection {
// Add genres associated with each item // Add genres associated with each item
$rows = $query->fetchAll(PDO::FETCH_ASSOC); $rows = $query->fetchAll(PDO::FETCH_ASSOC);
if ($rows === FALSE)
{
return [];
}
$genres = $this->getGenreList(); $genres = $this->getGenreList();
foreach($rows as &$row) foreach($rows as &$row)

View File

@ -29,15 +29,9 @@ class Collection extends DB {
/** /**
* The query builder object * The query builder object
* @var QueryBuilderInterface * @var QueryBuilderInterface|null
*/ */
protected QueryBuilderInterface $db; protected ?QueryBuilderInterface $db;
/**
* Whether the database is valid for querying
* @var boolean
*/
protected bool $validDatabase = FALSE;
/** /**
* Create a new collection object * Create a new collection object
@ -51,9 +45,8 @@ class Collection extends DB {
try try
{ {
$this->db = Query($this->dbConfig); $this->db = Query($this->dbConfig);
$this->validDatabase = TRUE;
} }
catch (PDOException $e) catch (PDOException)
{ {
$this->db = Query([ $this->db = Query([
'type' => 'sqlite', 'type' => 'sqlite',
@ -67,19 +60,12 @@ class Collection extends DB {
{ {
$dbFileName = $this->dbConfig['file']; $dbFileName = $this->dbConfig['file'];
if ($dbFileName !== ':memory:' && file_exists($dbFileName)) if ($dbFileName !== ':memory:')
{ {
$dbFile = file_get_contents($dbFileName); $rawFile = file_get_contents($dbFileName);
$this->validDatabase = (strpos($dbFile, 'SQLite format 3') === 0); $dbFile = ($rawFile !== FALSE) ? $rawFile : '';
$this->db = (str_starts_with($dbFile, 'SQLite format 3')) ? $this->db : NULL;
} }
else
{
$this->validDatabase = FALSE;
}
}
else if ($this->db === NULL)
{
$this->validDatabase = FALSE;
} }
} }
} }

View File

@ -85,7 +85,7 @@ trait MediaTrait {
* @param string $itemId * @param string $itemId
* @return AnimeListItem|MangaListItem * @return AnimeListItem|MangaListItem
*/ */
public function getLibraryItem(string $itemId) public function getLibraryItem(string $itemId): AnimeListItem|MangaListItem
{ {
return $this->kitsuModel->getListItem($itemId); return $this->kitsuModel->getListItem($itemId);
} }
@ -100,7 +100,13 @@ trait MediaTrait {
public function createLibraryItem(array $data): bool public function createLibraryItem(array $data): bool
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->createListItem($data), 'kitsu'); $kitsuRequest = $this->kitsuModel->createListItem($data);
if ($kitsuRequest === NULL)
{
return FALSE;
}
$requester->addRequest($kitsuRequest, 'kitsu');
if ($this->anilistEnabled && $data['mal_id'] !== null) if ($this->anilistEnabled && $data['mal_id'] !== null)
{ {

View File

@ -69,9 +69,7 @@ final class Settings {
{ {
$output = []; $output = [];
$settings = $this->getSettings(); foreach($this->getSettings() as $file => $values)
foreach($settings as $file => $values)
{ {
$values = $values ?? []; $values = $values ?? [];
@ -126,6 +124,10 @@ final class Settings {
public function validateSettings(array $settings): array public function validateSettings(array $settings): array
{ {
$cfg = Config::check($settings); $cfg = Config::check($settings);
if ( ! is_iterable($cfg))
{
return [];
}
$looseConfig = []; $looseConfig = [];
$keyedConfig = []; $keyedConfig = [];

View File

@ -22,7 +22,7 @@ use Aviat\Ion\Di\Exception\ContainerException;
use Aviat\Ion\Di\Exception\NotFoundException; use Aviat\Ion\Di\Exception\NotFoundException;
use Aviat\Ion\Exception\ConfigException; use Aviat\Ion\Exception\ConfigException;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface;
/** /**
* Base for routing/url classes * Base for routing/url classes
@ -43,9 +43,9 @@ class RoutingBase {
/** /**
* Class wrapper for input superglobals * Class wrapper for input superglobals
* @var RequestInterface * @var ServerRequestInterface
*/ */
protected RequestInterface $request; protected ServerRequestInterface $request;
/** /**
* Constructor * Constructor
@ -64,8 +64,7 @@ class RoutingBase {
/** /**
* Get the current url path * Get the current url path
* @throws ContainerException *
* @throws NotFoundException
* @return string * @return string
*/ */
public function path(): string public function path(): string
@ -82,8 +81,7 @@ class RoutingBase {
/** /**
* Get the url segments * Get the url segments
* @throws ContainerException *
* @throws NotFoundException
* @return array * @return array
*/ */
public function segments(): array public function segments(): array
@ -96,11 +94,10 @@ class RoutingBase {
* Get a segment of the current url * Get a segment of the current url
* *
* @param int $num * @param int $num
* @throws ContainerException *
* @throws NotFoundException
* @return string|null * @return string|null
*/ */
public function getSegment($num): ?string public function getSegment(int $num): ?string
{ {
$segments = $this->segments(); $segments = $this->segments();
return $segments[$num] ?? NULL; return $segments[$num] ?? NULL;
@ -109,8 +106,6 @@ class RoutingBase {
/** /**
* Retrieve the last url segment * Retrieve the last url segment
* *
* @throws ContainerException
* @throws NotFoundException
* @return string * @return string
*/ */
public function lastSegment(): string public function lastSegment(): string

View File

@ -23,10 +23,10 @@ abstract class AbstractType implements ArrayAccess, Countable {
/** /**
* Populate values for un-serializing data * Populate values for un-serializing data
* *
* @param $properties * @param mixed $properties
* @return self * @return self
*/ */
public static function __set_state($properties): self public static function __set_state(mixed $properties): self
{ {
return new static($properties); return new static($properties);
} }
@ -43,7 +43,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
if (get_parent_class($currentClass) !== FALSE) if (get_parent_class($currentClass) !== FALSE)
{ {
return (new $currentClass($data))->toArray(); return static::class::from($data)->toArray();
} }
return NULL; return NULL;
@ -55,7 +55,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param mixed $data * @param mixed $data
* @return static * @return static
*/ */
final public static function from($data): self final public static function from(mixed $data): static
{ {
return new static($data); return new static($data);
} }
@ -65,7 +65,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* *
* @param mixed $data * @param mixed $data
*/ */
final private function __construct($data = []) final private function __construct(mixed $data = [])
{ {
$typeKeys = array_keys((array)$this); $typeKeys = array_keys((array)$this);
$dataKeys = array_keys((array)$data); $dataKeys = array_keys((array)$data);
@ -87,10 +87,10 @@ abstract class AbstractType implements ArrayAccess, Countable {
/** /**
* See if a property is set * See if a property is set
* *
* @param $name * @param string $name
* @return bool * @return bool
*/ */
final public function __isset($name): bool final public function __isset(string $name): bool
{ {
return property_exists($this, $name) && isset($this->$name); return property_exists($this, $name) && isset($this->$name);
} }
@ -102,7 +102,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param mixed $value * @param mixed $value
* @return void * @return void
*/ */
final public function __set($name, $value): void final public function __set(string $name, mixed $value): void
{ {
$setterMethod = 'set' . ucfirst($name); $setterMethod = 'set' . ucfirst($name);
@ -128,7 +128,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param string $name * @param string $name
* @return mixed * @return mixed
*/ */
final public function __get($name) final public function __get(string $name): mixed
{ {
// Be a bit more lenient here, so that you can easily typecast missing // Be a bit more lenient here, so that you can easily typecast missing
// values to reasonable defaults, and not have to resort to array indexes // values to reasonable defaults, and not have to resort to array indexes
@ -148,46 +148,47 @@ abstract class AbstractType implements ArrayAccess, Countable {
/** /**
* Implementing ArrayAccess * Implementing ArrayAccess
* *
* @param $offset * @param mixed $offset
* @return bool * @return bool
*/ */
final public function offsetExists($offset): bool final public function offsetExists(mixed $offset): bool
{ {
return $this->__isset($offset); return $this->__isset((string)$offset);
} }
/** /**
* Implementing ArrayAccess * Implementing ArrayAccess
* *
* @param $offset * @param mixed $offset
* @return mixed * @return mixed
*/ */
final public function offsetGet($offset) final public function offsetGet(mixed $offset): mixed
{ {
return $this->__get($offset); return $this->__get((string)$offset);
} }
/** /**
* Implementing ArrayAccess * Implementing ArrayAccess
* *
* @param $offset * @param mixed $offset
* @param $value * @param mixed $value
*/ */
final public function offsetSet($offset, $value): void final public function offsetSet(mixed $offset, mixed $value): void
{ {
$this->__set($offset, $value); $this->__set((string)$offset, $value);
} }
/** /**
* Implementing ArrayAccess * Implementing ArrayAccess
* *
* @param $offset * @param mixed $offset
*/ */
final public function offsetUnset($offset): void final public function offsetUnset(mixed $offset): void
{ {
if ($this->offsetExists($offset)) if ($this->offsetExists($offset))
{ {
unset($this->$offset); $strOffset = (string)$offset;
unset($this->$strOffset);
} }
} }
@ -198,17 +199,44 @@ abstract class AbstractType implements ArrayAccess, Countable {
*/ */
final public function count(): int final public function count(): int
{ {
$keys = array_keys($this->toArray()); $keys = array_keys((array)$this->toArray());
return count($keys); return count($keys);
} }
/** /**
* Recursively cast properties to an array * Recursively cast properties to an array
* *
* Returns early on primitive values to work recursively.
*
* @param mixed $parent * @param mixed $parent
* @return mixed * @return array
*/ */
final public function toArray($parent = null) final public function toArray(mixed $parent = null): array
{
$fromObject = $this->fromObject($parent);
return (is_array($fromObject)) ? $fromObject : [];
}
/**
* Determine whether the type has any properties set
*
* @return bool
*/
final public function isEmpty(): bool
{
$self = (array)$this->toArray();
foreach ($self as $value)
{
if ( ! empty($value))
{
return FALSE;
}
}
return TRUE;
}
final protected function fromObject(mixed $parent = null): float|null|bool|int|array|string
{ {
$object = $parent ?? $this; $object = $parent ?? $this;
@ -223,27 +251,9 @@ abstract class AbstractType implements ArrayAccess, Countable {
{ {
$output[$key] = (is_scalar($value) || empty($value)) $output[$key] = (is_scalar($value) || empty($value))
? $value ? $value
: $this->toArray((array) $value); : $this->fromObject((array) $value);
} }
return $output; return $output;
} }
/**
* Determine whether the type has any properties set
*
* @return bool
*/
final public function isEmpty(): bool
{
foreach ($this as $value)
{
if ( ! empty($value))
{
return FALSE;
}
}
return TRUE;
}
} }

View File

@ -35,9 +35,9 @@ class Anime extends AbstractType {
public array $genres = []; public array $genres = [];
/** /**
* @var string|int * @var string
*/ */
public $id = ''; public string $id = '';
public array $included = []; public array $included = [];

View File

@ -24,11 +24,6 @@ final class AnimeListItem extends AbstractType {
public ?string $mal_id; public ?string $mal_id;
/**
* @var string
*/
public $anilist_item_id;
public array $episodes = [ public array $episodes = [
'length' => 0, 'length' => 0,
'total' => 0, 'total' => 0,
@ -54,14 +49,14 @@ final class AnimeListItem extends AbstractType {
/** /**
* @var string|int * @var string|int
*/ */
public $user_rating = ''; public string|int $user_rating = '';
/** /**
* One of Aviat\AnimeClient\API\Enum\AnimeWatchingStatus * One of Aviat\AnimeClient\API\Enum\AnimeWatchingStatus
*/ */
public string $watching_status; public string $watching_status;
public function setAnime($anime): void public function setAnime(mixed $anime): void
{ {
$this->anime = Anime::from($anime); $this->anime = Anime::from($anime);
} }

View File

@ -27,7 +27,7 @@ final class Character extends AbstractType {
/** /**
* @var string * @var string
*/ */
public $id; public string $id;
public array $included = []; public array $included = [];
@ -39,7 +39,7 @@ final class Character extends AbstractType {
public array $otherNames = []; public array $otherNames = [];
public function setMedia ($media): void public function setMedia (mixed $media): void
{ {
$this->media = Media::from($media); $this->media = Media::from($media);
} }

View File

@ -47,8 +47,9 @@ class Config extends AbstractType {
/** /**
* The list to redirect to from the root url * The list to redirect to from the root url
* 'anime' or 'manga'
* *
* @var 'anime' | 'manga' * @var string|null
*/ */
public ?string $default_list; public ?string $default_list;
@ -59,7 +60,10 @@ class Config extends AbstractType {
public ?string $default_manga_list_path; public ?string $default_manga_list_path;
/** /**
* @var 'cover_view' | 'list_view' * Default list view type
* 'cover_view' or 'list_view'
*
* @var string
*/ */
public ?string $default_view_type; public ?string $default_view_type;
@ -70,17 +74,18 @@ class Config extends AbstractType {
/** /**
* @var string|bool * @var string|bool
*/ */
public $show_anime_collection = FALSE; public string|bool $show_anime_collection = FALSE;
/** /**
* @var string|bool * @var string|bool
*/ */
public $show_manga_collection = FALSE; public string|bool $show_manga_collection = FALSE;
/** /**
* CSS theme: light, dark, or auto-switching * CSS theme: light, dark, or auto-switching
* 'auto', 'light', or 'dark'
* *
* @var 'auto' | 'light' | 'dark' * @var string|null
*/ */
public ?string $theme = 'auto'; public ?string $theme = 'auto';
@ -116,17 +121,17 @@ class Config extends AbstractType {
public ?string $view_path; public ?string $view_path;
public function setAnilist ($data): void public function setAnilist (mixed $data): void
{ {
$this->anilist = Config\Anilist::from($data); $this->anilist = Config\Anilist::from($data);
} }
public function setCache ($data): void public function setCache (mixed $data): void
{ {
$this->cache = Config\Cache::from($data); $this->cache = Config\Cache::from($data);
} }
public function setDatabase ($data): void public function setDatabase (mixed $data): void
{ {
$this->database = Config\Database::from($data); $this->database = Config\Database::from($data);
} }

View File

@ -23,18 +23,16 @@ class FormItem extends AbstractType {
/** /**
* @var string|int * @var string|int
*/ */
public $id; public string|int $id;
public ?string $anilist_item_id;
/** /**
* @var string|int * @var string|int|null
*/ */
public $mal_id; public string|int|null $mal_id;
public ?FormItemData $data; public ?FormItemData $data;
public function setData($value): void public function setData(mixed $value): void
{ {
$this->data = FormItemData::from($value); $this->data = FormItemData::from($value);
} }

View File

@ -23,12 +23,12 @@ use Aviat\AnimeClient\API\Kitsu\Enum\MangaPublishingStatus;
*/ */
final class MangaPage extends AbstractType { final class MangaPage extends AbstractType {
/** /**
* @var string * @var string|null
*/ */
public ?string $age_rating; public ?string $age_rating;
/** /**
* @var string * @var string|null
*/ */
public ?string $age_rating_guide; public ?string $age_rating_guide;
@ -38,14 +38,14 @@ final class MangaPage extends AbstractType {
public array $characters; public array $characters;
/** /**
* @var int * @var int|null
*/ */
public $chapter_count; public ?int $chapter_count;
/** /**
* @var string * @var string|null
*/ */
public $cover_image; public ?string $cover_image;
/** /**
* @var array * @var array
@ -60,15 +60,15 @@ final class MangaPage extends AbstractType {
/** /**
* @var string * @var string
*/ */
public $id; public string $id;
/** /**
* @var string * @var string
*/ */
public $manga_type; public string $manga_type;
/** /**
* @var MangaPublishingStatus * @var string
*/ */
public string $status = MangaPublishingStatus::FINISHED; public string $status = MangaPublishingStatus::FINISHED;
@ -103,7 +103,7 @@ final class MangaPage extends AbstractType {
public string $url; public string $url;
/** /**
* @var int * @var int|null
*/ */
public ?int $volume_count; public ?int $volume_count;
} }

View File

@ -21,7 +21,7 @@ namespace Aviat\AnimeClient\Types;
*/ */
final class Person extends AbstractType { final class Person extends AbstractType {
public $id; public string $id;
public ?string $name; public ?string $name;

View File

@ -49,7 +49,7 @@ class UrlGenerator extends RoutingBase {
/** /**
* Get the base url for css/js/images * Get the base url for css/js/images
* *
* @param array $args * @param string ...$args
* @return string * @return string
*/ */
public function assetUrl(string ...$args): string public function assetUrl(string ...$args): string
@ -72,7 +72,7 @@ class UrlGenerator extends RoutingBase {
{ {
$path = trim($path, '/'); $path = trim($path, '/');
$path = preg_replace('`{/.*?}`i', '', $path); $path = preg_replace('`{/.*?}`i', '', $path) ?? "";
// Remove any optional parameters from the route // Remove any optional parameters from the route
// and replace them with existing route parameters, if they exist // and replace them with existing route parameters, if they exist
@ -87,7 +87,7 @@ class UrlGenerator extends RoutingBase {
$segments[$i + 1] = ''; $segments[$i + 1] = '';
} }
$pathSegments[$i] = preg_replace('`{.*?}`', $segments[$i + 1], $pathSegments[$i]); $pathSegments[$i] = preg_replace('`{.*?}`', $segments[$i + 1], $pathSegments[$i] ?? '');
} }
$path = implode('/', $pathSegments); $path = implode('/', $pathSegments);

View File

@ -57,11 +57,11 @@ class Util {
/** /**
* Absolutely equal? * Absolutely equal?
* *
* @param $left * @param mixed $left
* @param $right * @param mixed $right
* @return bool * @return bool
*/ */
public static function eq($left, $right): bool public static function eq(mixed $left, mixed $right): bool
{ {
return $left === $right; return $left === $right;
} }

View File

@ -29,21 +29,21 @@ class Container implements ContainerInterface {
* *
* @var Callable[] * @var Callable[]
*/ */
protected $container = []; protected array $container = [];
/** /**
* Array of object instances * Array of object instances
* *
* @var array * @var array
*/ */
protected $instances = []; protected array $instances = [];
/** /**
* Map of logger instances * Map of logger instances
* *
* @var array * @var array
*/ */
protected $loggers = []; protected array $loggers = [];
/** /**
* Constructor * Constructor
@ -66,7 +66,7 @@ class Container implements ContainerInterface {
* *
* @return mixed Entry. * @return mixed Entry.
*/ */
public function get($id) public function get($id): mixed
{ {
if ( ! \is_string($id)) if ( ! \is_string($id))
{ {
@ -99,7 +99,7 @@ class Container implements ContainerInterface {
* @throws ContainerException - Error while retrieving the entry. * @throws ContainerException - Error while retrieving the entry.
* @return mixed * @return mixed
*/ */
public function getNew($id, array $args = NULL) public function getNew($id, array $args = NULL): mixed
{ {
if ( ! \is_string($id)) if ( ! \is_string($id))
{ {
@ -141,7 +141,7 @@ class Container implements ContainerInterface {
* @throws NotFoundException - No entry was found for this identifier. * @throws NotFoundException - No entry was found for this identifier.
* @return ContainerInterface * @return ContainerInterface
*/ */
public function setInstance(string $id, $value): ContainerInterface public function setInstance(string $id, mixed $value): ContainerInterface
{ {
if ( ! $this->has($id)) if ( ! $this->has($id))
{ {
@ -209,15 +209,20 @@ class Container implements ContainerInterface {
* @param mixed $obj * @param mixed $obj
* @return mixed * @return mixed
*/ */
private function applyContainer($obj) private function applyContainer(mixed $obj): mixed
{ {
$trait_name = ContainerAware::class; $traitName = ContainerAware::class;
$interface_name = ContainerAwareInterface::class; $interfaceName = ContainerAwareInterface::class;
$uses_trait = \in_array($trait_name, class_uses($obj), TRUE); $traits = class_uses($obj);
$implements_interface = \in_array($interface_name, class_implements($obj), TRUE); $traitsUsed = (is_array($traits)) ? $traits : [];
$usesTrait = in_array($traitName, $traitsUsed, TRUE);
if ($uses_trait || $implements_interface) $interfaces = class_implements($obj);
$implemented = (is_array($interfaces)) ? $interfaces : [];
$implementsInterface = in_array($interfaceName, $implemented, TRUE);
if ($usesTrait || $implementsInterface)
{ {
$obj->setContainer($this); $obj->setContainer($this);
} }

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\Ion;
/**
* View Interface abstracting an HTTP Response
*/
interface HttpViewInterface extends ViewInterface {
/**
* Set the status code of the request
*
* @param int $code
* @throws \InvalidArgumentException
* @return self
*/
public function setStatusCode(int $code): self;
}

View File

@ -32,11 +32,12 @@ class Json {
* @throws JsonException * @throws JsonException
* @return string * @return string
*/ */
public static function encode($data, $options = 0, $depth = 512): string public static function encode(mixed $data, int $options = 0, int $depth = 512): string
{ {
$json = json_encode($data, $options, $depth); $json = json_encode($data, $options, $depth);
self::check_json_error(); self::check_json_error();
return $json;
return ($json !== FALSE) ? $json : '';
} }
/** /**
@ -47,12 +48,14 @@ class Json {
* @param int $jsonOptions - Options to pass to json_encode * @param int $jsonOptions - Options to pass to json_encode
* @param int $fileOptions - Options to pass to file_get_contents * @param int $fileOptions - Options to pass to file_get_contents
* @throws JsonException * @throws JsonException
* @return int * @return bool
*/ */
public static function encodeFile(string $filename, $data, int $jsonOptions = 0, int $fileOptions = 0): int public static function encodeFile(string $filename, mixed $data, int $jsonOptions = 0, int $fileOptions = 0): bool
{ {
$json = self::encode($data, $jsonOptions); $json = self::encode($data, $jsonOptions);
return file_put_contents($filename, $json, $fileOptions);
$res = file_put_contents($filename, $json, $fileOptions);
return $res !== FALSE;
} }
/** /**
@ -65,7 +68,7 @@ class Json {
* @throws JsonException * @throws JsonException
* @return mixed * @return mixed
*/ */
public static function decode($json, bool $assoc = TRUE, int $depth = 512, int $options = 0) public static function decode(?string $json, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
{ {
// Don't try to decode null // Don't try to decode null
if ($json === NULL) if ($json === NULL)
@ -89,9 +92,11 @@ class Json {
* @throws JsonException * @throws JsonException
* @return mixed * @return mixed
*/ */
public static function decodeFile(string $filename, bool $assoc = TRUE, int $depth = 512, int $options = 0) public static function decodeFile(string $filename, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
{ {
$json = file_get_contents($filename); $rawJson = file_get_contents($filename);
$json = ($rawJson !== FALSE) ? $rawJson : '';
return self::decode($json, $assoc, $depth, $options); return self::decode($json, $assoc, $depth, $options);
} }

View File

@ -28,7 +28,7 @@ abstract class AbstractTransformer implements TransformerInterface {
* @param array|object $item * @param array|object $item
* @return mixed * @return mixed
*/ */
abstract public function transform($item); abstract public function transform(array|object $item): mixed;
/** /**
* Transform a set of structures * Transform a set of structures

View File

@ -27,5 +27,5 @@ interface TransformerInterface {
* @param array|object $item * @param array|object $item
* @return mixed * @return mixed
*/ */
public function transform($item); public function transform(array|object $item): mixed;
} }

View File

@ -101,7 +101,7 @@ class ArrayType {
* @return mixed * @return mixed
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function __call(string $method, array $args) public function __call(string $method, array $args): mixed
{ {
// Simple mapping for the majority of methods // Simple mapping for the majority of methods
if (array_key_exists($method, $this->nativeMethods)) if (array_key_exists($method, $this->nativeMethods))
@ -128,7 +128,7 @@ class ArrayType {
* @param int|string|array $key * @param int|string|array $key
* @return bool * @return bool
*/ */
public function hasKey($key): bool public function hasKey(int|string|array $key): bool
{ {
if (\is_array($key)) if (\is_array($key))
{ {
@ -158,7 +158,7 @@ class ArrayType {
* @param mixed $value * @param mixed $value
* @return array * @return array
*/ */
public function fill(int $start_index, int $num, $value): array public function fill(int $start_index, int $num, mixed $value): array
{ {
return array_fill($start_index, $num, $value); return array_fill($start_index, $num, $value);
} }
@ -179,9 +179,9 @@ class ArrayType {
* *
* @param mixed $value * @param mixed $value
* @param bool $strict * @param bool $strict
* @return false|integer|string * @return false|integer|string|null
*/ */
public function search($value, bool $strict = TRUE) public function search(mixed $value, bool $strict = TRUE): int|string|false|null
{ {
return array_search($value, $this->arr, $strict); return array_search($value, $this->arr, $strict);
} }
@ -193,7 +193,7 @@ class ArrayType {
* @param bool $strict * @param bool $strict
* @return bool * @return bool
*/ */
public function has($value, bool $strict = TRUE): bool public function has(mixed $value, bool $strict = TRUE): bool
{ {
return \in_array($value, $this->arr, $strict); return \in_array($value, $this->arr, $strict);
} }
@ -204,7 +204,7 @@ class ArrayType {
* @param string|integer|null $key * @param string|integer|null $key
* @return mixed * @return mixed
*/ */
public function &get($key = NULL) public function &get(string|int|null $key = NULL): mixed
{ {
$value = NULL; $value = NULL;
if ($key === NULL) if ($key === NULL)
@ -229,7 +229,7 @@ class ArrayType {
* @param mixed $value * @param mixed $value
* @return ArrayType * @return ArrayType
*/ */
public function set($key, $value): ArrayType public function set(mixed $key, mixed $value): ArrayType
{ {
$this->arr[$key] = $value; $this->arr[$key] = $value;
return $this; return $this;
@ -244,7 +244,7 @@ class ArrayType {
* @param array $key An array of keys of the array * @param array $key An array of keys of the array
* @return mixed * @return mixed
*/ */
public function &getDeepKey(array $key) public function &getDeepKey(array $key): mixed
{ {
$pos =& $this->arr; $pos =& $this->arr;
@ -273,7 +273,7 @@ class ArrayType {
* @param mixed $value * @param mixed $value
* @return array * @return array
*/ */
public function setDeepKey(array $key, $value): array public function setDeepKey(array $key, mixed $value): array
{ {
$pos =& $this->arr; $pos =& $this->arr;

View File

@ -27,7 +27,7 @@ class StringType extends Stringy {
* Alias for `create` static constructor * Alias for `create` static constructor
* *
* @param string $str * @param string $str
* @return $this * @return self
*/ */
public static function from(string $str): self public static function from(string $str): self
{ {

View File

@ -18,8 +18,6 @@ namespace Aviat\Ion\View;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException;
use Aviat\Ion\Di\Exception\NotFoundException;
use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\HtmlResponse;
use const EXTR_OVERWRITE; use const EXTR_OVERWRITE;
@ -40,8 +38,6 @@ class HtmlView extends HttpView {
* Create the Html View * Create the Html View
* *
* @param ContainerInterface $container * @param ContainerInterface $container
* @throws ContainerException
* @throws NotFoundException
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
@ -57,6 +53,7 @@ class HtmlView extends HttpView {
* @param string $path * @param string $path
* @param array $data * @param array $data
* @return string * @return string
* @throws \Throwable
*/ */
public function renderTemplate(string $path, array $data): string public function renderTemplate(string $path, array $data): string
{ {
@ -69,13 +66,12 @@ class HtmlView extends HttpView {
ob_start(); ob_start();
extract($data, EXTR_OVERWRITE); extract($data, EXTR_OVERWRITE);
include_once $path; include_once $path;
$buffer = ob_get_clean(); $rawBuffer = ob_get_clean();
$buffer = ($rawBuffer === FALSE) ? '' : $rawBuffer;
// Very basic html minify, that won't affect content between html tags // Very basic html minify, that won't affect content between html tags
$buffer = preg_replace('/>\s+</', '> <', $buffer); return preg_replace('/>\s+</', '> <', $buffer) ?? $buffer;
return $buffer;
} }
} }
// End of HtmlView.php // End of HtmlView.php

View File

@ -16,7 +16,7 @@
namespace Aviat\Ion\View; namespace Aviat\Ion\View;
use Aviat\Ion\ViewInterface; use Aviat\Ion\HttpViewInterface;
use Laminas\Diactoros\Response; use Laminas\Diactoros\Response;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter; use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
@ -26,7 +26,7 @@ use Psr\Http\Message\ResponseInterface;
/** /**
* Base view class for Http output * Base view class for Http output
*/ */
class HttpView implements ViewInterface{ class HttpView implements HttpViewInterface{
/** /**
* HTTP response Object * HTTP response Object
@ -103,9 +103,9 @@ class HttpView implements ViewInterface{
* Set the output string * Set the output string
* *
* @param mixed $string * @param mixed $string
* @return HttpView * @return HttpViewInterface
*/ */
public function setOutput($string): self public function setOutput($string): HttpViewInterface
{ {
$this->response->getBody()->write($string); $this->response->getBody()->write($string);
@ -117,9 +117,9 @@ class HttpView implements ViewInterface{
* Append additional output. * Append additional output.
* *
* @param string $string * @param string $string
* @return HttpView * @return HttpViewInterface
*/ */
public function appendOutput(string $string): self public function appendOutput(string $string): HttpViewInterface
{ {
return $this->setOutput($string); return $this->setOutput($string);
} }

View File

@ -17,6 +17,7 @@
namespace Aviat\Ion\View; namespace Aviat\Ion\View;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use Aviat\Ion\HttpViewInterface;
/** /**
* View class to serialize Json * View class to serialize Json
@ -34,9 +35,9 @@ class JsonView extends HttpView {
* Set the output string * Set the output string
* *
* @param mixed $string * @param mixed $string
* @return JsonView * @return HttpViewInterface
*/ */
public function setOutput($string): self public function setOutput(mixed $string): HttpViewInterface
{ {
if ( ! is_string($string)) if ( ! is_string($string))
{ {

View File

@ -1,6 +1,5 @@
empty: false empty: false
id: 14047981 id: 14047981
anilist_item_id: null
mal_id: null mal_id: null
data: data:
empty: false empty: false

View File

@ -1,6 +1,5 @@
empty: false empty: false
id: 14047981 id: 14047981
anilist_item_id: null
mal_id: '12345' mal_id: '12345'
data: data:
empty: false empty: false

View File

@ -1,6 +1,5 @@
empty: false empty: false
id: 14047983 id: 14047983
anilist_item_id: null
mal_id: '12347' mal_id: '12347'
data: data:
empty: false empty: false

View File

@ -122,4 +122,36 @@ class KitsuTest extends TestCase {
$this->assertEquals($expected, $actual); $this->assertEquals($expected, $actual);
} }
public function testFilterLocalizedTitles()
{
$input = [
'canonical' => 'foo',
'localized' => [
'en' => 'Foo the Movie',
'fr' => '',
'jp' => NULL,
],
'alternatives' => [],
];
$actual = Kitsu::filterLocalizedTitles($input);
$this->assertEquals(['Foo the Movie'], $actual);
}
public function testGetFilteredTitles()
{
$input = [
'canonical' => 'foo',
'localized' => [
'en' => 'Foo the Movie'
],
'alternatives' => [],
];
$actual = Kitsu::getFilteredTitles($input);
$this->assertEquals(['Foo the Movie'], $actual);
}
} }

View File

@ -66,7 +66,7 @@ class FriendTestClass extends FriendParentTestClass {
class TestTransformer extends AbstractTransformer { class TestTransformer extends AbstractTransformer {
public function transform($item) public function transform(array|object $item): array
{ {
$out = []; $out = [];
$genre_list = (array) $item; $genre_list = (array) $item;